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 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:
- .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:
===================================== .gitlab/ci/jobs/update-translations.yml ===================================== @@ -17,12 +17,48 @@ { "name": "brand.ftl", "where": ["browser/branding/tb-release", "toolkit/torbutton"], + "branding": { + "versions": [ + { + "name": "Alpha", + "suffix": "_alpha", + "where": ["browser/branding/tb-alpha"] + }, + { + "name": "Nightly", + "suffix": "_nightly", + "where": ["browser/branding/tb-nightly"] + } + ], + "ids": [ + "-brand-short-name", + "-brand-full-name" + ] + }, "branch": "tor-browser", "directory": "branding" }, { "name": "brand.properties", "where": ["browser/branding/tb-release", "toolkit/torbutton"], + "branding": { + "versions": [ + { + "name": "Alpha", + "suffix": "_alpha", + "where": ["browser/branding/tb-alpha"] + }, + { + "name": "Nightly", + "suffix": "_nightly", + "where": ["browser/branding/tb-nightly"] + } + ], + "ids": [ + "brandShortName", + "brandFullName" + ] + }, "branch": "tor-browser" }, { "name": "tor-browser.ftl", "branch": "tor-browser" },
===================================== browser/branding/tb-alpha/locales/en-US/brand.ftl ===================================== @@ -2,12 +2,16 @@ # that is used by Firefox) to avoid picking up the -brand-short-name values # that Mozilla includes in the Firefox language packs.
+# The shortened application name for Tor Browser. Shared between versions. -brand-shorter-name = Tor Browser --brand-short-name = Tor Browser --brand-full-name = Tor Browser +# The default application name for the "alpha" release. +-brand-short-name = Tor Browser Alpha +# The full application name for the "alpha" release. +-brand-full-name = Tor Browser Alpha # This brand name can be used in messages where the product name needs to # remain unchanged across different versions (Nightly, Beta, etc.). -brand-product-name = Tor Browser +# The name of the Tor Project organisation. -vendor-short-name = Tor Project # "Tor" is a trademark names, so should not be translated (not including the quote marks, which can be localized). # "The Tor Project, Inc." is an organisation name.
===================================== browser/branding/tb-alpha/locales/en-US/brand.properties ===================================== @@ -3,6 +3,9 @@ # 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/.
+# The shortened application name for Tor Browser. Shared between versions. brandShorterName=Tor Browser -brandShortName=Tor Browser -brandFullName=Tor Browser +# The default application name for the "alpha" release. +brandShortName=Tor Browser Alpha +# The full application name for the "alpha" release. +brandFullName=Tor Browser Alpha
===================================== browser/branding/tb-nightly/locales/en-US/brand.ftl ===================================== @@ -2,12 +2,16 @@ # that is used by Firefox) to avoid picking up the -brand-short-name values # that Mozilla includes in the Firefox language packs.
+# The shortened application name for Tor Browser. Shared between versions. -brand-shorter-name = Tor Browser --brand-short-name = Tor Browser --brand-full-name = Tor Browser +# The default application name for the "nightly" release. +-brand-short-name = Tor Browser Nightly +# The full application name for the "nightly" release. +-brand-full-name = Tor Browser Nightly # This brand name can be used in messages where the product name needs to # remain unchanged across different versions (Nightly, Beta, etc.). -brand-product-name = Tor Browser +# The name of the Tor Project organisation. -vendor-short-name = Tor Project # "Tor" is a trademark names, so should not be translated (not including the quote marks, which can be localized). # "The Tor Project, Inc." is an organisation name.
===================================== browser/branding/tb-nightly/locales/en-US/brand.properties ===================================== @@ -3,6 +3,9 @@ # 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/.
+# The shortened application name for Tor Browser. Shared between versions. brandShorterName=Tor Browser -brandShortName=Tor Browser -brandFullName=Tor Browser +# The default application name for the "nightly" release. +brandShortName=Tor Browser Nightly +# The full application name for the "nightly" release. +brandFullName=Tor Browser Nightly
===================================== browser/branding/tb-release/locales/en-US/brand.ftl ===================================== @@ -2,12 +2,16 @@ # that is used by Firefox) to avoid picking up the -brand-short-name values # that Mozilla includes in the Firefox language packs.
+# The shortened application name for Tor Browser. Shared between versions. -brand-shorter-name = Tor Browser +# The default application name for the stable release. -brand-short-name = Tor Browser +# The full application name for the stable release. -brand-full-name = Tor Browser # This brand name can be used in messages where the product name needs to # remain unchanged across different versions (Nightly, Beta, etc.). -brand-product-name = Tor Browser +# The name of the Tor Project organisation. -vendor-short-name = Tor Project # "Tor" is a trademark names, so should not be translated (not including the quote marks, which can be localized). # "The Tor Project, Inc." is an organisation name.
===================================== browser/branding/tb-release/locales/en-US/brand.properties ===================================== @@ -3,6 +3,9 @@ # 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/.
+# The shortened application name for Tor Browser. Shared between versions. brandShorterName=Tor Browser +# The default application name for the stable release. brandShortName=Tor Browser +# The full application name for the stable release. brandFullName=Tor Browser
===================================== tools/base-browser/l10n/combine-translation-versions.py ===================================== @@ -306,9 +306,34 @@ for file_dict in json.loads(args.files): f"{current_file.path} : {stable_file.path}" )
+ content = None if current_file is None else current_file.content + + # If we have a branding file, we want to also include strings from the other + # branding directories that differ from the stable release. + # The strings that *differ* per release should be specified in + # file_dict["branding"]["ids"]. These strings will be copied from the other + # release's branding directory, with an addition suffix added to their ID, + # as specified in the version_dict["suffix"]. + branding = file_dict.get("branding", None) + if branding: + include_ids = branding["ids"] + for version_dict in branding["versions"]: + branding_dirs = version_dict.get("where", None) + branding_file = current_branch.get_file(name, branding_dirs) + if branding_file is None: + raise Exception(f"{name} does not exist in {branding_dirs}") + content = combine_files( + name, + content, + branding_file.content, + f'{version_dict["name"]} Release.', + include_ids, + version_dict["suffix"], + ) + content = combine_files( name, - None if current_file is None else current_file.content, + content, None if stable_file is None else stable_file.content, f"Will be unused in {current_branch.browser_version_name}!", )
===================================== tools/base-browser/l10n/combine/combine.py ===================================== @@ -14,26 +14,32 @@ if TYPE_CHECKING:
def combine_files( filename: str, - new_content: str | None, - old_content: str | None, + primary_content: str | None, + alternative_content: str | None, comment_prefix: str, + include_ids: list[str] | None = None, + alternative_suffix: str = "", ) -> str | None: """Combine two translation files into one to include all strings from both. - The new content is presented first, and any strings only found in the old - content are placed at the end with an additional comment. + The primary content is presented first, followed by the alternative content + at the end with an additional comment.
:param filename: The filename for the file, determines the format. - :param new_content: The new content for the file, or None if it has been - deleted. - :param old_content: The old content for the file, or None if it did not - exist before. - :comment_prefix: A comment to include for any strings that are only found in - the old content. This will be placed before any other comments for the - string. + :param primary_content: The primary content for the file, or None if it does + not exist. + :param alternative_content: The alternative content for the file, or None if + it does not exist. + :param comment_prefix: A comment to include for any strings that are + appended to the content. This will be placed before any other comments for + the string. + :param include_ids: String IDs from `alternative_content` we want to + include. If this is `None` then we include all strings that do not already + have a matching ID in `primary_content`. + :param duplicate_suffix: The suffix to apply to the alternative IDs.
:returns: The combined content, or None if both given contents are None. """ - if new_content is None and old_content is None: + if primary_content is None and alternative_content is None: return None
# getParser from compare_locale returns the same instance for the same file @@ -41,7 +47,7 @@ def combine_files( parser = getParser(filename)
is_android = filename.endswith(".xml") - if new_content is None: + if primary_content is None: if is_android: # File was deleted, add some document parts. content_start = ( @@ -54,7 +60,7 @@ def combine_files( content_end = "" existing_keys = [] else: - parser.readUnicode(new_content) + parser.readUnicode(primary_content)
# Start with the same content as the current file. # For android strings, we want to keep the final "</resources>" until after. @@ -96,8 +102,8 @@ def combine_files(
entry_iter: Iterable[Any] = () # If the file does not exist in the old branch, don't make any additions. - if old_content is not None: - parser.readUnicode(old_content) + if alternative_content is not None: + parser.readUnicode(alternative_content) entry_iter = parser.walk(only_localizable=False) for entry in entry_iter: if isinstance(entry, Junk): @@ -134,13 +140,19 @@ def combine_files( if not isinstance(entry, Entity): raise ValueError(f"Unexpected type: {entry.__class__.__name__}")
- if entry.key in existing_keys: - # Already included this string in the new translation file. + if include_ids is None: + # We include the entry if it is not already included. + include_entry = entry.key not in existing_keys + else: + # We include the entry if it is in our list. + include_entry = entry.key in include_ids + if not include_entry: # Drop the gathered comments for this Entity. stacked_comments.clear() continue
if isinstance(entry, FluentEntity): + id_regex = rf"^({re.escape(entry.key)})( *=)" if fluent_group_comment is not None: # We have a found GroupComment which has not been included yet. # All following Entity's will be under its scope, until the next @@ -149,12 +161,15 @@ def combine_files( # Added GroupComment, so don't need to add again. fluent_group_comment = None elif isinstance(entry, DTDEntity): + id_regex = rf"^(\s*<!ENTITY\s*{re.escape(entry.key)})(\s)" # Include our additional comment before we print the rest for this # Entity. additions.append(f"<!-- LOCALIZATION NOTE: {comment_prefix} -->") elif isinstance(entry, PropertiesEntity): + id_regex = rf"^({re.escape(entry.key)})( *=)" additions.append(f"# {comment_prefix}") elif isinstance(entry, AndroidEntity): + id_regex = rf'^(\s*<string\s[^>]*name="{re.escape(entry.key)})(")' additions.append(f"<!-- {comment_prefix} -->") else: raise ValueError(f"Unexpected Entity type: {entry.__class__.__name__}") @@ -162,7 +177,17 @@ def combine_files( # Add any other comment lines that came directly before this Entity. additions.extend(stacked_comments) stacked_comments.clear() - additions.append(entry.all) + entry_content = entry.all + if alternative_suffix: + # NOTE: compare_locales does not allow us to set the entry.key + # value. Instead we use a regular expression to append the suffix to + # the expected key. + entry_content, count = re.subn( + id_regex, rf"\1{alternative_suffix}\2", entry_content, flags=re.M + ) + if count != 1: + raise ValueError(f"Failed to substitute the ID for {entry.key}") + additions.append(entry_content)
content_middle = ""
===================================== tools/base-browser/l10n/combine/tests/test_android.py ===================================== @@ -24,6 +24,20 @@ def assert_result(new_content, old_content, expect): )
+def assert_alternative(content, alternative_content, alternative_ids, expect): + content = wrap_in_xml(content) + alternative_content = wrap_in_xml(alternative_content) + expect = wrap_in_xml(expect) + assert expect == combine_files( + "test_strings.xml", + content, + alternative_content, + "ALTERNATIVE STRING", + alternative_ids, + "_alt", + ) + + def test_combine_empty(): assert_result(None, None, None)
@@ -328,3 +342,74 @@ def test_removed_string_with_comment(): <string name="removed_4">Fourth removed</string> """, ) + + +def test_alternatives(): + assert_alternative( + """\ + <string name="string_1">First string</string> + """, + """\ + <string name="string_1">Alternative string</string> + """, + ["string_1"], + """\ + <string name="string_1">First string</string> + + <!-- ALTERNATIVE STRING --> + <string name="string_1_alt">Alternative string</string> + """, + ) + assert_alternative( + """\ + <!-- Comment 1 --> + <string name="string_1">First string</string> + <!-- Comment 2 --> + <string name="string_2">Second string</string> + <string name="string_3">Third string</string> + """, + """\ + <string name="string_1">First string</string> + <!-- Alt comment --> + <string name="string_2">Alternative string</string> + <string name="string_3">Third string different</string> + <string name="string_4">Other string</string> + """, + ["string_2"], + """\ + <!-- Comment 1 --> + <string name="string_1">First string</string> + <!-- Comment 2 --> + <string name="string_2">Second string</string> + <string name="string_3">Third string</string> + + <!-- ALTERNATIVE STRING --> + <!-- Alt comment --> + <string name="string_2_alt">Alternative string</string> + """, + ) + assert_alternative( + """\ + <string name="string_1">First string</string> + <string name="string_2">Second string</string> + <string name="string_3">Third string</string> + """, + """\ + <string name="string_1">Alternative string</string> + <string name="string_3">Third string</string> + <!-- comment --> + <string name="string_4">Other string</string> + """, + ["string_1", "string_4"], + """\ + <string name="string_1">First string</string> + <string name="string_2">Second string</string> + <string name="string_3">Third string</string> + + <!-- ALTERNATIVE STRING --> + <string name="string_1_alt">Alternative string</string> + <!-- ALTERNATIVE STRING --> + <!-- comment --> + <string name="string_4_alt">Other string</string> + """, + )
===================================== tools/base-browser/l10n/combine/tests/test_dtd.py ===================================== @@ -16,6 +16,23 @@ def assert_result(new_content, old_content, expect): )
+def assert_alternative(content, alternative_content, alternative_ids, expect): + if content is not None: + content = textwrap.dedent(content) + if alternative_content is not None: + alternative_content = textwrap.dedent(alternative_content) + if expect is not None: + expect = textwrap.dedent(expect) + assert expect == combine_files( + "test.dtd", + content, + alternative_content, + "ALTERNATIVE STRING", + alternative_ids, + ".alt", + ) + + def test_combine_empty(): assert_result(None, None, None)
@@ -323,3 +340,74 @@ def test_removed_string_with_comment(): <!ENTITY removed.4 "Fourth removed"> """, ) + + +def test_alternatives(): + assert_alternative( + """\ + <!ENTITY string.1 "First string"> + """, + """\ + <!ENTITY string.1 "Alternative string"> + """, + ["string.1"], + """\ + <!ENTITY string.1 "First string"> + + <!-- LOCALIZATION NOTE: ALTERNATIVE STRING --> + <!ENTITY string.1.alt "Alternative string"> + """, + ) + assert_alternative( + """\ + <!-- LOCALIZATION NOTE: Comment 1 --> + <!ENTITY string.1 "First string"> + <!-- LOCALIZATION NOTE: Comment 2 --> + <!ENTITY string.2 "Second string"> + <!ENTITY string.3 "Third string"> + """, + """\ + <!ENTITY string.1 "First string"> + <!-- LOCALIZATION NOTE: Alt comment --> + <!ENTITY string.2 "Alternative string"> + <!ENTITY string.3 "Third string different"> + <!ENTITY string.4 "Other string"> + """, + ["string.2"], + """\ + <!-- LOCALIZATION NOTE: Comment 1 --> + <!ENTITY string.1 "First string"> + <!-- LOCALIZATION NOTE: Comment 2 --> + <!ENTITY string.2 "Second string"> + <!ENTITY string.3 "Third string"> + + <!-- LOCALIZATION NOTE: ALTERNATIVE STRING --> + <!-- LOCALIZATION NOTE: Alt comment --> + <!ENTITY string.2.alt "Alternative string"> + """, + ) + assert_alternative( + """\ + <!ENTITY string.1 "First string"> + <!ENTITY string.2 "Second string"> + <!ENTITY string.3 "Third string"> + """, + """\ + <!ENTITY string.1 "Alternative string"> + <!ENTITY string.3 "Third string"> + <!-- LOCALIZATION NOTE: comment --> + <!ENTITY string.4 "Other string"> + """, + ["string.1", "string.4"], + """\ + <!ENTITY string.1 "First string"> + <!ENTITY string.2 "Second string"> + <!ENTITY string.3 "Third string"> + + <!-- LOCALIZATION NOTE: ALTERNATIVE STRING --> + <!ENTITY string.1.alt "Alternative string"> + <!-- LOCALIZATION NOTE: ALTERNATIVE STRING --> + <!-- LOCALIZATION NOTE: comment --> + <!ENTITY string.4.alt "Other string"> + """, + )
===================================== tools/base-browser/l10n/combine/tests/test_fluent.py ===================================== @@ -16,6 +16,23 @@ def assert_result(new_content, old_content, expect): )
+def assert_alternative(content, alternative_content, alternative_ids, expect): + if content is not None: + content = textwrap.dedent(content) + if alternative_content is not None: + alternative_content = textwrap.dedent(alternative_content) + if expect is not None: + expect = textwrap.dedent(expect) + assert expect == combine_files( + "test.ftl", + content, + alternative_content, + "ALTERNATIVE STRING", + alternative_ids, + "-alt", + ) + + def test_combine_empty(): assert_result(None, None, None)
@@ -342,3 +359,119 @@ def test_removed_string_with_comment(): removed-4 = Fourth removed """, ) + + +def test_alternatives(): + assert_alternative( + """\ + string-1 = First string + .title = hello + """, + """\ + string-1 = Alternative string + .title = different + """, + ["string-1"], + """\ + string-1 = First string + .title = hello + + + ## ALTERNATIVE STRING + + string-1-alt = Alternative string + .title = different + """, + ) + assert_alternative( + """\ + string-1 = First string + .title = hello + """, + """\ + string-1 = Alternative string + """, + ["string-1"], + """\ + string-1 = First string + .title = hello + + + ## ALTERNATIVE STRING + + string-1-alt = Alternative string + """, + ) + assert_alternative( + """\ + -term-1 = First string + """, + """\ + -term-1 = Alternative string + """, + ["-term-1"], + """\ + -term-1 = First string + + + ## ALTERNATIVE STRING + + -term-1-alt = Alternative string + """, + ) + assert_alternative( + """\ + # Comment 1 + string-1 = First string + # Comment 2 + string-2 = Second string + string-3 = Third string + """, + """\ + string-1 = First string + # Alt comment + string-2 = Alternative string + string-3 = Third string different + string-4 = Other string + """, + ["string-2"], + """\ + # Comment 1 + string-1 = First string + # Comment 2 + string-2 = Second string + string-3 = Third string + + + ## ALTERNATIVE STRING + + # Alt comment + string-2-alt = Alternative string + """, + ) + assert_alternative( + """\ + string-1 = First string + string-2 = Second string + string-3 = Third string + """, + """\ + string-1 = Alternative string + string-3 = Third string + # comment + -string-4 = Other string + """, + ["string-1", "-string-4"], + """\ + string-1 = First string + string-2 = Second string + string-3 = Third string + + + ## ALTERNATIVE STRING + + string-1-alt = Alternative string + # comment + -string-4-alt = Other string + """, + )
===================================== tools/base-browser/l10n/combine/tests/test_properties.py ===================================== @@ -16,6 +16,23 @@ def assert_result(new_content, old_content, expect): )
+def assert_alternative(content, alternative_content, alternative_ids, expect): + if content is not None: + content = textwrap.dedent(content) + if alternative_content is not None: + alternative_content = textwrap.dedent(alternative_content) + if expect is not None: + expect = textwrap.dedent(expect) + assert expect == combine_files( + "test.properties", + content, + alternative_content, + "ALTERNATIVE STRING", + alternative_ids, + ".alt", + ) + + def test_combine_empty(): assert_result(None, None, None)
@@ -320,3 +337,74 @@ def test_removed_string_with_comment(): removed.4 = Fourth removed """, ) + + +def test_alternatives(): + assert_alternative( + """\ + string.1 = First string + """, + """\ + string.1 = Alternative string + """, + ["string.1"], + """\ + string.1 = First string + + # ALTERNATIVE STRING + string.1.alt = Alternative string + """, + ) + assert_alternative( + """\ + # Comment 1 + string.1 = First string + # Comment 2 + string.2 = Second string + string.3 = Third string + """, + """\ + string.1 = First string + # Alt comment + string.2 = Alternative string + string.3 = Third string different + string.4 = Other string + """, + ["string.2"], + """\ + # Comment 1 + string.1 = First string + # Comment 2 + string.2 = Second string + string.3 = Third string + + # ALTERNATIVE STRING + # Alt comment + string.2.alt = Alternative string + """, + ) + assert_alternative( + """\ + string.1 = First string + string.2 = Second string + string.3 = Third string + """, + """\ + string.1 = Alternative string + string.3 = Third string + # comment + string.4 = Other string + """, + ["string.1", "string.4"], + """\ + string.1 = First string + string.2 = Second string + string.3 = Third string + + # ALTERNATIVE STRING + string.1.alt = Alternative string + # ALTERNATIVE STRING + # comment + string.4.alt = Other string + """, + )
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/d94603f...