"""i18n translation support. Loads Java .properties files and provides locale-specific message lookup with parameter interpolation. Ported from net.i2p.util.Translate and TranslateReader. """ from __future__ import annotations import os import re class TranslateReader: """Load Java .properties translation files.""" def load(self, path: str) -> dict[str, str]: """Load a .properties file into a dict. Returns empty dict if file not found. """ result: dict[str, str] = {} try: with open(path, encoding="utf-8") as f: for line in f: line = line.rstrip("\n\r") if not line or line.startswith("#") or line.startswith("!"): continue # Split on first = or : m = re.match(r"([^=:]+?)\s*[=:]\s*(.*)", line) if m: result[m.group(1).strip()] = m.group(2) except OSError: pass return result class Translate: """Locale-specific message lookup with interpolation.""" def __init__(self) -> None: self._locale: str = "en" self._strings: dict[str, str] = {} self._reader = TranslateReader() def set_locale(self, locale: str) -> None: self._locale = locale def get_locale(self) -> str: return self._locale def add_strings(self, strings: dict[str, str]) -> None: """Add translations to the current set.""" self._strings.update(strings) def load_bundle(self, directory: str, bundle_name: str) -> None: """Load a properties file from directory for current locale.""" path = os.path.join(directory, f"{bundle_name}_{self._locale}.properties") props = self._reader.load(path) self._strings.update(props) def get_string(self, key: str, *params: str) -> str: """Look up a translated string, with optional parameter substitution. Returns the key itself if not found (Java convention). Parameters are substituted for {0}, {1}, etc. """ value = self._strings.get(key, key) for i, param in enumerate(params): value = value.replace(f"{{{i}}}", str(param)) return value