summaryrefslogtreecommitdiff
path: root/solenv/bin/desktop-translate.py
blob: 7075a51f9d977d2af42296c07f0923d07bad5f43 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
#
# This file is part of the LibreOffice project.
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This file incorporates work covered by the following license notice:
#
#   Licensed to the Apache Software Foundation (ASF) under one or more
#   contributor license agreements. See the NOTICE file distributed
#   with this work for additional information regarding copyright
#   ownership. The ASF licenses this file to you under the Apache
#   License, Version 2.0 (the "License"); you may not use this file
#   except in compliance with the License. You may obtain a copy of
#   the License at http://www.apache.org/licenses/LICENSE-2.0 .
#

"""Translates multiple .desktop files at once with strings from .ulf
files; if you add new translatable .ulf files please add them to
l10ntools/source/localize.cxx in case the module is not already listed."""

import os
import sys
import argparse
import io


def encode_desktop_string(s_value):
    """Function encoding strings to be used as values in .desktop files."""
    # <https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.1.html#
    # value-types> says "The escape sequences \s, \n, \t, \r, and \\ are supported for values of
    # type string and localestring, meaning ASCII space, newline, tab, carriage return, and
    # backslash, respectively."  <https://specifications.freedesktop.org/desktop-entry-spec/
    # desktop-entry-spec-1.1.html#basic-format> says "A file is interpreted as a series of lines
    # that are separated by linefeed characters", so it is apparently necessary to escape at least
    # linefeed and backslash characters.  It is unclear why that spec talks about "linefeed" in
    # one place and about "newline" ("\n") and "carriage return" ("\r") in another, and how they are
    # supposed to relate, so just escape any U+000A LINE FEED as "\n" and any U+000D CARRIAGE RETURN
    # as "\r"; it is unclear exactly which occurrences of U+0020 SPACE and U+0009 CHARACTER
    # TABULATION would need to be escaped, so they are mostly left unescaped, for readability:
    s_value = s_value.replace("\\", "\\\\").replace("\n", "\\n").replace("\r", "\\r")
    if s_value.startswith(" "):
        # <https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.1.html#
        # entries> says "Space before and after the equals sign should be ignored", so escape a
        # leading U+0020 SPACE as "\s" (while it is not clear whether "space" there means just
        # U+0020 SPACE or any kind of white space, in which case at least a leading U+0009 CHARACTER
        # TABULATION should similarly be escaped as "\t"; also, it is unclear whether such
        # characters should also be escaped at the end):
        s_value = "\\s" + s_value[1:]
    return s_value


parser = argparse.ArgumentParser()
parser.add_argument("-p", dest="productname", default="LibreOffice")
parser.add_argument("-d", dest="workdir", default=".")
parser.add_argument("--prefix", dest="prefix", default="")
parser.add_argument("--ext", dest="ext")
parser.add_argument("--template-dir", dest="template_dir", default=None)
parser.add_argument("ifile")

o = parser.parse_args()

if o.template_dir is None:
    template_dir = f"{o.workdir}/{o.prefix}"
else:
    template_dir = o.template_dir

templates = {}

# open input file
source = io.open(o.ifile, encoding="utf-8")

template = None

# read ulf file
for line in source:
    if line.strip() == "":
        continue
    # the headings in the ulf files for .desktop files are in the form [filename_Key]
    if line[0] == "[":
        heading = line.split("]", 1)[0][1:]
        template = heading.split("_", 1)[0]
        key = heading.split("_", 1)[1]
        entry = {}
        # For every section in the specified ulf file there should exist
        # a template file in $workdir ..
        entry["outfile"] = f"{template_dir}{template}.{o.ext}"
        entry["translations"] = {}
        entry["key"] = key
        templates[heading] = entry
    else:
        # split locale = "value" into 2 strings
        if " = " not in line:
            continue
        locale, value = line.split(" = ")

        if locale != line:
            # replace en-US with en
            locale = locale.replace("en-US", "en")

            # use just anything inside the ""
            assert value[0] == '"'
            # Some entries span multiple lines.
            # An entry will always end on a double quote.
            while not value.endswith('"\n'):
                value += source.readline()
            value = value[1:-2]

            # replace resource placeholder
            value = value.replace("%PRODUCTNAME", o.productname)

            locale = locale.replace("-", "_")

            templates[heading]["translations"][locale] = value

source.close()

processed = 0
# process templates
for template, entries in templates.items():
    outfilename = entries["outfile"]

    # open the template file - ignore sections for which no
    # templates exist
    try:
        template_file = io.open(outfilename, encoding="utf-8")
    except OSError:
        # string files processed one by one
        if o.ext == "str":
            continue
        sys.exit(
            f"Warning: No template found for item '{template}' : '{outfilename}'\n"
        )
    processed += 1

    # open output file
    tmpfilename = f"{outfilename}.tmp"
    outfile = io.open(tmpfilename, "w", encoding="utf-8")

    # emit the template to the output file
    for line in template_file:
        keyline = line
        if keyline.startswith(entries["key"]):
            # hack for Unity section
            if entries["key"] == "UnityQuickList":
                OUTKEY = "Name"
            else:
                OUTKEY = entries["key"]
            keyline = OUTKEY + keyline[len(entries["key"]) :]
        outfile.write(keyline)
        if entries["key"] in line:
            translations = entries["translations"]
            for locale in sorted(translations.keys()):
                value = translations.get(locale, None)
                if value:
                    if o.ext in ("desktop", "str"):
                        if o.ext == "desktop":
                            value = encode_desktop_string(value)
                        outfile.write(f"{OUTKEY}[{locale}]={value}\n")
                    else:
                        outfile.write(f"\t[{locale}]{OUTKEY}={value}\n")

    template_file.close()

    outfile.close()
    if os.path.exists(outfilename):
        os.unlink(outfilename)
    os.rename(tmpfilename, outfilename)

if o.ext == "str" and processed == 0:
    sys.exit("Warning: No matching templates processed")