207 lines
5.0 KiB
Python
207 lines
5.0 KiB
Python
"""Activate, get and deactivate translations."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import gettext as gettext_module
|
|
from threading import local
|
|
|
|
TYPE_CHECKING = False
|
|
if TYPE_CHECKING:
|
|
import os
|
|
import pathlib
|
|
|
|
__all__ = ["activate", "deactivate", "decimal_separator", "thousands_separator"]
|
|
|
|
_TRANSLATIONS: dict[str | None, gettext_module.NullTranslations] = {
|
|
None: gettext_module.NullTranslations()
|
|
}
|
|
_CURRENT = local()
|
|
|
|
|
|
# Mapping of locale to thousands separator
|
|
_THOUSANDS_SEPARATOR = {
|
|
"de_DE": ".",
|
|
"fr_FR": " ",
|
|
"it_IT": ".",
|
|
"pt_BR": ".",
|
|
"hu_HU": " ",
|
|
}
|
|
|
|
# Mapping of locale to decimal separator
|
|
_DECIMAL_SEPARATOR = {
|
|
"de_DE": ",",
|
|
"fr_FR": ".",
|
|
"it_IT": ",",
|
|
"pt_BR": ",",
|
|
"hu_HU": ",",
|
|
}
|
|
|
|
|
|
def _get_default_locale_path() -> pathlib.Path | None:
|
|
package = __spec__ and __spec__.parent
|
|
if not package:
|
|
return None
|
|
|
|
import importlib.resources
|
|
|
|
with importlib.resources.as_file(importlib.resources.files(package)) as pkg:
|
|
return pkg / "locale"
|
|
|
|
|
|
def get_translation() -> gettext_module.NullTranslations:
|
|
try:
|
|
return _TRANSLATIONS[_CURRENT.locale]
|
|
except (AttributeError, KeyError):
|
|
return _TRANSLATIONS[None]
|
|
|
|
|
|
def activate(
|
|
locale: str | None, path: str | os.PathLike[str] | None = None
|
|
) -> gettext_module.NullTranslations:
|
|
"""Activate internationalisation.
|
|
|
|
Set `locale` as current locale. Search for locale in directory `path`.
|
|
|
|
Args:
|
|
locale (str | None): Language name, e.g. `en_GB`. If `None`, defaults to no
|
|
transaltion. Similar to calling ``deactivate()``.
|
|
path (str | pathlib.Path): Path to search for locales.
|
|
|
|
Returns:
|
|
dict: Translations.
|
|
|
|
Raises:
|
|
Exception: If humanize cannot find the locale folder.
|
|
"""
|
|
if locale is None or locale.startswith("en"):
|
|
_CURRENT.locale = None
|
|
return _TRANSLATIONS[None]
|
|
|
|
if path is None:
|
|
path = _get_default_locale_path()
|
|
|
|
if path is None:
|
|
msg = (
|
|
"Humanize cannot determinate the default location of the 'locale' folder. "
|
|
"You need to pass the path explicitly."
|
|
)
|
|
raise Exception(msg)
|
|
if locale not in _TRANSLATIONS:
|
|
translation = gettext_module.translation("humanize", path, [locale])
|
|
_TRANSLATIONS[locale] = translation
|
|
_CURRENT.locale = locale
|
|
return _TRANSLATIONS[locale]
|
|
|
|
|
|
def deactivate() -> None:
|
|
"""Deactivate internationalisation."""
|
|
_CURRENT.locale = None
|
|
|
|
|
|
def _gettext(message: str) -> str:
|
|
"""Get translation.
|
|
|
|
Args:
|
|
message (str): Text to translate.
|
|
|
|
Returns:
|
|
str: Translated text.
|
|
"""
|
|
return get_translation().gettext(message)
|
|
|
|
|
|
def _pgettext(msgctxt: str, message: str) -> str:
|
|
"""Fetches a particular translation.
|
|
|
|
It works with `msgctxt` .po modifiers and allows duplicate keys with different
|
|
translations.
|
|
|
|
Args:
|
|
msgctxt (str): Context of the translation.
|
|
message (str): Text to translate.
|
|
|
|
Returns:
|
|
str: Translated text.
|
|
"""
|
|
return get_translation().pgettext(msgctxt, message)
|
|
|
|
|
|
def _ngettext(message: str, plural: str, num: int) -> str:
|
|
"""Plural version of _gettext.
|
|
|
|
Args:
|
|
message (str): Singular text to translate.
|
|
plural (str): Plural text to translate.
|
|
num (int): The number (e.g. item count) to determine translation for the
|
|
respective grammatical number.
|
|
|
|
Returns:
|
|
str: Translated text.
|
|
"""
|
|
return get_translation().ngettext(message, plural, num)
|
|
|
|
|
|
def _gettext_noop(message: str) -> str:
|
|
"""Mark a string as a translation string without translating it.
|
|
|
|
Example usage:
|
|
```python
|
|
CONSTANTS = [_gettext_noop('first'), _gettext_noop('second')]
|
|
def num_name(n):
|
|
return _gettext(CONSTANTS[n])
|
|
```
|
|
|
|
Args:
|
|
message (str): Text to translate in the future.
|
|
|
|
Returns:
|
|
str: Original text, unchanged.
|
|
"""
|
|
return message
|
|
|
|
|
|
def _ngettext_noop(singular: str, plural: str) -> tuple[str, str]:
|
|
"""Mark two strings as pluralized translations without translating them.
|
|
|
|
Example usage:
|
|
```python
|
|
CONSTANTS = [ngettext_noop('first', 'firsts'), ngettext_noop('second', 'seconds')]
|
|
def num_name(n):
|
|
return _ngettext(*CONSTANTS[n])
|
|
```
|
|
|
|
Args:
|
|
singular (str): Singular text to translate in the future.
|
|
plural (str): Plural text to translate in the future.
|
|
|
|
Returns:
|
|
tuple: Original text, unchanged.
|
|
"""
|
|
return singular, plural
|
|
|
|
|
|
def thousands_separator() -> str:
|
|
"""Return the thousands separator for a locale, default to comma.
|
|
|
|
Returns:
|
|
str: Thousands separator.
|
|
"""
|
|
try:
|
|
sep = _THOUSANDS_SEPARATOR[_CURRENT.locale]
|
|
except (AttributeError, KeyError):
|
|
sep = ","
|
|
return sep
|
|
|
|
|
|
def decimal_separator() -> str:
|
|
"""Return the decimal separator for a locale, default to dot.
|
|
|
|
Returns:
|
|
str: Decimal separator.
|
|
"""
|
|
try:
|
|
sep = _DECIMAL_SEPARATOR[_CURRENT.locale]
|
|
except (AttributeError, KeyError):
|
|
sep = "."
|
|
return sep
|