Files
homepage.veen.world/utils/generate_languages.py
Kevin Veen-Birkenbach ef1c8ff09a feat(i18n): offer every ISO 639-1 language
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>
2026-08-22 01:18:30 +02:00

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())