feat(i18n): localize start/help/menu, practice, words, import, reminder, vocabulary, tasks/stats for RU/EN/JA; add JSON-based i18n helper\n\nfeat(lang): support learning/translation languages across AI flows; hide translations with buttons; store examples per lang\n\nfeat(vocab): add source_lang and translation_lang to Vocabulary, unique constraint (user_id, source_lang, word_original); filter /vocabulary by user.learning_language\n\nchore(migrations): add Alembic setup + migration to add vocab lang columns; env.py reads app settings and supports asyncpg URLs\n\nfix(words/import): pass learning_lang + translation_lang everywhere; fix menu themes generation\n\nfeat(settings): add learning language selector; update main menu on language change
This commit is contained in:
51
utils/i18n.py
Normal file
51
utils/i18n.py
Normal file
@@ -0,0 +1,51 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
from functools import lru_cache
|
||||
from typing import Any, Dict
|
||||
|
||||
|
||||
FALLBACK_LANG = "ru"
|
||||
|
||||
|
||||
@lru_cache(maxsize=16)
|
||||
def _load_lang(lang: str) -> Dict[str, Any]:
|
||||
base_dir = Path(__file__).resolve().parents[1] / "locales"
|
||||
file_path = base_dir / f"{lang}.json"
|
||||
if not file_path.exists():
|
||||
# fallback to default
|
||||
if lang != FALLBACK_LANG:
|
||||
return _load_lang(FALLBACK_LANG)
|
||||
return {}
|
||||
try:
|
||||
return json.loads(file_path.read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
def _resolve_key(data: Dict[str, Any], dotted_key: str) -> Any:
|
||||
cur: Any = data
|
||||
for part in dotted_key.split("."):
|
||||
if not isinstance(cur, dict) or part not in cur:
|
||||
return None
|
||||
cur = cur[part]
|
||||
return cur
|
||||
|
||||
|
||||
def t(lang: str, key: str, **kwargs) -> str:
|
||||
"""Translate key for given lang; fallback to ru and to key itself.
|
||||
|
||||
Supports dotted keys and str.format(**kwargs) placeholders.
|
||||
"""
|
||||
data = _load_lang(lang or FALLBACK_LANG)
|
||||
value = _resolve_key(data, key)
|
||||
if value is None and lang != FALLBACK_LANG:
|
||||
value = _resolve_key(_load_lang(FALLBACK_LANG), key)
|
||||
if value is None:
|
||||
value = key # last resort: return the key
|
||||
try:
|
||||
if isinstance(value, str) and kwargs:
|
||||
return value.format(**kwargs)
|
||||
except Exception:
|
||||
pass
|
||||
return value if isinstance(value, str) else str(value)
|
||||
|
||||
Reference in New Issue
Block a user