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