1from __future__ import annotations
2
3import re
4
5from importlib import import_module, resources
6from pathlib import Path
7from typing import Any
8from typing import ClassVar
9from typing import Dict
10from typing import cast
11
12
13class Locale:
14 """
15 Represent a specific locale.
16 """
17
18 _cache: ClassVar[dict[str, Locale]] = {}
19
20 def __init__(self, locale: str, data: Any) -> None:
21 self._locale: str = locale
22 self._data: Any = data
23 self._key_cache: dict[str, str] = {}
24
25 @classmethod
26 def load(cls, locale: str | Locale) -> Locale:
27 if isinstance(locale, Locale):
28 return locale
29
30 locale = cls.normalize_locale(locale)
31 if locale in cls._cache:
32 return cls._cache[locale]
33
34 # Checking locale existence
35 actual_locale = locale
36 locale_path = cast(Path, resources.files(__package__).joinpath(actual_locale))
37 while not locale_path.exists():
38 if actual_locale == locale:
39 raise ValueError(f"Locale [{locale}] does not exist.")
40
41 actual_locale = actual_locale.split("_")[0]
42
43 m = import_module(f"pendulum.locales.{actual_locale}.locale")
44
45 cls._cache[locale] = cls(locale, m.locale)
46
47 return cls._cache[locale]
48
49 @classmethod
50 def normalize_locale(cls, locale: str) -> str:
51 m = re.match("([a-z]{2})[-_]([a-z]{2})", locale, re.I)
52 if m:
53 return f"{m.group(1).lower()}_{m.group(2).lower()}"
54 else:
55 return locale.lower()
56
57 def get(self, key: str, default: Any | None = None) -> Any:
58 if key in self._key_cache:
59 return self._key_cache[key]
60
61 parts = key.split(".")
62 try:
63 result = self._data[parts[0]]
64 for part in parts[1:]:
65 result = result[part]
66 except KeyError:
67 result = default
68
69 self._key_cache[key] = result
70
71 return self._key_cache[key]
72
73 def translation(self, key: str) -> Any:
74 return self.get(f"translations.{key}")
75
76 def plural(self, number: int) -> str:
77 return cast(str, self._data["plural"](number))
78
79 def ordinal(self, number: int) -> str:
80 return cast(str, self._data["ordinal"](number))
81
82 def ordinalize(self, number: int) -> str:
83 ordinal = self.get(f"custom.ordinal.{self.ordinal(number)}")
84
85 if not ordinal:
86 return f"{number}"
87
88 return f"{number}{ordinal}"
89
90 def match_translation(self, key: str, value: Any) -> dict[str, str] | None:
91 translations = self.translation(key)
92 if value not in translations.values():
93 return None
94
95 return cast(Dict[str, str], {v: k for k, v in translations.items()}[value])
96
97 def __repr__(self) -> str:
98 return f"{self.__class__.__name__}('{self._locale}')"