Repository: danhper/python-i18n
Branch: master
Commit: 75a49d6c4b8d
Files: 38
Total size: 39.3 KB
Directory structure:
gitextract_0c1geu1c/
├── .github/
│ └── workflows/
│ └── ci.yml
├── .gitignore
├── .travis.yml
├── LICENSE
├── MANIFEST.in
├── README.md
├── i18n/
│ ├── __init__.py
│ ├── config.py
│ ├── loaders/
│ │ ├── __init__.py
│ │ ├── json_loader.py
│ │ ├── loader.py
│ │ ├── python_loader.py
│ │ └── yaml_loader.py
│ ├── resource_loader.py
│ ├── tests/
│ │ ├── __init__.py
│ │ ├── loader_tests.py
│ │ ├── resources/
│ │ │ ├── settings/
│ │ │ │ ├── dummy_config.json
│ │ │ │ ├── dummy_config.py
│ │ │ │ ├── dummy_config.yml
│ │ │ │ └── eucjp_config.json
│ │ │ └── translations/
│ │ │ ├── bar/
│ │ │ │ └── baz.en.json
│ │ │ ├── en.json
│ │ │ ├── foo.en.yml
│ │ │ ├── foo.ja.yml
│ │ │ ├── gb.json
│ │ │ ├── ja.json
│ │ │ └── nested_dict_json/
│ │ │ ├── en.json
│ │ │ └── pl.json
│ │ ├── run_tests.py
│ │ └── translation_tests.py
│ ├── translations.py
│ └── translator.py
├── pyproject.toml
├── requirements/
│ ├── base.txt
│ ├── dev.txt
│ └── test.txt
├── requirements.txt
└── setup.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install .[yaml]
pip install ruff pyright
- name: Lint with ruff
run: ruff check i18n
- name: Lint with pyright
run: pyright .
- name: Run tests
run: python -m i18n.tests.run_tests
================================================
FILE: .gitignore
================================================
__pycache__/
*.egg-info/
build/
*.pyc
*~
dist/
.python-version
.idea/
.pytest_cache/
*.lock
.ruff_cache
.venv
================================================
FILE: .travis.yml
================================================
language: python
python:
- "2.7"
- "3.6"
- "3.7"
- "3.8"
# command to install dependencies
install: "pip install -r requirements.txt"
# command to run tests
script:
- python setup.py test
- coverage run --source=i18n setup.py test
after_success:
- coveralls
================================================
FILE: LICENSE
================================================
Copyright (c) 2012 Daniel Perez
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: MANIFEST.in
================================================
include *.md
include LICENSE
include pyproject.toml
include setup.py
recursive-include requirements *.txt
recursive-include i18n *.py
recursive-include i18n *.json
recursive-include i18n *.yml
recursive-include i18n *.yaml
================================================
FILE: README.md
================================================
# python-i18n [](https://github.com/danhper/python-i18n/actions/workflows/ci.yml) [](https://coveralls.io/github/danhper/python-i18n?branch=master) [](https://codeclimate.com/github/danhper/python-i18n)
This library provides i18n functionality for Python 3 out of the box. The usage is mostly based on Rails i18n library.
## Installation
Just run
pip install python-i18n
If you want to use YAML to store your translations, use
pip install python-i18n[YAML]
## Usage
### Basic usage
The simplest, though not very useful usage would be
import i18n
i18n.add_translation('foo', 'bar')
i18n.t('foo') # bar
### Using translation files
YAML and JSON formats are supported to store translations. With the default configuration, if you have the following `foo.en.yml` file
en:
hi: Hello world !
in `/path/to/translations` folder, you simply need to add the folder to the translations path.
import i18n
i18n.load_path.append('/path/to/translations')
i18n.t('foo.hi') # Hello world !
Please note that YAML format is used as default file format if you have `yaml` module installed.
If both `yaml` and `json` modules available and you want to use JSON to store translations, explicitly specify that: `i18n.set('file_format', 'json')`
### Memoization
Setting the configuration value `enable_memoization` in the settings dir will load the files from disk the first time they
are loaded and then store their content in memory. On the next use the file content will be provided from memory and not
loaded from disk, preventing disk access. While this can be useful in some contexts, keep in mind there is no current way of
issuing a command to the reloader to re-read the files from disk, so if you are updating your translation file without restarting
the interpreter do not use this option.
### Namespaces
#### File namespaces
In the above example, the translation key is `foo.hi` and not just `hi`. This is because the translation filename format is by default `{namespace}.{locale}.{format}`, so the {namespace} part of the file is used as translation.
To remove `{namespace}` from filename format please change the `filename_format` configuration.
i18n.set('filename_format', '{locale}.{format}')
#### Directory namespaces
If your files are in subfolders, the foldernames are also used as namespaces, so for example if your translation root path is `/path/to/translations` and you have the file `/path/to/translations/my/app/name/foo.en.yml`, the translation namespace for the file will be `my.app.name` and the file keys will therefore be accessible from `my.app.name.foo.my_key`.
## Functionalities
### Placeholder
You can of course use placeholders in your translations. With the default configuration, the placeholders are used by inserting `%{placeholder_name}` in the ntranslation string. Here is a sample usage.
i18n.add_translation('hi', 'Hello %{name} !')
i18n.t('hi', name='Bob') # Hello Bob !
### Pluralization
Pluralization is based on Rail i18n module. By passing a `count` variable to your translation, it will be pluralized. The translation value should be a dictionnary with at least the keys `one` and `many`. You can add a `zero` or `few` key when needed, if it is not present `many` will be used instead. Here is a sample usage.
i18n.add_translation('mail_number', {
'zero': 'You do not have any mail.',
'one': 'You have a new mail.',
'few': 'You only have %{count} mails.',
'many': 'You have %{count} new mails.'
})
i18n.t('mail_number', count=0) # You do not have any mail.
i18n.t('mail_number', count=1) # You have a new mail.
i18n.t('mail_number', count=3) # You only have 3 new mails.
i18n.t('mail_number', count=12) # You have 12 new mails.
### Fallback
You can set a fallback which will be used when the key is not found in the default locale.
i18n.set('locale', 'jp')
i18n.set('fallback', 'en')
i18n.add_translation('foo', 'bar', locale='en')
i18n.t('foo') # bar
### Skip locale from root
Sometimes i18n structure file came from another project or not contains root element with locale eg. `en` name.
{
"foo": "FooBar"
}
However we would like to use this i18n .json file in our Python sub-project or micro service as base file for translations.
`python-i18n` has special configuration tha is skipping locale eg. `en` root data element from the file.
i18n.set('skip_locale_root_data', True)
================================================
FILE: i18n/__init__.py
================================================
from . import config, resource_loader
from .resource_loader import I18nFileLoadError as I18nFileLoadError
from .resource_loader import load_config as load_config
from .resource_loader import register_loader as register_loader
from .translations import add as add_translation # noqa: F401
from .translator import t as t
resource_loader.init_loaders()
load_path = config.get("load_path")
================================================
FILE: i18n/config.py
================================================
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]
================================================
FILE: i18n/loaders/__init__.py
================================================
================================================
FILE: i18n/loaders/json_loader.py
================================================
import json
from .loader import I18nFileLoadError, Loader
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(str(e)))
================================================
FILE: i18n/loaders/loader.py
================================================
import io
from .. import config
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)
================================================
FILE: i18n/loaders/python_loader.py
================================================
import os.path
import sys
from .loader import I18nFileLoadError, Loader
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)
================================================
FILE: i18n/loaders/yaml_loader.py
================================================
import yaml
from .loader import I18nFileLoadError, Loader
class YamlLoader(Loader):
"""class to load yaml files"""
def __init__(self):
super(YamlLoader, self).__init__()
def parse_file(self, file_content):
try:
loader = getattr(yaml, "FullLoader", yaml.SafeLoader)
return yaml.load(file_content, Loader=loader)
except Exception as e:
raise I18nFileLoadError("invalid YAML: {0}".format(str(e)))
================================================
FILE: i18n/resource_loader.py
================================================
import os.path
from . import config, translations
from .loaders.loader import I18nFileLoadError
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 isinstance(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 locale not 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,
)
================================================
FILE: i18n/tests/__init__.py
================================================
================================================
FILE: i18n/tests/loader_tests.py
================================================
# -*- encoding: utf-8 -*-
from __future__ import unicode_literals
import os
import os.path
import tempfile
import unittest
# Python 3 only: always import reload from importlib
from importlib import reload
from i18n import config, resource_loader, translations
from i18n.config import json_available, yaml_available
from i18n.resource_loader import I18nFileLoadError
from i18n.translator import t
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.assertRaisesRegex(I18nFileLoadError, "no loader .*"):
resource_loader.load_resource("foo.bar", "baz")
with self.assertRaisesRegex(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.assertRaisesRegex(I18nFileLoadError, "error loading file .*"):
resource_loader.load_resource("foo.py", "bar")
with self.assertRaisesRegex(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.assertRaisesRegex(I18nFileLoadError, "error loading file .*"):
resource_loader.load_resource("foo.yml", "bar")
with self.assertRaisesRegex(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.assertRaisesRegex(I18nFileLoadError, "error getting data .*"):
resource_loader.load_resource(
os.path.join(RESOURCE_FOLDER, "settings", "dummy_config.json"), "foo"
)
with self.assertRaisesRegex(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)
================================================
FILE: i18n/tests/resources/settings/dummy_config.json
================================================
{
"settings" : {
"foo": "bar",
"baz": "qux"
}
}
================================================
FILE: i18n/tests/resources/settings/dummy_config.py
================================================
settings = {"foo": "bar", "baz": "qux"}
================================================
FILE: i18n/tests/resources/settings/dummy_config.yml
================================================
settings:
foo: bar
baz: qux
================================================
FILE: i18n/tests/resources/settings/eucjp_config.json
================================================
{
"settings": {
"ۤ": "ۥ"
}
}
================================================
FILE: i18n/tests/resources/translations/bar/baz.en.json
================================================
{
"en": {
"qux": "hoge"
}
}
================================================
FILE: i18n/tests/resources/translations/en.json
================================================
{
"en": {
"foo": "FooBar"
}
}
================================================
FILE: i18n/tests/resources/translations/foo.en.yml
================================================
en:
normal_key: normal_value
parent:
nested_key: nested_value
mail_number:
zero: You do not have any mail.
one: You have a new mail.
few: You only have %{count} mails.
many: You have %{count} new mails.
================================================
FILE: i18n/tests/resources/translations/foo.ja.yml
================================================
ja:
normal_key: 普通
fallback_key: フォールバック
================================================
FILE: i18n/tests/resources/translations/gb.json
================================================
{
"foo": "Lorry"
}
================================================
FILE: i18n/tests/resources/translations/ja.json
================================================
{
"ja": {
"foo": "ふぉお",
"bar": "ばば"
}
}
================================================
FILE: i18n/tests/resources/translations/nested_dict_json/en.json
================================================
{
"TOP_MENU": {
"TOP_BAR": {
"LOGS": "Logs",
"LOGS_ITEMS": {
"MAIN": "Main process",
"ERROR": "Errors",
"CUSTOM": "Custom Logs"
},
"NOTIFICATIONS": "Notifications",
"CONFIG": "Config",
"LICENSE": "License"
}
},
"COMMON": {
"HOME": "Home",
"START": "Start",
"STOP": "Stop",
"REFRESH": "Refresh",
"LOAD_FILE": "Load file",
"DELETE_FILE": "Delete file",
"EXECUTE": "Execute",
"DELETE": "Delete",
"VERSION": "version",
"SUBMIT": "Submit"
}
}
================================================
FILE: i18n/tests/resources/translations/nested_dict_json/pl.json
================================================
{
"TOP_MENU": {
"TOP_BAR": {
"LOGS": "Logi",
"LOGS_ITEMS": {
"MAIN": "Główny proces",
"ERROR": "Błędy",
"CUSTOM": "Logi dodatkowe"
},
"NOTIFICATIONS": "Powiadomienia",
"CONFIG": "Konfiguracja",
"LICENSE": "Licencja"
}
},
"COMMON": {
"HOME": "Początek",
"START": "Start",
"STOP": "Stop",
"REFRESH": "Odśwież",
"LOAD_FILE": "Załaduj",
"DELETE_FILE": "Usuń",
"EXECUTE": "Wykonaj",
"DELETE": "Usuń",
"VERSION": "wersja",
"SUBMIT": "Wyślij"
}
}
================================================
FILE: i18n/tests/run_tests.py
================================================
import unittest
from i18n.tests.loader_tests import TestFileLoader
from i18n.tests.translation_tests import TestTranslationFormat
def suite():
suite = unittest.TestSuite()
loader = unittest.TestLoader()
suite.addTest(loader.loadTestsFromTestCase(TestFileLoader))
suite.addTest(loader.loadTestsFromTestCase(TestTranslationFormat))
return suite
if __name__ == "__main__":
runner = unittest.TextTestRunner()
test_suite = suite()
runner.run(test_suite)
================================================
FILE: i18n/tests/translation_tests.py
================================================
# -*- encoding: utf-8 -*-
from __future__ import unicode_literals
import os
import os.path
import unittest
# Python 3 only: always import reload from importlib
from importlib import reload
from i18n import config, resource_loader, translations
from i18n.translator import t
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")
================================================
FILE: i18n/translations.py
================================================
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]
================================================
FILE: i18n/translator.py
================================================
from string import Template
from . import config, resource_loader, 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 not isinstance(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
================================================
FILE: pyproject.toml
================================================
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "python-i18n"
version = "0.3.9"
description = "Translation library for Python"
readme = "README.md"
authors = [
{ name = "Daniel Perez", email = "tuvistavie@gmail.com" }
]
license = { file = "LICENSE" }
requires-python = ">=3.7"
dependencies = [
"pyyaml>=3.10"
]
classifiers = [
"Development Status :: 4 - Beta",
"Environment :: Other Environment",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Topic :: Software Development :: Libraries"
]
[project.optional-dependencies]
yaml = ["pyyaml>=3.10"]
[tool.setuptools.packages.find]
where = ["."]
include = ["i18n*", "i18n.loaders*", "i18n.tests*"]
================================================
FILE: requirements/base.txt
================================================
pyyaml>=3.10
================================================
FILE: requirements/dev.txt
================================================
-r base.txt
coveralls
================================================
FILE: requirements/test.txt
================================================
-r dev.txt
pytest
================================================
FILE: requirements.txt
================================================
pyyaml>=3.10
coveralls
================================================
FILE: setup.py
================================================
from setuptools import setup # type: ignore
setup(
name="python-i18n",
version="0.3.9",
description="Translation library for Python",
long_description=open("README.md").read(),
long_description_content_type="text/markdown",
author="Daniel Perez",
author_email="tuvistavie@gmail.com",
url="https://github.com/tuvistavie/python-i18n",
download_url="https://github.com/tuvistavie/python-i18n/archive/master.zip",
license="MIT",
packages=["i18n", "i18n.loaders", "i18n.tests"],
include_package_data=True,
zip_safe=True,
test_suite="i18n.tests",
extras_require={
"YAML": ["pyyaml>=3.10"],
},
classifiers=[
"Development Status :: 4 - Beta",
"Environment :: Other Environment",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Topic :: Software Development :: Libraries",
],
)
gitextract_0c1geu1c/ ├── .github/ │ └── workflows/ │ └── ci.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── MANIFEST.in ├── README.md ├── i18n/ │ ├── __init__.py │ ├── config.py │ ├── loaders/ │ │ ├── __init__.py │ │ ├── json_loader.py │ │ ├── loader.py │ │ ├── python_loader.py │ │ └── yaml_loader.py │ ├── resource_loader.py │ ├── tests/ │ │ ├── __init__.py │ │ ├── loader_tests.py │ │ ├── resources/ │ │ │ ├── settings/ │ │ │ │ ├── dummy_config.json │ │ │ │ ├── dummy_config.py │ │ │ │ ├── dummy_config.yml │ │ │ │ └── eucjp_config.json │ │ │ └── translations/ │ │ │ ├── bar/ │ │ │ │ └── baz.en.json │ │ │ ├── en.json │ │ │ ├── foo.en.yml │ │ │ ├── foo.ja.yml │ │ │ ├── gb.json │ │ │ ├── ja.json │ │ │ └── nested_dict_json/ │ │ │ ├── en.json │ │ │ └── pl.json │ │ ├── run_tests.py │ │ └── translation_tests.py │ ├── translations.py │ └── translator.py ├── pyproject.toml ├── requirements/ │ ├── base.txt │ ├── dev.txt │ └── test.txt ├── requirements.txt └── setup.py
SYMBOL INDEX (90 symbols across 11 files)
FILE: i18n/config.py
function set (line 32) | def set(key, value):
function get (line 36) | def get(key):
FILE: i18n/loaders/json_loader.py
class JsonLoader (line 6) | class JsonLoader(Loader):
method __init__ (line 9) | def __init__(self):
method parse_file (line 12) | def parse_file(self, file_content):
FILE: i18n/loaders/loader.py
class I18nFileLoadError (line 6) | class I18nFileLoadError(Exception):
method __init__ (line 7) | def __init__(self, value):
method __str__ (line 10) | def __str__(self):
class Loader (line 14) | class Loader(object):
method __init__ (line 17) | def __init__(self):
method _load_file_data (line 21) | def _load_file_data(self, filename):
method load_file (line 30) | def load_file(self, filename):
method parse_file (line 39) | def parse_file(self, file_content):
method check_data (line 46) | def check_data(self, data, root_data):
method get_data (line 49) | def get_data(self, data, root_data):
method load_resource (line 52) | def load_resource(self, filename, root_data):
FILE: i18n/loaders/python_loader.py
class PythonLoader (line 7) | class PythonLoader(Loader):
method __init__ (line 10) | def __init__(self):
method load_file (line 13) | def load_file(self, filename):
method parse_file (line 23) | def parse_file(self, file_content):
method check_data (line 26) | def check_data(self, data, root_data):
method get_data (line 29) | def get_data(self, data, root_data):
FILE: i18n/loaders/yaml_loader.py
class YamlLoader (line 6) | class YamlLoader(Loader):
method __init__ (line 9) | def __init__(self):
method parse_file (line 12) | def parse_file(self, file_content):
FILE: i18n/resource_loader.py
function register_loader (line 11) | def register_loader(loader_class, supported_extensions):
function load_resource (line 19) | def load_resource(filename, root_data):
function init_loaders (line 28) | def init_loaders():
function init_python_loader (line 36) | def init_python_loader():
function init_yaml_loader (line 42) | def init_yaml_loader():
function init_json_loader (line 48) | def init_json_loader():
function load_config (line 54) | def load_config(filename):
function get_namespace_from_filepath (line 60) | def get_namespace_from_filepath(filename):
function load_translation_file (line 79) | def load_translation_file(filename, base_directory, locale=config.get("l...
function load_translation_dic (line 87) | def load_translation_dic(dic, namespace, locale):
function load_directory (line 97) | def load_directory(directory, locale=config.get("locale")):
function search_translation (line 106) | def search_translation(key, locale=config.get("locale")):
function recursive_search_dir (line 119) | def recursive_search_dir(
FILE: i18n/tests/loader_tests.py
class TestFileLoader (line 21) | class TestFileLoader(unittest.TestCase):
method setUp (line 22) | def setUp(self):
method test_load_unavailable_extension (line 30) | def test_load_unavailable_extension(self):
method test_register_wrong_loader (line 36) | def test_register_wrong_loader(self):
method test_register_python_loader (line 43) | def test_register_python_loader(self):
method test_register_yaml_loader (line 51) | def test_register_yaml_loader(self):
method test_load_wrong_json_file (line 59) | def test_load_wrong_json_file(self):
method test_load_yaml_file (line 72) | def test_load_yaml_file(self):
method test_load_json_file (line 81) | def test_load_json_file(self):
method test_load_python_file (line 89) | def test_load_python_file(self):
method test_memoization_with_file (line 98) | def test_memoization_with_file(self):
method test_load_file_with_strange_encoding (line 131) | def test_load_file_with_strange_encoding(self):
method test_get_namespace_from_filepath_with_filename (line 140) | def test_get_namespace_from_filepath_with_filename(self):
method test_get_namespace_from_filepath_without_filename (line 150) | def test_get_namespace_from_filepath_without_filename(self):
method test_load_translation_file (line 162) | def test_load_translation_file(self):
method test_load_plural (line 172) | def test_load_plural(self):
method test_search_translation_yaml (line 185) | def test_search_translation_yaml(self):
method test_search_translation_json (line 192) | def test_search_translation_json(self):
method test_search_translation_without_ns (line 200) | def test_search_translation_without_ns(self):
method test_search_translation_without_ns_nested_dict__two_levels_neting__default_locale (line 208) | def test_search_translation_without_ns_nested_dict__two_levels_neting_...
method test_search_translation_without_ns_nested_dict__two_levels_neting__other_locale (line 225) | def test_search_translation_without_ns_nested_dict__two_levels_neting_...
method test_search_translation_without_ns_nested_dict__default_locale (line 242) | def test_search_translation_without_ns_nested_dict__default_locale(self):
method test_search_translation_without_ns_nested_dict__other_locale (line 257) | def test_search_translation_without_ns_nested_dict__other_locale(self):
FILE: i18n/tests/run_tests.py
function suite (line 7) | def suite():
FILE: i18n/tests/translation_tests.py
class TestTranslationFormat (line 18) | class TestTranslationFormat(unittest.TestCase):
method setUpClass (line 20) | def setUpClass(cls):
method setUp (line 40) | def setUp(self):
method test_basic_translation (line 46) | def test_basic_translation(self):
method test_missing_translation (line 49) | def test_missing_translation(self):
method test_missing_translation_error (line 52) | def test_missing_translation_error(self):
method test_locale_change (line 57) | def test_locale_change(self):
method test_fallback (line 61) | def test_fallback(self):
method test_fallback_from_resource (line 65) | def test_fallback_from_resource(self):
method test_basic_placeholder (line 69) | def test_basic_placeholder(self):
method test_missing_placehoder (line 72) | def test_missing_placehoder(self):
method test_missing_placeholder_error (line 75) | def test_missing_placeholder_error(self):
method test_basic_pluralization (line 80) | def test_basic_pluralization(self):
method test_full_pluralization (line 85) | def test_full_pluralization(self):
method test_bad_pluralization (line 91) | def test_bad_pluralization(self):
method test_default (line 98) | def test_default(self):
method test_skip_locale_root_data (line 101) | def test_skip_locale_root_data(self):
method test_skip_locale_root_data_nested_json_dict__default_locale (line 110) | def test_skip_locale_root_data_nested_json_dict__default_locale(self):
method test_skip_locale_root_data_nested_json_dict__other_locale (line 122) | def test_skip_locale_root_data_nested_json_dict__other_locale(self):
FILE: i18n/translations.py
function add (line 6) | def add(key, value, locale=config.get("locale")):
function has (line 10) | def has(key, locale=config.get("locale")):
function get (line 14) | def get(key, locale=config.get("locale")):
FILE: i18n/translator.py
class TranslationFormatter (line 6) | class TranslationFormatter(Template):
method __init__ (line 9) | def __init__(self, template):
method format (line 12) | def format(self, **kwargs):
function t (line 19) | def t(key, **kwargs):
function translate (line 37) | def translate(key, **kwargs):
function pluralize (line 45) | def pluralize(key, translation, count):
Condensed preview — 38 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (45K chars).
[
{
"path": ".github/workflows/ci.yml",
"chars": 777,
"preview": "name: CI\n\non:\n push:\n branches: [master]\n pull_request:\n branches: [master]\n\njobs:\n build:\n runs-on: ubuntu-"
},
{
"path": ".gitignore",
"chars": 109,
"preview": "__pycache__/\n*.egg-info/\nbuild/\n*.pyc\n*~\ndist/\n.python-version\n.idea/\n.pytest_cache/\n*.lock\n.ruff_cache\n.venv"
},
{
"path": ".travis.yml",
"chars": 272,
"preview": "language: python\npython:\n - \"2.7\"\n - \"3.6\"\n - \"3.7\"\n - \"3.8\"\n# command to install dependencies\ninstall: \"pip install"
},
{
"path": "LICENSE",
"chars": 1056,
"preview": "Copyright (c) 2012 Daniel Perez\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this so"
},
{
"path": "MANIFEST.in",
"chars": 223,
"preview": "include *.md\ninclude LICENSE\ninclude pyproject.toml\ninclude setup.py\nrecursive-include requirements *.txt\nrecursive-incl"
},
{
"path": "README.md",
"chars": 4761,
"preview": "# python-i18n [](https://githu"
},
{
"path": "i18n/__init__.py",
"chars": 388,
"preview": "from . import config, resource_loader\nfrom .resource_loader import I18nFileLoadError as I18nFileLoadError\nfrom .resource"
},
{
"path": "i18n/config.py",
"chars": 850,
"preview": "try:\n __import__(\"yaml\")\n yaml_available = True\nexcept ImportError:\n yaml_available = False\n\ntry:\n __import_"
},
{
"path": "i18n/loaders/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "i18n/loaders/json_loader.py",
"chars": 392,
"preview": "import json\n\nfrom .loader import I18nFileLoadError, Loader\n\n\nclass JsonLoader(Loader):\n \"\"\"class to load yaml files\"\""
},
{
"path": "i18n/loaders/loader.py",
"chars": 1900,
"preview": "import io\n\nfrom .. import config\n\n\nclass I18nFileLoadError(Exception):\n def __init__(self, value):\n self.value"
},
{
"path": "i18n/loaders/python_loader.py",
"chars": 806,
"preview": "import os.path\nimport sys\n\nfrom .loader import I18nFileLoadError, Loader\n\n\nclass PythonLoader(Loader):\n \"\"\"class to l"
},
{
"path": "i18n/loaders/yaml_loader.py",
"chars": 471,
"preview": "import yaml\n\nfrom .loader import I18nFileLoadError, Loader\n\n\nclass YamlLoader(Loader):\n \"\"\"class to load yaml files\"\""
},
{
"path": "i18n/resource_loader.py",
"chars": 4473,
"preview": "import os.path\n\nfrom . import config, translations\nfrom .loaders.loader import I18nFileLoadError\n\nloaders = {}\n\nPLURALS "
},
{
"path": "i18n/tests/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "i18n/tests/loader_tests.py",
"chars": 12097,
"preview": "# -*- encoding: utf-8 -*-\n\nfrom __future__ import unicode_literals\n\nimport os\nimport os.path\nimport tempfile\nimport unit"
},
{
"path": "i18n/tests/resources/settings/dummy_config.json",
"chars": 72,
"preview": "{\n \"settings\" : {\n \"foo\": \"bar\",\n \"baz\": \"qux\"\n }\n}\n"
},
{
"path": "i18n/tests/resources/settings/dummy_config.py",
"chars": 40,
"preview": "settings = {\"foo\": \"bar\", \"baz\": \"qux\"}\n"
},
{
"path": "i18n/tests/resources/settings/dummy_config.yml",
"chars": 32,
"preview": "settings:\n foo: bar\n baz: qux\n"
},
{
"path": "i18n/tests/resources/settings/eucjp_config.json",
"chars": 45,
"preview": "{\n \"settings\": {\n \"ۤ\": \"ۥ\"\n }\n}\n"
},
{
"path": "i18n/tests/resources/translations/bar/baz.en.json",
"chars": 44,
"preview": "{\n \"en\": {\n \"qux\": \"hoge\"\n }\n}\n"
},
{
"path": "i18n/tests/resources/translations/en.json",
"chars": 38,
"preview": "{\n \"en\": {\n \"foo\": \"FooBar\"\n }\n}\n"
},
{
"path": "i18n/tests/resources/translations/foo.en.yml",
"chars": 229,
"preview": "en:\n normal_key: normal_value\n parent:\n nested_key: nested_value\n mail_number:\n zero: You do not have any mail."
},
{
"path": "i18n/tests/resources/translations/foo.ja.yml",
"chars": 45,
"preview": "ja:\n normal_key: 普通\n fallback_key: フォールバック\n"
},
{
"path": "i18n/tests/resources/translations/gb.json",
"chars": 20,
"preview": "{\n \"foo\": \"Lorry\"\n}"
},
{
"path": "i18n/tests/resources/translations/ja.json",
"chars": 52,
"preview": "{\n \"ja\": {\n \"foo\": \"ふぉお\",\n \"bar\": \"ばば\"\n }\n}\n"
},
{
"path": "i18n/tests/resources/translations/nested_dict_json/en.json",
"chars": 553,
"preview": "{\n \"TOP_MENU\": {\n \"TOP_BAR\": {\n \"LOGS\": \"Logs\",\n \"LOGS_ITEMS\": {\n \"MAIN\": \"Main process\",\n \""
},
{
"path": "i18n/tests/resources/translations/nested_dict_json/pl.json",
"chars": 579,
"preview": "{\n \"TOP_MENU\": {\n \"TOP_BAR\": {\n \"LOGS\": \"Logi\",\n \"LOGS_ITEMS\": {\n \"MAIN\": \"Główny proces\",\n "
},
{
"path": "i18n/tests/run_tests.py",
"chars": 486,
"preview": "import unittest\n\nfrom i18n.tests.loader_tests import TestFileLoader\nfrom i18n.tests.translation_tests import TestTransla"
},
{
"path": "i18n/tests/translation_tests.py",
"chars": 4815,
"preview": "# -*- encoding: utf-8 -*-\n\nfrom __future__ import unicode_literals\n\nimport os\nimport os.path\nimport unittest\n\n# Python 3"
},
{
"path": "i18n/translations.py",
"chars": 307,
"preview": "from . import config\n\ncontainer = {}\n\n\ndef add(key, value, locale=config.get(\"locale\")):\n container.setdefault(locale"
},
{
"path": "i18n/translator.py",
"chars": 2398,
"preview": "from string import Template\n\nfrom . import config, resource_loader, translations\n\n\nclass TranslationFormatter(Template):"
},
{
"path": "pyproject.toml",
"chars": 856,
"preview": "[build-system]\nrequires = [\"setuptools>=61.0\", \"wheel\"]\nbuild-backend = \"setuptools.build_meta\"\n\n[project]\nname = \"pytho"
},
{
"path": "requirements/base.txt",
"chars": 13,
"preview": "pyyaml>=3.10\n"
},
{
"path": "requirements/dev.txt",
"chars": 22,
"preview": "-r base.txt\ncoveralls\n"
},
{
"path": "requirements/test.txt",
"chars": 18,
"preview": "-r dev.txt\npytest\n"
},
{
"path": "requirements.txt",
"chars": 23,
"preview": "pyyaml>=3.10\ncoveralls\n"
},
{
"path": "setup.py",
"chars": 1014,
"preview": "from setuptools import setup # type: ignore\n\nsetup(\n name=\"python-i18n\",\n version=\"0.3.9\",\n description=\"Trans"
}
]
About this extraction
This page contains the full source code of the danhper/python-i18n GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 38 files (39.3 KB), approximately 10.3k tokens, and a symbol index with 90 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.