done
This commit is contained in:
10
lib/python3.11/site-packages/i18n/__init__.py
Normal file
10
lib/python3.11/site-packages/i18n/__init__.py
Normal file
@ -0,0 +1,10 @@
|
||||
from . import resource_loader
|
||||
from .resource_loader import I18nFileLoadError, register_loader, load_config
|
||||
from .translator import t
|
||||
from .translations import add as add_translation
|
||||
from . import config
|
||||
from .config import set, get
|
||||
|
||||
resource_loader.init_loaders()
|
||||
|
||||
load_path = config.get('load_path')
|
35
lib/python3.11/site-packages/i18n/config.py
Normal file
35
lib/python3.11/site-packages/i18n/config.py
Normal file
@ -0,0 +1,35 @@
|
||||
try:
|
||||
__import__("yaml")
|
||||
yaml_available = True
|
||||
except ImportError:
|
||||
yaml_available = False
|
||||
|
||||
try:
|
||||
__import__("json")
|
||||
json_available = True
|
||||
except ImportError:
|
||||
json_available = False
|
||||
|
||||
settings = {
|
||||
'filename_format': '{namespace}.{locale}.{format}',
|
||||
'file_format': 'yml' if yaml_available else 'json' if json_available else 'py',
|
||||
'available_locales': ['en'],
|
||||
'load_path': [],
|
||||
'locale': 'en',
|
||||
'fallback': 'en',
|
||||
'placeholder_delimiter': '%',
|
||||
'error_on_missing_translation': False,
|
||||
'error_on_missing_placeholder': False,
|
||||
'error_on_missing_plural': False,
|
||||
'encoding': 'utf-8',
|
||||
'namespace_delimiter': '.',
|
||||
'plural_few': 5,
|
||||
'skip_locale_root_data': False,
|
||||
'enable_memoization': False
|
||||
}
|
||||
|
||||
def set(key, value):
|
||||
settings[key] = value
|
||||
|
||||
def get(key):
|
||||
return settings[key]
|
14
lib/python3.11/site-packages/i18n/loaders/json_loader.py
Normal file
14
lib/python3.11/site-packages/i18n/loaders/json_loader.py
Normal file
@ -0,0 +1,14 @@
|
||||
import json
|
||||
|
||||
from .loader import Loader, I18nFileLoadError
|
||||
|
||||
class JsonLoader(Loader):
|
||||
"""class to load yaml files"""
|
||||
def __init__(self):
|
||||
super(JsonLoader, self).__init__()
|
||||
|
||||
def parse_file(self, file_content):
|
||||
try:
|
||||
return json.loads(file_content)
|
||||
except ValueError as e:
|
||||
raise I18nFileLoadError("invalid JSON: {0}".format(e.strerror))
|
49
lib/python3.11/site-packages/i18n/loaders/loader.py
Normal file
49
lib/python3.11/site-packages/i18n/loaders/loader.py
Normal file
@ -0,0 +1,49 @@
|
||||
from .. import config
|
||||
import io
|
||||
|
||||
|
||||
class I18nFileLoadError(Exception):
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def __str__(self):
|
||||
return str(self.value)
|
||||
|
||||
|
||||
class Loader(object):
|
||||
"""Base class to load resources"""
|
||||
def __init__(self):
|
||||
super(Loader, self).__init__()
|
||||
self.memoization_dict = {}
|
||||
|
||||
def _load_file_data(self, filename):
|
||||
try:
|
||||
with io.open(filename, 'r', encoding=config.get('encoding')) as f:
|
||||
return f.read()
|
||||
except IOError as e:
|
||||
raise I18nFileLoadError("error loading file {0}: {1}".format(filename, e.strerror))
|
||||
|
||||
def load_file(self, filename):
|
||||
enable_memoization = config.get('enable_memoization')
|
||||
if enable_memoization:
|
||||
if filename not in self.memoization_dict:
|
||||
self.memoization_dict[filename] = self._load_file_data(filename)
|
||||
return self.memoization_dict[filename]
|
||||
else:
|
||||
return self._load_file_data(filename)
|
||||
|
||||
def parse_file(self, file_content):
|
||||
raise NotImplementedError("the method parse_file has not been implemented for class {0}".format(self.__class__.name__))
|
||||
|
||||
def check_data(self, data, root_data):
|
||||
return True if root_data is None else root_data in data
|
||||
|
||||
def get_data(self, data, root_data):
|
||||
return data if root_data is None else data[root_data]
|
||||
|
||||
def load_resource(self, filename, root_data):
|
||||
file_content = self.load_file(filename)
|
||||
data = self.parse_file(file_content)
|
||||
if not self.check_data(data, root_data):
|
||||
raise I18nFileLoadError("error getting data from {0}: {1} not defined".format(filename, root_data))
|
||||
return self.get_data(data, root_data)
|
29
lib/python3.11/site-packages/i18n/loaders/python_loader.py
Normal file
29
lib/python3.11/site-packages/i18n/loaders/python_loader.py
Normal file
@ -0,0 +1,29 @@
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
from .loader import Loader, I18nFileLoadError
|
||||
|
||||
|
||||
class PythonLoader(Loader):
|
||||
"""class to load python files"""
|
||||
def __init__(self):
|
||||
super(PythonLoader, self).__init__()
|
||||
|
||||
def load_file(self, filename):
|
||||
path, name = os.path.split(filename)
|
||||
module_name, ext = os.path.splitext(name)
|
||||
if path not in sys.path:
|
||||
sys.path.append(path)
|
||||
try:
|
||||
return __import__(module_name)
|
||||
except ImportError:
|
||||
raise I18nFileLoadError("error loading file {0}".format(filename))
|
||||
|
||||
def parse_file(self, file_content):
|
||||
return file_content
|
||||
|
||||
def check_data(self, data, root_data):
|
||||
return hasattr(data, root_data)
|
||||
|
||||
def get_data(self, data, root_data):
|
||||
return getattr(data, root_data)
|
17
lib/python3.11/site-packages/i18n/loaders/yaml_loader.py
Normal file
17
lib/python3.11/site-packages/i18n/loaders/yaml_loader.py
Normal file
@ -0,0 +1,17 @@
|
||||
import yaml
|
||||
|
||||
from .loader import Loader, I18nFileLoadError
|
||||
|
||||
class YamlLoader(Loader):
|
||||
"""class to load yaml files"""
|
||||
def __init__(self):
|
||||
super(YamlLoader, self).__init__()
|
||||
|
||||
def parse_file(self, file_content):
|
||||
try:
|
||||
if hasattr(yaml, "FullLoader"):
|
||||
return yaml.load(file_content, Loader=yaml.FullLoader)
|
||||
else:
|
||||
return yaml.load(file_content)
|
||||
except yaml.scanner.ScannerError as e:
|
||||
raise I18nFileLoadError("invalid YAML: {0}".format(str(e)))
|
117
lib/python3.11/site-packages/i18n/resource_loader.py
Normal file
117
lib/python3.11/site-packages/i18n/resource_loader.py
Normal file
@ -0,0 +1,117 @@
|
||||
import os.path
|
||||
|
||||
from . import config
|
||||
from .loaders.loader import I18nFileLoadError
|
||||
from . import translations
|
||||
|
||||
loaders = {}
|
||||
|
||||
PLURALS = ["zero", "one", "few", "many", "other"]
|
||||
|
||||
|
||||
def register_loader(loader_class, supported_extensions):
|
||||
if not hasattr(loader_class, "load_resource"):
|
||||
raise ValueError("loader class should have a 'load_resource' method")
|
||||
|
||||
for extension in supported_extensions:
|
||||
loaders[extension] = loader_class()
|
||||
|
||||
|
||||
def load_resource(filename, root_data):
|
||||
extension = os.path.splitext(filename)[1][1:]
|
||||
if extension not in loaders:
|
||||
raise I18nFileLoadError("no loader available for extension {0}".format(extension))
|
||||
return getattr(loaders[extension], "load_resource")(filename, root_data)
|
||||
|
||||
|
||||
def init_loaders():
|
||||
init_python_loader()
|
||||
if config.yaml_available:
|
||||
init_yaml_loader()
|
||||
if config.json_available:
|
||||
init_json_loader()
|
||||
|
||||
|
||||
def init_python_loader():
|
||||
from .loaders.python_loader import PythonLoader
|
||||
register_loader(PythonLoader, ["py"])
|
||||
|
||||
|
||||
def init_yaml_loader():
|
||||
from .loaders.yaml_loader import YamlLoader
|
||||
register_loader(YamlLoader, ["yml", "yaml"])
|
||||
|
||||
|
||||
def init_json_loader():
|
||||
from .loaders.json_loader import JsonLoader
|
||||
register_loader(JsonLoader, ["json"])
|
||||
|
||||
|
||||
def load_config(filename):
|
||||
settings_data = load_resource(filename, "settings")
|
||||
for key, value in settings_data.items():
|
||||
config.settings[key] = value
|
||||
|
||||
|
||||
def get_namespace_from_filepath(filename):
|
||||
namespace = os.path.dirname(filename).strip(os.sep).replace(os.sep, config.get('namespace_delimiter'))
|
||||
if '{namespace}' in config.get('filename_format'):
|
||||
try:
|
||||
splitted_filename = os.path.basename(filename).split('.')
|
||||
if namespace:
|
||||
namespace += config.get('namespace_delimiter')
|
||||
namespace += splitted_filename[config.get('filename_format').index('{namespace}')]
|
||||
except ValueError:
|
||||
raise I18nFileLoadError("incorrect file format.")
|
||||
return namespace
|
||||
|
||||
|
||||
def load_translation_file(filename, base_directory, locale=config.get('locale')):
|
||||
skip_locale_root_data = config.get('skip_locale_root_data')
|
||||
root_data = None if skip_locale_root_data else locale
|
||||
translations_dic = load_resource(os.path.join(base_directory, filename), root_data)
|
||||
namespace = get_namespace_from_filepath(filename)
|
||||
load_translation_dic(translations_dic, namespace, locale)
|
||||
|
||||
|
||||
def load_translation_dic(dic, namespace, locale):
|
||||
if namespace:
|
||||
namespace += config.get('namespace_delimiter')
|
||||
for key, value in dic.items():
|
||||
if type(value) == dict and len(set(PLURALS).intersection(value)) < 2:
|
||||
load_translation_dic(value, namespace + key, locale)
|
||||
else:
|
||||
translations.add(namespace + key, value, locale)
|
||||
|
||||
|
||||
def load_directory(directory, locale=config.get('locale')):
|
||||
for f in os.listdir(directory):
|
||||
path = os.path.join(directory, f)
|
||||
if os.path.isfile(path) and path.endswith(config.get('file_format')):
|
||||
if '{locale}' in config.get('filename_format') and not locale in f:
|
||||
continue
|
||||
load_translation_file(f, directory, locale)
|
||||
|
||||
|
||||
def search_translation(key, locale=config.get('locale')):
|
||||
splitted_key = key.split(config.get('namespace_delimiter'))
|
||||
if not splitted_key:
|
||||
return
|
||||
namespace = splitted_key[:-1]
|
||||
if not namespace and '{namespace}' not in config.get('filename_format'):
|
||||
for directory in config.get('load_path'):
|
||||
load_directory(directory, locale)
|
||||
else:
|
||||
for directory in config.get('load_path'):
|
||||
recursive_search_dir(namespace, '', directory, locale)
|
||||
|
||||
|
||||
def recursive_search_dir(splitted_namespace, directory, root_dir, locale=config.get('locale')):
|
||||
if not splitted_namespace:
|
||||
return
|
||||
seeked_file = config.get('filename_format').format(namespace=splitted_namespace[0], format=config.get('file_format'), locale=locale)
|
||||
dir_content = os.listdir(os.path.join(root_dir, directory))
|
||||
if seeked_file in dir_content:
|
||||
load_translation_file(os.path.join(directory, seeked_file), root_dir, locale)
|
||||
elif splitted_namespace[0] in dir_content:
|
||||
recursive_search_dir(splitted_namespace[1:], os.path.join(directory, splitted_namespace[0]), root_dir, locale)
|
0
lib/python3.11/site-packages/i18n/tests/__init__.py
Normal file
0
lib/python3.11/site-packages/i18n/tests/__init__.py
Normal file
240
lib/python3.11/site-packages/i18n/tests/loader_tests.py
Normal file
240
lib/python3.11/site-packages/i18n/tests/loader_tests.py
Normal file
@ -0,0 +1,240 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import unittest
|
||||
import os
|
||||
import os.path
|
||||
import tempfile
|
||||
|
||||
import i18n
|
||||
from i18n import resource_loader
|
||||
from i18n.resource_loader import I18nFileLoadError
|
||||
from i18n.translator import t
|
||||
from i18n import config
|
||||
from i18n.config import json_available, yaml_available
|
||||
from i18n import translations
|
||||
|
||||
try:
|
||||
reload # Python 2.7
|
||||
except NameError:
|
||||
try:
|
||||
from importlib import reload # Python 3.4+
|
||||
except ImportError:
|
||||
from imp import reload # Python 3.0 - 3.3
|
||||
|
||||
|
||||
RESOURCE_FOLDER = os.path.join(os.path.dirname(__file__), "resources")
|
||||
|
||||
|
||||
class TestFileLoader(unittest.TestCase):
|
||||
def setUp(self):
|
||||
resource_loader.loaders = {}
|
||||
translations.container = {}
|
||||
reload(config)
|
||||
config.set("load_path", [os.path.join(RESOURCE_FOLDER, "translations")])
|
||||
config.set("filename_format", "{namespace}.{locale}.{format}")
|
||||
config.set("encoding", "utf-8")
|
||||
|
||||
def test_load_unavailable_extension(self):
|
||||
with self.assertRaisesRegexp(I18nFileLoadError, "no loader .*"):
|
||||
resource_loader.load_resource("foo.bar", "baz")
|
||||
|
||||
def test_register_wrong_loader(self):
|
||||
class WrongLoader(object):
|
||||
pass
|
||||
with self.assertRaises(ValueError):
|
||||
resource_loader.register_loader(WrongLoader, [])
|
||||
|
||||
def test_register_python_loader(self):
|
||||
resource_loader.init_python_loader()
|
||||
with self.assertRaisesRegexp(I18nFileLoadError, "error loading file .*"):
|
||||
resource_loader.load_resource("foo.py", "bar")
|
||||
|
||||
@unittest.skipUnless(yaml_available, "yaml library not available")
|
||||
def test_register_yaml_loader(self):
|
||||
resource_loader.init_yaml_loader()
|
||||
with self.assertRaisesRegexp(I18nFileLoadError, "error loading file .*"):
|
||||
resource_loader.load_resource("foo.yml", "bar")
|
||||
|
||||
@unittest.skipUnless(json_available, "json library not available")
|
||||
def test_load_wrong_json_file(self):
|
||||
resource_loader.init_json_loader()
|
||||
with self.assertRaisesRegexp(I18nFileLoadError, "error getting data .*"):
|
||||
resource_loader.load_resource(os.path.join(RESOURCE_FOLDER, "settings", "dummy_config.json"), "foo")
|
||||
|
||||
@unittest.skipUnless(yaml_available, "yaml library not available")
|
||||
def test_load_yaml_file(self):
|
||||
resource_loader.init_yaml_loader()
|
||||
data = resource_loader.load_resource(os.path.join(RESOURCE_FOLDER, "settings", "dummy_config.yml"), "settings")
|
||||
self.assertIn("foo", data)
|
||||
self.assertEqual("bar", data["foo"])
|
||||
|
||||
@unittest.skipUnless(json_available, "json library not available")
|
||||
def test_load_json_file(self):
|
||||
resource_loader.init_json_loader()
|
||||
data = resource_loader.load_resource(os.path.join(RESOURCE_FOLDER, "settings", "dummy_config.json"), "settings")
|
||||
self.assertIn("foo", data)
|
||||
self.assertEqual("bar", data["foo"])
|
||||
|
||||
def test_load_python_file(self):
|
||||
resource_loader.init_python_loader()
|
||||
data = resource_loader.load_resource(os.path.join(RESOURCE_FOLDER, "settings", "dummy_config.py"), "settings")
|
||||
self.assertIn("foo", data)
|
||||
self.assertEqual("bar", data["foo"])
|
||||
|
||||
@unittest.skipUnless(yaml_available, "yaml library not available")
|
||||
def test_memoization_with_file(self):
|
||||
'''This test creates a temporary file with the help of the
|
||||
tempfile library and writes a simple key: value dictionary in it.
|
||||
It will then use that file to load the translations and, after having
|
||||
enabled memoization, try to access it, causing the file to be (hopefully)
|
||||
memoized. It will then _remove_ the temporary file and try to access again,
|
||||
asserting that an error is not raised, thus making sure the data is
|
||||
actually loaded from memory and not from disk access.'''
|
||||
memoization_file_name = 'memoize.en.yml'
|
||||
# create the file and write the data in it
|
||||
try:
|
||||
d = tempfile.TemporaryDirectory()
|
||||
tmp_dir_name = d.name
|
||||
except AttributeError:
|
||||
# we are running python2, use mkdtemp
|
||||
tmp_dir_name = tempfile.mkdtemp()
|
||||
fd = open('{}/{}'.format(tmp_dir_name, memoization_file_name), 'w')
|
||||
fd.write('en:\n key: value')
|
||||
fd.close()
|
||||
# create the loader and pass the file to it
|
||||
resource_loader.init_yaml_loader()
|
||||
resource_loader.load_translation_file(memoization_file_name, tmp_dir_name)
|
||||
# try loading the value to make sure it's working
|
||||
self.assertEqual(t('memoize.key'), 'value')
|
||||
# now delete the file and directory
|
||||
# we are running python2, delete manually
|
||||
import shutil
|
||||
shutil.rmtree(tmp_dir_name)
|
||||
# test the translation again to make sure it's loaded from memory
|
||||
self.assertEqual(t('memoize.key'), 'value')
|
||||
|
||||
|
||||
@unittest.skipUnless(json_available, "json library not available")
|
||||
def test_load_file_with_strange_encoding(self):
|
||||
resource_loader.init_json_loader()
|
||||
config.set("encoding", "euc-jp")
|
||||
data = resource_loader.load_resource(os.path.join(RESOURCE_FOLDER, "settings", "eucjp_config.json"), "settings")
|
||||
self.assertIn("ほげ", data)
|
||||
self.assertEqual("ホゲ", data["ほげ"])
|
||||
|
||||
def test_get_namespace_from_filepath_with_filename(self):
|
||||
tests = {
|
||||
"foo": "foo.ja.yml",
|
||||
"foo.bar": os.path.join("foo", "bar.ja.yml"),
|
||||
"foo.bar.baz": os.path.join("foo", "bar", "baz.en.yml"),
|
||||
}
|
||||
for expected, test_val in tests.items():
|
||||
namespace = resource_loader.get_namespace_from_filepath(test_val)
|
||||
self.assertEqual(expected, namespace)
|
||||
|
||||
def test_get_namespace_from_filepath_without_filename(self):
|
||||
tests = {
|
||||
"": "ja.yml",
|
||||
"foo": os.path.join("foo", "ja.yml"),
|
||||
"foo.bar": os.path.join("foo", "bar", "en.yml"),
|
||||
}
|
||||
config.set("filename_format", "{locale}.{format}")
|
||||
for expected, test_val in tests.items():
|
||||
namespace = resource_loader.get_namespace_from_filepath(test_val)
|
||||
self.assertEqual(expected, namespace)
|
||||
|
||||
@unittest.skipUnless(yaml_available, "yaml library not available")
|
||||
def test_load_translation_file(self):
|
||||
resource_loader.init_yaml_loader()
|
||||
resource_loader.load_translation_file("foo.en.yml", os.path.join(RESOURCE_FOLDER, "translations"))
|
||||
|
||||
self.assertTrue(translations.has("foo.normal_key"))
|
||||
self.assertTrue(translations.has("foo.parent.nested_key"))
|
||||
|
||||
@unittest.skipUnless(json_available, "json library not available")
|
||||
def test_load_plural(self):
|
||||
resource_loader.init_yaml_loader()
|
||||
resource_loader.load_translation_file("foo.en.yml", os.path.join(RESOURCE_FOLDER, "translations"))
|
||||
self.assertTrue(translations.has("foo.mail_number"))
|
||||
translated_plural = translations.get("foo.mail_number")
|
||||
self.assertIsInstance(translated_plural, dict)
|
||||
self.assertEqual(translated_plural["zero"], "You do not have any mail.")
|
||||
self.assertEqual(translated_plural["one"], "You have a new mail.")
|
||||
self.assertEqual(translated_plural["many"], "You have %{count} new mails.")
|
||||
|
||||
@unittest.skipUnless(yaml_available, "yaml library not available")
|
||||
def test_search_translation_yaml(self):
|
||||
resource_loader.init_yaml_loader()
|
||||
config.set("file_format", "yml")
|
||||
resource_loader.search_translation("foo.normal_key")
|
||||
self.assertTrue(translations.has("foo.normal_key"))
|
||||
|
||||
@unittest.skipUnless(json_available, "json library not available")
|
||||
def test_search_translation_json(self):
|
||||
resource_loader.init_json_loader()
|
||||
config.set("file_format", "json")
|
||||
|
||||
resource_loader.search_translation("bar.baz.qux")
|
||||
self.assertTrue(translations.has("bar.baz.qux"))
|
||||
|
||||
@unittest.skipUnless(json_available, "json library not available")
|
||||
def test_search_translation_without_ns(self):
|
||||
resource_loader.init_json_loader()
|
||||
config.set("file_format", "json")
|
||||
config.set("filename_format", "{locale}.{format}")
|
||||
resource_loader.search_translation("foo")
|
||||
self.assertTrue(translations.has("foo"))
|
||||
|
||||
@unittest.skipUnless(json_available, "json library not available")
|
||||
def test_search_translation_without_ns_nested_dict__two_levels_neting__default_locale(self):
|
||||
resource_loader.init_json_loader()
|
||||
config.set("file_format", "json")
|
||||
config.set("load_path", [os.path.join(RESOURCE_FOLDER, "translations", "nested_dict_json")])
|
||||
config.set("filename_format", "{locale}.{format}")
|
||||
config.set('skip_locale_root_data', True)
|
||||
config.set("locale", ["en", "pl"])
|
||||
resource_loader.search_translation("COMMON.VERSION")
|
||||
self.assertTrue(translations.has("COMMON.VERSION"))
|
||||
self.assertEqual(translations.get("COMMON.VERSION"), "version")
|
||||
|
||||
@unittest.skipUnless(json_available, "json library not available")
|
||||
def test_search_translation_without_ns_nested_dict__two_levels_neting__other_locale(self):
|
||||
resource_loader.init_json_loader()
|
||||
config.set("file_format", "json")
|
||||
config.set("load_path", [os.path.join(RESOURCE_FOLDER, "translations", "nested_dict_json")])
|
||||
config.set("filename_format", "{locale}.{format}")
|
||||
config.set('skip_locale_root_data', True)
|
||||
config.set("locale", ["en", "pl"])
|
||||
resource_loader.search_translation("COMMON.VERSION", locale="pl")
|
||||
self.assertTrue(translations.has("COMMON.VERSION", locale="pl"))
|
||||
self.assertEqual(translations.get("COMMON.VERSION", locale="pl"), "wersja")
|
||||
|
||||
@unittest.skipUnless(json_available, "json library not available")
|
||||
def test_search_translation_without_ns_nested_dict__default_locale(self):
|
||||
resource_loader.init_json_loader()
|
||||
config.set("file_format", "json")
|
||||
config.set("load_path", [os.path.join(RESOURCE_FOLDER, "translations", "nested_dict_json")])
|
||||
config.set("filename_format", "{locale}.{format}")
|
||||
config.set('skip_locale_root_data', True)
|
||||
config.set("locale", "en")
|
||||
resource_loader.search_translation("TOP_MENU.TOP_BAR.LOGS")
|
||||
self.assertTrue(translations.has("TOP_MENU.TOP_BAR.LOGS"))
|
||||
self.assertEqual(translations.get("TOP_MENU.TOP_BAR.LOGS"), "Logs")
|
||||
|
||||
@unittest.skipUnless(json_available, "json library not available")
|
||||
def test_search_translation_without_ns_nested_dict__other_locale(self):
|
||||
resource_loader.init_json_loader()
|
||||
config.set("file_format", "json")
|
||||
config.set("load_path", [os.path.join(RESOURCE_FOLDER, "translations", "nested_dict_json")])
|
||||
config.set("filename_format", "{locale}.{format}")
|
||||
config.set('skip_locale_root_data', True)
|
||||
config.set("locale", "en")
|
||||
resource_loader.search_translation("TOP_MENU.TOP_BAR.LOGS", locale="pl")
|
||||
self.assertTrue(translations.has("TOP_MENU.TOP_BAR.LOGS", locale="pl"))
|
||||
self.assertEqual(translations.get("TOP_MENU.TOP_BAR.LOGS", locale="pl"), "Logi")
|
||||
|
||||
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(TestFileLoader)
|
||||
unittest.TextTestRunner(verbosity=2).run(suite)
|
20
lib/python3.11/site-packages/i18n/tests/run_tests.py
Normal file
20
lib/python3.11/site-packages/i18n/tests/run_tests.py
Normal file
@ -0,0 +1,20 @@
|
||||
import unittest
|
||||
|
||||
from i18n.tests.translation_tests import TestTranslationFormat
|
||||
|
||||
from i18n.tests.loader_tests import TestFileLoader
|
||||
|
||||
|
||||
def suite():
|
||||
suite = unittest.TestSuite()
|
||||
|
||||
suite.addTest(unittest.makeSuite(TestFileLoader))
|
||||
suite.addTest(unittest.makeSuite(TestTranslationFormat))
|
||||
|
||||
return suite
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
runner = unittest.TextTestRunner()
|
||||
test_suite = suite()
|
||||
runner.run(test_suite)
|
133
lib/python3.11/site-packages/i18n/tests/translation_tests.py
Normal file
133
lib/python3.11/site-packages/i18n/tests/translation_tests.py
Normal file
@ -0,0 +1,133 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import unittest
|
||||
import os
|
||||
import os.path
|
||||
|
||||
from i18n import resource_loader
|
||||
from i18n.translator import t
|
||||
from i18n import translations
|
||||
from i18n import config
|
||||
|
||||
try:
|
||||
reload # Python 2.7
|
||||
except NameError:
|
||||
try:
|
||||
from importlib import reload # Python 3.4+
|
||||
except ImportError:
|
||||
from imp import reload # Python 3.0 - 3.3
|
||||
|
||||
RESOURCE_FOLDER = os.path.dirname(__file__) + os.sep + 'resources' + os.sep
|
||||
|
||||
|
||||
class TestTranslationFormat(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
resource_loader.init_loaders()
|
||||
reload(config)
|
||||
config.set('load_path', [os.path.join(RESOURCE_FOLDER, 'translations')])
|
||||
translations.add('foo.hi', 'Hello %{name} !')
|
||||
translations.add('foo.hello', 'Salut %{name} !', locale='fr')
|
||||
translations.add('foo.basic_plural', {
|
||||
'one': '1 elem',
|
||||
'many': '%{count} elems'
|
||||
})
|
||||
translations.add('foo.plural', {
|
||||
'zero': 'no mail',
|
||||
'one': '1 mail',
|
||||
'few': 'only %{count} mails',
|
||||
'many': '%{count} mails'
|
||||
})
|
||||
translations.add('foo.bad_plural', {
|
||||
'bar': 'foo elems'
|
||||
})
|
||||
|
||||
def setUp(self):
|
||||
config.set('error_on_missing_translation', False)
|
||||
config.set('error_on_missing_placeholder', False)
|
||||
config.set('fallback', 'en')
|
||||
config.set('locale', 'en')
|
||||
|
||||
def test_basic_translation(self):
|
||||
self.assertEqual(t('foo.normal_key'), 'normal_value')
|
||||
|
||||
def test_missing_translation(self):
|
||||
self.assertEqual(t('foo.inexistent'), 'foo.inexistent')
|
||||
|
||||
def test_missing_translation_error(self):
|
||||
config.set('error_on_missing_translation', True)
|
||||
with self.assertRaises(KeyError):
|
||||
t('foo.inexistent')
|
||||
|
||||
def test_locale_change(self):
|
||||
config.set('locale', 'fr')
|
||||
self.assertEqual(t('foo.hello', name='Bob'), 'Salut Bob !')
|
||||
|
||||
def test_fallback(self):
|
||||
config.set('fallback', 'fr')
|
||||
self.assertEqual(t('foo.hello', name='Bob'), 'Salut Bob !')
|
||||
|
||||
def test_fallback_from_resource(self):
|
||||
config.set('fallback', 'ja')
|
||||
self.assertEqual(t('foo.fallback_key'), 'フォールバック')
|
||||
|
||||
def test_basic_placeholder(self):
|
||||
self.assertEqual(t('foo.hi', name='Bob'), 'Hello Bob !')
|
||||
|
||||
def test_missing_placehoder(self):
|
||||
self.assertEqual(t('foo.hi'), 'Hello %{name} !')
|
||||
|
||||
def test_missing_placeholder_error(self):
|
||||
config.set('error_on_missing_placeholder', True)
|
||||
with self.assertRaises(KeyError):
|
||||
t('foo.hi')
|
||||
|
||||
def test_basic_pluralization(self):
|
||||
self.assertEqual(t('foo.basic_plural', count=0), '0 elems')
|
||||
self.assertEqual(t('foo.basic_plural', count=1), '1 elem')
|
||||
self.assertEqual(t('foo.basic_plural', count=2), '2 elems')
|
||||
|
||||
def test_full_pluralization(self):
|
||||
self.assertEqual(t('foo.plural', count=0), 'no mail')
|
||||
self.assertEqual(t('foo.plural', count=1), '1 mail')
|
||||
self.assertEqual(t('foo.plural', count=4), 'only 4 mails')
|
||||
self.assertEqual(t('foo.plural', count=12), '12 mails')
|
||||
|
||||
def test_bad_pluralization(self):
|
||||
config.set('error_on_missing_plural', False)
|
||||
self.assertEqual(t('foo.normal_key', count=5), 'normal_value')
|
||||
config.set('error_on_missing_plural', True)
|
||||
with self.assertRaises(KeyError):
|
||||
t('foo.bad_plural', count=0)
|
||||
|
||||
def test_default(self):
|
||||
self.assertEqual(t('inexistent_key', default='foo'), 'foo')
|
||||
|
||||
def test_skip_locale_root_data(self):
|
||||
config.set('filename_format', '{locale}.{format}')
|
||||
config.set('file_format', 'json')
|
||||
config.set('locale', 'gb')
|
||||
config.set('skip_locale_root_data', True)
|
||||
resource_loader.init_loaders()
|
||||
self.assertEqual(t('foo'), 'Lorry')
|
||||
config.set('skip_locale_root_data', False)
|
||||
|
||||
def test_skip_locale_root_data_nested_json_dict__default_locale(self):
|
||||
config.set("file_format", "json")
|
||||
config.set("load_path", [os.path.join(RESOURCE_FOLDER, "translations", "nested_dict_json")])
|
||||
config.set("filename_format", "{locale}.{format}")
|
||||
config.set('skip_locale_root_data', True)
|
||||
config.set("locale", "en")
|
||||
resource_loader.init_json_loader()
|
||||
self.assertEqual(t('COMMON.START'), 'Start')
|
||||
|
||||
def test_skip_locale_root_data_nested_json_dict__other_locale(self):
|
||||
config.set("file_format", "json")
|
||||
config.set("load_path", [os.path.join(RESOURCE_FOLDER, "translations", "nested_dict_json")])
|
||||
config.set("filename_format", "{locale}.{format}")
|
||||
config.set('skip_locale_root_data', True)
|
||||
config.set("locale", "en")
|
||||
resource_loader.init_json_loader()
|
||||
self.assertEqual(t('COMMON.EXECUTE', locale="pl"), 'Wykonaj')
|
15
lib/python3.11/site-packages/i18n/translations.py
Normal file
15
lib/python3.11/site-packages/i18n/translations.py
Normal file
@ -0,0 +1,15 @@
|
||||
from . import config
|
||||
|
||||
container = {}
|
||||
|
||||
|
||||
def add(key, value, locale=config.get('locale')):
|
||||
container.setdefault(locale, {})[key] = value
|
||||
|
||||
|
||||
def has(key, locale=config.get('locale')):
|
||||
return key in container.get(locale, {})
|
||||
|
||||
|
||||
def get(key, locale=config.get('locale')):
|
||||
return container[locale][key]
|
73
lib/python3.11/site-packages/i18n/translator.py
Normal file
73
lib/python3.11/site-packages/i18n/translator.py
Normal file
@ -0,0 +1,73 @@
|
||||
from string import Template
|
||||
|
||||
from . import config
|
||||
from . import resource_loader
|
||||
from . import translations
|
||||
|
||||
|
||||
class TranslationFormatter(Template):
|
||||
delimiter = config.get('placeholder_delimiter')
|
||||
|
||||
def __init__(self, template):
|
||||
super(TranslationFormatter, self).__init__(template)
|
||||
|
||||
def format(self, **kwargs):
|
||||
if config.get('error_on_missing_placeholder'):
|
||||
return self.substitute(**kwargs)
|
||||
else:
|
||||
return self.safe_substitute(**kwargs)
|
||||
|
||||
|
||||
def t(key, **kwargs):
|
||||
locale = kwargs.pop('locale', config.get('locale'))
|
||||
if translations.has(key, locale):
|
||||
return translate(key, locale=locale, **kwargs)
|
||||
else:
|
||||
resource_loader.search_translation(key, locale)
|
||||
if translations.has(key, locale):
|
||||
return translate(key, locale=locale, **kwargs)
|
||||
elif locale != config.get('fallback'):
|
||||
return t(key, locale=config.get('fallback'), **kwargs)
|
||||
if 'default' in kwargs:
|
||||
return kwargs['default']
|
||||
if config.get('error_on_missing_translation'):
|
||||
raise KeyError('key {0} not found'.format(key))
|
||||
else:
|
||||
return key
|
||||
|
||||
|
||||
def translate(key, **kwargs):
|
||||
locale = kwargs.pop('locale', config.get('locale'))
|
||||
translation = translations.get(key, locale=locale)
|
||||
if 'count' in kwargs:
|
||||
translation = pluralize(key, translation, kwargs['count'])
|
||||
return TranslationFormatter(translation).format(**kwargs)
|
||||
|
||||
|
||||
def pluralize(key, translation, count):
|
||||
return_value = key
|
||||
try:
|
||||
if type(translation) != dict:
|
||||
return_value = translation
|
||||
raise KeyError('use of count witouth dict for key {0}'.format(key))
|
||||
if count == 0:
|
||||
if 'zero' in translation:
|
||||
return translation['zero']
|
||||
elif count == 1:
|
||||
if 'one' in translation:
|
||||
return translation['one']
|
||||
elif count <= config.get('plural_few'):
|
||||
if 'few' in translation:
|
||||
return translation['few']
|
||||
# TODO: deprecate other
|
||||
if 'other' in translation:
|
||||
return translation['other']
|
||||
if 'many' in translation:
|
||||
return translation['many']
|
||||
else:
|
||||
raise KeyError('"many" not defined for key {0}'.format(key))
|
||||
except KeyError as e:
|
||||
if config.get('error_on_missing_plural'):
|
||||
raise e
|
||||
else:
|
||||
return return_value
|
Reference in New Issue
Block a user