63 lines
1.8 KiB
Python
63 lines
1.8 KiB
Python
"""i18n module — loads translations from app/locales/*.json.
|
|
|
|
Usage in templates: {{ 'key'|t(user.language) }}
|
|
Usage in Python: from app.i18n import t; t("key", lang)
|
|
|
|
To add a new language:
|
|
1. Create app/locales/<lang>.json with translation keys
|
|
2. Done — it will be auto-detected and available in the UI
|
|
"""
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
_LOCALES_DIR = Path(__file__).resolve().parent / "locales"
|
|
_cache: dict[str, dict[str, str]] = {}
|
|
_loaded = False
|
|
|
|
|
|
def _load() -> None:
|
|
global _loaded
|
|
if _loaded:
|
|
return
|
|
_LOCALES_DIR.mkdir(exist_ok=True)
|
|
for f in _LOCALES_DIR.glob("*.json"):
|
|
lang = f.stem # e.g. "zh", "en", "ja"
|
|
with open(f, encoding="utf-8") as fh:
|
|
_cache[lang] = json.load(fh)
|
|
_loaded = True
|
|
|
|
|
|
def t(key: str, lang: str = "zh") -> str:
|
|
"""Translate *key* to *lang*. Falls back to zh, then to the raw key."""
|
|
_load()
|
|
# Try requested language
|
|
val = _cache.get(lang, {}).get(key)
|
|
if val is not None:
|
|
return val
|
|
# Fallback to zh
|
|
if lang != "zh":
|
|
val = _cache.get("zh", {}).get(key)
|
|
if val is not None:
|
|
return val
|
|
# Fallback to any available language
|
|
for translations in _cache.values():
|
|
if key in translations:
|
|
return translations[key]
|
|
return key
|
|
|
|
|
|
def get_languages() -> dict[str, str]:
|
|
"""Return available languages as {code: native_name}.
|
|
|
|
Each JSON file can optionally include a top-level ``_meta`` key
|
|
with ``{"name": "语言名称"}`` to set the display name.
|
|
If absent, the language code is used as-is.
|
|
"""
|
|
_load()
|
|
result: dict[str, str] = {}
|
|
for lang in sorted(_cache):
|
|
meta = _cache[lang].get("_meta", {})
|
|
result[lang] = meta.get("name", lang)
|
|
return result
|