Internationalization (i18n)¶
Castella provides a built-in internationalization system for multi-language support with runtime locale switching.
Overview¶
The i18n system consists of:
- I18nManager - Singleton manager for translations and locale switching
- LocaleString - Reactive string that auto-updates UI when locale changes
- t() / tn() - Convenience functions for translation
- Loaders - Load translations from YAML or JSON files
Basic Usage¶
from castella.i18n import I18nManager, load_yaml_catalog
# Get the singleton manager
manager = I18nManager()
# Load translation catalogs
manager.load_catalog("en", load_yaml_catalog("locales/en.yaml"))
manager.load_catalog("ja", load_yaml_catalog("locales/ja.yaml"))
# Set the active locale
manager.set_locale("en")
# Translate a key
text = manager.t("greeting") # "Hello!"
# With string interpolation
text = manager.t("welcome", name="World") # "Hello, World!"
I18nManager Methods¶
| Method | Description |
|---|---|
load_catalog(locale, catalog) |
Load a translation catalog for a locale |
set_locale(locale) |
Set the active locale |
set_fallback_locale(locale) |
Set fallback locale for missing translations |
t(key, **kwargs) |
Translate a key with optional interpolation |
tn(key, count, **kwargs) |
Translate with pluralization |
locale |
Property: current locale |
available_locales |
Property: list of loaded locales |
Loading Translations¶
From YAML Files¶
from castella.i18n import load_yaml_catalog
catalog = load_yaml_catalog("locales/en.yaml")
manager.load_catalog("en", catalog)
From JSON Files¶
from castella.i18n import load_json_catalog
catalog = load_json_catalog("locales/en.json")
manager.load_catalog("en", catalog)
From a Directory¶
from castella.i18n import load_catalogs_from_directory
# Load all .yaml and .json files from a directory
catalogs = load_catalogs_from_directory("locales/")
for locale, catalog in catalogs.items():
manager.load_catalog(locale, catalog)
Programmatically¶
from castella.i18n import create_catalog_from_dict
catalog = create_catalog_from_dict("en", {
"greeting": "Hello!",
"button": {
"save": "Save",
"cancel": "Cancel",
},
})
manager.load_catalog("en", catalog)
LocaleString (Reactive)¶
LocaleString automatically updates the UI when the locale changes. Use it with widgets for reactive translations.
from castella import Text
from castella.i18n import LocaleString
# UI automatically updates when locale changes
Text(LocaleString("dashboard.title"))
# With interpolation
Text(LocaleString("greeting", name="World"))
How It Works¶
LocaleStringobserves theI18nManagerfor locale changes- When locale changes,
LocaleStringnotifies attached widgets - Widgets re-render with the new translation
Comparison: t() vs LocaleString¶
| Use Case | Function |
|---|---|
| Static translation (one-time) | t("key") |
| Reactive translation (auto-update UI) | LocaleString("key") |
# Static - won't update when locale changes
label = t("greeting")
# Reactive - updates automatically
Text(LocaleString("greeting"))
Pluralization¶
Handle pluralized strings that vary based on count.
Using tn()¶
from castella.i18n import tn
# Returns "1 item" or "5 items" based on count and locale
text = tn("items", count=1) # "1 item"
text = tn("items", count=5) # "5 items"
Using LocalePluralString¶
from castella import Text
from castella.i18n import LocalePluralString
# Reactive pluralized string
Text(LocalePluralString("items", count=5))
Defining Plural Forms in YAML¶
# locales/ja.yaml
locale: ja
items:
other: "{count}個のアイテム" # Japanese doesn't distinguish singular/plural
Supported Plural Categories¶
Castella follows CLDR plural rules:
| Category | Description | Example Languages |
|---|---|---|
zero |
Zero quantity | Arabic |
one |
Singular | English, German, French |
two |
Dual | Arabic, Welsh |
few |
Few | Russian, Polish |
many |
Many | Russian, Polish |
other |
Default/Plural | All languages |
Built-in rules for: English, Japanese, Chinese, French, German, Russian.
Translation File Format¶
YAML Format¶
# locales/en.yaml
locale: en
# Simple strings
greeting: "Hello!"
farewell: "Goodbye!"
# Nested keys (accessed as "button.save")
button:
save: "Save"
cancel: "Cancel"
delete: "Delete"
# String interpolation
welcome: "Welcome, {name}!"
# Plural forms
items:
one: "{count} item"
other: "{count} items"
notifications:
one: "You have {count} notification"
other: "You have {count} notifications"
JSON Format¶
{
"locale": "en",
"greeting": "Hello!",
"button": {
"save": "Save",
"cancel": "Cancel"
},
"welcome": "Welcome, {name}!",
"items": {
"one": "{count} item",
"other": "{count} items"
}
}
Accessing Nested Keys¶
Use dot notation to access nested translations:
System Locale Detection¶
I18nManager can auto-detect the system locale:
CASTELLA_LOCALEenvironment variable (highest priority)- System locale (
LC_ALL,LANG) - Default:
"en"
Complete Example¶
from castella import App, Button, Column, Component, Row, Text
from castella.frame import Frame
from castella.i18n import I18nManager, LocaleString, load_yaml_catalog
class I18nDemo(Component):
def __init__(self):
super().__init__()
self._manager = I18nManager()
# Load translations
self._manager.load_catalog("en", load_yaml_catalog("locales/en.yaml"))
self._manager.load_catalog("ja", load_yaml_catalog("locales/ja.yaml"))
self._manager.set_locale("en")
def view(self):
return Column(
# Reactive translation - auto-updates on locale change
Text(LocaleString("greeting")),
Text(LocaleString("welcome", name="Castella")),
Row(
Button(LocaleString("button.save")).on_click(self._save),
Button(LocaleString("button.cancel")),
),
# Language switcher
Row(
Button("English").on_click(lambda _: self._manager.set_locale("en")),
Button("日本語").on_click(lambda _: self._manager.set_locale("ja")),
),
)
def _save(self, _):
print("Saved!")
App(Frame("I18n Demo", 500, 300), I18nDemo()).run()
locales/en.yaml:
locales/ja.yaml: