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.

1

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.

Terminal
pip install python-i18n
python-i18n supports JSON by default. For YAML translation files, install the optional YAML dependency with pip install python-i18n[YAML], which adds PyYAML.
Terminal
# To use YAML translation files instead of JSON:
pip install python-i18n[YAML]
2

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.

i18n_config.py
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)
The load_path must point to the directory containing your translation files, not to a specific file. If translations return raw keys, check that your load_path is correct and that file names match your locale codes (e.g., en.json, de.json).
3

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
// 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"
  }
}
Name keys by what they describe, not where they appear: 'cart.item_count' is better than 'homepage_cart_label'. Keys should survive UI redesigns.
4

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.

app.py
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"
Nested keys use dot notation: i18n.t('nav.home'). If your JSON keys contain literal dots, python-i18n will interpret them as nesting separators. Avoid dots in key names.
5

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.

Placeholders
# 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"
Pluralization
# 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"
python-i18n's pluralization uses three categories: zero, one, and many. This covers English and many languages but does not support the full CLDR plural rules (few, two, other). For languages like Arabic, Russian, or Polish with complex plural forms, you may need to handle edge cases manually or use a more advanced library.
6

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.

Locale switching
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!"
app.py
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")
i18n.set('locale', ...) changes the locale globally. In multi-threaded web servers (gunicorn with workers, Django), this can cause race conditions where one request changes the locale while another is mid-render. Use per-call locale overrides or thread-local storage to avoid this.
7

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.

python-i18n-locale-chain is a free, open-source package. One function call activates 75 built-in fallback chains for Chinese, Portuguese, Spanish, French, German, Italian, Dutch, English, Arabic, Norwegian, and Malay regional variants.
Terminal
pip install python-i18n-locale-chain
i18n_config.py
from 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")
Advanced configuration
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()
The most impactful chains to test: pt-BR -> pt-PT -> pt -> en (Portuguese), es-MX -> es-419 -> es -> en (Spanish), zh-Hant-HK -> zh-Hant-TW -> zh-Hant -> en (Chinese Traditional). These cover the most common regional fallback scenarios.
8

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.

Terminal
# 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,es
Translate incrementally. When you add new keys to your source file, translate just the diff rather than regenerating all files. This preserves any human-reviewed translations.

Automate Translation Quality

Catch missing keys and broken placeholders before they ship with i18n-validate. Test your UI with pseudo-translations using i18n-pseudo before real translations arrive.

Common Pitfalls

Translations Return Raw Keys

Causes: load_path not set or pointing to the wrong directory, file_format not matching your file extensions, or file names not matching locale codes. Verify that i18n.load_path contains the correct directory and that files are named correctly (e.g., en.json, de.json).

YAML Files Not Loading

python-i18n requires PyYAML for YAML support, but it is not installed by default. Install with pip install python-i18n[YAML]. Without it, YAML files are silently ignored and translations return missing-key placeholders.

Nested Key Lookups Fail

python-i18n uses dot notation for nested keys: i18n.t('nav.home'). If your JSON uses flat keys with dots in the name (e.g., 'nav.home' as a single key), the library interprets it as a nested lookup and fails. Use actual nested JSON objects instead.

Locale Changes Leak Between Requests

i18n.set('locale', ...) is a global operation. In multi-threaded servers, one request can change the locale while another is rendering. Use the locale= keyword argument on individual i18n.t() calls, or set the locale in thread-local storage with a middleware.

Recommended File Structure

Project 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

No signup requiredInstant estimate

Frequently Asked Questions