Skip to main content

Simple internationalization (i18n) utilities for Python applications

Project description

kiarina-i18n

PyPI version Python License: MIT

English | 日本語

[!NOTE] What is this? A package for using translation catalogs from dictionaries or YAML through functions or typed Pydantic models.

Dependencies

Package Version License
Pydantic >=2.0.0 MIT
pydantic-settings >=2.0.0 MIT
pydantic-settings-manager >=3.2.0 MIT
PyYAML >=6.0.0 MIT

Installation

pip install kiarina-i18n

Features

  • Translating Text Retrieve catalog text by language, scope, and key, then substitute template variables.
  • Loading Catalogs Build catalogs from dictionaries, file-system YAML, and package-resource YAML.
  • Defining Typed Translations Define translation keys and default text as fields on a Pydantic model.
  • Translating Pydantic Schemas Translate Pydantic model docstrings and field descriptions by language.

Translating Text

Catalog data is structured as language → scope → key → text.

from kiarina.i18n import catalog, get_translator

catalog.add_from_dict(
    {
        "en": {"app.greeting": {"hello": "Hello, $name!"}},
        "ja": {"app.greeting": {"hello": "こんにちは、$name!"}},
    }
)

translate = get_translator("ja", "app.greeting")
message = translate("hello", name="World")

Translations are searched from the most specific requested language tag through its parent tags, followed by the default language. For example, ja-JP searches ja-JP, ja, then en.

If no translation exists, the translator returns default, or <scope>#<key> when no default is given. Template variables use string.Template.safe_substitute, so variables without supplied values remain unchanged.

Loading Catalogs

Catalog additions are deeply merged. A later value replaces an earlier value with the same language, scope, and key.

from kiarina.i18n import catalog

catalog.add_from_file("translations.yaml")
catalog.add_from_dir("translations")
catalog.add_from_package_file("my_app.catalogs", "ja.yaml")
catalog.add_from_package_dir("my_app.catalogs")

add_from_dir searches child directories. add_from_package_dir searches only the specified package's immediate directory.

When a YAML file is named after a language tag, such as ja.yaml or en-US.yaml, its contents are treated as the catalog for that language.

app.greeting:
  hello: "こんにちは、$name!"

For other file names, declare languages at the top level.

en:
  app.greeting:
    hello: "Hello, $name!"
ja:
  app.greeting:
    hello: "こんにちは、$name!"

Defining Typed Translations

In an I18n subclass, field names are keys and default values are translation fallbacks.

from kiarina.i18n import I18n, catalog, get_i18n


class ProfileText(I18n, scope="app.profile"):
    title: str = "Profile"
    description: str = "Edit your profile."


catalog.add_from_dict(
    {
        "ja": {
            "app.profile": {
                "title": "プロフィール",
                "description": "プロフィールを編集します。",
            }
        }
    }
)

text = get_i18n(ProfileText, "ja")

If scope is omitted, <module>.<class name> is used. If language is omitted from get_i18n, the system language is used.

Regular Pydantic models can also be passed to get_i18n. Their scope is the public module path before the first private module segment.

Translating Pydantic Schemas

translate_pydantic_model creates a translated model class without changing the original model. It translates the model docstring with the __doc__ key and each field description with the corresponding field-name key.

from pydantic import BaseModel, Field

from kiarina.i18n import catalog
from kiarina.i18n_pydantic import translate_pydantic_model


class UserInput(BaseModel):
    name: str = Field(description="Your name")


catalog.add_from_dict(
    {
        "ja": {
            "__main__": {
                "__doc__": "ユーザー入力",
                "name": "名前",
            }
        }
    }
)

JapaneseUserInput = translate_pydantic_model(UserInput, "ja")

Field annotations of list[I18nSubclass] and dict[str, I18nSubclass] recursively translate their inner models.

API Reference

kiarina.i18n

from kiarina.i18n import (
    Catalog,
    I18n,
    I18nKey,
    I18nScope,
    I18nSettings,
    Language,
    Translator,
    catalog,
    get_i18n,
    get_system_language,
    get_translator,
    settings_manager,
)

Translation functions

def get_i18n(
    model_class: type[T],
    language: Language | None = None,
) -> T: ...

def get_system_language() -> Language: ...

def get_translator(
    language: Language,
    scope: I18nScope,
) -> Translator: ...

get_i18n returns an instance with each model field translated. When language is omitted, it uses the result of get_system_language.

get_system_language checks the LANG, LC_ALL, LC_MESSAGES, and LANGUAGE environment variables in that order, followed by locale.getlocale(). It returns en when detection fails or the locale is C / POSIX.

get_translator creates a Translator using the shared catalog and settings_manager.settings.default_language.

Translator

class Translator:
    def __init__(
        self,
        *,
        catalog: Catalog,
        language: Language,
        scope: I18nScope,
        default_language: Language = "en",
    ) -> None: ...

    def __call__(
        self,
        key: I18nKey,
        default: str | None = None,
        **kwargs: Any,
    ) -> str: ...

language and default_language are normalized during initialization. An invalid language tag raises ValueError.

Catalog

class Catalog:
    def __init__(self) -> None: ...

    def add_from_dict(
        self,
        data: dict[
            Language,
            dict[I18nScope, dict[I18nKey, str]],
        ],
    ) -> None: ...

    def add_from_file(self, file_path: str) -> None: ...

    def add_from_dir(self, dir_path: str) -> None: ...

    def add_from_package_file(
        self,
        package: str,
        file_path: str,
    ) -> None: ...

    def add_from_package_dir(self, package: str) -> None: ...

    def clear(self) -> None: ...

    def get_text(
        self,
        language: Language,
        scope: I18nScope,
        key: I18nKey,
    ) -> str | None: ...

add_from_file and add_from_dir read file-system YAML. add_from_package_file and add_from_package_dir read resources from an importable package.

A missing directory raises NotADirectoryError. Missing YAML files or packages raise FileNotFoundError. get_text returns None when no text exists, including when the language tag is invalid.

I18n

class I18n(BaseModel):
    def __init_subclass__(
        cls,
        scope: I18nScope = "",
        **kwargs: Any,
    ) -> None: ...

Subclasses are frozen Pydantic models and reject undefined fields. Define the fields to translate on each subclass.

Settings

class I18nSettings(BaseSettings):
    default_language: Language = "en"

settings_manager: SettingsManager[I18nSettings]

default_language is used when a translation is unavailable. settings_manager is the shared configuration manager provided by pydantic-settings-manager.

Catalog instance

catalog: Catalog

The package shares this catalog instance. Call catalog.clear() to discard state between tests or other isolated operations.

Types

Language: TypeAlias = str
I18nScope: TypeAlias = str
I18nKey: TypeAlias = str

Language represents a BCP 47 language tag, I18nScope a namespace for keys, and I18nKey a translation key within a scope.

kiarina.i18n_pydantic

from kiarina.i18n_pydantic import translate_pydantic_model

translate_pydantic_model

def translate_pydantic_model(
    model: type[T],
    language: str,
) -> type[T]: ...

Returns a new model class with translated docstrings and field descriptions while preserving the original Pydantic model's configuration, base class, module, and field attributes.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

kiarina_i18n-2.3.1.tar.gz (24.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

kiarina_i18n-2.3.1-py3-none-any.whl (13.9 kB view details)

Uploaded Python 3

File details

Details for the file kiarina_i18n-2.3.1.tar.gz.

File metadata

  • Download URL: kiarina_i18n-2.3.1.tar.gz
  • Upload date:
  • Size: 24.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for kiarina_i18n-2.3.1.tar.gz
Algorithm Hash digest
SHA256 bdf241ee9f5e299cd2aa5eb6106f37e0591a14f38c8a09dd9255e7c7c07d2bdd
MD5 f3a59fae59837dab35deb06361c25a1c
BLAKE2b-256 b5e9e3b22ca501c146174b90567cdcc85a784d1d6d705c4def1ae753083169ad

See more details on using hashes here.

Provenance

The following attestation bundles were made for kiarina_i18n-2.3.1.tar.gz:

Publisher: release-pypi.yml on kiarina/kiarina-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file kiarina_i18n-2.3.1-py3-none-any.whl.

File metadata

  • Download URL: kiarina_i18n-2.3.1-py3-none-any.whl
  • Upload date:
  • Size: 13.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for kiarina_i18n-2.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 86a944d84197086518c1a70eaf041944425045ff4a8e6f434c1a233fb7ba3d3b
MD5 80007cb7deb90195b95f28bb4bbb6d9b
BLAKE2b-256 77bb5cebe82c09852e9cd12aadecbec07b61c08da050b9b96db085adcb6c3baa

See more details on using hashes here.

Provenance

The following attestation bundles were made for kiarina_i18n-2.3.1-py3-none-any.whl:

Publisher: release-pypi.yml on kiarina/kiarina-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page