Source code for iabm.utils
"""Internationalization helpers for the Model_A command-line interface."""
from __future__ import annotations
import ast
from pathlib import Path
from typing import Callable
[docs]
def setup_i18n(lang: str = "en") -> Callable[[str], str]:
"""Return a translation function for the requested interface language.
The project stores human-maintained translations in ``locales/*/LC_MESSAGES``
as ``.po`` files. This helper reads those catalogs directly so the CLI can be
translated even when ``.mo`` files have not been compiled yet.
Args:
lang: ISO language code requested by the user.
Returns:
A callable compatible with ``gettext`` usage that translates a message
identifier into the configured language. English falls back to the
original message identifiers.
"""
if lang == "en":
return lambda message: message
locale_dir = Path(__file__).resolve().parents[1] / "locales" / lang / "LC_MESSAGES"
catalog_path = locale_dir / "messages.po"
catalog = _load_po_catalog(catalog_path)
return lambda message: catalog.get(message, message)
def _load_po_catalog(path: Path) -> dict[str, str]:
"""Parse a minimal ``.po`` catalog into an in-memory translation mapping.
The parser intentionally supports the subset of the GNU gettext format used
by this repository: singular ``msgid`` and ``msgstr`` entries with optional
multiline string fragments.
Args:
path: Path to the ``.po`` file to parse.
Returns:
A dictionary keyed by source ``msgid`` values.
"""
if not path.exists():
return {}
catalog: dict[str, str] = {}
current_field: str | None = None
current_msgid: list[str] = []
current_msgstr: list[str] = []
def flush_entry() -> None:
if not current_msgid:
return
msgid = "".join(current_msgid)
msgstr = "".join(current_msgstr)
if msgid:
catalog[msgid] = msgstr or msgid
for raw_line in path.read_text(encoding="utf-8").splitlines():
line = raw_line.strip()
if not line or line.startswith("#"):
continue
if line.startswith("msgid "):
flush_entry()
current_msgid = [_decode_po_string(line[6:])]
current_msgstr = []
current_field = "msgid"
continue
if line.startswith("msgstr "):
current_msgstr = [_decode_po_string(line[7:])]
current_field = "msgstr"
continue
if line.startswith('"'):
if current_field == "msgid":
current_msgid.append(_decode_po_string(line))
elif current_field == "msgstr":
current_msgstr.append(_decode_po_string(line))
flush_entry()
return catalog
def _decode_po_string(value: str) -> str:
"""Decode a quoted gettext string fragment into plain Python text.
Args:
value: One quoted fragment from a gettext catalog.
Returns:
The decoded Unicode string represented by the fragment.
"""
return ast.literal_eval(value)