diff --git a/.github/workflows/refresh-v3-languages.yml b/.github/workflows/refresh-v3-languages.yml index aab3e09..05ce2f5 100644 --- a/.github/workflows/refresh-v3-languages.yml +++ b/.github/workflows/refresh-v3-languages.yml @@ -17,7 +17,7 @@ concurrency: env: REFRESH_BRANCH: chore/refresh-v3-languages - COMMIT_PATHS: data/v3-languages + COMMIT_PATHS: data/v3-languages snippets/language-table.jsx jobs: refresh: @@ -58,6 +58,9 @@ jobs: DEEPL_AUTH_KEY: ${{ secrets.DEEPL_API_KEY }} run: python3 scripts/fetch_v3_languages.py + - name: Regenerate language-table.jsx from vended data + run: python3 scripts/generate_language_table.py + - name: Detect content changes id: diff run: | @@ -101,7 +104,7 @@ jobs: --base main \ --head "$REFRESH_BRANCH" \ --title "chore(v3-languages): refresh vended responses" \ - --body "Automated refresh of \`data/v3-languages/\` from \`https://api.deepl.com/v3/languages\`, opened by the \`refresh-v3-languages\` workflow. + --body "Automated refresh of \`data/v3-languages/\` from \`https://api.deepl.com/v3/languages\`, with \`snippets/language-table.jsx\` regenerated from the new responses. Opened by the \`refresh-v3-languages\` workflow. Subsequent runs append new commits to this PR when the responses change again, so the diff against \`main\` represents the cumulative update. Review the latest state and merge when ready. diff --git a/data/translation-memory.json b/data/translation-memory.json new file mode 100644 index 0000000..b08b2c8 --- /dev/null +++ b/data/translation-memory.json @@ -0,0 +1,17 @@ +{ + "_comment": "Languages that support Translation Memory in the DeepL platform. Translation Memory is not exposed via /v3/languages, so this list is maintained manually. Codes use the same casing as the generated language-table.jsx (uppercase, hyphen-separated). See docs/learning-how-tos/examples-and-guides/how-to-use-translation-memories.", + "languages": [ + "DE", + "EN", + "EN-GB", + "EN-US", + "ES", + "ES-419", + "FR", + "IT", + "JA", + "KO", + "ZH", + "ZH-HANS" + ] +} diff --git a/scripts/generate_language_table.py b/scripts/generate_language_table.py new file mode 100755 index 0000000..37c5612 --- /dev/null +++ b/scripts/generate_language_table.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python3 +"""Regenerate the inline language data inside snippets/language-table.jsx. + +Reads the vended JSON under data/v3-languages/ plus the manual +translation-memory list at data/translation-memory.json, then rewrites the +`languageData` array between the BEGIN/END GENERATED markers in +snippets/language-table.jsx. Everything else in the JSX file is left +alone. + +Source-of-truth mappings (per language code returned by /v3/languages): + + code uppercased BCP 47 from translate_text.json (de-CH -> DE-CH) + name translate_text "name" + translation true if present in translate_text.json + isVariant translate_text usable_as_source=false and usable_as_target=true + isBeta translate_text status != "stable" + glossaries translate_text features contain "glossary" + tagHandling translate_text features contain "tag_handling" + textImprovement language is present in write.json + styleRules translate_text features contain "style_rules" + translationMemory code is in data/translation-memory.json +""" +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parent.parent +DATA_DIR = REPO_ROOT / "data" / "v3-languages" +TM_FILE = REPO_ROOT / "data" / "translation-memory.json" +JSX_FILE = REPO_ROOT / "snippets" / "language-table.jsx" + +BEGIN_MARKER = " // BEGIN GENERATED: languageData (do not edit; run scripts/generate_language_table.py)" +END_MARKER = " // END GENERATED" + + +def load_json(path: Path) -> object: + with path.open("r", encoding="utf-8") as f: + return json.load(f) + + +def upper_code(lang: str) -> str: + return lang.upper() + + +def build_rows(tm_codes: set[str]) -> list[dict]: + translate_text = load_json(DATA_DIR / "translate_text.json") + write_langs = {entry["lang"] for entry in load_json(DATA_DIR / "write.json")} + + rows: list[dict] = [] + for entry in translate_text: + lang = entry["lang"] + code = upper_code(lang) + features = entry.get("features", {}) + row = { + "code": code, + "name": entry["name"], + "translation": True, + "isVariant": not entry["usable_as_source"] and entry["usable_as_target"], + "glossaries": "glossary" in features, + "tagHandling": "tag_handling" in features, + "textImprovement": lang in write_langs, + "translationMemory": code in tm_codes, + "styleRules": "style_rules" in features, + } + if entry.get("status") != "stable": + row["isBeta"] = True + rows.append(row) + + rows.sort(key=lambda r: (not r["isVariant"], r["code"])) + # Existing layout grouped non-variants first, then variants. Preserve + # a stable, readable order: base languages alphabetically, then variants + # alphabetically. + rows.sort(key=lambda r: (r["isVariant"], r["code"])) + return rows + + +def js_literal(value: object) -> str: + if value is True: + return "true" + if value is False: + return "false" + if isinstance(value, str): + escaped = value.replace("\\", "\\\\").replace("'", "\\'") + return f"'{escaped}'" + raise TypeError(f"Unsupported literal: {value!r}") + + +def format_row(row: dict) -> str: + # Key order is fixed so diffs remain readable. + ordered_keys = [ + "code", + "name", + "translation", + "isVariant", + "isBeta", + "glossaries", + "tagHandling", + "textImprovement", + "translationMemory", + "styleRules", + ] + parts = [f"{k}: {js_literal(row[k])}" for k in ordered_keys if k in row] + return " { " + ", ".join(parts) + " }," + + +def render_block(rows: list[dict]) -> str: + lines = [ + BEGIN_MARKER, + " const languageData = [", + *(format_row(r) for r in rows), + " ]", + END_MARKER, + ] + return "\n".join(lines) + + +def replace_block(jsx: str, new_block: str) -> str: + if BEGIN_MARKER in jsx and END_MARKER in jsx: + before, _, rest = jsx.partition(BEGIN_MARKER) + _, _, after = rest.partition(END_MARKER) + return before + new_block + after + + # First-time install: find the existing `const languageData = [ ... ]` + # block (up to and including its closing `]`) and replace it. + needle = " // Language data with individual feature support\n const languageData = [" + start = jsx.find(needle) + if start == -1: + raise RuntimeError( + "Could not locate languageData block to replace. Insert the BEGIN/END " + "GENERATED markers manually before running this script." + ) + end_anchor = "\n ]\n" + end = jsx.find(end_anchor, start) + if end == -1: + raise RuntimeError("Could not find closing ']' of languageData array.") + return jsx[:start] + new_block + jsx[end + len(end_anchor) :] + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--check", + action="store_true", + help="Exit 1 if the file would change instead of writing.", + ) + args = parser.parse_args() + + tm = load_json(TM_FILE) + if not isinstance(tm, dict) or "languages" not in tm: + print(f"error: {TM_FILE} is missing the 'languages' key", file=sys.stderr) + return 2 + tm_codes = {c.upper() for c in tm["languages"]} + + rows = build_rows(tm_codes) + new_block = render_block(rows) + + current = JSX_FILE.read_text(encoding="utf-8") + updated = replace_block(current, new_block) + + if updated == current: + print(f"{JSX_FILE}: up to date") + return 0 + if args.check: + print(f"{JSX_FILE}: out of date", file=sys.stderr) + return 1 + JSX_FILE.write_text(updated, encoding="utf-8") + print(f"{JSX_FILE}: regenerated ({len(rows)} languages)") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/snippets/language-table.jsx b/snippets/language-table.jsx index 1ee1c7b..62e8f45 100644 --- a/snippets/language-table.jsx +++ b/snippets/language-table.jsx @@ -11,140 +11,135 @@ export const LanguageTable = () => { styleRules: false }) - // Language data with individual feature support + // BEGIN GENERATED: languageData (do not edit; run scripts/generate_language_table.py) const languageData = [ - // Fully supported languages (source + target + glossaries + tag handling) + { code: 'ACE', name: 'Acehnese', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'AF', name: 'Afrikaans', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'AN', name: 'Aragonese', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, { code: 'AR', name: 'Arabic', translation: true, isVariant: false, glossaries: true, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'AS', name: 'Assamese', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'AY', name: 'Aymara', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'AZ', name: 'Azerbaijani', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'BA', name: 'Bashkir', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'BE', name: 'Belarusian', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, { code: 'BG', name: 'Bulgarian', translation: true, isVariant: false, glossaries: true, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'BHO', name: 'Bhojpuri', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'BN', name: 'Bengali', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'BR', name: 'Breton', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'BS', name: 'Bosnian', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'CA', name: 'Catalan', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'CEB', name: 'Cebuano', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'CKB', name: 'Kurdish (Sorani)', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, { code: 'CS', name: 'Czech', translation: true, isVariant: false, glossaries: true, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'CY', name: 'Welsh', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, { code: 'DA', name: 'Danish', translation: true, isVariant: false, glossaries: true, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, { code: 'DE', name: 'German', translation: true, isVariant: false, glossaries: true, tagHandling: true, textImprovement: true, translationMemory: true, styleRules: true }, { code: 'EL', name: 'Greek', translation: true, isVariant: false, glossaries: true, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'EN', name: 'English (all variants)', translation: true, isVariant: false, glossaries: true, tagHandling: true, textImprovement: false, translationMemory: true, styleRules: true }, + { code: 'EN', name: 'English', translation: true, isVariant: false, glossaries: true, tagHandling: true, textImprovement: true, translationMemory: true, styleRules: true }, + { code: 'EO', name: 'Esperanto', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, { code: 'ES', name: 'Spanish', translation: true, isVariant: false, glossaries: true, tagHandling: true, textImprovement: true, translationMemory: true, styleRules: true }, { code: 'ET', name: 'Estonian', translation: true, isVariant: false, glossaries: true, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'EU', name: 'Basque', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'FA', name: 'Persian', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, { code: 'FI', name: 'Finnish', translation: true, isVariant: false, glossaries: true, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, { code: 'FR', name: 'French', translation: true, isVariant: false, glossaries: true, tagHandling: true, textImprovement: true, translationMemory: true, styleRules: true }, + { code: 'GA', name: 'Irish', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'GL', name: 'Galician', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'GN', name: 'Guarani', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'GOM', name: 'Konkani', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'GU', name: 'Gujarati', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'HA', name: 'Hausa', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'HE', name: 'Hebrew', translation: true, isVariant: false, glossaries: true, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'HI', name: 'Hindi', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'HR', name: 'Croatian', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'HT', name: 'Haitian Creole', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, { code: 'HU', name: 'Hungarian', translation: true, isVariant: false, glossaries: true, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'HY', name: 'Armenian', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, { code: 'ID', name: 'Indonesian', translation: true, isVariant: false, glossaries: true, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'IG', name: 'Igbo', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'IS', name: 'Icelandic', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, { code: 'IT', name: 'Italian', translation: true, isVariant: false, glossaries: true, tagHandling: true, textImprovement: true, translationMemory: true, styleRules: true }, { code: 'JA', name: 'Japanese', translation: true, isVariant: false, glossaries: true, tagHandling: true, textImprovement: true, translationMemory: true, styleRules: true }, + { code: 'JV', name: 'Javanese', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'KA', name: 'Georgian', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'KK', name: 'Kazakh', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'KMR', name: 'Kurdish (Kurmanji)', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, { code: 'KO', name: 'Korean', translation: true, isVariant: false, glossaries: true, tagHandling: true, textImprovement: true, translationMemory: true, styleRules: true }, + { code: 'KY', name: 'Kyrgyz', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'LA', name: 'Latin', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'LB', name: 'Luxembourgish', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'LMO', name: 'Lombard', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'LN', name: 'Lingala', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, { code: 'LT', name: 'Lithuanian', translation: true, isVariant: false, glossaries: true, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, { code: 'LV', name: 'Latvian', translation: true, isVariant: false, glossaries: true, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'NB', name: 'Norwegian Bokmål', translation: true, isVariant: false, glossaries: true, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'MAI', name: 'Maithili', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'MG', name: 'Malagasy', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'MI', name: 'Maori', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'MK', name: 'Macedonian', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'ML', name: 'Malayalam', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'MN', name: 'Mongolian', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'MR', name: 'Marathi', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'MS', name: 'Malay', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'MT', name: 'Maltese', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'MY', name: 'Burmese', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'NB', name: 'Norwegian (bokmål)', translation: true, isVariant: false, glossaries: true, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'NE', name: 'Nepali', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, { code: 'NL', name: 'Dutch', translation: true, isVariant: false, glossaries: true, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'OC', name: 'Occitan', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'OM', name: 'Oromo', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'PA', name: 'Punjabi', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'PAG', name: 'Pangasinan', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'PAM', name: 'Kapampangan', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, { code: 'PL', name: 'Polish', translation: true, isVariant: false, glossaries: true, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'PT', name: 'Portuguese (unspecified variant)', translation: true, isVariant: false, glossaries: true, tagHandling: true, textImprovement: true, translationMemory: false, styleRules: false }, + { code: 'PRS', name: 'Dari', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'PS', name: 'Pashto', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'PT', name: 'Portuguese', translation: true, isVariant: false, glossaries: true, tagHandling: true, textImprovement: true, translationMemory: false, styleRules: false }, + { code: 'QU', name: 'Quechua', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, { code: 'RO', name: 'Romanian', translation: true, isVariant: false, glossaries: true, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, { code: 'RU', name: 'Russian', translation: true, isVariant: false, glossaries: true, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'SA', name: 'Sanskrit', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'SCN', name: 'Sicilian', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, { code: 'SK', name: 'Slovak', translation: true, isVariant: false, glossaries: true, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, { code: 'SL', name: 'Slovenian', translation: true, isVariant: false, glossaries: true, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'SQ', name: 'Albanian', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'SR', name: 'Serbian', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'ST', name: 'Sesotho', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'SU', name: 'Sundanese', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, { code: 'SV', name: 'Swedish', translation: true, isVariant: false, glossaries: true, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'SW', name: 'Swahili', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'TA', name: 'Tamil', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'TE', name: 'Telugu', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'TG', name: 'Tajik', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'TH', name: 'Thai', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'TK', name: 'Turkmen', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'TL', name: 'Tagalog', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'TN', name: 'Tswana', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, { code: 'TR', name: 'Turkish', translation: true, isVariant: false, glossaries: true, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'TS', name: 'Tsonga', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'TT', name: 'Tatar', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, { code: 'UK', name: 'Ukrainian', translation: true, isVariant: false, glossaries: true, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'ZH', name: 'Chinese (unspecified variant)', translation: true, isVariant: false, glossaries: true, tagHandling: true, textImprovement: false, translationMemory: true, styleRules: true }, - - // Target-only language variants (cannot be used as source) - { code: 'DE-CH', name: 'German (Swiss)', translation: true, isVariant: true, isBeta: true, glossaries: true, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: true }, - { code: 'EN-GB', name: 'English (British)', translation: true, isVariant: true, glossaries: true, tagHandling: true, textImprovement: true, translationMemory: true, styleRules: false }, - { code: 'EN-US', name: 'English (American)', translation: true, isVariant: true, glossaries: true, tagHandling: true, textImprovement: true, translationMemory: true, styleRules: false }, - { code: 'FR-CA', name: 'French (Canadian)', translation: true, isVariant: true, isBeta: true, glossaries: true, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: true }, + { code: 'UR', name: 'Urdu', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'UZ', name: 'Uzbek', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'VI', name: 'Vietnamese', translation: true, isVariant: false, glossaries: true, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'WO', name: 'Wolof', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'XH', name: 'Xhosa', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'YI', name: 'Yiddish', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'YUE', name: 'Cantonese', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'ZH', name: 'Chinese', translation: true, isVariant: false, glossaries: true, tagHandling: true, textImprovement: true, translationMemory: true, styleRules: true }, + { code: 'ZU', name: 'Zulu', translation: true, isVariant: false, glossaries: false, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'DE-CH', name: 'German (Swiss)', translation: true, isVariant: true, isBeta: true, glossaries: true, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'DE-DE', name: 'German', translation: true, isVariant: true, glossaries: true, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: true }, + { code: 'EN-GB', name: 'English (British)', translation: true, isVariant: true, glossaries: true, tagHandling: true, textImprovement: true, translationMemory: true, styleRules: true }, + { code: 'EN-US', name: 'English (American)', translation: true, isVariant: true, glossaries: true, tagHandling: true, textImprovement: true, translationMemory: true, styleRules: true }, + { code: 'ES-419', name: 'Spanish (Latin American)', translation: true, isVariant: true, glossaries: true, tagHandling: true, textImprovement: false, translationMemory: true, styleRules: true }, + { code: 'FR-CA', name: 'French (Canadian)', translation: true, isVariant: true, isBeta: true, glossaries: true, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: true }, + { code: 'FR-FR', name: 'French', translation: true, isVariant: true, glossaries: true, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: true }, { code: 'PT-BR', name: 'Portuguese (Brazilian)', translation: true, isVariant: true, glossaries: true, tagHandling: true, textImprovement: true, translationMemory: false, styleRules: false }, - { code: 'PT-PT', name: 'Portuguese (European)', translation: true, isVariant: true, glossaries: true, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'ZH-HANS', name: 'Chinese (simplified)', translation: true, isVariant: true, glossaries: true, tagHandling: true, textImprovement: true, translationMemory: true, styleRules: false }, - { code: 'ZH-HANT', name: 'Chinese (traditional)', translation: true, isVariant: true, glossaries: true, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: false }, - - // Target-only variant - { code: 'ES-419', name: 'Spanish (Latin American)', translation: true, isVariant: true, glossaries: true, tagHandling: true, textImprovement: false, translationMemory: true, styleRules: false }, - - // Text-only languages (both source and target, but no glossaries or tag handling) - { code: 'ACE', name: 'Acehnese', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'AF', name: 'Afrikaans', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'AN', name: 'Aragonese', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'AS', name: 'Assamese', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'AY', name: 'Aymara', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'AZ', name: 'Azerbaijani', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'BA', name: 'Bashkir', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'BE', name: 'Belarusian', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'BHO', name: 'Bhojpuri', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'BN', name: 'Bengali', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'BR', name: 'Breton', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'BS', name: 'Bosnian', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'CA', name: 'Catalan', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'CEB', name: 'Cebuano', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'CKB', name: 'Kurdish (Sorani)', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'CY', name: 'Welsh', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'EO', name: 'Esperanto', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'EU', name: 'Basque', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'FA', name: 'Persian', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'GA', name: 'Irish', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'GL', name: 'Galician', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'GN', name: 'Guarani', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'GOM', name: 'Konkani', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'GU', name: 'Gujarati', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'HA', name: 'Hausa', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'HE', name: 'Hebrew', translation: true, isVariant: false, glossaries: true, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'HI', name: 'Hindi', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'HR', name: 'Croatian', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'HT', name: 'Haitian Creole', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'HY', name: 'Armenian', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'IG', name: 'Igbo', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'IS', name: 'Icelandic', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'JV', name: 'Javanese', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'KA', name: 'Georgian', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'KK', name: 'Kazakh', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'KMR', name: 'Kurdish (Kurmanji)', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'KY', name: 'Kyrgyz', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'LA', name: 'Latin', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'LB', name: 'Luxembourgish', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'LMO', name: 'Lombard', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'LN', name: 'Lingala', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'MAI', name: 'Maithili', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'MG', name: 'Malagasy', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'MI', name: 'Maori', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'MK', name: 'Macedonian', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'ML', name: 'Malayalam', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'MN', name: 'Mongolian', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'MR', name: 'Marathi', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'MS', name: 'Malay', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'MT', name: 'Maltese', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'MY', name: 'Burmese', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'NE', name: 'Nepali', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'OC', name: 'Occitan', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'OM', name: 'Oromo', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'PA', name: 'Punjabi', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'PAG', name: 'Pangasinan', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'PAM', name: 'Kapampangan', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'PRS', name: 'Dari', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'PS', name: 'Pashto', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'QU', name: 'Quechua', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'SA', name: 'Sanskrit', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'SCN', name: 'Sicilian', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'SQ', name: 'Albanian', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'SR', name: 'Serbian', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'ST', name: 'Sesotho', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'SU', name: 'Sundanese', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'SW', name: 'Swahili', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'TA', name: 'Tamil', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'TE', name: 'Telugu', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'TG', name: 'Tajik', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'TH', name: 'Thai', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'TK', name: 'Turkmen', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'TL', name: 'Tagalog', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'TN', name: 'Tswana', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'TS', name: 'Tsonga', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'TT', name: 'Tatar', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'UR', name: 'Urdu', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'UZ', name: 'Uzbek', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'VI', name: 'Vietnamese', translation: true, isVariant: false, glossaries: true, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'WO', name: 'Wolof', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'XH', name: 'Xhosa', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'YI', name: 'Yiddish', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'YUE', name: 'Cantonese', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, - { code: 'ZU', name: 'Zulu', translation: true, isVariant: false, glossaries: false, tagHandling: false, textImprovement: false, translationMemory: false, styleRules: false }, + { code: 'PT-PT', name: 'Portuguese (European)', translation: true, isVariant: true, glossaries: true, tagHandling: true, textImprovement: true, translationMemory: false, styleRules: false }, + { code: 'ZH-HANS', name: 'Chinese (simplified)', translation: true, isVariant: true, glossaries: true, tagHandling: true, textImprovement: true, translationMemory: true, styleRules: true }, + { code: 'ZH-HANT', name: 'Chinese (traditional)', translation: true, isVariant: true, glossaries: true, tagHandling: true, textImprovement: false, translationMemory: false, styleRules: true }, ] - + // END GENERATED // Filter and sort data const filteredData = useMemo(() => { let filtered = languageData.filter(lang => {