The table was thirty languages typed by hand. It is now generated: utils/generate_languages.py takes the 184 alpha-2 codes from pycountry, the display names from CLDR through babel, and the writing direction from CLDR character order. 159 languages carry their endonym; the remaining 25 have no CLDR entry and carry their English ISO name. That corrects the right-to-left set, which had four entries and needs ten — dv, ks, ps, sd, ug and yi were simply missed. Only 29 languages ship an interface catalogue, so the other 155 render in English until one is filled. make i18n-ui fills app/i18n/ui/ for them, and make i18n now covers the interface strings as well; neither asks for a string a shipped catalogue already answers, so hand-written entries stay. 184 entries do not fit on a screen, so the language menu scrolls inside itself. overscroll-behavior keeps the page behind it from moving once the list reaches its end. babel and pycountry are dev dependencies: the generator needs them, the application does not. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
84 lines
2.5 KiB
Python
84 lines
2.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Regenerate app/utils/languages.py from ISO 639-1 and CLDR.
|
|
|
|
Every ISO 639-1 alpha-2 code becomes an entry. The display name is the CLDR
|
|
endonym where CLDR knows the language, and the English ISO name for the
|
|
remainder. Writing direction comes from CLDR's character order.
|
|
|
|
Needs the dev extra: pip install -e ".[dev]"
|
|
"""
|
|
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pycountry
|
|
from babel import Locale
|
|
from babel.localedata import locale_identifiers
|
|
|
|
TARGET = Path(__file__).resolve().parents[1] / "app" / "utils" / "languages.py"
|
|
|
|
HEADER = '''"""ISO 639-1 languages, their display names and writing direction.
|
|
|
|
Generated by utils/generate_languages.py — edit that script, not this file.
|
|
|
|
Display names are CLDR endonyms where CLDR covers the language and the English
|
|
ISO 639-1 name for the {fallback} codes it does not.
|
|
"""
|
|
|
|
LANGUAGES = {{
|
|
'''
|
|
|
|
FOOTER = """}}
|
|
|
|
RTL_LANGUAGES = frozenset({rtl})
|
|
"""
|
|
|
|
|
|
def collect():
|
|
"""Return (code -> display name) and the set of right-to-left codes."""
|
|
cldr = {code for code in locale_identifiers() if len(code) == 2 and code.isalpha()}
|
|
names, rtl, fallbacks = {}, set(), 0
|
|
|
|
for language in sorted(
|
|
pycountry.languages, key=lambda item: getattr(item, "alpha_2", "")
|
|
):
|
|
code = getattr(language, "alpha_2", None)
|
|
if not code:
|
|
continue
|
|
if code in cldr:
|
|
locale = Locale.parse(code)
|
|
names[code] = locale.get_display_name(code)
|
|
if locale.character_order == "right-to-left":
|
|
rtl.add(code)
|
|
else:
|
|
names[code] = language.name
|
|
fallbacks += 1
|
|
|
|
return names, rtl, fallbacks
|
|
|
|
|
|
def render(names, rtl, fallbacks):
|
|
"""Return the module source, English first and the rest by code."""
|
|
ordered = ["en"] + [code for code in sorted(names) if code != "en"]
|
|
body = "".join(f' "{code}": "{names[code]}",\n' for code in ordered)
|
|
listed = ", ".join(f'"{code}"' for code in sorted(rtl))
|
|
return (
|
|
HEADER.format(fallback=fallbacks) + body + FOOTER.format(rtl="{" + listed + "}")
|
|
)
|
|
|
|
|
|
def main():
|
|
names, rtl, fallbacks = collect()
|
|
TARGET.write_text(render(names, rtl, fallbacks), encoding="utf-8")
|
|
subprocess.run([sys.executable, "-m", "ruff", "format", str(TARGET)], check=False)
|
|
print(
|
|
f"{len(names)} languages, {len(rtl)} right-to-left, "
|
|
f"{fallbacks} without a CLDR endonym"
|
|
)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|