PRODUCTNAME -> %PRODUCTNAME
[LibreOffice.git] / solenv / bin / desktop-translate.py
blobedc377dab15bf610673e65632cba8746d1b42331
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("--key", dest="key")
58 parser.add_argument("--prefix", dest="prefix", default="")
59 parser.add_argument("--ext", dest="ext")
60 parser.add_argument("--template-dir", dest="template_dir", default=None)
61 parser.add_argument("ifile")
63 o = parser.parse_args()
65 if o.template_dir is None:
66 template_dir = f"{o.workdir}/{o.prefix}"
67 else:
68 template_dir = o.template_dir
70 # hack for unity section
71 if o.key == "UnityQuickList":
72 OUTKEY = "Name"
73 else:
74 OUTKEY = o.key
77 templates = {}
79 # open input file
80 source = io.open(o.ifile, encoding="utf-8")
82 template = None
84 # read ulf file
85 for line in source:
86 if line.strip() == "":
87 continue
88 if line[0] == "[":
89 template = line.split("]", 1)[0][1:]
90 entry = {}
91 # For every section in the specified ulf file there should exist
92 # a template file in $workdir ..
93 entry["outfile"] = f"{template_dir}{template}.{o.ext}"
94 entry["translations"] = {}
95 templates[template] = entry
96 else:
97 # split locale = "value" into 2 strings
98 if " = " not in line:
99 continue
100 locale, value = line.split(" = ")
102 if locale != line:
103 # replace en-US with en
104 locale = locale.replace("en-US", "en")
106 # use just anything inside the ""
107 assert value[0] == '"'
108 # Some entries span multiple lines.
109 # An entry will always end on a double quote.
110 while not value.endswith('"\n'):
111 value += source.readline()
112 value = value[1:-2]
114 # replace resource placeholder
115 value = value.replace("%PRODUCTNAME", o.productname)
117 locale = locale.replace("-", "_")
119 templates[template]["translations"][locale] = value
121 source.close()
123 processed = 0
124 # process templates
125 for template, entries in templates.items():
126 outfilename = entries["outfile"]
128 # open the template file - ignore sections for which no
129 # templates exist
130 try:
131 template_file = io.open(outfilename, encoding="utf-8")
132 except OSError:
133 # string files processed one by one
134 if o.ext == "str":
135 continue
136 sys.exit(
137 f"Warning: No template found for item '{template}' : '{outfilename}'\n"
139 processed += 1
141 # open output file
142 tmpfilename = f"{outfilename}.tmp"
143 outfile = io.open(tmpfilename, "w", encoding="utf-8")
145 # emit the template to the output file
146 for line in template_file:
147 keyline = line
148 if keyline.startswith(o.key):
149 keyline = OUTKEY + keyline[len(o.key) :]
150 outfile.write(keyline)
151 if o.key in line:
152 translations = entries["translations"]
153 for locale in sorted(translations.keys()):
154 value = translations.get(locale, None)
155 # print "locale is $locale\n";
156 # print "value is $value\n";
157 if value:
158 if o.ext in ("desktop", "str"):
159 if o.ext == "desktop":
160 value = encode_desktop_string(value)
161 outfile.write(f"{OUTKEY}[{locale}]={value}\n")
162 else:
163 outfile.write(f"\t[{locale}]{OUTKEY}={value}\n")
165 template_file.close()
167 outfile.close()
168 if os.path.exists(outfilename):
169 os.unlink(outfilename)
170 os.rename(tmpfilename, outfilename)
172 if o.ext == "str" and processed == 0:
173 sys.exit("Warning: No matching templates processed")