Coverage for src/debputy/l10n.py: 43%
46 statements
« prev ^ index » next coverage.py v7.6.0, created at 2025-01-27 13:59 +0000
« prev ^ index » next coverage.py v7.6.0, created at 2025-01-27 13:59 +0000
1import atexit
2import gettext
3import os
4from functools import lru_cache
5from gettext import NullTranslations, GNUTranslations
6from tempfile import TemporaryDirectory
7from typing import Optional, Union, Set, Iterable, TYPE_CHECKING
9from debputy import DEBPUTY_IS_RUN_FROM_SOURCE, DEBPUTY_ROOT_DIR
10from debputy.util import _debug_log
12try:
13 import polib
15 HAS_POLIB = True
16except ImportError:
17 HAS_POLIB = False
19if TYPE_CHECKING:
20 import polib
22Translations = Union[NullTranslations, GNUTranslations]
25def N_(v: str) -> str:
26 return v
29@lru_cache
30def _temp_messages_dir() -> str:
31 temp_dir = TemporaryDirectory(delete=False, ignore_cleanup_errors=True)
32 atexit.register(lambda: temp_dir.cleanup())
33 return temp_dir.name
36_GENERATED_MO_FILES_FOR: Set[str] = set()
39def translation(
40 domain: str,
41 *,
42 languages: Optional[Iterable[str]] = None,
43) -> Translations:
44 if DEBPUTY_IS_RUN_FROM_SOURCE and HAS_POLIB: 44 ↛ 45line 44 didn't jump to line 45 because the condition on line 44 was never true
45 po_domain_dir = DEBPUTY_ROOT_DIR / "po" / domain
46 locale_dir = _temp_messages_dir()
47 if po_domain_dir.is_dir() and domain not in _GENERATED_MO_FILES_FOR:
48 for child in po_domain_dir.iterdir():
49 if not child.is_file() or not child.name.endswith(".po"):
50 continue
51 language = child.name[:-3]
52 mo_dir = os.path.join(locale_dir, language, "LC_MESSAGES")
53 os.makedirs(mo_dir, exist_ok=True)
54 mo_path = os.path.join(mo_dir, f"{domain}.mo")
55 parsed_po_file = polib.pofile(child)
56 parsed_po_file.save_as_mofile(mo_path)
57 _GENERATED_MO_FILES_FOR.add(domain)
58 try:
59 r = gettext.translation(
60 domain,
61 localedir=locale_dir,
62 languages=languages,
63 fallback=True,
64 )
65 _debug_log(f"Found in-source translation for {domain}")
66 return r
67 except FileNotFoundError:
68 # Fall through
69 _debug_log(
70 f"No in-source translation for {domain}, trying to installed translations"
71 )
73 # For some reason, the type checking thinks that `fallback` must be `Literal[False]` which
74 # defeats the purpose of being able to provide a different value than the default for it.
75 #
76 # So `type: ignore` to "fix" that for now.
77 return gettext.translation(
78 domain,
79 languages=languages,
80 fallback=True,
81 )
84__all__ = ["N_", "translation", "Translations"]