Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / solenv / bin / desktop-translate.py
blob7075a51f9d977d2af42296c07f0923d07bad5f43
2 # This file is part of the LibreOffice project.
4 # This Source Code Form is subject to the terms of the Mozilla Public
5 # License, v. 2.0. If a copy of the MPL was not distributed with this
6 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 # This file incorporates work covered by the following license notice:
10 # Licensed to the Apache Software Foundation (ASF) under one or more
11 # contributor license agreements. See the NOTICE file distributed
12 # with this work for additional information regarding copyright
13 # ownership. The ASF licenses this file to you under the Apache
14 # License, Version 2.0 (the "License"); you may not use this file
15 # except in compliance with the License. You may obtain a copy of
16 # the License at http://www.apache.org/licenses/LICENSE-2.0 .
19 """Translates multiple .desktop files at once with strings from .ulf
20 files; if you add new translatable .ulf files please add them to
21 l10ntools/source/localize.cxx in case the module is not already listed."""
23 import os
24 import sys
25 import argparse
26 import io
29 def encode_desktop_string(s_value):
30 """Function encoding strings to be used as values in .desktop files."""
31 # <https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.1.html#
32 # value-types> says "The escape sequences \s, \n, \t, \r, and \\ are supported for values of
33 # type string and localestring, meaning ASCII space, newline, tab, carriage return, and
34 # backslash, respectively." <https://specifications.freedesktop.org/desktop-entry-spec/
35 # desktop-entry-spec-1.1.html#basic-format> says "A file is interpreted as a series of lines
36 # that are separated by linefeed characters", so it is apparently necessary to escape at least
37 # linefeed and backslash characters. It is unclear why that spec talks about "linefeed" in
38 # one place and about "newline" ("\n") and "carriage return" ("\r") in another, and how they are
39 # supposed to relate, so just escape any U+000A LINE FEED as "\n" and any U+000D CARRIAGE RETURN
40 # as "\r"; it is unclear exactly which occurrences of U+0020 SPACE and U+0009 CHARACTER
41 # TABULATION would need to be escaped, so they are mostly left unescaped, for readability:
42 s_value = s_value.replace("\\", "\\\\").replace("\n", "\\n").replace("\r", "\\r")
43 if s_value.startswith(" "):
44 # <https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.1.html#
45 # entries> says "Space before and after the equals sign should be ignored", so escape a
46 # leading U+0020 SPACE as "\s" (while it is not clear whether "space" there means just
47 # U+0020 SPACE or any kind of white space, in which case at least a leading U+0009 CHARACTER
48 # TABULATION should similarly be escaped as "\t"; also, it is unclear whether such
49 # characters should also be escaped at the end):
50 s_value = "\\s" + s_value[1:]
51 return s_value
54 parser = argparse.ArgumentParser()
55 parser.add_argument("-p", dest="productname", default="LibreOffice")
56 parser.add_argument("-d", dest="workdir", default=".")
57 parser.add_argument("--prefix", dest="prefix", default="")
58 parser.add_argument("--ext", dest="ext")
59 parser.add_argument("--template-dir", dest="template_dir", default=None)
60 parser.add_argument("ifile")
62 o = parser.parse_args()
64 if o.template_dir is None:
65 template_dir = f"{o.workdir}/{o.prefix}"
66 else:
67 template_dir = o.template_dir
69 templates = {}
71 # open input file
72 source = io.open(o.ifile, encoding="utf-8")
74 template = None
76 # read ulf file
77 for line in source:
78 if line.strip() == "":
79 continue
80 # the headings in the ulf files for .desktop files are in the form [filename_Key]
81 if line[0] == "[":
82 heading = line.split("]", 1)[0][1:]
83 template = heading.split("_", 1)[0]
84 key = heading.split("_", 1)[1]
85 entry = {}
86 # For every section in the specified ulf file there should exist
87 # a template file in $workdir ..
88 entry["outfile"] = f"{template_dir}{template}.{o.ext}"
89 entry["translations"] = {}
90 entry["key"] = key
91 templates[heading] = entry
92 else:
93 # split locale = "value" into 2 strings
94 if " = " not in line:
95 continue
96 locale, value = line.split(" = ")
98 if locale != line:
99 # replace en-US with en
100 locale = locale.replace("en-US", "en")
102 # use just anything inside the ""
103 assert value[0] == '"'
104 # Some entries span multiple lines.
105 # An entry will always end on a double quote.
106 while not value.endswith('"\n'):
107 value += source.readline()
108 value = value[1:-2]
110 # replace resource placeholder
111 value = value.replace("%PRODUCTNAME", o.productname)
113 locale = locale.replace("-", "_")
115 templates[heading]["translations"][locale] = value
117 source.close()
119 processed = 0
120 # process templates
121 for template, entries in templates.items():
122 outfilename = entries["outfile"]
124 # open the template file - ignore sections for which no
125 # templates exist
126 try:
127 template_file = io.open(outfilename, encoding="utf-8")
128 except OSError:
129 # string files processed one by one
130 if o.ext == "str":
131 continue
132 sys.exit(
133 f"Warning: No template found for item '{template}' : '{outfilename}'\n"
135 processed += 1
137 # open output file
138 tmpfilename = f"{outfilename}.tmp"
139 outfile = io.open(tmpfilename, "w", encoding="utf-8")
141 # emit the template to the output file
142 for line in template_file:
143 keyline = line
144 if keyline.startswith(entries["key"]):
145 # hack for Unity section
146 if entries["key"] == "UnityQuickList":
147 OUTKEY = "Name"
148 else:
149 OUTKEY = entries["key"]
150 keyline = OUTKEY + keyline[len(entries["key"]) :]
151 outfile.write(keyline)
152 if entries["key"] in line:
153 translations = entries["translations"]
154 for locale in sorted(translations.keys()):
155 value = translations.get(locale, None)
156 if value:
157 if o.ext in ("desktop", "str"):
158 if o.ext == "desktop":
159 value = encode_desktop_string(value)
160 outfile.write(f"{OUTKEY}[{locale}]={value}\n")
161 else:
162 outfile.write(f"\t[{locale}]{OUTKEY}={value}\n")
164 template_file.close()
166 outfile.close()
167 if os.path.exists(outfilename):
168 os.unlink(outfilename)
169 os.rename(tmpfilename, outfilename)
171 if o.ext == "str" and processed == 0:
172 sys.exit("Warning: No matching templates processed")