source_utils_languages.bs

' Maps 3-letter ISO 639-2 codes (T = terminological, B = bibliographic) and
' deprecated ISO 639-1 codes to the 2-letter base codes used by the app's
' translation system. Only languages the app supports as a UI locale are
' covered (see scripts/update-translations.cjs LANGUAGE_METADATA).
'
' Why this exists: ffmpeg/Jellyfin pass through whatever the container
' metadata holds — most modern files use 639-2/T (e.g., "fra"), older or
' hand-tagged files use 639-2/B (e.g., "fre"), and some use 639-1 ("fr"
' already, no alias needed). MediaStream.Language is therefore not a single
' standardized format; this map normalizes to one canonical form.
'
' 3-letter codes that have no 2-letter ISO 639-1 equivalent (ckb, fil, gsw,
' jbo, kab) are not aliased here — they're looked up directly in
' languageTranslationKeys() under their 3-letter form.
'
' Cached on the caller's m scope after first build to avoid reallocating
' on every audio/subtitle render.
'
' @returns {object} - AssocArray of 3-letter alias to 2-letter base code
function mediaLanguageAliases() as object
  if isValid(m.mediaLanguageAliasesCache) then return m.mediaLanguageAliasesCache

  m.mediaLanguageAliasesCache = {
    "afr": "af",
    "ara": "ar",
    "asm": "as",
    "bel": "be",
    "bul": "bg",
    "ben": "bn",
    "bre": "br",
    "cat": "ca",
    "ces": "cs", "cze": "cs",
    "cym": "cy", "wel": "cy",
    "dan": "da",
    "deu": "de", "ger": "de",
    "div": "dv",
    "ell": "el", "gre": "el",
    "eng": "en",
    "epo": "eo",
    "spa": "es",
    "est": "et",
    "eus": "eu", "baq": "eu",
    "fas": "fa", "per": "fa",
    "fin": "fi",
    "fao": "fo",
    "fra": "fr", "fre": "fr",
    "frc": "fr",
    "gle": "ga",
    "glg": "gl",
    "guj": "gu",
    "heb": "he", "iw": "he",
    "hin": "hi",
    "hrv": "hr",
    "hat": "ht",
    "hun": "hu",
    "hye": "hy", "arm": "hy",
    "ind": "id", "in": "id",
    "isl": "is", "ice": "is",
    "ita": "it",
    "jpn": "ja",
    "ji": "yid",
    "kat": "ka", "geo": "ka",
    "kaz": "kk",
    "kan": "kn",
    "kor": "ko",
    "cor": "kw",
    "ltz": "lb",
    "lit": "lt",
    "lav": "lv",
    "mlg": "mg",
    "mri": "mi", "mao": "mi",
    "mkd": "mk", "mac": "mk",
    "mal": "ml",
    "mon": "mn",
    "mar": "mr",
    "msa": "ms", "may": "ms",
    "mlt": "mt",
    "mya": "my", "bur": "my",
    "nob": "nb",
    "nep": "ne",
    "nld": "nl", "dut": "nl",
    "nno": "nn",
    "nor": "no",
    "pan": "pa",
    "pob": "pt",
    "pol": "pl",
    "por": "pt",
    "ron": "ro", "rum": "ro",
    "rus": "ru",
    "sin": "si",
    "slk": "sk", "slo": "sk",
    "slv": "sl",
    "som": "so",
    "sqi": "sq", "alb": "sq",
    "srp": "sr",
    "swe": "sv",
    "tam": "ta",
    "tel": "te",
    "tha": "th",
    "tur": "tr",
    "uig": "ug",
    "ukr": "uk",
    "urd": "ur",
    "uzb": "uz",
    "vie": "vi",
    "zho": "zh", "chi": "zh",
    "zul": "zu"
  }

  return m.mediaLanguageAliasesCache
end function

' Maps a normalized 2-letter base code (or a 3-letter code for languages
' without a 639-1 equivalent) to the translation key used by translate()
' for the localized language name. Keys must exist in locale/custom/en_US.json.
'
' Region/script suffixes (e.g., "_BR", "_Hant") are stripped before lookup
' by resolveLanguageName(), so en_GB and en_US both land at "en" → LanguageEn.
'
' Cached on the caller's m scope after first build.
'
' @returns {object} - AssocArray of base code to translationKeys.* string
function languageTranslationKeys() as object
  if isValid(m.languageTranslationKeysCache) then return m.languageTranslationKeysCache

  m.languageTranslationKeysCache = {
    "af": translationKeys.LanguageAf,
    "ar": translationKeys.LanguageAr,
    "as": translationKeys.LanguageAs,
    "be": translationKeys.LanguageBe,
    "bg": translationKeys.LanguageBg,
    "bn": translationKeys.LanguageBn,
    "br": translationKeys.LanguageBr,
    "ca": translationKeys.LanguageCa,
    "ckb": translationKeys.LanguageCkb,
    "cs": translationKeys.LanguageCs,
    "cy": translationKeys.LanguageCy,
    "da": translationKeys.LanguageDa,
    "de": translationKeys.LanguageDe,
    "dv": translationKeys.LanguageDv,
    "el": translationKeys.LanguageEl,
    "en": translationKeys.LanguageEn,
    "eo": translationKeys.LanguageEo,
    "es": translationKeys.LanguageEs,
    "et": translationKeys.LanguageEt,
    "eu": translationKeys.LanguageEu,
    "fa": translationKeys.LanguageFa,
    "fi": translationKeys.LanguageFi,
    "fil": translationKeys.LanguageFil,
    "fo": translationKeys.LanguageFo,
    "fr": translationKeys.LanguageFr,
    "ga": translationKeys.LanguageGa,
    "gl": translationKeys.LanguageGl,
    "gsw": translationKeys.LanguageGsw,
    "gu": translationKeys.LanguageGu,
    "he": translationKeys.LanguageHe,
    "hi": translationKeys.LanguageHi,
    "hr": translationKeys.LanguageHr,
    "ht": translationKeys.LanguageHt,
    "hu": translationKeys.LanguageHu,
    "hy": translationKeys.LanguageHy,
    "id": translationKeys.LanguageId,
    "is": translationKeys.LanguageIs,
    "it": translationKeys.LanguageIt,
    "ja": translationKeys.LanguageJa,
    "jbo": translationKeys.LanguageJbo,
    "ka": translationKeys.LanguageKa,
    "kab": translationKeys.LanguageKab,
    "kk": translationKeys.LanguageKk,
    "kn": translationKeys.LanguageKn,
    "ko": translationKeys.LanguageKo,
    "kw": translationKeys.LanguageKw,
    "lb": translationKeys.LanguageLb,
    "lt": translationKeys.LanguageLt,
    "lv": translationKeys.LanguageLv,
    "mg": translationKeys.LanguageMg,
    "mi": translationKeys.LanguageMi,
    "mk": translationKeys.LanguageMk,
    "ml": translationKeys.LanguageMl,
    "mn": translationKeys.LanguageMn,
    "mr": translationKeys.LanguageMr,
    "ms": translationKeys.LanguageMs,
    "mt": translationKeys.LanguageMt,
    "my": translationKeys.LanguageMy,
    "nb": translationKeys.LanguageNb,
    "ne": translationKeys.LanguageNe,
    "nl": translationKeys.LanguageNl,
    "nn": translationKeys.LanguageNn,
    "no": translationKeys.LanguageNo,
    "pa": translationKeys.LanguagePa,
    "pl": translationKeys.LanguagePl,
    "pt": translationKeys.LanguagePt,
    "ro": translationKeys.LanguageRo,
    "ru": translationKeys.LanguageRu,
    "si": translationKeys.LanguageSi,
    "sk": translationKeys.LanguageSk,
    "sl": translationKeys.LanguageSl,
    "so": translationKeys.LanguageSo,
    "sq": translationKeys.LanguageSq,
    "sr": translationKeys.LanguageSr,
    "sv": translationKeys.LanguageSv,
    "ta": translationKeys.LanguageTa,
    "te": translationKeys.LanguageTe,
    "th": translationKeys.LanguageTh,
    "tr": translationKeys.LanguageTr,
    "ug": translationKeys.LanguageUg,
    "uk": translationKeys.LanguageUk,
    "ur": translationKeys.LanguageUr,
    "uz": translationKeys.LanguageUz,
    "vi": translationKeys.LanguageVi,
    "zh": translationKeys.LanguageZh,
    "zu": translationKeys.LanguageZu
  }

  return m.languageTranslationKeysCache
end function

' Comprehensive English-name fallback for ISO 639-2 codes that the app does
' NOT have a UI translation for (e.g., "lat" Latin, "swa" Swahili). Used as
' tier 2 by resolveLanguageName() — translatable languages always go through
' translate() first; this only fires when no LanguageX key exists for the
' resolved base code, ensuring no recognized 3-letter code ever displays raw.
'
' Names are the official ISO 639-2 forms. They are intentionally NOT routed
' through the i18n pipeline: these languages are unlikely to be a UI locale
' for our users, so the maintenance cost of translating "Akkadian" into
' Polish (etc.) outweighs the UX benefit. Users see the English name in any
' UI when an audio/subtitle track is in one of these less-common languages.
'
' Cached on the caller's m scope after first build.
'
' @returns {object} - AssocArray of 3-letter ISO 639-2 code to English name
function languageEnglishFallbacks() as object
  if isValid(m.languageEnglishFallbacksCache) then return m.languageEnglishFallbacksCache

  m.languageEnglishFallbacksCache = {
    "aar": "Afar",
    "abk": "Abkhazian",
    "ace": "Achinese",
    "ach": "Acoli",
    "ada": "Adangme",
    "ady": "Adyghe",
    "afa": "Afro-Asiatic languages",
    "afh": "Afrihili",
    "ain": "Ainu",
    "aka": "Akan",
    "akk": "Akkadian",
    "ale": "Aleut",
    "alg": "Algonquian languages",
    "alt": "Southern Altai",
    "amh": "Amharic",
    "ang": "Old English",
    "anp": "Angika",
    "apa": "Apache languages",
    "arc": "Aramaic",
    "arg": "Aragonese",
    "arn": "Mapudungun",
    "arp": "Arapaho",
    "art": "Artificial languages",
    "arw": "Arawak",
    "ast": "Asturian",
    "ath": "Athapascan languages",
    "aus": "Australian languages",
    "ava": "Avaric",
    "ave": "Avestan",
    "awa": "Awadhi",
    "aym": "Aymara",
    "aze": "Azerbaijani",
    "bad": "Banda languages",
    "bai": "Bamileke languages",
    "bak": "Bashkir",
    "bal": "Baluchi",
    "bam": "Bambara",
    "ban": "Balinese",
    "bas": "Basa",
    "bat": "Baltic languages",
    "bej": "Beja",
    "bem": "Bemba",
    "ber": "Berber languages",
    "bho": "Bhojpuri",
    "bih": "Bihari languages",
    "bik": "Bikol",
    "bin": "Bini",
    "bis": "Bislama",
    "bla": "Siksika",
    "bnt": "Bantu languages",
    "bod": "Tibetan",
    "bos": "Bosnian",
    "bra": "Braj",
    "btk": "Batak languages",
    "bua": "Buriat",
    "bug": "Buginese",
    "byn": "Blin",
    "cad": "Caddo",
    "cai": "Central American Indian languages",
    "car": "Galibi Carib",
    "cau": "Caucasian languages",
    "ceb": "Cebuano",
    "cel": "Celtic languages",
    "cha": "Chamorro",
    "chb": "Chibcha",
    "che": "Chechen",
    "chg": "Chagatai",
    "chk": "Chuukese",
    "chm": "Mari",
    "chn": "Chinook jargon",
    "cho": "Choctaw",
    "chp": "Chipewyan",
    "chr": "Cherokee",
    "chu": "Church Slavic",
    "chv": "Chuvash",
    "chy": "Cheyenne",
    "cmc": "Chamic languages",
    "cnr": "Montenegrin",
    "cop": "Coptic",
    "cos": "Corsican",
    "cpe": "Creoles and pidgins, English-based",
    "cpf": "Creoles and pidgins, French-based",
    "cpp": "Creoles and pidgins, Portuguese-based",
    "cre": "Cree",
    "crh": "Crimean Tatar",
    "crp": "Creoles and pidgins",
    "csb": "Kashubian",
    "cus": "Cushitic languages",
    "dak": "Dakota",
    "dar": "Dargwa",
    "day": "Land Dayak languages",
    "del": "Delaware",
    "den": "Slave",
    "dgr": "Dogrib",
    "din": "Dinka",
    "doi": "Dogri",
    "dra": "Dravidian languages",
    "dsb": "Lower Sorbian",
    "dua": "Duala",
    "dum": "Middle Dutch",
    "dyu": "Dyula",
    "dzo": "Dzongkha",
    "efi": "Efik",
    "egy": "Ancient Egyptian",
    "eka": "Ekajuk",
    "elx": "Elamite",
    "enm": "Middle English",
    "ewe": "Ewe",
    "ewo": "Ewondo",
    "fan": "Fang",
    "fat": "Fanti",
    "fij": "Fijian",
    "fiu": "Finno-Ugrian languages",
    "fon": "Fon",
    "frc": "Cajun French",
    "frm": "Middle French",
    "fro": "Old French",
    "frr": "Northern Frisian",
    "frs": "Eastern Frisian",
    "fry": "Western Frisian",
    "ful": "Fulah",
    "fur": "Friulian",
    "gaa": "Ga",
    "gay": "Gayo",
    "gba": "Gbaya",
    "gem": "Germanic languages",
    "gez": "Geez",
    "gil": "Gilbertese",
    "gla": "Scottish Gaelic",
    "gmh": "Middle High German",
    "goh": "Old High German",
    "gon": "Gondi",
    "gor": "Gorontalo",
    "got": "Gothic",
    "glv": "Manx",
    "grb": "Grebo",
    "grc": "Ancient Greek",
    "grn": "Guarani",
    "gwi": "Gwich'in",
    "hai": "Haida",
    "hau": "Hausa",
    "haw": "Hawaiian",
    "her": "Herero",
    "hil": "Hiligaynon",
    "him": "Himachali languages",
    "hit": "Hittite",
    "hmn": "Hmong",
    "hmo": "Hiri Motu",
    "hsb": "Upper Sorbian",
    "hup": "Hupa",
    "iba": "Iban",
    "ibo": "Igbo",
    "ido": "Ido",
    "iii": "Sichuan Yi",
    "ijo": "Ijo languages",
    "iku": "Inuktitut",
    "ile": "Interlingue",
    "ilo": "Iloko",
    "ina": "Interlingua",
    "inc": "Indic languages",
    "ine": "Indo-European languages",
    "inh": "Ingush",
    "ipk": "Inupiaq",
    "ira": "Iranian languages",
    "iro": "Iroquoian languages",
    "jav": "Javanese",
    "jbo": "Lojban",
    "jpr": "Judeo-Persian",
    "jrb": "Judeo-Arabic",
    "kaa": "Kara-Kalpak",
    "kac": "Kachin",
    "kal": "Kalaallisut",
    "kam": "Kamba",
    "kar": "Karen languages",
    "kas": "Kashmiri",
    "kau": "Kanuri",
    "kaw": "Kawi",
    "kbd": "Kabardian",
    "kha": "Khasi",
    "khi": "Khoisan languages",
    "khm": "Khmer",
    "kho": "Khotanese",
    "kik": "Kikuyu",
    "kin": "Kinyarwanda",
    "kir": "Kirghiz",
    "kmb": "Kimbundu",
    "kok": "Konkani",
    "kom": "Komi",
    "kon": "Kongo",
    "kos": "Kosraean",
    "kpe": "Kpelle",
    "krc": "Karachay-Balkar",
    "krl": "Karelian",
    "kro": "Kru languages",
    "kru": "Kurukh",
    "kua": "Kuanyama",
    "kum": "Kumyk",
    "kur": "Kurdish",
    "kut": "Kutenai",
    "lad": "Ladino",
    "lah": "Lahnda",
    "lam": "Lamba",
    "lao": "Lao",
    "lat": "Latin",
    "lez": "Lezghian",
    "lim": "Limburgish",
    "lin": "Lingala",
    "lol": "Mongo",
    "loz": "Lozi",
    "lua": "Luba-Lulua",
    "lub": "Luba-Katanga",
    "lug": "Ganda",
    "lui": "Luiseno",
    "lun": "Lunda",
    "luo": "Luo",
    "lus": "Lushai",
    "mad": "Madurese",
    "mag": "Magahi",
    "mah": "Marshallese",
    "mai": "Maithili",
    "mak": "Makasar",
    "man": "Mandingo",
    "map": "Austronesian languages",
    "mas": "Masai",
    "mdf": "Moksha",
    "mdr": "Mandar",
    "men": "Mende",
    "mga": "Middle Irish",
    "mic": "Mi'kmaq",
    "min": "Minangkabau",
    "mis": "Uncoded languages",
    "mkh": "Mon-Khmer languages",
    "mnc": "Manchu",
    "mni": "Manipuri",
    "mno": "Manobo languages",
    "moh": "Mohawk",
    "mos": "Mossi",
    "mul": "Multiple languages",
    "mun": "Munda languages",
    "mus": "Creek",
    "mwl": "Mirandese",
    "mwr": "Marwari",
    "myn": "Mayan languages",
    "myv": "Erzya",
    "nah": "Nahuatl languages",
    "nai": "North American Indian languages",
    "nap": "Neapolitan",
    "nau": "Nauru",
    "nav": "Navajo",
    "nbl": "South Ndebele",
    "nde": "North Ndebele",
    "ndo": "Ndonga",
    "nds": "Low German",
    "new": "Newari",
    "nia": "Nias",
    "nic": "Niger-Kordofanian languages",
    "niu": "Niuean",
    "nog": "Nogai",
    "non": "Old Norse",
    "nqo": "N'Ko",
    "nso": "Northern Sotho",
    "nub": "Nubian languages",
    "nwc": "Classical Newari",
    "nya": "Nyanja",
    "nym": "Nyamwezi",
    "nyn": "Nyankole",
    "nyo": "Nyoro",
    "nzi": "Nzima",
    "oci": "Occitan",
    "oji": "Ojibwa",
    "ori": "Oriya",
    "orm": "Oromo",
    "osa": "Osage",
    "oss": "Ossetian",
    "ota": "Ottoman Turkish",
    "oto": "Otomian languages",
    "paa": "Papuan languages",
    "pag": "Pangasinan",
    "pal": "Pahlavi",
    "pam": "Pampanga",
    "pap": "Papiamento",
    "pau": "Palauan",
    "peo": "Old Persian",
    "phi": "Philippine languages",
    "phn": "Phoenician",
    "pli": "Pali",
    "pon": "Pohnpeian",
    "pra": "Prakrit languages",
    "pro": "Old Provençal",
    "pus": "Pashto",
    "que": "Quechua",
    "raj": "Rajasthani",
    "rap": "Rapanui",
    "rar": "Rarotongan",
    "roa": "Romance languages",
    "roh": "Romansh",
    "rom": "Romany",
    "run": "Rundi",
    "rup": "Aromanian",
    "sad": "Sandawe",
    "sag": "Sango",
    "sah": "Yakut",
    "sai": "South American Indian languages",
    "sal": "Salishan languages",
    "sam": "Samaritan Aramaic",
    "san": "Sanskrit",
    "sas": "Sasak",
    "sat": "Santali",
    "scn": "Sicilian",
    "sco": "Scots",
    "sel": "Selkup",
    "sem": "Semitic languages",
    "sga": "Old Irish",
    "sgn": "Sign Languages",
    "shn": "Shan",
    "sid": "Sidamo",
    "sio": "Siouan languages",
    "sit": "Sino-Tibetan languages",
    "sla": "Slavic languages",
    "sma": "Southern Sami",
    "sme": "Northern Sami",
    "smi": "Sami languages",
    "smj": "Lule Sami",
    "smn": "Inari Sami",
    "smo": "Samoan",
    "sms": "Skolt Sami",
    "sna": "Shona",
    "snd": "Sindhi",
    "snk": "Soninke",
    "sog": "Sogdian",
    "son": "Songhai languages",
    "sot": "Southern Sotho",
    "srd": "Sardinian",
    "srn": "Sranan Tongo",
    "srr": "Serer",
    "ssa": "Nilo-Saharan languages",
    "ssw": "Swati",
    "suk": "Sukuma",
    "sun": "Sundanese",
    "sus": "Susu",
    "sux": "Sumerian",
    "swa": "Swahili",
    "syc": "Classical Syriac",
    "syr": "Syriac",
    "tah": "Tahitian",
    "tai": "Tai languages",
    "tat": "Tatar",
    "tem": "Timne",
    "ter": "Tereno",
    "tet": "Tetum",
    "tgk": "Tajik",
    "tgl": "Tagalog",
    "tib": "Tibetan",
    "tig": "Tigre",
    "tir": "Tigrinya",
    "tiv": "Tiv",
    "tkl": "Tokelau",
    "tlh": "Klingon",
    "tli": "Tlingit",
    "tmh": "Tamashek",
    "tog": "Nyasa Tonga",
    "ton": "Tonga",
    "tpi": "Tok Pisin",
    "tsi": "Tsimshian",
    "tsn": "Tswana",
    "tso": "Tsonga",
    "tuk": "Turkmen",
    "tum": "Tumbuka",
    "tup": "Tupi languages",
    "tut": "Altaic languages",
    "tvl": "Tuvalu",
    "twi": "Twi",
    "tyv": "Tuvinian",
    "udm": "Udmurt",
    "uga": "Ugaritic",
    "umb": "Umbundu",
    "vai": "Vai",
    "ven": "Venda",
    "vol": "Volapük",
    "vot": "Votic",
    "wak": "Wakashan languages",
    "wal": "Walamo",
    "war": "Waray",
    "was": "Washo",
    "wen": "Sorbian languages",
    "wln": "Walloon",
    "wol": "Wolof",
    "xal": "Kalmyk",
    "xho": "Xhosa",
    "yao": "Yao",
    "yap": "Yapese",
    "yid": "Yiddish",
    "yor": "Yoruba",
    "ypk": "Yupik languages",
    "zap": "Zapotec",
    "zbl": "Blissymbols",
    "zen": "Zenaga",
    "zgh": "Standard Moroccan Tamazight",
    "zha": "Zhuang",
    "znd": "Zande languages",
    "zun": "Zuni",
    "zza": "Zaza"
  }

  return m.languageEnglishFallbacksCache
end function