Skip to main content

Simple internationalization (i18n) utilities for Python applications

Project description

kiarina-i18n

Simple internationalization (i18n) utilities for Python applications.

Purpose

kiarina-i18n provides a lightweight and straightforward approach to internationalization in Python applications. It focuses on simplicity and predictability, avoiding complex grammar rules or plural forms. For applications requiring advanced features like plural forms or complex localization, consider using established tools like gettext.

Installation

pip install kiarina-i18n

Quick Start

Basic Usage (Functional API)

from kiarina.i18n import get_translator, settings_manager

# Configure the catalog
settings_manager.user_config = {
    "catalog": {
        "en": {
            "app.greeting": {
                "hello": "Hello, $name!",
                "goodbye": "Goodbye!"
            }
        },
        "ja": {
            "app.greeting": {
                "hello": "こんにちは、$name!",
                "goodbye": "さようなら!"
            }
        }
    }
}

# Get a translator
t = get_translator("ja", "app.greeting")

# Translate with template variables
print(t("hello", name="World"))  # Output: こんにちは、World!
print(t("goodbye"))  # Output: さようなら!

Type-Safe Class-Based API (Recommended)

For better type safety and IDE support, use the class-based API:

from kiarina.i18n import I18n, get_i18n, settings_manager

# Define your i18n class with explicit scope
class AppI18n(I18n, scope="app.greeting"):
    hello: str = "Hello, $name!"
    goodbye: str = "Goodbye!"
    welcome: str = "Welcome to our app!"

# Or let scope be auto-generated from module.class_name
# If defined in my_app/i18n.py, scope will be: my_app.i18n.UserProfileI18n
class UserProfileI18n(I18n):
    name: str = "Name"
    email: str = "Email"
    bio: str = "Biography"

# Configure the catalog
settings_manager.user_config = {
    "catalog": {
        "ja": {
            "app.greeting": {
                "hello": "こんにちは、$name!",
                "goodbye": "さようなら!",
                "welcome": "アプリへようこそ!"
            }
        }
    }
}

# Get translated instance
t = get_i18n(AppI18n, "ja")

# Access translations with full type safety and IDE completion
print(t.hello)     # Output: こんにちは、$name!
print(t.goodbye)   # Output: さようなら!
print(t.welcome)   # Output: アプリへようこそ!

# Template variables are handled by the functional API
from kiarina.i18n import get_translator
translator = get_translator("ja", "app.greeting")
print(translator("hello", name="World"))  # Output: こんにちは、World!

Benefits of Class-Based API:

  • Type Safety: IDE detects typos in field names
  • Auto-completion: IDE suggests available translation keys
  • Self-documenting: Class definition serves as documentation
  • Default Values: Explicit fallback values when translation is missing
  • Immutable: Translation instances are frozen and cannot be modified
  • Clean Syntax: Scope is defined at class level, not as a field

Using Catalog File

from kiarina.i18n import get_translator, settings_manager

# Load catalog from YAML file
settings_manager.user_config = {
    "catalog_file": "i18n_catalog.yaml"
}

t = get_translator("en", "app.greeting")
print(t("hello", name="Alice"))

Example i18n_catalog.yaml:

en:
  app.greeting:
    hello: "Hello, $name!"
    goodbye: "Goodbye!"
ja:
  app.greeting:
    hello: "こんにちは、$name!"
    goodbye: "さようなら!"

Translating Pydantic Models

For LLM tool schemas or API documentation, you can translate Pydantic model field descriptions and docstrings:

from pydantic import BaseModel, Field
from kiarina.i18n import translate_pydantic_model, settings_manager

# Define your Pydantic model with English descriptions
class UserInput(BaseModel):
    """User input model for registration."""
    
    name: str = Field(description="User's full name")
    age: int = Field(description="User's age in years")
    email: str = Field(description="User's email address")

# Configure translations (including __doc__)
settings_manager.user_config = {
    "catalog": {
        "ja": {
            "user.input.fields": {
                "__doc__": "ユーザー登録用の入力モデル。",
                "name": "ユーザーのフルネーム",
                "age": "ユーザーの年齢(年単位)",
                "email": "ユーザーのメールアドレス",
            }
        }
    }
}

# Create translated model
UserInputJa = translate_pydantic_model(UserInput, "ja", "user.input.fields")

# Use in LLM tool definitions
from langchain.tools import tool

@tool(args_schema=UserInputJa)
def register_user(name: str, age: int, email: str) -> str:
    """ユーザーを登録します"""
    return f"Registered: {name}"

# The tool schema will have Japanese descriptions
schema = UserInputJa.model_json_schema()
print(UserInputJa.__doc__)  # "ユーザー登録用の入力モデル。"
print(schema["properties"]["name"]["description"])  # "ユーザーのフルネーム"

Note: If you're using I18n subclass for translation definitions, you can omit the scope parameter in translate_pydantic_model():

from kiarina.i18n import I18n

# With explicit scope
class UserInputI18n(I18n, scope="user.input.fields"):
    name: str = "User's full name"
    age: str = "User's age in years"
    email: str = "User's email address"

# Translate I18n class (scope is automatically detected from I18n._scope)
UserInputI18nJa = translate_pydantic_model(UserInputI18n, "ja")

# Or with auto-generated scope (from module.class_name)
# If defined in my_app/models.py, scope will be: my_app.models.UserInputI18n
class UserInputI18n(I18n):  # No scope parameter
    name: str = "User's full name"
    age: str = "User's age in years"

# Configure catalog with auto-generated scope
settings_manager.user_config = {
    "catalog": {
        "ja": {
            "my_app.models.UserInputI18n": {  # Use auto-generated scope
                "name": "ユーザーのフルネーム",
                "age": "ユーザーの年齢(年単位)",
            }
        }
    }
}

# Translate with auto-detected scope
UserInputI18nJa = translate_pydantic_model(UserInputI18n, "ja")

API Reference

Class-Based API

I18n

Base class for defining i18n translations with type safety.

Usage:

from kiarina.i18n import I18n

# Explicit scope
class MyI18n(I18n, scope="my.module"):
    title: str = "Default Title"
    description: str = "Default Description"

# Auto-generated scope (from module.class_name)
# If defined in my_app/i18n.py, scope will be: my_app.i18n.UserProfileI18n
class UserProfileI18n(I18n):
    name: str = "Name"
    email: str = "Email"

Features:

  • Immutable: Instances are frozen and cannot be modified
  • Type-safe: Full type hints and validation
  • Self-documenting: Field names are translation keys, field values are defaults
  • Clean Syntax: Scope is defined at class level using inheritance parameter
  • Auto-scope: Automatically generates scope from module and class name if not provided

get_i18n(i18n_class: type[T], language: str) -> T

Get a translated i18n instance.

Parameters:

  • i18n_class: I18n class to instantiate (not instance!)
  • language: Target language code (e.g., "en", "ja")

Returns:

  • Translated i18n instance with all fields translated

Example:

from kiarina.i18n import I18n, get_i18n

class AppI18n(I18n, scope="app"):
    title: str = "My App"

t = get_i18n(AppI18n, "ja")
print(t.title)  # Translated title

Pydantic Model Translation

translate_pydantic_model(model: type[T], language: str, scope: str | None = None) -> type[T]

Translate Pydantic model field descriptions.

Parameters:

  • model: Pydantic model class to translate
  • language: Target language code (e.g., "ja", "en")
  • scope: Translation scope (e.g., "hoge.fields"). Optional if model is an I18n subclass (automatically uses model._scope)

Returns:

  • New model class with translated field descriptions

Example:

from pydantic import BaseModel, Field
from kiarina.i18n import I18n, translate_pydantic_model

# With explicit scope (for regular BaseModel)
class Hoge(BaseModel):
    name: str = Field(description="Your Name")

HogeJa = translate_pydantic_model(Hoge, "ja", "hoge.fields")

# With I18n subclass (scope auto-detected)
class HogeI18n(I18n, scope="hoge.fields"):
    name: str = "Your Name"

HogeI18nJa = translate_pydantic_model(HogeI18n, "ja")  # scope is optional

Cache Management

clear_cache() -> None

Clear all i18n-related caches.

This function clears the internal caches used by get_catalog() and get_translator(). Useful when you need to reload configuration or reset state during testing.

Example:

from kiarina.i18n import clear_cache, settings_manager

# Change settings
settings_manager.user_config = {"catalog": {...}}

# Clear caches to apply new settings
clear_cache()

Functional API

get_catalog() -> Catalog

Get the translation catalog from settings.

This function is cached to avoid loading the catalog multiple times. It can be used independently for custom translation logic or direct catalog access.

Returns:

  • Catalog: Translation catalog loaded from file or settings

Example:

from kiarina.i18n import get_catalog, settings_manager

# Configure catalog
settings_manager.user_config = {
    "catalog": {
        "en": {"app.greeting": {"hello": "Hello!"}},
        "ja": {"app.greeting": {"hello": "こんにちは!"}}
    }
}

# Get catalog
catalog = get_catalog()
print(catalog["en"]["app.greeting"]["hello"])  # "Hello!"

# Use for custom translation logic
def custom_translate(lang: str, scope: str, key: str) -> str:
    return catalog.get(lang, {}).get(scope, {}).get(key, "")

print(custom_translate("ja", "app.greeting", "hello"))  # "こんにちは!"

get_translator(language: str, scope: str) -> Translator

Get a translator for the specified language and scope.

Parameters:

  • language: Target language code (e.g., "en", "ja", "fr")
  • scope: Translation scope (e.g., "app.greeting", "app.error")

Returns:

  • Translator: Translator instance configured for the specified language and scope

Example:

t = get_translator("ja", "app.greeting")

Translator(catalog, language, scope, fallback_language="en")

Translator class for internationalization support.

Parameters:

  • catalog: Translation catalog mapping languages to scopes to keys to translations
  • language: Target language for translation
  • scope: Scope for translation keys
  • fallback_language: Fallback language when translation is not found (default: "en")

Methods:

  • __call__(key, default=None, **kwargs): Translate a key with optional template variables

Example:

from kiarina.i18n import Translator

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

t = Translator(catalog=catalog, language="ja", scope="app.greeting")
print(t("hello", name="World"))  # Output: こんにちは、World!

Translation Behavior

  1. Primary lookup: Searches for the key in the target language
  2. Fallback lookup: If not found, searches in the fallback language
  3. Default value: If still not found, uses the provided default value
  4. Error handling: If no default is provided, returns "{scope}#{key}" and logs an error

Configuration

Using pydantic-settings-manager

# config.yaml
kiarina.i18n:
  default_language: "en"
  catalog:
    en:
      app.greeting:
        hello: "Hello, $name!"
    ja:
      app.greeting:
        hello: "こんにちは、$name!"
from pydantic_settings_manager import load_user_configs
import yaml

with open("config.yaml") as f:
    config = yaml.safe_load(f)

load_user_configs(config)

Settings Fields

  • default_language (str): Default language to use when translation is not found (default: "en")
  • catalog_file (str | None): Path to YAML file containing translation catalog
  • catalog (dict): Translation catalog mapping languages to scopes to keys to translations

Testing

# Run tests
pytest

# Run tests with coverage
pytest --cov=kiarina.i18n --cov-report=html

Dependencies

  • pydantic>=2.0.0
  • pydantic-settings>=2.0.0
  • pydantic-settings-manager>=2.3.0
  • pyyaml>=6.0.0

License

This project is licensed under the MIT License - see the LICENSE file for details.

Related Projects

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-1.18.2.tar.gz (15.2 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-1.18.2-py3-none-any.whl (14.5 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: kiarina_i18n-1.18.2.tar.gz
  • Upload date:
  • Size: 15.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for kiarina_i18n-1.18.2.tar.gz
Algorithm Hash digest
SHA256 2e0c0d17a30254428bc749d2335fc02338e4368201dfef4b5b6a3f85f79938f4
MD5 898417591c49ed018f83762cd1925682
BLAKE2b-256 fd4876192fe8b564211942e007f1feac755400f711a672d85a85e038da0acef6

See more details on using hashes here.

File details

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

File metadata

  • Download URL: kiarina_i18n-1.18.2-py3-none-any.whl
  • Upload date:
  • Size: 14.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for kiarina_i18n-1.18.2-py3-none-any.whl
Algorithm Hash digest
SHA256 c6a88d6ecb4c1c6751fdc754aef58d753ad80476578bd41b42b74928116247e5
MD5 13652529312ae3c286303c2aa2f59d0d
BLAKE2b-256 4d094962bcab2315ca06085a585001908d4af90c4474dfd94049d80ea9a14513

See more details on using hashes here.

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