diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 5b69020..dfc7c54 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -40,7 +40,8 @@ jobs: python-versions: - '3.11' - '3.12' - - '3.12' + - '3.13' + - '3.14' steps: - name: Check out repository uses: actions/checkout@v5 diff --git a/tests/test_string_projectconfig.py b/tests/test_string_projectconfig.py index 29bb407..4ad7c82 100644 --- a/tests/test_string_projectconfig.py +++ b/tests/test_string_projectconfig.py @@ -21,9 +21,17 @@ def testGetAndroidStrings(self): strings_locale = extraction.translations self.assertEqual(len(strings_locale), 11) self.assertEqual(len(strings_locale["it"]), 6) - self.assertEqual(len(strings_locale["en-US"]), 17) + self.assertEqual(len(strings_locale["en-US"]), 18) self.assertEqual(len(strings_locale["es-ES"]), 5) + # Check plurals + self.assertEqual( + strings_locale["en-US"][ + "test/MozillaReality/FirefoxReality/app/src/main/res/values/strings.xml:close_tabs_plural" + ], + "[one] Close %d tab\n*[other] Close %d tabs", + ) + # Check escapes self.assertEqual( strings_locale["it"][ diff --git a/tests/testfiles/android/MozillaReality/FirefoxReality/app/src/main/res/values/strings.xml b/tests/testfiles/android/MozillaReality/FirefoxReality/app/src/main/res/values/strings.xml index 0e6f193..3e6e66f 100644 --- a/tests/testfiles/android/MozillaReality/FirefoxReality/app/src/main/res/values/strings.xml +++ b/tests/testfiles/android/MozillaReality/FirefoxReality/app/src/main/res/values/strings.xml @@ -1,55 +1,61 @@ - - - - ENTER - - - GO - - - SEARCH - - - SEND - - - NEXT - - + + + ENTER + + + GO + + + SEARCH + + + SEND + + + NEXT + + - DONE + --> + DONE - - space + + space - - Allow + + Allow - - Don’t Allow + + Don’t Allow - - Enable + + Enable - - Enabled + + Enabled + + + + Close %d tab + Close %d tabs + diff --git a/tmx_products/functions.py b/tmx_products/functions.py index 1416a6b..a3bf625 100644 --- a/tmx_products/functions.py +++ b/tmx_products/functions.py @@ -2,10 +2,18 @@ import os from configparser import ConfigParser +from typing import Union from moz.l10n.formats import Format from moz.l10n.message import serialize_message -from moz.l10n.model import Entry, Message, Resource +from moz.l10n.model import ( + CatchallKey, + Entry, + Message, + PatternMessage, + Resource, + SelectMessage, +) def get_config() -> str: @@ -112,6 +120,18 @@ def get_entry_value(value: Message) -> str: return entry_value + def serialize_select_variants(entry: Entry) -> str: + msg: SelectMessage = entry.value + lines: list[str] = [] + for key_tuple, pattern in msg.variants.items(): + key: Union[str, CatchallKey] = key_tuple[0] if key_tuple else "other" + default = "*" if isinstance(key, CatchallKey) else "" + label: str | None = key.value if isinstance(key, CatchallKey) else str(key) + lines.append( + f"{default}[{label}] {serialize_message(resource.format, PatternMessage(pattern))}" + ) + return "\n".join(lines) + try: for section in resource.sections: for entry in section.entries: @@ -130,7 +150,16 @@ def get_entry_value(value: Message) -> str: attr_id = f"{string_id}.{attribute}" storage[attr_id] = get_entry_value(attr_value) else: - storage[string_id] = get_entry_value(entry.value) + if resource.format == Format.android: + # If it's a plural string in Android, each variant + # is stored within the message, following a format + # similar to Fluent. + if hasattr(entry.value, "variants"): + storage[string_id] = serialize_select_variants(entry) + else: + storage[string_id] = get_entry_value(entry.value) + else: + storage[string_id] = get_entry_value(entry.value) except Exception as e: print(f"Error parsing file: {filename}") print(e)