"""Translation helpers and language-aware mixins.""" from __future__ import annotations import contextlib from dataclasses import dataclass, field from pathlib import Path from typing import Any, Dict try: # Python 3.11+ import tomllib # type: ignore[attr-defined] except ModuleNotFoundError: # pragma: no cover - fallback with contextlib.suppress(ModuleNotFoundError): import tomli as tomllib # type: ignore[assignment] if "tomllib" not in globals(): tomllib = None # type: ignore[assignment] LANG_DIR = Path(__file__).resolve().parent / "lang" FALLBACK_LANGUAGE = "en" def _available_language_files() -> Dict[str, Path]: files: Dict[str, Path] = {} if LANG_DIR.exists(): for path in LANG_DIR.glob("*.toml"): files[path.stem.lower()] = path return files def _load_translations(lang: str) -> Dict[str, str]: if tomllib is None: return {} lang_files = _available_language_files() path = lang_files.get(lang.lower()) if path is None: return {} try: with path.open("rb") as handle: data = tomllib.load(handle) except (OSError, AttributeError, ValueError, TypeError): # type: ignore[arg-type] return {} translations = data.get("translations") if not isinstance(translations, dict): return {} out: Dict[str, str] = {} for key, value in translations.items(): if isinstance(key, str) and isinstance(value, str): out[key] = value return out @dataclass class Translator: """Simple lookup-based translator with file-backed dictionaries.""" language: str = FALLBACK_LANGUAGE _translations: Dict[str, str] = field(default_factory=dict, init=False) _fallback: Dict[str, str] = field(default_factory=dict, init=False) def __post_init__(self) -> None: self._fallback = _load_translations(FALLBACK_LANGUAGE) self.set_language(self.language) def set_language(self, language: str) -> None: chosen = language.lower() data = _load_translations(chosen) if not data: chosen = FALLBACK_LANGUAGE data = _load_translations(FALLBACK_LANGUAGE) self.language = chosen self._translations = data or {} def translate(self, key: str, **values: Any) -> str: template = self._translations.get(key) or self._fallback.get(key) or key if values: try: return template.format(**values) except (KeyError, ValueError): return template return template class I18nMixin: """Mixin providing translated text helpers.""" language: str translator: Translator def init_i18n(self, language: str | None = None) -> None: self.translator = Translator() self.set_language(language or FALLBACK_LANGUAGE) def set_language(self, language: str) -> None: self.translator.set_language(language) self.language = self.translator.language def _t(self, key: str, **values: Any) -> str: return self.translator.translate(key, **values) @property def available_languages(self) -> tuple[str, ...]: return tuple(sorted(_available_language_files().keys())) __all__ = ["I18nMixin", "Translator", "LANG_DIR", "FALLBACK_LANGUAGE"]