Commits:
-
d9fe34de
by Henry Wilkes at 2025-02-18T10:08:15+00:00
fixup! Add CI for Tor Browser
TB 43446: Include alpha and nightly branding in the translation CI.
-
34838694
by Henry Wilkes at 2025-02-18T10:10:32+00:00
fixup! BB 42305: Add script to combine translation files across versions.
TB 43446: Allow the combine translation script to select some branding
strings from different files.
-
6fa642ea
by Henry Wilkes at 2025-02-18T10:10:33+00:00
fixup! TB 2176: Rebrand Firefox to TorBrowser
TB 43446: Change the branding name for the alpha and nightly releases.
13 changed files:
Changes:
.gitlab/ci/jobs/update-translations.yml
| ... |
... |
@@ -17,12 +17,48 @@ |
|
17
|
17
|
{
|
|
18
|
18
|
"name": "brand.ftl",
|
|
19
|
19
|
"where": ["browser/branding/tb-release", "toolkit/torbutton"],
|
|
|
20
|
+ "branding": {
|
|
|
21
|
+ "versions": [
|
|
|
22
|
+ {
|
|
|
23
|
+ "name": "Alpha",
|
|
|
24
|
+ "suffix": "_alpha",
|
|
|
25
|
+ "where": ["browser/branding/tb-alpha"]
|
|
|
26
|
+ },
|
|
|
27
|
+ {
|
|
|
28
|
+ "name": "Nightly",
|
|
|
29
|
+ "suffix": "_nightly",
|
|
|
30
|
+ "where": ["browser/branding/tb-nightly"]
|
|
|
31
|
+ }
|
|
|
32
|
+ ],
|
|
|
33
|
+ "ids": [
|
|
|
34
|
+ "-brand-short-name",
|
|
|
35
|
+ "-brand-full-name"
|
|
|
36
|
+ ]
|
|
|
37
|
+ },
|
|
20
|
38
|
"branch": "tor-browser",
|
|
21
|
39
|
"directory": "branding"
|
|
22
|
40
|
},
|
|
23
|
41
|
{
|
|
24
|
42
|
"name": "brand.properties",
|
|
25
|
43
|
"where": ["browser/branding/tb-release", "toolkit/torbutton"],
|
|
|
44
|
+ "branding": {
|
|
|
45
|
+ "versions": [
|
|
|
46
|
+ {
|
|
|
47
|
+ "name": "Alpha",
|
|
|
48
|
+ "suffix": "_alpha",
|
|
|
49
|
+ "where": ["browser/branding/tb-alpha"]
|
|
|
50
|
+ },
|
|
|
51
|
+ {
|
|
|
52
|
+ "name": "Nightly",
|
|
|
53
|
+ "suffix": "_nightly",
|
|
|
54
|
+ "where": ["browser/branding/tb-nightly"]
|
|
|
55
|
+ }
|
|
|
56
|
+ ],
|
|
|
57
|
+ "ids": [
|
|
|
58
|
+ "brandShortName",
|
|
|
59
|
+ "brandFullName"
|
|
|
60
|
+ ]
|
|
|
61
|
+ },
|
|
26
|
62
|
"branch": "tor-browser"
|
|
27
|
63
|
},
|
|
28
|
64
|
{ "name": "tor-browser.ftl", "branch": "tor-browser" },
|
browser/branding/tb-alpha/locales/en-US/brand.ftl
| ... |
... |
@@ -2,12 +2,16 @@ |
|
2
|
2
|
# that is used by Firefox) to avoid picking up the -brand-short-name values
|
|
3
|
3
|
# that Mozilla includes in the Firefox language packs.
|
|
4
|
4
|
|
|
|
5
|
+# The shortened application name for Tor Browser. Shared between versions.
|
|
5
|
6
|
-brand-shorter-name = Tor Browser
|
|
6
|
|
--brand-short-name = Tor Browser
|
|
7
|
|
--brand-full-name = Tor Browser
|
|
|
7
|
+# The default application name for the "alpha" release.
|
|
|
8
|
+-brand-short-name = Tor Browser Alpha
|
|
|
9
|
+# The full application name for the "alpha" release.
|
|
|
10
|
+-brand-full-name = Tor Browser Alpha
|
|
8
|
11
|
# This brand name can be used in messages where the product name needs to
|
|
9
|
12
|
# remain unchanged across different versions (Nightly, Beta, etc.).
|
|
10
|
13
|
-brand-product-name = Tor Browser
|
|
|
14
|
+# The name of the Tor Project organisation.
|
|
11
|
15
|
-vendor-short-name = Tor Project
|
|
12
|
16
|
# "Tor" is a trademark names, so should not be translated (not including the quote marks, which can be localized).
|
|
13
|
17
|
# "The Tor Project, Inc." is an organisation name.
|
browser/branding/tb-alpha/locales/en-US/brand.properties
| ... |
... |
@@ -3,6 +3,9 @@ |
|
3
|
3
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
4
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
5
|
|
|
|
6
|
+# The shortened application name for Tor Browser. Shared between versions.
|
|
6
|
7
|
brandShorterName=Tor Browser
|
|
7
|
|
-brandShortName=Tor Browser
|
|
8
|
|
-brandFullName=Tor Browser |
|
|
8
|
+# The default application name for the "alpha" release.
|
|
|
9
|
+brandShortName=Tor Browser Alpha
|
|
|
10
|
+# The full application name for the "alpha" release.
|
|
|
11
|
+brandFullName=Tor Browser Alpha |
browser/branding/tb-nightly/locales/en-US/brand.ftl
| ... |
... |
@@ -2,12 +2,16 @@ |
|
2
|
2
|
# that is used by Firefox) to avoid picking up the -brand-short-name values
|
|
3
|
3
|
# that Mozilla includes in the Firefox language packs.
|
|
4
|
4
|
|
|
|
5
|
+# The shortened application name for Tor Browser. Shared between versions.
|
|
5
|
6
|
-brand-shorter-name = Tor Browser
|
|
6
|
|
--brand-short-name = Tor Browser
|
|
7
|
|
--brand-full-name = Tor Browser
|
|
|
7
|
+# The default application name for the "nightly" release.
|
|
|
8
|
+-brand-short-name = Tor Browser Nightly
|
|
|
9
|
+# The full application name for the "nightly" release.
|
|
|
10
|
+-brand-full-name = Tor Browser Nightly
|
|
8
|
11
|
# This brand name can be used in messages where the product name needs to
|
|
9
|
12
|
# remain unchanged across different versions (Nightly, Beta, etc.).
|
|
10
|
13
|
-brand-product-name = Tor Browser
|
|
|
14
|
+# The name of the Tor Project organisation.
|
|
11
|
15
|
-vendor-short-name = Tor Project
|
|
12
|
16
|
# "Tor" is a trademark names, so should not be translated (not including the quote marks, which can be localized).
|
|
13
|
17
|
# "The Tor Project, Inc." is an organisation name.
|
browser/branding/tb-nightly/locales/en-US/brand.properties
| ... |
... |
@@ -3,6 +3,9 @@ |
|
3
|
3
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
4
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
5
|
|
|
|
6
|
+# The shortened application name for Tor Browser. Shared between versions.
|
|
6
|
7
|
brandShorterName=Tor Browser
|
|
7
|
|
-brandShortName=Tor Browser
|
|
8
|
|
-brandFullName=Tor Browser |
|
|
8
|
+# The default application name for the "nightly" release.
|
|
|
9
|
+brandShortName=Tor Browser Nightly
|
|
|
10
|
+# The full application name for the "nightly" release.
|
|
|
11
|
+brandFullName=Tor Browser Nightly |
browser/branding/tb-release/locales/en-US/brand.ftl
| ... |
... |
@@ -2,12 +2,16 @@ |
|
2
|
2
|
# that is used by Firefox) to avoid picking up the -brand-short-name values
|
|
3
|
3
|
# that Mozilla includes in the Firefox language packs.
|
|
4
|
4
|
|
|
|
5
|
+# The shortened application name for Tor Browser. Shared between versions.
|
|
5
|
6
|
-brand-shorter-name = Tor Browser
|
|
|
7
|
+# The default application name for the stable release.
|
|
6
|
8
|
-brand-short-name = Tor Browser
|
|
|
9
|
+# The full application name for the stable release.
|
|
7
|
10
|
-brand-full-name = Tor Browser
|
|
8
|
11
|
# This brand name can be used in messages where the product name needs to
|
|
9
|
12
|
# remain unchanged across different versions (Nightly, Beta, etc.).
|
|
10
|
13
|
-brand-product-name = Tor Browser
|
|
|
14
|
+# The name of the Tor Project organisation.
|
|
11
|
15
|
-vendor-short-name = Tor Project
|
|
12
|
16
|
# "Tor" is a trademark names, so should not be translated (not including the quote marks, which can be localized).
|
|
13
|
17
|
# "The Tor Project, Inc." is an organisation name.
|
browser/branding/tb-release/locales/en-US/brand.properties
| ... |
... |
@@ -3,6 +3,9 @@ |
|
3
|
3
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
4
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
5
|
|
|
|
6
|
+# The shortened application name for Tor Browser. Shared between versions.
|
|
6
|
7
|
brandShorterName=Tor Browser
|
|
|
8
|
+# The default application name for the stable release.
|
|
7
|
9
|
brandShortName=Tor Browser
|
|
|
10
|
+# The full application name for the stable release.
|
|
8
|
11
|
brandFullName=Tor Browser |
tools/base-browser/l10n/combine-translation-versions.py
| ... |
... |
@@ -306,9 +306,34 @@ for file_dict in json.loads(args.files): |
|
306
|
306
|
f"{current_file.path} : {stable_file.path}"
|
|
307
|
307
|
)
|
|
308
|
308
|
|
|
|
309
|
+ content = None if current_file is None else current_file.content
|
|
|
310
|
+
|
|
|
311
|
+ # If we have a branding file, we want to also include strings from the other
|
|
|
312
|
+ # branding directories that differ from the stable release.
|
|
|
313
|
+ # The strings that *differ* per release should be specified in
|
|
|
314
|
+ # file_dict["branding"]["ids"]. These strings will be copied from the other
|
|
|
315
|
+ # release's branding directory, with an addition suffix added to their ID,
|
|
|
316
|
+ # as specified in the version_dict["suffix"].
|
|
|
317
|
+ branding = file_dict.get("branding", None)
|
|
|
318
|
+ if branding:
|
|
|
319
|
+ include_ids = branding["ids"]
|
|
|
320
|
+ for version_dict in branding["versions"]:
|
|
|
321
|
+ branding_dirs = version_dict.get("where", None)
|
|
|
322
|
+ branding_file = current_branch.get_file(name, branding_dirs)
|
|
|
323
|
+ if branding_file is None:
|
|
|
324
|
+ raise Exception(f"{name} does not exist in {branding_dirs}")
|
|
|
325
|
+ content = combine_files(
|
|
|
326
|
+ name,
|
|
|
327
|
+ content,
|
|
|
328
|
+ branding_file.content,
|
|
|
329
|
+ f'{version_dict["name"]} Release.',
|
|
|
330
|
+ include_ids,
|
|
|
331
|
+ version_dict["suffix"],
|
|
|
332
|
+ )
|
|
|
333
|
+
|
|
309
|
334
|
content = combine_files(
|
|
310
|
335
|
name,
|
|
311
|
|
- None if current_file is None else current_file.content,
|
|
|
336
|
+ content,
|
|
312
|
337
|
None if stable_file is None else stable_file.content,
|
|
313
|
338
|
f"Will be unused in {current_branch.browser_version_name}!",
|
|
314
|
339
|
)
|
tools/base-browser/l10n/combine/combine.py
| ... |
... |
@@ -14,26 +14,32 @@ if TYPE_CHECKING: |
|
14
|
14
|
|
|
15
|
15
|
def combine_files(
|
|
16
|
16
|
filename: str,
|
|
17
|
|
- new_content: str | None,
|
|
18
|
|
- old_content: str | None,
|
|
|
17
|
+ primary_content: str | None,
|
|
|
18
|
+ alternative_content: str | None,
|
|
19
|
19
|
comment_prefix: str,
|
|
|
20
|
+ include_ids: list[str] | None = None,
|
|
|
21
|
+ alternative_suffix: str = "",
|
|
20
|
22
|
) -> str | None:
|
|
21
|
23
|
"""Combine two translation files into one to include all strings from both.
|
|
22
|
|
- The new content is presented first, and any strings only found in the old
|
|
23
|
|
- content are placed at the end with an additional comment.
|
|
|
24
|
+ The primary content is presented first, followed by the alternative content
|
|
|
25
|
+ at the end with an additional comment.
|
|
24
|
26
|
|
|
25
|
27
|
:param filename: The filename for the file, determines the format.
|
|
26
|
|
- :param new_content: The new content for the file, or None if it has been
|
|
27
|
|
- deleted.
|
|
28
|
|
- :param old_content: The old content for the file, or None if it did not
|
|
29
|
|
- exist before.
|
|
30
|
|
- :comment_prefix: A comment to include for any strings that are only found in
|
|
31
|
|
- the old content. This will be placed before any other comments for the
|
|
32
|
|
- string.
|
|
|
28
|
+ :param primary_content: The primary content for the file, or None if it does
|
|
|
29
|
+ not exist.
|
|
|
30
|
+ :param alternative_content: The alternative content for the file, or None if
|
|
|
31
|
+ it does not exist.
|
|
|
32
|
+ :param comment_prefix: A comment to include for any strings that are
|
|
|
33
|
+ appended to the content. This will be placed before any other comments for
|
|
|
34
|
+ the string.
|
|
|
35
|
+ :param include_ids: String IDs from `alternative_content` we want to
|
|
|
36
|
+ include. If this is `None` then we include all strings that do not already
|
|
|
37
|
+ have a matching ID in `primary_content`.
|
|
|
38
|
+ :param duplicate_suffix: The suffix to apply to the alternative IDs.
|
|
33
|
39
|
|
|
34
|
40
|
:returns: The combined content, or None if both given contents are None.
|
|
35
|
41
|
"""
|
|
36
|
|
- if new_content is None and old_content is None:
|
|
|
42
|
+ if primary_content is None and alternative_content is None:
|
|
37
|
43
|
return None
|
|
38
|
44
|
|
|
39
|
45
|
# getParser from compare_locale returns the same instance for the same file
|
| ... |
... |
@@ -41,7 +47,7 @@ def combine_files( |
|
41
|
47
|
parser = getParser(filename)
|
|
42
|
48
|
|
|
43
|
49
|
is_android = filename.endswith(".xml")
|
|
44
|
|
- if new_content is None:
|
|
|
50
|
+ if primary_content is None:
|
|
45
|
51
|
if is_android:
|
|
46
|
52
|
# File was deleted, add some document parts.
|
|
47
|
53
|
content_start = (
|
| ... |
... |
@@ -54,7 +60,7 @@ def combine_files( |
|
54
|
60
|
content_end = ""
|
|
55
|
61
|
existing_keys = []
|
|
56
|
62
|
else:
|
|
57
|
|
- parser.readUnicode(new_content)
|
|
|
63
|
+ parser.readUnicode(primary_content)
|
|
58
|
64
|
|
|
59
|
65
|
# Start with the same content as the current file.
|
|
60
|
66
|
# For android strings, we want to keep the final "</resources>" until after.
|
| ... |
... |
@@ -96,8 +102,8 @@ def combine_files( |
|
96
|
102
|
|
|
97
|
103
|
entry_iter: Iterable[Any] = ()
|
|
98
|
104
|
# If the file does not exist in the old branch, don't make any additions.
|
|
99
|
|
- if old_content is not None:
|
|
100
|
|
- parser.readUnicode(old_content)
|
|
|
105
|
+ if alternative_content is not None:
|
|
|
106
|
+ parser.readUnicode(alternative_content)
|
|
101
|
107
|
entry_iter = parser.walk(only_localizable=False)
|
|
102
|
108
|
for entry in entry_iter:
|
|
103
|
109
|
if isinstance(entry, Junk):
|
| ... |
... |
@@ -134,13 +140,19 @@ def combine_files( |
|
134
|
140
|
if not isinstance(entry, Entity):
|
|
135
|
141
|
raise ValueError(f"Unexpected type: {entry.__class__.__name__}")
|
|
136
|
142
|
|
|
137
|
|
- if entry.key in existing_keys:
|
|
138
|
|
- # Already included this string in the new translation file.
|
|
|
143
|
+ if include_ids is None:
|
|
|
144
|
+ # We include the entry if it is not already included.
|
|
|
145
|
+ include_entry = entry.key not in existing_keys
|
|
|
146
|
+ else:
|
|
|
147
|
+ # We include the entry if it is in our list.
|
|
|
148
|
+ include_entry = entry.key in include_ids
|
|
|
149
|
+ if not include_entry:
|
|
139
|
150
|
# Drop the gathered comments for this Entity.
|
|
140
|
151
|
stacked_comments.clear()
|
|
141
|
152
|
continue
|
|
142
|
153
|
|
|
143
|
154
|
if isinstance(entry, FluentEntity):
|
|
|
155
|
+ id_regex = rf"^({re.escape(entry.key)})( *=)"
|
|
144
|
156
|
if fluent_group_comment is not None:
|
|
145
|
157
|
# We have a found GroupComment which has not been included yet.
|
|
146
|
158
|
# All following Entity's will be under its scope, until the next
|
| ... |
... |
@@ -149,12 +161,15 @@ def combine_files( |
|
149
|
161
|
# Added GroupComment, so don't need to add again.
|
|
150
|
162
|
fluent_group_comment = None
|
|
151
|
163
|
elif isinstance(entry, DTDEntity):
|
|
|
164
|
+ id_regex = rf"^(\s*<!ENTITY\s*{re.escape(entry.key)})(\s)"
|
|
152
|
165
|
# Include our additional comment before we print the rest for this
|
|
153
|
166
|
# Entity.
|
|
154
|
167
|
additions.append(f"<!-- LOCALIZATION NOTE: {comment_prefix} -->")
|
|
155
|
168
|
elif isinstance(entry, PropertiesEntity):
|
|
|
169
|
+ id_regex = rf"^({re.escape(entry.key)})( *=)"
|
|
156
|
170
|
additions.append(f"# {comment_prefix}")
|
|
157
|
171
|
elif isinstance(entry, AndroidEntity):
|
|
|
172
|
+ id_regex = rf'^(\s*<string\s[^>]*name="{re.escape(entry.key)})(")'
|
|
158
|
173
|
additions.append(f"<!-- {comment_prefix} -->")
|
|
159
|
174
|
else:
|
|
160
|
175
|
raise ValueError(f"Unexpected Entity type: {entry.__class__.__name__}")
|
| ... |
... |
@@ -162,7 +177,17 @@ def combine_files( |
|
162
|
177
|
# Add any other comment lines that came directly before this Entity.
|
|
163
|
178
|
additions.extend(stacked_comments)
|
|
164
|
179
|
stacked_comments.clear()
|
|
165
|
|
- additions.append(entry.all)
|
|
|
180
|
+ entry_content = entry.all
|
|
|
181
|
+ if alternative_suffix:
|
|
|
182
|
+ # NOTE: compare_locales does not allow us to set the entry.key
|
|
|
183
|
+ # value. Instead we use a regular _expression_ to append the suffix to
|
|
|
184
|
+ # the expected key.
|
|
|
185
|
+ entry_content, count = re.subn(
|
|
|
186
|
+ id_regex, rf"\1{alternative_suffix}\2", entry_content, flags=re.M
|
|
|
187
|
+ )
|
|
|
188
|
+ if count != 1:
|
|
|
189
|
+ raise ValueError(f"Failed to substitute the ID for {entry.key}")
|
|
|
190
|
+ additions.append(entry_content)
|
|
166
|
191
|
|
|
167
|
192
|
content_middle = ""
|
|
168
|
193
|
|
tools/base-browser/l10n/combine/tests/test_android.py
| ... |
... |
@@ -24,6 +24,20 @@ def assert_result(new_content, old_content, expect): |
|
24
|
24
|
)
|
|
25
|
25
|
|
|
26
|
26
|
|
|
|
27
|
+def assert_alternative(content, alternative_content, alternative_ids, expect):
|
|
|
28
|
+ content = wrap_in_xml(content)
|
|
|
29
|
+ alternative_content = wrap_in_xml(alternative_content)
|
|
|
30
|
+ expect = wrap_in_xml(expect)
|
|
|
31
|
+ assert expect == combine_files(
|
|
|
32
|
+ "test_strings.xml",
|
|
|
33
|
+ content,
|
|
|
34
|
+ alternative_content,
|
|
|
35
|
+ "ALTERNATIVE STRING",
|
|
|
36
|
+ alternative_ids,
|
|
|
37
|
+ "_alt",
|
|
|
38
|
+ )
|
|
|
39
|
+
|
|
|
40
|
+
|
|
27
|
41
|
def test_combine_empty():
|
|
28
|
42
|
assert_result(None, None, None)
|
|
29
|
43
|
|
| ... |
... |
@@ -328,3 +342,74 @@ def test_removed_string_with_comment(): |
|
328
|
342
|
<string name="removed_4">Fourth removed</string>
|
|
329
|
343
|
""",
|
|
330
|
344
|
)
|
|
|
345
|
+
|
|
|
346
|
+
|
|
|
347
|
+def test_alternatives():
|
|
|
348
|
+ assert_alternative(
|
|
|
349
|
+ """\
|
|
|
350
|
+ <string name="string_1">First string</string>
|
|
|
351
|
+ """,
|
|
|
352
|
+ """\
|
|
|
353
|
+ <string name="string_1">Alternative string</string>
|
|
|
354
|
+ """,
|
|
|
355
|
+ ["string_1"],
|
|
|
356
|
+ """\
|
|
|
357
|
+ <string name="string_1">First string</string>
|
|
|
358
|
+
|
|
|
359
|
+ <!-- ALTERNATIVE STRING -->
|
|
|
360
|
+ <string name="string_1_alt">Alternative string</string>
|
|
|
361
|
+ """,
|
|
|
362
|
+ )
|
|
|
363
|
+ assert_alternative(
|
|
|
364
|
+ """\
|
|
|
365
|
+ <!-- Comment 1 -->
|
|
|
366
|
+ <string name="string_1">First string</string>
|
|
|
367
|
+ <!-- Comment 2 -->
|
|
|
368
|
+ <string name="string_2">Second string</string>
|
|
|
369
|
+ <string name="string_3">Third string</string>
|
|
|
370
|
+ """,
|
|
|
371
|
+ """\
|
|
|
372
|
+ <string name="string_1">First string</string>
|
|
|
373
|
+ <!-- Alt comment -->
|
|
|
374
|
+ <string name="string_2">Alternative string</string>
|
|
|
375
|
+ <string name="string_3">Third string different</string>
|
|
|
376
|
+ <string name="string_4">Other string</string>
|
|
|
377
|
+ """,
|
|
|
378
|
+ ["string_2"],
|
|
|
379
|
+ """\
|
|
|
380
|
+ <!-- Comment 1 -->
|
|
|
381
|
+ <string name="string_1">First string</string>
|
|
|
382
|
+ <!-- Comment 2 -->
|
|
|
383
|
+ <string name="string_2">Second string</string>
|
|
|
384
|
+ <string name="string_3">Third string</string>
|
|
|
385
|
+
|
|
|
386
|
+ <!-- ALTERNATIVE STRING -->
|
|
|
387
|
+ <!-- Alt comment -->
|
|
|
388
|
+ <string name="string_2_alt">Alternative string</string>
|
|
|
389
|
+ """,
|
|
|
390
|
+ )
|
|
|
391
|
+ assert_alternative(
|
|
|
392
|
+ """\
|
|
|
393
|
+ <string name="string_1">First string</string>
|
|
|
394
|
+ <string name="string_2">Second string</string>
|
|
|
395
|
+ <string name="string_3">Third string</string>
|
|
|
396
|
+ """,
|
|
|
397
|
+ """\
|
|
|
398
|
+ <string name="string_1">Alternative string</string>
|
|
|
399
|
+ <string name="string_3">Third string</string>
|
|
|
400
|
+ <!-- comment -->
|
|
|
401
|
+ <string name="string_4">Other string</string>
|
|
|
402
|
+ """,
|
|
|
403
|
+ ["string_1", "string_4"],
|
|
|
404
|
+ """\
|
|
|
405
|
+ <string name="string_1">First string</string>
|
|
|
406
|
+ <string name="string_2">Second string</string>
|
|
|
407
|
+ <string name="string_3">Third string</string>
|
|
|
408
|
+
|
|
|
409
|
+ <!-- ALTERNATIVE STRING -->
|
|
|
410
|
+ <string name="string_1_alt">Alternative string</string>
|
|
|
411
|
+ <!-- ALTERNATIVE STRING -->
|
|
|
412
|
+ <!-- comment -->
|
|
|
413
|
+ <string name="string_4_alt">Other string</string>
|
|
|
414
|
+ """,
|
|
|
415
|
+ ) |
tools/base-browser/l10n/combine/tests/test_dtd.py
| ... |
... |
@@ -16,6 +16,23 @@ def assert_result(new_content, old_content, expect): |
|
16
|
16
|
)
|
|
17
|
17
|
|
|
18
|
18
|
|
|
|
19
|
+def assert_alternative(content, alternative_content, alternative_ids, expect):
|
|
|
20
|
+ if content is not None:
|
|
|
21
|
+ content = textwrap.dedent(content)
|
|
|
22
|
+ if alternative_content is not None:
|
|
|
23
|
+ alternative_content = textwrap.dedent(alternative_content)
|
|
|
24
|
+ if expect is not None:
|
|
|
25
|
+ expect = textwrap.dedent(expect)
|
|
|
26
|
+ assert expect == combine_files(
|
|
|
27
|
+ "test.dtd",
|
|
|
28
|
+ content,
|
|
|
29
|
+ alternative_content,
|
|
|
30
|
+ "ALTERNATIVE STRING",
|
|
|
31
|
+ alternative_ids,
|
|
|
32
|
+ ".alt",
|
|
|
33
|
+ )
|
|
|
34
|
+
|
|
|
35
|
+
|
|
19
|
36
|
def test_combine_empty():
|
|
20
|
37
|
assert_result(None, None, None)
|
|
21
|
38
|
|
| ... |
... |
@@ -323,3 +340,74 @@ def test_removed_string_with_comment(): |
|
323
|
340
|
<!ENTITY removed.4 "Fourth removed">
|
|
324
|
341
|
""",
|
|
325
|
342
|
)
|
|
|
343
|
+
|
|
|
344
|
+
|
|
|
345
|
+def test_alternatives():
|
|
|
346
|
+ assert_alternative(
|
|
|
347
|
+ """\
|
|
|
348
|
+ <!ENTITY string.1 "First string">
|
|
|
349
|
+ """,
|
|
|
350
|
+ """\
|
|
|
351
|
+ <!ENTITY string.1 "Alternative string">
|
|
|
352
|
+ """,
|
|
|
353
|
+ ["string.1"],
|
|
|
354
|
+ """\
|
|
|
355
|
+ <!ENTITY string.1 "First string">
|
|
|
356
|
+
|
|
|
357
|
+ <!-- LOCALIZATION NOTE: ALTERNATIVE STRING -->
|
|
|
358
|
+ <!ENTITY string.1.alt "Alternative string">
|
|
|
359
|
+ """,
|
|
|
360
|
+ )
|
|
|
361
|
+ assert_alternative(
|
|
|
362
|
+ """\
|
|
|
363
|
+ <!-- LOCALIZATION NOTE: Comment 1 -->
|
|
|
364
|
+ <!ENTITY string.1 "First string">
|
|
|
365
|
+ <!-- LOCALIZATION NOTE: Comment 2 -->
|
|
|
366
|
+ <!ENTITY string.2 "Second string">
|
|
|
367
|
+ <!ENTITY string.3 "Third string">
|
|
|
368
|
+ """,
|
|
|
369
|
+ """\
|
|
|
370
|
+ <!ENTITY string.1 "First string">
|
|
|
371
|
+ <!-- LOCALIZATION NOTE: Alt comment -->
|
|
|
372
|
+ <!ENTITY string.2 "Alternative string">
|
|
|
373
|
+ <!ENTITY string.3 "Third string different">
|
|
|
374
|
+ <!ENTITY string.4 "Other string">
|
|
|
375
|
+ """,
|
|
|
376
|
+ ["string.2"],
|
|
|
377
|
+ """\
|
|
|
378
|
+ <!-- LOCALIZATION NOTE: Comment 1 -->
|
|
|
379
|
+ <!ENTITY string.1 "First string">
|
|
|
380
|
+ <!-- LOCALIZATION NOTE: Comment 2 -->
|
|
|
381
|
+ <!ENTITY string.2 "Second string">
|
|
|
382
|
+ <!ENTITY string.3 "Third string">
|
|
|
383
|
+
|
|
|
384
|
+ <!-- LOCALIZATION NOTE: ALTERNATIVE STRING -->
|
|
|
385
|
+ <!-- LOCALIZATION NOTE: Alt comment -->
|
|
|
386
|
+ <!ENTITY string.2.alt "Alternative string">
|
|
|
387
|
+ """,
|
|
|
388
|
+ )
|
|
|
389
|
+ assert_alternative(
|
|
|
390
|
+ """\
|
|
|
391
|
+ <!ENTITY string.1 "First string">
|
|
|
392
|
+ <!ENTITY string.2 "Second string">
|
|
|
393
|
+ <!ENTITY string.3 "Third string">
|
|
|
394
|
+ """,
|
|
|
395
|
+ """\
|
|
|
396
|
+ <!ENTITY string.1 "Alternative string">
|
|
|
397
|
+ <!ENTITY string.3 "Third string">
|
|
|
398
|
+ <!-- LOCALIZATION NOTE: comment -->
|
|
|
399
|
+ <!ENTITY string.4 "Other string">
|
|
|
400
|
+ """,
|
|
|
401
|
+ ["string.1", "string.4"],
|
|
|
402
|
+ """\
|
|
|
403
|
+ <!ENTITY string.1 "First string">
|
|
|
404
|
+ <!ENTITY string.2 "Second string">
|
|
|
405
|
+ <!ENTITY string.3 "Third string">
|
|
|
406
|
+
|
|
|
407
|
+ <!-- LOCALIZATION NOTE: ALTERNATIVE STRING -->
|
|
|
408
|
+ <!ENTITY string.1.alt "Alternative string">
|
|
|
409
|
+ <!-- LOCALIZATION NOTE: ALTERNATIVE STRING -->
|
|
|
410
|
+ <!-- LOCALIZATION NOTE: comment -->
|
|
|
411
|
+ <!ENTITY string.4.alt "Other string">
|
|
|
412
|
+ """,
|
|
|
413
|
+ ) |
tools/base-browser/l10n/combine/tests/test_fluent.py
| ... |
... |
@@ -16,6 +16,23 @@ def assert_result(new_content, old_content, expect): |
|
16
|
16
|
)
|
|
17
|
17
|
|
|
18
|
18
|
|
|
|
19
|
+def assert_alternative(content, alternative_content, alternative_ids, expect):
|
|
|
20
|
+ if content is not None:
|
|
|
21
|
+ content = textwrap.dedent(content)
|
|
|
22
|
+ if alternative_content is not None:
|
|
|
23
|
+ alternative_content = textwrap.dedent(alternative_content)
|
|
|
24
|
+ if expect is not None:
|
|
|
25
|
+ expect = textwrap.dedent(expect)
|
|
|
26
|
+ assert expect == combine_files(
|
|
|
27
|
+ "test.ftl",
|
|
|
28
|
+ content,
|
|
|
29
|
+ alternative_content,
|
|
|
30
|
+ "ALTERNATIVE STRING",
|
|
|
31
|
+ alternative_ids,
|
|
|
32
|
+ "-alt",
|
|
|
33
|
+ )
|
|
|
34
|
+
|
|
|
35
|
+
|
|
19
|
36
|
def test_combine_empty():
|
|
20
|
37
|
assert_result(None, None, None)
|
|
21
|
38
|
|
| ... |
... |
@@ -342,3 +359,119 @@ def test_removed_string_with_comment(): |
|
342
|
359
|
removed-4 = Fourth removed
|
|
343
|
360
|
""",
|
|
344
|
361
|
)
|
|
|
362
|
+
|
|
|
363
|
+
|
|
|
364
|
+def test_alternatives():
|
|
|
365
|
+ assert_alternative(
|
|
|
366
|
+ """\
|
|
|
367
|
+ string-1 = First string
|
|
|
368
|
+ .title = hello
|
|
|
369
|
+ """,
|
|
|
370
|
+ """\
|
|
|
371
|
+ string-1 = Alternative string
|
|
|
372
|
+ .title = different
|
|
|
373
|
+ """,
|
|
|
374
|
+ ["string-1"],
|
|
|
375
|
+ """\
|
|
|
376
|
+ string-1 = First string
|
|
|
377
|
+ .title = hello
|
|
|
378
|
+
|
|
|
379
|
+
|
|
|
380
|
+ ## ALTERNATIVE STRING
|
|
|
381
|
+
|
|
|
382
|
+ string-1-alt = Alternative string
|
|
|
383
|
+ .title = different
|
|
|
384
|
+ """,
|
|
|
385
|
+ )
|
|
|
386
|
+ assert_alternative(
|
|
|
387
|
+ """\
|
|
|
388
|
+ string-1 = First string
|
|
|
389
|
+ .title = hello
|
|
|
390
|
+ """,
|
|
|
391
|
+ """\
|
|
|
392
|
+ string-1 = Alternative string
|
|
|
393
|
+ """,
|
|
|
394
|
+ ["string-1"],
|
|
|
395
|
+ """\
|
|
|
396
|
+ string-1 = First string
|
|
|
397
|
+ .title = hello
|
|
|
398
|
+
|
|
|
399
|
+
|
|
|
400
|
+ ## ALTERNATIVE STRING
|
|
|
401
|
+
|
|
|
402
|
+ string-1-alt = Alternative string
|
|
|
403
|
+ """,
|
|
|
404
|
+ )
|
|
|
405
|
+ assert_alternative(
|
|
|
406
|
+ """\
|
|
|
407
|
+ -term-1 = First string
|
|
|
408
|
+ """,
|
|
|
409
|
+ """\
|
|
|
410
|
+ -term-1 = Alternative string
|
|
|
411
|
+ """,
|
|
|
412
|
+ ["-term-1"],
|
|
|
413
|
+ """\
|
|
|
414
|
+ -term-1 = First string
|
|
|
415
|
+
|
|
|
416
|
+
|
|
|
417
|
+ ## ALTERNATIVE STRING
|
|
|
418
|
+
|
|
|
419
|
+ -term-1-alt = Alternative string
|
|
|
420
|
+ """,
|
|
|
421
|
+ )
|
|
|
422
|
+ assert_alternative(
|
|
|
423
|
+ """\
|
|
|
424
|
+ # Comment 1
|
|
|
425
|
+ string-1 = First string
|
|
|
426
|
+ # Comment 2
|
|
|
427
|
+ string-2 = Second string
|
|
|
428
|
+ string-3 = Third string
|
|
|
429
|
+ """,
|
|
|
430
|
+ """\
|
|
|
431
|
+ string-1 = First string
|
|
|
432
|
+ # Alt comment
|
|
|
433
|
+ string-2 = Alternative string
|
|
|
434
|
+ string-3 = Third string different
|
|
|
435
|
+ string-4 = Other string
|
|
|
436
|
+ """,
|
|
|
437
|
+ ["string-2"],
|
|
|
438
|
+ """\
|
|
|
439
|
+ # Comment 1
|
|
|
440
|
+ string-1 = First string
|
|
|
441
|
+ # Comment 2
|
|
|
442
|
+ string-2 = Second string
|
|
|
443
|
+ string-3 = Third string
|
|
|
444
|
+
|
|
|
445
|
+
|
|
|
446
|
+ ## ALTERNATIVE STRING
|
|
|
447
|
+
|
|
|
448
|
+ # Alt comment
|
|
|
449
|
+ string-2-alt = Alternative string
|
|
|
450
|
+ """,
|
|
|
451
|
+ )
|
|
|
452
|
+ assert_alternative(
|
|
|
453
|
+ """\
|
|
|
454
|
+ string-1 = First string
|
|
|
455
|
+ string-2 = Second string
|
|
|
456
|
+ string-3 = Third string
|
|
|
457
|
+ """,
|
|
|
458
|
+ """\
|
|
|
459
|
+ string-1 = Alternative string
|
|
|
460
|
+ string-3 = Third string
|
|
|
461
|
+ # comment
|
|
|
462
|
+ -string-4 = Other string
|
|
|
463
|
+ """,
|
|
|
464
|
+ ["string-1", "-string-4"],
|
|
|
465
|
+ """\
|
|
|
466
|
+ string-1 = First string
|
|
|
467
|
+ string-2 = Second string
|
|
|
468
|
+ string-3 = Third string
|
|
|
469
|
+
|
|
|
470
|
+
|
|
|
471
|
+ ## ALTERNATIVE STRING
|
|
|
472
|
+
|
|
|
473
|
+ string-1-alt = Alternative string
|
|
|
474
|
+ # comment
|
|
|
475
|
+ -string-4-alt = Other string
|
|
|
476
|
+ """,
|
|
|
477
|
+ ) |
tools/base-browser/l10n/combine/tests/test_properties.py
| ... |
... |
@@ -16,6 +16,23 @@ def assert_result(new_content, old_content, expect): |
|
16
|
16
|
)
|
|
17
|
17
|
|
|
18
|
18
|
|
|
|
19
|
+def assert_alternative(content, alternative_content, alternative_ids, expect):
|
|
|
20
|
+ if content is not None:
|
|
|
21
|
+ content = textwrap.dedent(content)
|
|
|
22
|
+ if alternative_content is not None:
|
|
|
23
|
+ alternative_content = textwrap.dedent(alternative_content)
|
|
|
24
|
+ if expect is not None:
|
|
|
25
|
+ expect = textwrap.dedent(expect)
|
|
|
26
|
+ assert expect == combine_files(
|
|
|
27
|
+ "test.properties",
|
|
|
28
|
+ content,
|
|
|
29
|
+ alternative_content,
|
|
|
30
|
+ "ALTERNATIVE STRING",
|
|
|
31
|
+ alternative_ids,
|
|
|
32
|
+ ".alt",
|
|
|
33
|
+ )
|
|
|
34
|
+
|
|
|
35
|
+
|
|
19
|
36
|
def test_combine_empty():
|
|
20
|
37
|
assert_result(None, None, None)
|
|
21
|
38
|
|
| ... |
... |
@@ -320,3 +337,74 @@ def test_removed_string_with_comment(): |
|
320
|
337
|
removed.4 = Fourth removed
|
|
321
|
338
|
""",
|
|
322
|
339
|
)
|
|
|
340
|
+
|
|
|
341
|
+
|
|
|
342
|
+def test_alternatives():
|
|
|
343
|
+ assert_alternative(
|
|
|
344
|
+ """\
|
|
|
345
|
+ string.1 = First string
|
|
|
346
|
+ """,
|
|
|
347
|
+ """\
|
|
|
348
|
+ string.1 = Alternative string
|
|
|
349
|
+ """,
|
|
|
350
|
+ ["string.1"],
|
|
|
351
|
+ """\
|
|
|
352
|
+ string.1 = First string
|
|
|
353
|
+
|
|
|
354
|
+ # ALTERNATIVE STRING
|
|
|
355
|
+ string.1.alt = Alternative string
|
|
|
356
|
+ """,
|
|
|
357
|
+ )
|
|
|
358
|
+ assert_alternative(
|
|
|
359
|
+ """\
|
|
|
360
|
+ # Comment 1
|
|
|
361
|
+ string.1 = First string
|
|
|
362
|
+ # Comment 2
|
|
|
363
|
+ string.2 = Second string
|
|
|
364
|
+ string.3 = Third string
|
|
|
365
|
+ """,
|
|
|
366
|
+ """\
|
|
|
367
|
+ string.1 = First string
|
|
|
368
|
+ # Alt comment
|
|
|
369
|
+ string.2 = Alternative string
|
|
|
370
|
+ string.3 = Third string different
|
|
|
371
|
+ string.4 = Other string
|
|
|
372
|
+ """,
|
|
|
373
|
+ ["string.2"],
|
|
|
374
|
+ """\
|
|
|
375
|
+ # Comment 1
|
|
|
376
|
+ string.1 = First string
|
|
|
377
|
+ # Comment 2
|
|
|
378
|
+ string.2 = Second string
|
|
|
379
|
+ string.3 = Third string
|
|
|
380
|
+
|
|
|
381
|
+ # ALTERNATIVE STRING
|
|
|
382
|
+ # Alt comment
|
|
|
383
|
+ string.2.alt = Alternative string
|
|
|
384
|
+ """,
|
|
|
385
|
+ )
|
|
|
386
|
+ assert_alternative(
|
|
|
387
|
+ """\
|
|
|
388
|
+ string.1 = First string
|
|
|
389
|
+ string.2 = Second string
|
|
|
390
|
+ string.3 = Third string
|
|
|
391
|
+ """,
|
|
|
392
|
+ """\
|
|
|
393
|
+ string.1 = Alternative string
|
|
|
394
|
+ string.3 = Third string
|
|
|
395
|
+ # comment
|
|
|
396
|
+ string.4 = Other string
|
|
|
397
|
+ """,
|
|
|
398
|
+ ["string.1", "string.4"],
|
|
|
399
|
+ """\
|
|
|
400
|
+ string.1 = First string
|
|
|
401
|
+ string.2 = Second string
|
|
|
402
|
+ string.3 = Third string
|
|
|
403
|
+
|
|
|
404
|
+ # ALTERNATIVE STRING
|
|
|
405
|
+ string.1.alt = Alternative string
|
|
|
406
|
+ # ALTERNATIVE STRING
|
|
|
407
|
+ # comment
|
|
|
408
|
+ string.4.alt = Other string
|
|
|
409
|
+ """,
|
|
|
410
|
+ ) |
|