debputy.l10n

src/debputy/l10n.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import atexit
import gettext
import os
from functools import lru_cache
from gettext import NullTranslations, GNUTranslations
from tempfile import TemporaryDirectory
from typing import Optional, Union, Set, TYPE_CHECKING
from collections.abc import Iterable

from debputy import DEBPUTY_IS_RUN_FROM_SOURCE, DEBPUTY_ROOT_DIR
from debputy.util import _debug_log

try:
    import polib

    HAS_POLIB = True
except ImportError:
    HAS_POLIB = False

if TYPE_CHECKING:
    import polib

Translations = Union[NullTranslations, GNUTranslations]


def N_(v: str) -> str:
    return v


@lru_cache
def _temp_messages_dir() -> str:
    temp_dir = TemporaryDirectory(delete=False, ignore_cleanup_errors=True)
    atexit.register(lambda: temp_dir.cleanup())
    return temp_dir.name


_GENERATED_MO_FILES_FOR: set[str] = set()


def translation(
    domain: str,
    *,
    languages: Iterable[str] | None = None,
) -> Translations:
    if DEBPUTY_IS_RUN_FROM_SOURCE and HAS_POLIB:
        po_domain_dir = DEBPUTY_ROOT_DIR / "po" / domain
        locale_dir = _temp_messages_dir()
        if po_domain_dir.is_dir() and domain not in _GENERATED_MO_FILES_FOR:
            for child in po_domain_dir.iterdir():
                if not child.is_file() or not child.name.endswith(".po"):
                    continue
                language = child.name[:-3]
                mo_dir = os.path.join(locale_dir, language, "LC_MESSAGES")
                os.makedirs(mo_dir, exist_ok=True)
                mo_path = os.path.join(mo_dir, f"{domain}.mo")
                parsed_po_file = polib.pofile(child)
                parsed_po_file.save_as_mofile(mo_path)
            _GENERATED_MO_FILES_FOR.add(domain)
        try:
            r = gettext.translation(
                domain,
                localedir=locale_dir,
                languages=languages,
                fallback=True,
            )
            _debug_log(f"Found in-source translation for {domain}")
            return r
        except FileNotFoundError:
            # Fall through
            _debug_log(
                f"No in-source translation for {domain}, trying to installed translations"
            )

    # For some reason, the type checking thinks that `fallback` must be `Literal[False]` which
    # defeats the purpose of being able to provide a different value than the default for it.
    #
    # So `type: ignore` to "fix" that for now.
    return gettext.translation(
        domain,
        languages=languages,
        fallback=True,
    )


__all__ = ["N_", "translation", "Translations"]