Compare commits
14 Commits
e481107444
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 8792aaeeb9 | |||
| 774a1b0ce3 | |||
| 633c92608f | |||
| 3f0fe22317 | |||
| 2a3eca2c31 | |||
| f26cc65c7f | |||
| 0b9f8b6feb | |||
| 3581e6ae44 | |||
| c261f7e140 | |||
| cf50296264 | |||
| 6a0347e7c4 | |||
| cb45e819c9 | |||
| 5148818aff | |||
| b6ca2f7d7a |
@@ -168,3 +168,4 @@ cython_debug/
|
|||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
#.idea/
|
#.idea/
|
||||||
|
|
||||||
|
resources/
|
||||||
@@ -1,3 +1,25 @@
|
|||||||
# I18N-Module
|
# I18N-Module
|
||||||
|
|
||||||
Internationalization (i18n) module
|
Internationalization (i18n) module
|
||||||
|
|
||||||
|
i18n инжектися в builtins:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from internationalization import I18N
|
||||||
|
|
||||||
|
I18N("localization_dir", "en", "utf-8")
|
||||||
|
|
||||||
|
print(i18n['key'])
|
||||||
|
print(i18n('key', some=123))
|
||||||
|
print(i18n.get_phrase('key', some=123))
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! luguru настроен под формат:
|
||||||
|
```python
|
||||||
|
fmt = "<green>{elapsed} -- {time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level:<8}</level> | {extra[module]:^12} | {extra[prefix]:<12} | {message}"
|
||||||
|
```
|
||||||
|
|
||||||
|
Добавление в проект:
|
||||||
|
```bash
|
||||||
|
git submodule add ssh://git@git.anidev.ru:222/AnidevSoft/I18N-Module.git src/modules/i18n
|
||||||
|
```
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
class _i18_proxy:
|
||||||
|
@staticmethod
|
||||||
|
def __getattr__(item):
|
||||||
|
"""
|
||||||
|
Get the phrase from the locale file. If the phrase is not found, return the key in uppercase.
|
||||||
|
|
||||||
|
:param key: Key to search in the locale file.
|
||||||
|
:param kwargs: Arguments to format the phrase.
|
||||||
|
:return: The phrase from the locale file.
|
||||||
|
"""
|
||||||
|
@staticmethod
|
||||||
|
def __init__(**kwargs):
|
||||||
|
"""
|
||||||
|
Get the phrase from the locale file. If the phrase is not found, return the key in uppercase.
|
||||||
|
|
||||||
|
:param key: Key to search in the locale file.
|
||||||
|
:param kwargs: Arguments to format the phrase.
|
||||||
|
:return: The phrase from the locale file.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class i18n:
|
||||||
|
@staticmethod
|
||||||
|
def set_lang(lang, encoding=None) -> None:
|
||||||
|
"""
|
||||||
|
Set the language and encoding for the locale file.
|
||||||
|
:param lang: Use the language file with the given language.
|
||||||
|
:param encoding: Encoding of the language file.
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
@staticmethod
|
||||||
|
def get_phrase(key: str, **kwargs) -> str:
|
||||||
|
"""
|
||||||
|
Get the phrase from the locale file. If the phrase is not found, return the key in uppercase.
|
||||||
|
|
||||||
|
:param key: Key to search in the locale file.
|
||||||
|
:param kwargs: Arguments to format the phrase.
|
||||||
|
:return: The phrase from the locale file.
|
||||||
|
"""
|
||||||
|
@staticmethod
|
||||||
|
def __getattr__(item):
|
||||||
|
"""
|
||||||
|
Get the phrase from the locale file. If the phrase is not found, return the key in uppercase.
|
||||||
|
|
||||||
|
:param key: Key to search in the locale file.
|
||||||
|
:param kwargs: Arguments to format the phrase.
|
||||||
|
:return: The phrase from the locale file.
|
||||||
|
"""
|
||||||
|
key = item.replace("_", ".") # заменяем _ на . для удобства
|
||||||
|
return _i18_proxy([key]) # Начинаем цепочку
|
||||||
|
@staticmethod
|
||||||
|
def __init__(key: str, **kwargs) -> str:
|
||||||
|
"""
|
||||||
|
Get the phrase from the locale file. If the phrase is not found, return the key in uppercase.
|
||||||
|
|
||||||
|
:param key: Key to search in the locale file.
|
||||||
|
:param kwargs: Arguments to format the phrase.
|
||||||
|
:return: The phrase from the locale file.
|
||||||
|
"""
|
||||||
|
@staticmethod
|
||||||
|
def __getitem__(key: str) -> str:
|
||||||
|
"""
|
||||||
|
Get the phrase from the locale file. If the phrase is not found, return the key in uppercase.
|
||||||
|
|
||||||
|
:param key: Key to search in the locale file.
|
||||||
|
:return: The phrase from the locale file.
|
||||||
|
"""
|
||||||
|
class i10n(i18n): ...
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
from .i18n import I18N
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
import builtins
|
||||||
|
import textwrap
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
import json5
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
try:
|
||||||
|
from . import utils
|
||||||
|
except ImportError:
|
||||||
|
import utils
|
||||||
|
|
||||||
|
log = logger.bind(module="i18n", prefix="init")
|
||||||
|
log_load = logger.bind(module="i18n", prefix="load")
|
||||||
|
|
||||||
|
str_funcs = {attr for attr in dir(str)}
|
||||||
|
str_funcs.add('__func__')
|
||||||
|
str_funcs.remove('__init__')
|
||||||
|
str_funcs.remove('__len__')
|
||||||
|
str_funcs.remove('__hash__')
|
||||||
|
str_funcs.remove('__add__')
|
||||||
|
str_funcs.remove('__str__')
|
||||||
|
str_funcs.remove('__repr__')
|
||||||
|
|
||||||
|
|
||||||
|
class NestedAccessor:
|
||||||
|
def __init__(self, parent, key_chain):
|
||||||
|
self.parent: "I18N" = parent
|
||||||
|
self.key_chain = key_chain
|
||||||
|
self.__name__ = f'\r<{".".join(key_chain)}>'.upper()
|
||||||
|
# self.__func__ = self.key_chain
|
||||||
|
|
||||||
|
def __getattr__(self, item):
|
||||||
|
# Если нет метода, то возвращаем str
|
||||||
|
key = ".".join((*self.key_chain, item))
|
||||||
|
if key in self.parent.locale:
|
||||||
|
if "{" not in self.parent.locale[key]:
|
||||||
|
return self.parent.get_phrase(key)
|
||||||
|
if item in str_funcs:
|
||||||
|
return getattr(self(), item)
|
||||||
|
return NestedAccessor(self.parent, self.key_chain + [item])
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self())
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self())
|
||||||
|
|
||||||
|
def __call__(self, **kwargs):
|
||||||
|
key = ".".join(self.key_chain)
|
||||||
|
return self.parent.get_phrase(key, **kwargs)
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
return self() + other
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<NestedAccessor key={'.'.join(self.key_chain)}>"
|
||||||
|
|
||||||
|
|
||||||
|
class I18N:
|
||||||
|
|
||||||
|
def __init__(self, locale_dir, load_lang, encoding):
|
||||||
|
"""
|
||||||
|
Load the language file and set the locale directory. Use JSON5 format for the language file.
|
||||||
|
|
||||||
|
:param locale_dir: {BASE_DIR}/resources/{locale_dir}
|
||||||
|
:param load_lang: some.lang
|
||||||
|
"""
|
||||||
|
self._locale_dir = utils.get_file(locale_dir) # {BASE_DIR}/resources/{locale_dir}
|
||||||
|
self._lang = load_lang
|
||||||
|
self._encoding = encoding
|
||||||
|
|
||||||
|
self._locale = {}
|
||||||
|
log.debug("Injecting to builtins")
|
||||||
|
builtins.i18n = self
|
||||||
|
builtins.i10n = self
|
||||||
|
log.debug("Ready")
|
||||||
|
self._load()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def locale(self):
|
||||||
|
return self._locale
|
||||||
|
|
||||||
|
def _load(self):
|
||||||
|
if not self._locale_dir:
|
||||||
|
raise FileNotFoundError(f"Locale directory not found: {self._locale_dir}")
|
||||||
|
if (locale_file:=self._locale_dir / f"{self._lang}.json5").exists():
|
||||||
|
log_load.debug(f"Loading locale file: {locale_file}")
|
||||||
|
with open(locale_file, "r", encoding=self._encoding) as f:
|
||||||
|
data = json5.load(f)
|
||||||
|
self._locale = utils.flatten_dict(data)
|
||||||
|
log_load.debug("Loaded and flattened.")
|
||||||
|
else:
|
||||||
|
raise FileNotFoundError(f"Locale file not found: {locale_file}")
|
||||||
|
|
||||||
|
def set_lang(self, lang, encoding=None):
|
||||||
|
"""
|
||||||
|
Set the language and encoding for the locale file.
|
||||||
|
:param lang: Use the language file with the given language.
|
||||||
|
:param encoding: Encoding of the language file.
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
self._lang = lang
|
||||||
|
self._encoding = encoding or self._encoding
|
||||||
|
self._load()
|
||||||
|
|
||||||
|
def get_phrase(self, key: str, **kwargs) -> str:
|
||||||
|
"""
|
||||||
|
Get the phrase from the locale file. If the phrase is not found, return the key in uppercase.
|
||||||
|
|
||||||
|
:param key: Key to search in the locale file.
|
||||||
|
:param kwargs: Arguments to format the phrase.
|
||||||
|
:return: The phrase from the locale file.
|
||||||
|
"""
|
||||||
|
# key: some.key.here
|
||||||
|
phrase = self._locale.get(key)
|
||||||
|
if not phrase:
|
||||||
|
return f"<{key.upper()}>"
|
||||||
|
return textwrap.dedent(phrase.format_map(defaultdict(lambda: '<unknown>', kwargs)))
|
||||||
|
|
||||||
|
def __getattr__(self, item):
|
||||||
|
key = item.replace("_", ".") # заменяем _ на . для удобства
|
||||||
|
return NestedAccessor(self, [key]) # Начинаем цепочку
|
||||||
|
|
||||||
|
def __call__(self, key: str, **kwargs):
|
||||||
|
return self.get_phrase(key, **kwargs)
|
||||||
|
|
||||||
|
def __getitem__(self, key: str):
|
||||||
|
return self.get_phrase(key)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
i18n = I18N("", "ru", "utf-8")
|
||||||
|
print(1, i18n.get_phrase("some.key.here", name="John"))
|
||||||
|
print(2, i18n("some.key.here", name="John"))
|
||||||
|
print(3, i18n["some.key.here"])
|
||||||
|
|
||||||
|
print(4, i18n.some_key_here(name="John"))
|
||||||
|
print(5, i18n.some_key_here)
|
||||||
|
print(6, i18n.some.key.here(name="John"))
|
||||||
|
print(7, i18n.some.key.here)
|
||||||
|
|
||||||
|
print(8, i18n.some.key.no.registered)
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
def base_path():
|
||||||
|
# PyInstaller creates a temp folder and stores path in _MEIPASS
|
||||||
|
try:
|
||||||
|
# noinspection PyUnresolvedReferences,PyProtectedMember
|
||||||
|
return Path(sys._MEIPASS).resolve()
|
||||||
|
except AttributeError:
|
||||||
|
return Path().resolve()
|
||||||
|
|
||||||
|
def get_file(filename):
|
||||||
|
return base_path() / "resources" / filename
|
||||||
|
|
||||||
|
def flatten_dict(d, parent_key='', sep='.'):
|
||||||
|
items = []
|
||||||
|
for k, v in d.items():
|
||||||
|
new_key = f"{parent_key}{sep}{k}" if parent_key else k
|
||||||
|
if isinstance(v, dict):
|
||||||
|
items.extend(flatten_dict(v, new_key, sep=sep).items())
|
||||||
|
else:
|
||||||
|
items.append((new_key, v))
|
||||||
|
return dict(items)
|
||||||
Reference in New Issue
Block a user