Pier Angelo Vendrame pushed to branch tor-browser-128.7.0esr-14.5-1 at The Tor Project / Applications / Tor Browser
Commits:
-
d9fe34de
by Henry Wilkes at 2025-02-18T10:08:15+00:00
-
34838694
by Henry Wilkes at 2025-02-18T10:10:32+00:00
-
6fa642ea
by Henry Wilkes at 2025-02-18T10:10:33+00:00
13 changed files:
- .gitlab/ci/jobs/update-translations.yml
- browser/branding/tb-alpha/locales/en-US/brand.ftl
- browser/branding/tb-alpha/locales/en-US/brand.properties
- browser/branding/tb-nightly/locales/en-US/brand.ftl
- browser/branding/tb-nightly/locales/en-US/brand.properties
- browser/branding/tb-release/locales/en-US/brand.ftl
- browser/branding/tb-release/locales/en-US/brand.properties
- tools/base-browser/l10n/combine-translation-versions.py
- tools/base-browser/l10n/combine/combine.py
- tools/base-browser/l10n/combine/tests/test_android.py
- tools/base-browser/l10n/combine/tests/test_dtd.py
- tools/base-browser/l10n/combine/tests/test_fluent.py
- tools/base-browser/l10n/combine/tests/test_properties.py
Changes:
... | ... | @@ -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" },
|
... | ... | @@ -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.
|
... | ... | @@ -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 |
... | ... | @@ -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.
|
... | ... | @@ -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 |
... | ... | @@ -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.
|
... | ... | @@ -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 |
... | ... | @@ -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 | )
|
... | ... | @@ -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 |
... | ... | @@ -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 | + ) |
... | ... | @@ -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 | + ) |
... | ... | @@ -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 | + ) |
... | ... | @@ -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 | + ) |