Skip to content

PyDrocsid.translations

Translations

Container of multiple translation namespaces

Source code in PyDrocsid/translations.py
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
class Translations:
    """Container of multiple translation namespaces"""

    LANGUAGE: str
    FALLBACK: str = "en"

    def __init__(self) -> None:
        self._namespaces: dict[str, _Namespace] = {}

    def register_namespace(self, name: str, path: Path, prio: int = 0) -> None:
        """Register a new source for a translation namespace."""

        if name not in self._namespaces:
            logger.debug("creating new translation namespace '%s'", name)
            self._namespaces[name] = _Namespace()
        else:
            logger.debug("extending translation namespace '%s'", name)

        # noinspection PyProtectedMember
        self._namespaces[name]._add_source(prio, path)

    def __getattr__(self, item: str) -> Any:
        """Return a translation namespace"""

        return self._namespaces[item]

__getattr__

__getattr__(item: str) -> Any

Return a translation namespace

Source code in PyDrocsid/translations.py
170
171
172
173
def __getattr__(self, item: str) -> Any:
    """Return a translation namespace"""

    return self._namespaces[item]

register_namespace

register_namespace(name: str, path: Path, prio: int = 0) -> None

Register a new source for a translation namespace.

Source code in PyDrocsid/translations.py
158
159
160
161
162
163
164
165
166
167
168
def register_namespace(self, name: str, path: Path, prio: int = 0) -> None:
    """Register a new source for a translation namespace."""

    if name not in self._namespaces:
        logger.debug("creating new translation namespace '%s'", name)
        self._namespaces[name] = _Namespace()
    else:
        logger.debug("extending translation namespace '%s'", name)

    # noinspection PyProtectedMember
    self._namespaces[name]._add_source(prio, path)

_FormatString

Bases: str

String which can be called directly for formatting

Source code in PyDrocsid/translations.py
31
32
33
34
class _FormatString(str):
    """String which can be called directly for formatting"""

    __call__ = str.format

_Namespace

Translation namespace containing translations for main and fallback language

Source code in PyDrocsid/translations.py
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
class _Namespace:
    """Translation namespace containing translations for main and fallback language"""

    def __init__(self) -> None:
        # list of source directories for translation files
        self._sources: list[Source] = []

        # map languages to translation dictionaries
        self._translations: dict[str, dict[str, Any]] = {}

    def _add_source(self, prio: int, source: Path) -> None:
        """
        Add a new translation source.

        :param prio: priority of translation source (higher priority overrides lower priority)
        :param source: path to the directory containing the translation files
        """

        self._sources.append(Source(prio, source))
        self._translations.clear()

    def _get_language(self, lang: str) -> dict[str, Any]:
        """Return (and load if necessary) the translation dictionary of a given language."""

        if lang not in self._translations:
            self._translations[lang] = {}

            # load translations from sources and merge them
            for _, source in sorted(self._sources):
                path = source.joinpath(f"{lang}.yml")
                if not path.exists():
                    continue

                with path.open() as file:
                    merge(self._translations[lang], yaml.safe_load(file) or {})

        return self._translations[lang]

    def _get_translation(self, key: str) -> Any:
        """Return an item from the translation dictionary."""

        translations: dict[str, Any] = self._get_language(Translations.LANGUAGE)

        if key not in translations:
            translations = self._get_language(Translations.FALLBACK)

        return translations[key]

    def __getattr__(self, item: str) -> Any:
        """Return an item and wrap it in a _FormatString or _PluralDict"""

        value = self._get_translation(item)

        if isinstance(value, str):
            value = _FormatString(value)
        elif isinstance(value, dict):
            value = _PluralDict(value)
            value._fallback = self._get_language(Translations.FALLBACK)[item]

        return value

__getattr__

__getattr__(item: str) -> Any

Return an item and wrap it in a _FormatString or _PluralDict

Source code in PyDrocsid/translations.py
135
136
137
138
139
140
141
142
143
144
145
146
def __getattr__(self, item: str) -> Any:
    """Return an item and wrap it in a _FormatString or _PluralDict"""

    value = self._get_translation(item)

    if isinstance(value, str):
        value = _FormatString(value)
    elif isinstance(value, dict):
        value = _PluralDict(value)
        value._fallback = self._get_language(Translations.FALLBACK)[item]

    return value

_add_source

_add_source(prio: int, source: Path) -> None

Add a new translation source.

Parameters:

  • prio (int) –

    priority of translation source (higher priority overrides lower priority)

  • source (Path) –

    path to the directory containing the translation files

Source code in PyDrocsid/translations.py
 97
 98
 99
100
101
102
103
104
105
106
def _add_source(self, prio: int, source: Path) -> None:
    """
    Add a new translation source.

    :param prio: priority of translation source (higher priority overrides lower priority)
    :param source: path to the directory containing the translation files
    """

    self._sources.append(Source(prio, source))
    self._translations.clear()

_get_language

_get_language(lang: str) -> dict[str, Any]

Return (and load if necessary) the translation dictionary of a given language.

Source code in PyDrocsid/translations.py
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
def _get_language(self, lang: str) -> dict[str, Any]:
    """Return (and load if necessary) the translation dictionary of a given language."""

    if lang not in self._translations:
        self._translations[lang] = {}

        # load translations from sources and merge them
        for _, source in sorted(self._sources):
            path = source.joinpath(f"{lang}.yml")
            if not path.exists():
                continue

            with path.open() as file:
                merge(self._translations[lang], yaml.safe_load(file) or {})

    return self._translations[lang]

_get_translation

_get_translation(key: str) -> Any

Return an item from the translation dictionary.

Source code in PyDrocsid/translations.py
125
126
127
128
129
130
131
132
133
def _get_translation(self, key: str) -> Any:
    """Return an item from the translation dictionary."""

    translations: dict[str, Any] = self._get_language(Translations.LANGUAGE)

    if key not in translations:
        translations = self._get_language(Translations.FALLBACK)

    return translations[key]

_PluralDict

Bases: dict[str, Any]

Dictionary for pluralization containing multiple _FormatStrings

Source code in PyDrocsid/translations.py
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
class _PluralDict(dict[str, Any]):
    """Dictionary for pluralization containing multiple _FormatStrings"""

    _fallback: Any

    def __call__(self, *args: Any, **kwargs: Any) -> str:
        """Choose and format the pluralized string."""

        # get count parameter from kwargs
        cnt = None
        if "cnt" in kwargs:
            cnt = kwargs["cnt"]
        elif "count" in kwargs:
            cnt = kwargs["count"]

        # choose pluralized string
        if cnt == 1:
            translation = self.one
        elif cnt == 0 and "zero" in self:  # zero is optional
            translation = self.zero
        else:
            translation = self.many

        # format and return string
        return cast(str, translation(*args, **kwargs))

    def __getattribute__(self, item: str) -> Any:
        """Use __getattr__ for non-protected attributes."""

        if item.startswith("_"):
            return super().__getattribute__(item)
        return self.__getattr__(item)

    def __getattr__(self, item: str) -> Any:
        """Return a nested item and wrap it in a _FormatString or _PluralDict"""

        value = self[item] if item in self else self._fallback[item]

        if isinstance(value, str):
            value = _FormatString(value)
        elif isinstance(value, dict):
            value = _PluralDict(value)
            value._fallback = self._fallback[item]

        return value

__call__

__call__(*args: Any, **kwargs: Any) -> str

Choose and format the pluralized string.

Source code in PyDrocsid/translations.py
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
def __call__(self, *args: Any, **kwargs: Any) -> str:
    """Choose and format the pluralized string."""

    # get count parameter from kwargs
    cnt = None
    if "cnt" in kwargs:
        cnt = kwargs["cnt"]
    elif "count" in kwargs:
        cnt = kwargs["count"]

    # choose pluralized string
    if cnt == 1:
        translation = self.one
    elif cnt == 0 and "zero" in self:  # zero is optional
        translation = self.zero
    else:
        translation = self.many

    # format and return string
    return cast(str, translation(*args, **kwargs))

__getattr__

__getattr__(item: str) -> Any

Return a nested item and wrap it in a _FormatString or _PluralDict

Source code in PyDrocsid/translations.py
70
71
72
73
74
75
76
77
78
79
80
81
def __getattr__(self, item: str) -> Any:
    """Return a nested item and wrap it in a _FormatString or _PluralDict"""

    value = self[item] if item in self else self._fallback[item]

    if isinstance(value, str):
        value = _FormatString(value)
    elif isinstance(value, dict):
        value = _PluralDict(value)
        value._fallback = self._fallback[item]

    return value

__getattribute__

__getattribute__(item: str) -> Any

Use getattr for non-protected attributes.

Source code in PyDrocsid/translations.py
63
64
65
66
67
68
def __getattribute__(self, item: str) -> Any:
    """Use __getattr__ for non-protected attributes."""

    if item.startswith("_"):
        return super().__getattribute__(item)
    return self.__getattr__(item)

load_translations

load_translations(path: Path, prio: int = 0) -> None

Recursively load all translations in a given directory and register the appropriate namespaces.

Source code in PyDrocsid/translations.py
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
def load_translations(path: Path, prio: int = 0) -> None:
    """Recursively load all translations in a given directory and register the appropriate namespaces."""

    # skip hidden directories
    if path.name.startswith("."):
        return

    # check if current directory contains a translations subdirectory
    if (p := path.joinpath("translations")).is_dir():
        # register translations directory and return
        t.register_namespace(path.name, p, prio=prio)
        return

    # recurse into subdirectories
    for p in path.iterdir():
        if p.is_dir() and not p.name.startswith("_"):
            load_translations(p, prio)

merge

merge(base: dict[Any, Any], src: dict[Any, Any]) -> None

Merge two dictionaries recursively by copying/merging the key-value pairs from src into base.

Parameters:

  • base (dict[Any, Any]) –

    the base dictionary

  • src (dict[Any, Any]) –

    the source dictionary to read from

Source code in PyDrocsid/translations.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def merge(base: dict[Any, Any], src: dict[Any, Any]) -> None:
    """
    Merge two dictionaries recursively by copying/merging the key-value pairs from src into base.

    :param base: the base dictionary
    :param src: the source dictionary to read from
    """

    for k, v in src.items():
        if k in base and isinstance(v, dict) and isinstance(base[k], dict):
            # recurse if value in both src and base is a dictionary
            merge(base[k], v)
        else:
            base[k] = v