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

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 

9 

10from debputy import DEBPUTY_IS_RUN_FROM_SOURCE, DEBPUTY_ROOT_DIR 

11from debputy.util import _debug_log 

12 

13try: 

14 import polib 

15 

16 HAS_POLIB = True 

17except ImportError: 

18 HAS_POLIB = False 

19 

20if TYPE_CHECKING: 

21 import polib 

22 

23Translations = Union[NullTranslations, GNUTranslations] 

24 

25 

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

27 return v 

28 

29 

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 

35 

36 

37_GENERATED_MO_FILES_FOR: set[str] = set() 

38 

39 

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 ) 

73 

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 ) 

83 

84 

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