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