locale.py 2.8 KB

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