diff --git a/AGENTS.md b/AGENTS.md index 96f8f49..4cd29ca 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -817,9 +817,7 @@ Tested in all 4 orientations with 5MB+ files. **To add/modify translations (i18n)**: 1. Edit or add YAML file: `lib/I18n/translations/.yaml` - - - Each file must contain: `_language_name`, `_language_code`, `_order`, and `STR_*` keys - + - Each file must contain: `_language_name`, `_language_code`, `_order`, `_bcp47`, and `STR_*` keys - English (`english.yaml`) is the reference; missing keys in other languages fall back to English 2. Run generator: `python scripts/gen_i18n.py lib/I18n/translations lib/I18n/` 3. Generated files update: `I18nKeys.h`, `I18nStrings.h`, `I18nStrings.cpp` diff --git a/docs/i18n.md b/docs/i18n.md index 736d6aa..dd35794 100644 --- a/docs/i18n.md +++ b/docs/i18n.md @@ -69,6 +69,7 @@ A file looks like this: _language_name: "Español" _language_code: "ES" _order: "1" +_bcp47: "es" STR_CROSSPOINT: "CrossPoint" STR_BOOTING: "BOOTING" @@ -78,7 +79,8 @@ STR_BROWSE_FILES: "Buscar archivos" **Metadata keys** (prefixed with `_`): - `_language_name` — Native display name shown to the user (e.g. "Français") - `_language_code` — C++ enum name (e.g. "FR"). Please use the [ISO Code](https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes) of the language. Must be a valid C++ identifier. -- `_order` — Controls the position in the Language enum (English is always 0) +- `_order` — Historical metadata, still validated for uniqueness but no longer used to compute the `Language` enum order (see `_bcp47` below). +- `_bcp47` — [BCP 47](https://www.rfc-editor.org/rfc/rfc5646) language tag (e.g. `"pt-PT"`, `"ca-valencia"`). Must be unique. Controls the position in the `Language` enum and the visible Settings menu order (English is always first, then the rest sorted alphabetically by this tag). Persisted user language preferences are stored by `_language_code` string, not by the enum ordinal, so changing `_bcp47` (and thus the enum/menu order) does not reset anyone's saved language. **Rules:** - Use UTF-8 encoding @@ -147,6 +149,7 @@ Create `lib/I18n/translations/italian.yaml`: _language_name: "Italiano" _language_code: "IT" _order: "7" +_bcp47: "it" STR_CROSSPOINT: "CrossPoint" STR_BOOTING: "AVVIO" diff --git a/lib/I18n/translations/arabic.yaml b/lib/I18n/translations/arabic.yaml index df67cad..2e69a50 100644 --- a/lib/I18n/translations/arabic.yaml +++ b/lib/I18n/translations/arabic.yaml @@ -1,6 +1,7 @@ _language_name: "العربية" _language_code: "AR" _order: "28" +_bcp47: "ar" STR_CROSSPOINT: "CrossPoint" STR_BOOTING: "جاري بدء التشغيل" diff --git a/lib/I18n/translations/belarusian.yaml b/lib/I18n/translations/belarusian.yaml index 5e832cf..3b7d29a 100644 --- a/lib/I18n/translations/belarusian.yaml +++ b/lib/I18n/translations/belarusian.yaml @@ -1,6 +1,7 @@ _language_name: "Беларуская" _language_code: "BE" _order: "11" +_bcp47: "be" STR_CROSSPOINT: "CrossPoint" STR_BOOTING: "Загрузка" diff --git a/lib/I18n/translations/bosnian.yaml b/lib/I18n/translations/bosnian.yaml index c65e16f..5e50635 100644 --- a/lib/I18n/translations/bosnian.yaml +++ b/lib/I18n/translations/bosnian.yaml @@ -1,6 +1,7 @@ _language_name: "Bosanski" _language_code: "BS" _order: "29" +_bcp47: "bs" STR_CROSSPOINT: "CrossPoint" STR_BOOTING: "POKRETANJE" STR_SLEEPING: "REŽIM MIROVANJA" diff --git a/lib/I18n/translations/catalan.yaml b/lib/I18n/translations/catalan.yaml index b3d3331..8443a52 100644 --- a/lib/I18n/translations/catalan.yaml +++ b/lib/I18n/translations/catalan.yaml @@ -1,6 +1,7 @@ _language_name: "Català" _language_code: "CA" _order: "9" +_bcp47: "ca" STR_CROSSPOINT: "CrossPoint" STR_BOOTING: "ARRENCANT" diff --git a/lib/I18n/translations/czech.yaml b/lib/I18n/translations/czech.yaml index 3202ecb..448b362 100644 --- a/lib/I18n/translations/czech.yaml +++ b/lib/I18n/translations/czech.yaml @@ -1,6 +1,7 @@ _language_name: "Čeština" _language_code: "CS" _order: "4" +_bcp47: "cs" STR_CROSSPOINT: "CrossPoint" STR_BOOTING: "SPUŠTĚNÍ" diff --git a/lib/I18n/translations/danish.yaml b/lib/I18n/translations/danish.yaml index 2495aec..2db0a5f 100644 --- a/lib/I18n/translations/danish.yaml +++ b/lib/I18n/translations/danish.yaml @@ -1,6 +1,7 @@ _language_name: "Dansk" _language_code: "DA" _order: "15" +_bcp47: "da" STR_CROSSPOINT: "CrossPoint" STR_BOOTING: "STARTER" diff --git a/lib/I18n/translations/dutch.yaml b/lib/I18n/translations/dutch.yaml index bfa4e7e..b913c31 100644 --- a/lib/I18n/translations/dutch.yaml +++ b/lib/I18n/translations/dutch.yaml @@ -1,6 +1,7 @@ _language_name: "Nederlands" _language_code: "NL" _order: "16" +_bcp47: "nl" STR_CROSSPOINT: "CrossPoint" STR_BOOTING: "OPSTARTEN" diff --git a/lib/I18n/translations/english.yaml b/lib/I18n/translations/english.yaml index dc09c83..4ac05c2 100644 --- a/lib/I18n/translations/english.yaml +++ b/lib/I18n/translations/english.yaml @@ -1,6 +1,7 @@ _language_name: "English" _language_code: "EN" _order: "0" +_bcp47: "en" STR_CROSSPOINT: "CrossPoint" STR_BOOTING: "BOOTING" diff --git a/lib/I18n/translations/finnish.yaml b/lib/I18n/translations/finnish.yaml index b5fe9d2..5ceca1d 100644 --- a/lib/I18n/translations/finnish.yaml +++ b/lib/I18n/translations/finnish.yaml @@ -1,6 +1,7 @@ _language_name: "Suomi" _language_code: "FI" _order: "14" +_bcp47: "fi" STR_CROSSPOINT: "CrossPoint" STR_BOOTING: "KÄYNNISTYY" diff --git a/lib/I18n/translations/french.yaml b/lib/I18n/translations/french.yaml index fb0836d..b54d329 100644 --- a/lib/I18n/translations/french.yaml +++ b/lib/I18n/translations/french.yaml @@ -1,6 +1,7 @@ _language_name: "Français" _language_code: "FR" _order: "2" +_bcp47: "fr" STR_CROSSPOINT: "CrossPoint" STR_BOOTING: "DÉMARRAGE EN COURS" diff --git a/lib/I18n/translations/german.yaml b/lib/I18n/translations/german.yaml index 26f4fcf..65b7b50 100644 --- a/lib/I18n/translations/german.yaml +++ b/lib/I18n/translations/german.yaml @@ -1,6 +1,7 @@ _language_name: "Deutsch" _language_code: "DE" _order: "3" +_bcp47: "de" STR_CROSSPOINT: "CrossPoint" STR_BOOTING: "STARTE" diff --git a/lib/I18n/translations/hebrew.yaml b/lib/I18n/translations/hebrew.yaml index 6d6650b..9f21d69 100644 --- a/lib/I18n/translations/hebrew.yaml +++ b/lib/I18n/translations/hebrew.yaml @@ -1,6 +1,7 @@ _language_name: "עברית" _language_code: "HE" _order: "23" +_bcp47: "he" STR_CROSSPOINT: "CrossPoint" STR_BOOTING: "מפעיל..." diff --git a/lib/I18n/translations/hungarian.yaml b/lib/I18n/translations/hungarian.yaml index 8b0880f..7bab96b 100644 --- a/lib/I18n/translations/hungarian.yaml +++ b/lib/I18n/translations/hungarian.yaml @@ -1,6 +1,7 @@ _language_name: "Magyar" _language_code: "HU" _order: "19" +_bcp47: "hu" STR_CROSSPOINT: "CrossPoint" STR_BOOTING: "INDÍTÁS" diff --git a/lib/I18n/translations/indonesia.yaml b/lib/I18n/translations/indonesia.yaml index 5644b04..c8c584d 100644 --- a/lib/I18n/translations/indonesia.yaml +++ b/lib/I18n/translations/indonesia.yaml @@ -1,6 +1,7 @@ _language_name: "Bahasa Indonesia" _language_code: "ID" _order: "30" +_bcp47: "id" STR_CROSSPOINT: "CrossPoint" STR_BOOTING: "MEMULAI" diff --git a/lib/I18n/translations/italian.yaml b/lib/I18n/translations/italian.yaml index 5d5d8fa..768090a 100644 --- a/lib/I18n/translations/italian.yaml +++ b/lib/I18n/translations/italian.yaml @@ -1,6 +1,7 @@ _language_name: "Italiano" _language_code: "IT" _order: "12" +_bcp47: "it" STR_ACCOUNT_CREATED: "Account creato" STR_ADD_HIDDEN_NETWORK: "Aggiungi rete nascosta..." diff --git a/lib/I18n/translations/kazakh.yaml b/lib/I18n/translations/kazakh.yaml index bf3c97f..1cd8ade 100644 --- a/lib/I18n/translations/kazakh.yaml +++ b/lib/I18n/translations/kazakh.yaml @@ -1,6 +1,7 @@ _language_name: "Қазақша" _language_code: "KK" _order: "18" +_bcp47: "kk" STR_CROSSPOINT: "CrossPoint" STR_BOOTING: "ЖҮКТЕЛУДЕ" diff --git a/lib/I18n/translations/lithuanian.yaml b/lib/I18n/translations/lithuanian.yaml index b0d7d40..02970dd 100644 --- a/lib/I18n/translations/lithuanian.yaml +++ b/lib/I18n/translations/lithuanian.yaml @@ -1,6 +1,7 @@ _language_name: "Lietuvių" _language_code: "LT" _order: "20" +_bcp47: "lt" STR_CROSSPOINT: "CrossPoint" STR_BOOTING: "KRAUNASI" diff --git a/lib/I18n/translations/norwegian.yaml b/lib/I18n/translations/norwegian.yaml index 7a38ae6..726e604 100644 --- a/lib/I18n/translations/norwegian.yaml +++ b/lib/I18n/translations/norwegian.yaml @@ -1,6 +1,7 @@ _language_name: "Norsk bokmål" _language_code: "NB" _order: "26" +_bcp47: "nb" STR_CROSSPOINT: "CrossPoint" STR_BOOTING: "STARTER" diff --git a/lib/I18n/translations/orangutan.yaml b/lib/I18n/translations/orangutan.yaml index b22b070..cb4cc64 100644 --- a/lib/I18n/translations/orangutan.yaml +++ b/lib/I18n/translations/orangutan.yaml @@ -1,6 +1,7 @@ _language_name: "Orangutan" _language_code: "OOK" _order: "34" +_bcp47: "ook" STR_CROSSPOINT: "CrossPoint" STR_BOOTING: "OOK" diff --git a/lib/I18n/translations/polish.yaml b/lib/I18n/translations/polish.yaml index 4f2074a..04d7721 100644 --- a/lib/I18n/translations/polish.yaml +++ b/lib/I18n/translations/polish.yaml @@ -1,6 +1,7 @@ _language_name: "Polski" _language_code: "PL" _order: "13" +_bcp47: "pl" STR_CROSSPOINT: "CrossPoint" STR_BOOTING: "Bootowanie" diff --git a/lib/I18n/translations/portuguese-BR.yaml b/lib/I18n/translations/portuguese-BR.yaml index 53496f1..d54bcbd 100644 --- a/lib/I18n/translations/portuguese-BR.yaml +++ b/lib/I18n/translations/portuguese-BR.yaml @@ -1,6 +1,7 @@ _language_name: "Português (Brasil)" _language_code: "PT" _order: "5" +_bcp47: "pt-BR" STR_CROSSPOINT: "CrossPoint" STR_BOOTING: "INICIANDO" diff --git a/lib/I18n/translations/portuguese-PT.yaml b/lib/I18n/translations/portuguese-PT.yaml index b55a565..a0e780a 100644 --- a/lib/I18n/translations/portuguese-PT.yaml +++ b/lib/I18n/translations/portuguese-PT.yaml @@ -1,6 +1,7 @@ _language_name: "Português (Portugal)" _language_code: "P2" _order: "27" +_bcp47: "pt-PT" STR_CROSSPOINT: "CrossPoint" STR_BOOTING: "A INICIAR" diff --git a/lib/I18n/translations/romanian.yaml b/lib/I18n/translations/romanian.yaml index c00aab9..7a97af4 100644 --- a/lib/I18n/translations/romanian.yaml +++ b/lib/I18n/translations/romanian.yaml @@ -1,6 +1,7 @@ _language_name: "Română" _language_code: "RO" _order: "8" +_bcp47: "ro" STR_CROSSPOINT: "CrossPoint" STR_BOOTING: "PORNEŞTE" diff --git a/lib/I18n/translations/russian.yaml b/lib/I18n/translations/russian.yaml index 346882f..64957c0 100644 --- a/lib/I18n/translations/russian.yaml +++ b/lib/I18n/translations/russian.yaml @@ -1,6 +1,7 @@ _language_name: "Русский" _language_code: "RU" _order: "6" +_bcp47: "ru" STR_CROSSPOINT: "CrossPoint" STR_BOOTING: "Загрузка" diff --git a/lib/I18n/translations/slovak.yaml b/lib/I18n/translations/slovak.yaml index 0c876c4..4ef6fe3 100644 --- a/lib/I18n/translations/slovak.yaml +++ b/lib/I18n/translations/slovak.yaml @@ -1,6 +1,7 @@ _language_name: "Slovenčina" _language_code: "SK" _order: "24" +_bcp47: "sk" STR_CROSSPOINT: "CrossPoint" STR_BOOTING: "SPÚŠŤANIE" diff --git a/lib/I18n/translations/slovenian.yaml b/lib/I18n/translations/slovenian.yaml index bf734c4..7817706 100644 --- a/lib/I18n/translations/slovenian.yaml +++ b/lib/I18n/translations/slovenian.yaml @@ -1,6 +1,7 @@ _language_name: "Slovenščina" _language_code: "SI" _order: "21" +_bcp47: "sl" STR_CROSSPOINT: "CrossPoint" STR_BOOTING: "ZAGON" diff --git a/lib/I18n/translations/spanish.yaml b/lib/I18n/translations/spanish.yaml index de22dea..f359c98 100644 --- a/lib/I18n/translations/spanish.yaml +++ b/lib/I18n/translations/spanish.yaml @@ -1,6 +1,7 @@ _language_name: "Español" _language_code: "ES" _order: "1" +_bcp47: "es" STR_CROSSPOINT: "CrossPoint" STR_BOOTING: "Iniciando..." diff --git a/lib/I18n/translations/swedish.yaml b/lib/I18n/translations/swedish.yaml index 0e2d80e..56bdf38 100644 --- a/lib/I18n/translations/swedish.yaml +++ b/lib/I18n/translations/swedish.yaml @@ -1,6 +1,7 @@ _language_name: "Svenska" _language_code: "SV" _order: "7" +_bcp47: "sv" STR_CROSSPOINT: "CrossPoint" STR_BOOTING: "STARTAR" diff --git a/lib/I18n/translations/turkish.yaml b/lib/I18n/translations/turkish.yaml index a00564e..f6bcba8 100644 --- a/lib/I18n/translations/turkish.yaml +++ b/lib/I18n/translations/turkish.yaml @@ -1,6 +1,7 @@ _language_name: "Türkçe" _language_code: "TR" _order: "17" +_bcp47: "tr" STR_CROSSPOINT: "CrossPoint" STR_BOOTING: "BAŞLATILIYOR" diff --git a/lib/I18n/translations/ukrainian.yaml b/lib/I18n/translations/ukrainian.yaml index e6da444..eb50b32 100644 --- a/lib/I18n/translations/ukrainian.yaml +++ b/lib/I18n/translations/ukrainian.yaml @@ -1,6 +1,7 @@ _language_name: "Українська" _language_code: "UK" _order: "10" +_bcp47: "uk" STR_CROSSPOINT: "CrossPoint" STR_BOOTING: "ЗАВАНТАЖЕННЯ" diff --git a/lib/I18n/translations/valencian.yaml b/lib/I18n/translations/valencian.yaml index 399fc31..0fb7686 100644 --- a/lib/I18n/translations/valencian.yaml +++ b/lib/I18n/translations/valencian.yaml @@ -1,6 +1,7 @@ _language_name: "Valencià" _language_code: "CAV" _order: "22" +_bcp47: "ca-valencia" STR_CROSSPOINT: "CrossPoint" STR_BOOTING: "ARRENCANT" diff --git a/lib/I18n/translations/vietnamese.yaml b/lib/I18n/translations/vietnamese.yaml index 76a819f..2d9e289 100644 --- a/lib/I18n/translations/vietnamese.yaml +++ b/lib/I18n/translations/vietnamese.yaml @@ -1,6 +1,7 @@ _language_name: "Tiếng Việt" _language_code: "VI" _order: "25" +_bcp47: "vi" STR_CROSSPOINT: "CrossPoint" STR_BOOTING: "ĐANG KHỞI ĐỘNG" diff --git a/scripts/gen_i18n.py b/scripts/gen_i18n.py index 2deb2a2..20457e8 100755 --- a/scripts/gen_i18n.py +++ b/scripts/gen_i18n.py @@ -10,6 +10,8 @@ Reads YAML files from a translations directory (one file per language) and gener Each YAML file must contain: _language_name: "Native Name" (e.g. "Español") _language_code: "ENUM_NAME" (e.g. "ES") + _order: "N" (e.g. "1"; historical metadata, unused) + _bcp47: "tag" (e.g. "es"; drives Language enum order) STR_KEY: "translation text" The English file is the reference. Missing keys in other languages are @@ -113,15 +115,16 @@ def parse_yaml_file(filepath: str) -> Dict[str, str]: def load_translations( translations_dir: str, verbose: bool = False, -) -> Tuple[List[str], List[str], List[str], Dict[str, List[str]], List[Set[str]]]: +) -> Tuple[List[str], List[str], List[str], List[str], Dict[str, List[str]], List[Set[str]]]: """ Read every YAML file in *translations_dir* and return: language_codes e.g. ["EN", "ES", ...] language_names e.g. ["English", "Español", ...] + language_bcp47 e.g. ["en", "es", ...] string_keys ordered list of STR_* keys (from English) translations {key: [translation_per_language]} - English is always first; + English is always first; the rest are sorted by _bcp47. """ yaml_dir = Path(translations_dir) if not yaml_dir.is_dir(): @@ -146,48 +149,51 @@ def load_translations( if english_file is None: raise ValueError("No YAML file with _language_code: EN found") - duplicate_orders: Dict[str, List[str]] = {} - order_to_files: Dict[str, List[str]] = {} for fname, data in parsed.items(): - order = data.get("_order") - if not order: - continue - order_to_files.setdefault(order, []).append(fname) - - for order, files in order_to_files.items(): - if len(files) > 1: - duplicate_orders[order] = sorted(files) - - if duplicate_orders: - duplicate_messages = [ - f"_order {order}: {', '.join(files)}" - for order, files in sorted( - duplicate_orders.items(), key=lambda item: int(item[0]) + if not data.get("_bcp47"): + raise ValueError(f"{fname}: missing _bcp47") + + def _check_unique(field: str) -> None: + """Raise if any two files share the same value for *field* (e.g. "_order", "_bcp47").""" + files_by_value: Dict[str, List[str]] = {} + for fname, data in parsed.items(): + value = data.get(field) + if not value: + continue + files_by_value.setdefault(value, []).append(fname) + + duplicates = {value: sorted(files) for value, files in files_by_value.items() if len(files) > 1} + if duplicates: + duplicate_messages = [ + f"{field} {value}: {', '.join(files)}" for value, files in sorted(duplicates.items()) + ] + raise ValueError( + f"Duplicate {field} values found:\n " + + "\n ".join(duplicate_messages) + + f"\nEach {field} value must be unique to ensure a deterministic language order." ) - ] - raise ValueError( - "Duplicate _order values found:\n " - + "\n ".join(duplicate_messages) - + "\nEach _order value must be unique to ensure a deterministic language order." - ) - # Order: English first, then by _order metadata (falls back to filename) - def sort_key(fname: str) -> Tuple[int, int, str]: - """English always first (0), then by _order, then by filename.""" + _check_unique("_order") + _check_unique("_bcp47") + + # Order: English first (enum value 0), then by _bcp47 tag alphabetically. + # This assigns the Language enum ordinal and also drives the visible + # Settings menu order (see SORTED_LANGUAGE_INDICES). It has no effect on + # stored user preferences, since settings.json persists the + # _language_code string, not the ordinal. _order is retained as metadata + # and still validated for uniqueness, but no longer drives enum assignment. + def sort_key(fname: str) -> Tuple[int, str]: + """English always first (0), then by BCP47 tag.""" if fname == english_file: - return (0, 0, fname) - order = parsed[fname].get("_order", "999") - try: - order_int = int(order) - except ValueError: - order_int = 999 - return (1, order_int, fname) + return (0, "") + return (1, parsed[fname]["_bcp47"]) ordered_files = sorted(parsed, key=sort_key) # Extract metadata language_codes: List[str] = [] language_names: List[str] = [] + language_bcp47: List[str] = [] for fname in ordered_files: data = parsed[fname] code = data.get("_language_code") @@ -196,6 +202,7 @@ def load_translations( raise ValueError(f"{fname}: missing _language_code or _language_name") language_codes.append(code) language_names.append(name) + language_bcp47.append(data["_bcp47"]) # String keys come from English (order matters) english_data = parsed[english_file] @@ -239,7 +246,7 @@ def load_translations( if verbose: print(f"Loaded {len(language_codes)} languages, {len(string_keys)} string keys") - return language_codes, language_names, string_keys, translations, inherited_sets + return language_codes, language_names, language_bcp47, string_keys, translations, inherited_sets # --------------------------------------------------------------------------- @@ -438,6 +445,7 @@ def compute_character_set(translations: Dict[str, List[str]], lang_index: int) - def generate_keys_header( languages: List[str], language_names: List[str], + language_bcp47: List[str], string_keys: List[str], output_path: str, verbose: bool = False, @@ -462,10 +470,11 @@ def generate_keys_header( lines.append("") # Language enum - lines.append("// Language enum") + lines.append("// Language enum (ordered by _bcp47, English first)") lines.append("enum class Language : uint8_t {") - for i, lang in enumerate(languages): - lines.append(f" {lang} = {i},") + code_width = max(len(lang) for lang in languages) + for i, (lang, bcp47) in enumerate(zip(languages, language_bcp47)): + lines.append(f" {lang:<{code_width}} = {i}, // {bcp47}") lines.append(" _COUNT") lines.append("};") lines.append("") @@ -527,16 +536,16 @@ def generate_keys_header( lines.append("") # Sorted language indices for display order - # (English first, then by native language name alphabetically) + # (English first, then by _bcp47 tag alphabetically) english_idx = languages.index("EN") rest = sorted( (i for i in range(len(languages)) if i != english_idx), - key=lambda i: language_names[i], + key=lambda i: language_bcp47[i], ) sorted_indices = [english_idx] + rest - lines.append("// Sorted language indices by native name (auto-generated by gen_i18n.py)") + lines.append("// Sorted language indices by _bcp47 (auto-generated by gen_i18n.py)") for rank, idx in enumerate(sorted_indices): - lines.append(f"// {rank:>2}: {languages[idx]:<4} {language_names[idx]}") + lines.append(f"// {rank:>2}: {languages[idx]:<4} {language_bcp47[idx]:<8} {language_names[idx]}") lines.append( "constexpr uint8_t SORTED_LANGUAGE_INDICES[] = {" f"{', '.join(str(i) for i in sorted_indices)}" @@ -724,6 +733,7 @@ def generate_strings_cpp( def _print_language_table( language_codes: List[str], language_names: List[str], + language_bcp47: List[str], inherited_sets: List[Set[str]], string_keys: List[str], unused_keys: Set[str], @@ -731,20 +741,20 @@ def _print_language_table( ) -> None: """Print a per-language summary table.""" total = len(string_keys) - headers = ("Language", "Code", "Own", "Fallback", "Unused", "Data (B)") + headers = ("Language", "Code", "BCP47", "Own", "Fallback", "Unused", "Data (B)") rows = [] - for code, name, inherited, size in zip( - language_codes, language_names, inherited_sets, data_sizes + for code, name, bcp47, inherited, size in zip( + language_codes, language_names, language_bcp47, inherited_sets, data_sizes ): own = total - len(inherited) fallback = len(inherited) # strings this language translated but the code never calls unused = len(unused_keys - inherited) - rows.append((name, code, str(own), str(fallback), str(unused), str(size))) + rows.append((name, code, bcp47, str(own), str(fallback), str(unused), str(size))) - # EN first, then alphabetically by ISO code - rows.sort(key=lambda r: (0 if r[1] == "EN" else 1, r[1])) + # EN first, then alphabetically by _bcp47 (matches the Language enum order) + rows.sort(key=lambda r: (0 if r[1] == "EN" else 1, r[2])) col_widths = [len(h) for h in headers] for row in rows: @@ -856,7 +866,7 @@ def main( print() try: - languages, language_names, string_keys, translations, inherited_sets = ( + languages, language_names, language_bcp47, string_keys, translations, inherited_sets = ( load_translations(translations_dir, verbose) ) @@ -900,6 +910,7 @@ def main( _print_language_table( languages, language_names, + language_bcp47, inherited_sets, string_keys, unused_set, @@ -923,7 +934,7 @@ def main( out = Path(output_dir) generate_keys_header( - languages, language_names, string_keys, str(out / "I18nKeys.h"), verbose + languages, language_names, language_bcp47, string_keys, str(out / "I18nKeys.h"), verbose ) generate_strings_header( languages, language_names, str(out / "I18nStrings.h"), verbose