
Python i18n: The Complete Localization Guide
Set up python-i18n with JSON or YAML translation files, handle placeholders and plurals, then automate translations with AI.
Install python-i18n
python-i18n is a lightweight internationalization library for Python. It supports JSON and YAML translation files, nested keys, placeholder interpolation, and pluralization out of the box.
pip install python-i18n# To use YAML translation files instead of JSON:
pip install python-i18n[YAML]Configure Translations
Set the file format, add translation file paths, and configure your default and fallback locales. Import this configuration at your application's entry point before any translation calls.
import i18n
# Set the file format (json or yaml)
i18n.set("file_format", "json")
# Add the directory containing your translation files
i18n.load_path.append("translations/")
# Set the default locale
i18n.set("locale", "en")
# Set the fallback locale (used when a key is missing)
i18n.set("fallback", "en")
# Enable/disable error on missing translations
i18n.set("error_on_missing_translation", False)Create Translation Files
Create one file per language in either JSON or YAML format. Use nested keys to organize strings by feature or page. Keep your source language (usually English) as the single source of truth.
// translations/en.json
{
"greeting": "Hello!",
"welcome": "Welcome to our application",
"nav": {
"home": "Home",
"about": "About",
"settings": "Settings"
},
"cart": {
"item_count": "%{count} item(s) in your cart"
}
}
// translations/de.json
{
"greeting": "Hallo!",
"welcome": "Willkommen in unserer Anwendung",
"nav": {
"home": "Startseite",
"about": "Über uns",
"settings": "Einstellungen"
},
"cart": {
"item_count": "%{count} Artikel in Ihrem Warenkorb"
}
}Use Translations in Your Code
Call i18n.t() with a dot-separated key path to look up translated strings. You can override the locale per-call without changing the global setting.
import i18n
# Simple translation
print(i18n.t("greeting")) # "Hello!"
print(i18n.t("nav.home")) # "Home"
print(i18n.t("nav.about")) # "About"
# Translation with a specific locale
print(i18n.t("greeting", locale="de")) # "Hallo!"
print(i18n.t("nav.home", locale="ja")) # "ホーム"
# Missing key returns a placeholder
print(i18n.t("missing.key")) # "Missing.Key"Placeholders and Pluralization
python-i18n supports placeholder interpolation with the %{name} syntax and basic pluralization using 'zero', 'one', and 'many' sub-keys. Pass keyword arguments to i18n.t() for both features.
# translations/en.json
# {
# "welcome_user": "Welcome, %{name}!",
# "order_status": "Order #%{order_id}: %{status}",
# "file_size": "File size: %{size} %{unit}"
# }
import i18n
# Single placeholder
print(i18n.t("welcome_user", name="Alice"))
# "Welcome, Alice!"
# Multiple placeholders
print(i18n.t("order_status", order_id=12345, status="shipped"))
# "Order #12345: shipped"
# Reusable with different values
print(i18n.t("file_size", size=2.5, unit="MB"))
# "File size: 2.5 MB"
print(i18n.t("file_size", size=800, unit="KB"))
# "File size: 800 KB"# translations/en.json
# {
# "inbox": {
# "zero": "No messages",
# "one": "1 message",
# "many": "%{count} messages"
# }
# }
import i18n
print(i18n.t("inbox", count=0)) # "No messages"
print(i18n.t("inbox", count=1)) # "1 message"
print(i18n.t("inbox", count=42)) # "42 messages"Locale Switching at Runtime
Switch the active locale globally with i18n.set('locale', code) or override per-call with the locale keyword argument. In web frameworks, detect the user's preferred language from the request and set the locale before rendering.
import i18n
# Set locale globally
i18n.set("locale", "de")
print(i18n.t("greeting")) # "Hallo!"
# Switch to Japanese
i18n.set("locale", "ja")
print(i18n.t("greeting")) # "こんにちは!"
# Override per-call without changing global locale
i18n.set("locale", "en")
print(i18n.t("greeting")) # "Hello!"
print(i18n.t("greeting", locale="de")) # "Hallo!"from flask import Flask, request, g
import i18n
app = Flask(__name__)
i18n.set("file_format", "json")
i18n.load_path.append("translations/")
SUPPORTED_LOCALES = ["en", "de", "ja", "es", "fr"]
@app.before_request
def set_locale():
# Check URL parameter, cookie, then Accept-Language header
locale = request.args.get("lang")
if not locale:
locale = request.cookies.get("locale")
if not locale:
locale = request.accept_languages.best_match(SUPPORTED_LOCALES)
g.locale = locale or "en"
i18n.set("locale", g.locale)
@app.route("/")
def index():
return i18n.t("welcome")Smart Locale Fallback with python-i18n-locale-chain
By default, python-i18n supports only a single fallback locale. When a pt-BR user has no pt-BR translations, the library jumps straight to the English fallback, ignoring perfectly good pt-PT translations. python-i18n-locale-chain fixes this with configurable fallback chains covering 75 locale variants.
pip install python-i18n-locale-chainfrom locale_chain import configure
import i18n
i18n.set("file_format", "json")
i18n.load_path.append("translations/")
# Activate smart fallback chains (75 built-in chains)
configure()
# Now pt-BR falls back to pt-PT -> pt -> en (instead of just en)
result = i18n.t("greeting", locale="pt-BR")
# es-MX falls back to es-419 -> es -> en
result = i18n.t("greeting", locale="es-MX")
# zh-Hant-HK falls back to zh-Hant-TW -> zh-Hant -> en
result = i18n.t("greeting", locale="zh-Hant-HK")from locale_chain import configure, reset
# Override specific chains
configure(overrides={
"pt-BR": ["pt"], # Skip pt-PT, go straight to pt
"ja-JP": ["ja"], # Add a new chain
})
# Full custom map (no defaults)
configure(
fallbacks={"pt-BR": ["pt-PT"]},
merge_defaults=False
)
# Use German as final fallback instead of English
configure(default_locale="de")
# Restore original i18n.t() behaviour
reset()Automate Translations
With your i18n setup complete, translate your locale files using AI. In your IDE, ask your AI assistant to translate your source file, or use the i18n Agent CLI in your CI/CD pipeline.
# In your IDE, ask your AI assistant:
> Translate translations/en.json to German, Japanese, and Spanish
translations/de.json created (1.2s)
translations/ja.json created (1.5s)
translations/es.json created (1.1s)
# Or use the CLI in CI/CD:
npx i18n-agent translate translations/en.json --lang de,ja,esAutomate Translation Quality
Common Pitfalls
Translations Return Raw Keys
YAML Files Not Loading
Nested Key Lookups Fail
Locale Changes Leak Between Requests
Recommended File Structure
my-python-app/
├── translations/
│ ├── en.json # Source language (JSON)
│ ├── de.json # German
│ ├── ja.json # Japanese
│ ├── es.json # Spanish
│ └── pt-BR.json # Brazilian Portuguese
├── app.py # Application entry point
├── i18n_config.py # i18n setup and configuration
├── requirements.txt # pip dependencies
└── pyproject.toml # Project metadata
# Or with YAML files:
my-python-app/
├── translations/
│ ├── en.yml
│ ├── de.yml
│ └── ja.yml
├── app.py
└── ...Try i18n Agent Now
Drop your translation file here
JSON, YAML, PO, XML, CSV, Markdown, Properties
or click to browse
Target languages
Locale Fallback with python-i18n-locale-chain
When a translation key is missing in a regional locale like es-419, python-i18n jumps straight to the default locale instead of checking the parent locale es first.
pip install python-i18n-locale-chainfrom i18n_locale_chain import configure_chain
configure_chain('{')
'es': ['en', 'ru'],
'pt-BR': ['pt', 'en'],
'zh-Hant-HK': ['zh-Hant', 'zh', 'en'],
'}')
# Usage: t('greeting', locale='es') — falls back through chainSee our Locale Fallback Guide for the full list of supported frameworks and 75 built-in chains. Learn more →