backend/config_language_data.py
Rama master · réplica de lectura del repositorio original.
"""Language tag data — ISO 639-1 to all known file/metadata variant mappings.
Extracted from config.py for size management. config.py re-exports everything
from this module for backwards compatibility.
"""
# Language tag mapping (ISO 639-1 -> all known file/metadata variants)
_LANGUAGE_TAGS: dict[str, set[str]] = {
# Western Europe
"de": {"de", "deu", "ger", "german"},
"en": {"en", "eng", "english"},
"fr": {"fr", "fra", "fre", "french"},
"es": {"es", "spa", "spanish"},
"it": {"it", "ita", "italian"},
# "pob"/"pb" are the Bazarr/OpenSubtitles conventions for pt-BR.
"pt": {"pt", "por", "portuguese", "pt-br", "pob", "pb"},
"nl": {"nl", "nld", "dut", "dutch"},
"sv": {"sv", "swe", "swedish"},
"da": {"da", "dan", "danish"},
# Bokmål (nb/nob) and Nynorsk (nn/nno) tags are common in real releases
# and must map onto the generic Norwegian target.
"no": {"no", "nor", "norwegian", "nb", "nob", "nn", "nno"},
"fi": {"fi", "fin", "finnish"},
"is": {"is", "isl", "ice", "icelandic"},
"eu": {"eu", "eus", "baq", "basque"},
"ca": {"ca", "cat", "catalan"},
"gl": {"gl", "glg", "galician"},
# Eastern Europe
"pl": {"pl", "pol", "polish"},
"cs": {"cs", "ces", "cze", "czech"},
"sk": {"sk", "slk", "slo", "slovak"},
"hu": {"hu", "hun", "hungarian"},
"ro": {"ro", "ron", "rum", "romanian"},
"bg": {"bg", "bul", "bulgarian"},
"hr": {"hr", "hrv", "croatian"},
"sr": {"sr", "srp", "serbian"},
"sl": {"sl", "slv", "slovenian"},
"bs": {"bs", "bos", "bosnian"},
"mk": {"mk", "mkd", "macedonian"},
"sq": {"sq", "alb", "sqi", "albanian"},
"lt": {"lt", "lit", "lithuanian"},
"lv": {"lv", "lav", "latvian"},
"et": {"et", "est", "estonian"},
"uk": {"uk", "ukr", "ukrainian"},
"ru": {"ru", "rus", "russian"},
# Caucasus / Central Asia
"hy": {"hy", "hye", "arm", "armenian"},
"ka": {"ka", "kat", "geo", "georgian"},
"az": {"az", "aze", "azerbaijani"},
"kk": {"kk", "kaz", "kazakh"},
"uz": {"uz", "uzb", "uzbek"},
# Middle East
"ar": {"ar", "ara", "arabic"},
"he": {"he", "heb", "hebrew"},
"fa": {"fa", "per", "fas", "persian"},
"tr": {"tr", "tur", "turkish"},
# South / Southeast Asia
"hi": {"hi", "hin", "hindi"},
"bn": {"bn", "ben", "bengali"},
"ur": {"ur", "urd", "urdu"},
"ta": {"ta", "tam", "tamil"},
"te": {"te", "tel", "telugu"},
"ml": {"ml", "mal", "malayalam"},
"kn": {"kn", "kan", "kannada"},
"si": {"si", "sin", "sinhala"},
"th": {"th", "tha", "thai"},
"vi": {"vi", "vie", "vietnamese"},
"id": {"id", "ind", "indonesian"},
"ms": {"ms", "msa", "may", "malay"},
"tl": {"tl", "fil", "tagalog", "filipino"},
# East Asia
"ja": {"ja", "jpn", "japanese"},
"ko": {"ko", "kor", "korean"},
"zh": {"zh", "zho", "chi", "chinese"},
"zh-hans": {"zh-hans", "zhs", "chs", "chi-sim", "chinese simplified"},
"zh-hant": {"zh-hant", "zht", "cht", "chi-tra", "chinese traditional"},
"mn": {"mn", "mon", "mongolian"},
# Other
"el": {"el", "ell", "gre", "greek"},
"af": {"af", "afr", "afrikaans"},
"sw": {"sw", "swa", "swahili"},
}
# Ordered list of supported languages for the UI language picker
_CORE_LANGUAGES: list[dict] = [
{"code": "af", "name": "Afrikaans"},
{"code": "sq", "name": "Albanian"},
{"code": "ar", "name": "Arabic"},
{"code": "hy", "name": "Armenian"},
{"code": "az", "name": "Azerbaijani"},
{"code": "eu", "name": "Basque"},
{"code": "bn", "name": "Bengali"},
{"code": "bs", "name": "Bosnian"},
{"code": "bg", "name": "Bulgarian"},
{"code": "ca", "name": "Catalan"},
{"code": "zh", "name": "Chinese"},
{"code": "zh-hans", "name": "Chinese (Simplified)"},
{"code": "zh-hant", "name": "Chinese (Traditional)"},
{"code": "hr", "name": "Croatian"},
{"code": "cs", "name": "Czech"},
{"code": "da", "name": "Danish"},
{"code": "nl", "name": "Dutch"},
{"code": "en", "name": "English"},
{"code": "et", "name": "Estonian"},
{"code": "tl", "name": "Filipino"},
{"code": "fi", "name": "Finnish"},
{"code": "fr", "name": "French"},
{"code": "gl", "name": "Galician"},
{"code": "ka", "name": "Georgian"},
{"code": "de", "name": "German"},
{"code": "el", "name": "Greek"},
{"code": "he", "name": "Hebrew"},
{"code": "hi", "name": "Hindi"},
{"code": "hu", "name": "Hungarian"},
{"code": "is", "name": "Icelandic"},
{"code": "id", "name": "Indonesian"},
{"code": "it", "name": "Italian"},
{"code": "ja", "name": "Japanese"},
{"code": "kn", "name": "Kannada"},
{"code": "kk", "name": "Kazakh"},
{"code": "ko", "name": "Korean"},
{"code": "lv", "name": "Latvian"},
{"code": "lt", "name": "Lithuanian"},
{"code": "mk", "name": "Macedonian"},
{"code": "ms", "name": "Malay"},
{"code": "ml", "name": "Malayalam"},
{"code": "mn", "name": "Mongolian"},
{"code": "no", "name": "Norwegian"},
{"code": "fa", "name": "Persian"},
{"code": "pl", "name": "Polish"},
{"code": "pt", "name": "Portuguese"},
{"code": "ro", "name": "Romanian"},
{"code": "ru", "name": "Russian"},
{"code": "sr", "name": "Serbian"},
{"code": "si", "name": "Sinhala"},
{"code": "sk", "name": "Slovak"},
{"code": "sl", "name": "Slovenian"},
{"code": "es", "name": "Spanish"},
{"code": "sw", "name": "Swahili"},
{"code": "sv", "name": "Swedish"},
{"code": "ta", "name": "Tamil"},
{"code": "te", "name": "Telugu"},
{"code": "th", "name": "Thai"},
{"code": "tr", "name": "Turkish"},
{"code": "uk", "name": "Ukrainian"},
{"code": "ur", "name": "Urdu"},
{"code": "uz", "name": "Uzbek"},
{"code": "vi", "name": "Vietnamese"},
]
def _with_extra_languages() -> list[dict]:
"""Core languages plus every other ISO 639-1 language, each with a German name.
New languages get tags too (code, both three-letter codes, English name),
so a track tagged ``bel`` resolves to ``be`` like ``ger`` resolves to
``de``. A tag another language already owns is never taken over.
"""
from config_language_names import EXTRA_LANGUAGES, GERMAN_NAMES
# "lat" names Latin in ISO 639-2 but Latin-American Spanish in common
# subtitle naming ("Movie.lat.srt"); claiming it for Latin would let the
# sidecar cleanup trash those files. It stays unknown, as before.
taken = {tag for tags in _LANGUAGE_TAGS.values() for tag in tags} | {"lat"}
entries = [
{**entry, "name_de": GERMAN_NAMES.get(entry["code"], entry["name"])}
for entry in _CORE_LANGUAGES
]
for code, alpha3, alpha3b, name_en, name_de in EXTRA_LANGUAGES:
if code in _LANGUAGE_TAGS:
continue
tags = {code} | ({alpha3, alpha3b, name_en.lower()} - taken)
_LANGUAGE_TAGS[code] = tags
taken |= tags
entries.append({"code": code, "name": name_en, "name_de": name_de})
return sorted(entries, key=lambda entry: entry["name"])
SUPPORTED_LANGUAGES: list[dict] = _with_extra_languages()
def _get_language_tags(lang_code: str) -> set[str]:
"""Get all known tags for a language code.
The input is canonicalised first, so 3-letter/legacy codes ("ger",
"eng") expand exactly like their 2-letter equivalents — previously a
keep-list entry of "ger" produced the 1-element set {"ger"}, which
failed to match "de"/"deu"-tagged tracks on destructive cleanup paths.
Script-variant codes (zh-hans, zh-hant) additionally include their base
language's tags: real containers tag Chinese tracks "chi"/"zho"/"zh",
and a zh-hans target must never classify those as foreign/disposable.
"""
key = (lang_code or "").lower().strip()
canonical = _REVERSE_LANGUAGE_TAGS.get(key, key)
tags = set(_LANGUAGE_TAGS.get(canonical, {key}))
if "-" in canonical:
base = canonical.split("-", 1)[0]
tags |= _LANGUAGE_TAGS.get(base, set())
return tags
# Reverse lookup: any known tag -> canonical ISO 639-1 code.
# Built lazily/deterministically at module import time so callers get O(1) lookup.
_REVERSE_LANGUAGE_TAGS: dict[str, str] = {
tag: code for code, tags in _LANGUAGE_TAGS.items() for tag in tags
}
def language_name(code: str) -> str:
"""English display name for an ISO 639-1 code; the code itself if unknown."""
normalized = normalize_language_code(code or "")
for entry in SUPPORTED_LANGUAGES:
if entry.get("code") == normalized:
return entry.get("name", normalized)
return code
def normalize_language_code(raw: str) -> str:
"""Map any known language tag to its canonical ISO 639-1 code.
Examples:
'ger', 'deu', 'german', 'DE' -> 'de'
'eng', 'english' -> 'en'
'jpn', 'japanese' -> 'ja'
Unknown tags (including empty strings and 'und') are returned in
lower-case/stripped form unchanged — callers decide whether to treat
an unknown tag as "keep" (safer) or "discard" (stricter).
"""
if not raw:
return ""
key = raw.lower().strip()
return _REVERSE_LANGUAGE_TAGS.get(key, key)