Smart locale fallback chains for python-i18n -- because pt-BR users deserve pt-PT, not English.
The python-i18n library supports a single fallback locale. When a translation key is missing in the active locale, it jumps directly to the fallback (typically "en"). There is no intermediate fallback.
Example: Your app sets locale = "pt-BR". You have pt-PT translations but no pt-BR locale files. python-i18n skips pt-PT entirely and shows the English fallback.
The same thing happens with es-MX -> es, fr-CA -> fr, de-AT -> de, and every other regional variant.
Your users see English when a perfectly good translation exists in a sibling locale.
One function call. Zero changes to your existing translation code.
python-i18n-locale-chain wraps i18n.t() with a chain-aware resolver that walks a sequence of fallback locales before reaching the default language. Your existing translation calls just work:
i18n.t('greeting')-- looks up the active locale, then its fallback chain, then the default localei18n.t('greeting', locale='pt-BR')-- walks the pt-BR chain: pt-PT -> pt -> eni18n.t('items', count=5)-- kwargs are passed through to the resolved translation
pip install python-i18n-locale-chainpip install python-i18n-locale-chainfrom i18n_locale_chain import configure
configure()import i18n
i18n.set("file_format", "json")
i18n.load_path.append("translations/")
# Chain fallback is automatic -- pt-BR falls back to pt-PT, then pt, then en
result = i18n.t("greeting", locale="pt-BR")That's it. All 75 default fallback chains are active. A pt-BR user will now see pt-PT translations when pt-BR is not available.
A runnable example is included in the example/ directory. It demonstrates fallback resolution for pt-BR with three translation files of decreasing coverage:
pt-BR.jsonhas onlygreetingpt.jsonhasgreetingandfarewellen.jsonhas all three keys
pip install -e .
cd example
python main.pyOutput:
greeting: Oi # resolved from pt-BR (direct match)
farewell: Adeus # fell back to pt (pt-BR -> pt-PT -> pt)
welcome: Welcome to LocaleChain # fell back to en (pt-BR -> pt-PT -> pt -> en)
Just call configure(). Uses all 75 built-in fallback chains covering Chinese, Portuguese, Spanish, French, German, Italian, Dutch, English, Arabic, Norwegian, and Malay regional variants.
from i18n_locale_chain import configure
configure()from i18n_locale_chain import configure
# Merge overrides on top of defaults
configure(overrides={
"pt-BR": ["pt"], # Skip pt-PT, go straight to pt
"ja-JP": ["ja"], # Add a new chain
})from i18n_locale_chain import configure
# Full custom map, merged with defaults
configure(fallbacks={"ja-JP": ["ja"]})
# Full custom map, no defaults
configure(fallbacks={"pt-BR": ["pt-PT"]}, merge_defaults=False)from i18n_locale_chain import configure
# Use German as the final fallback instead of English
configure(default_locale="de")from i18n_locale_chain import reset
# Remove chain-aware wrapper and restore original i18n.t()
reset()Activate chain-aware translation lookup.
| Parameter | Type | Default | Description |
|---|---|---|---|
overrides |
dict | None |
None |
Additional or replacement chains merged on top of defaults |
fallbacks |
dict | None |
None |
A complete fallback map |
merge_defaults |
bool |
True |
Whether to include defaults when fallbacks is supplied |
default_locale |
str |
"en" |
The locale to try as a last resort |
Calling conventions:
configure()-- use DEFAULT_FALLBACKS with"en"as final fallback.configure(overrides={...})-- merge overrides on top of defaults.configure(fallbacks={...}, merge_defaults=False)-- use only the supplied fallbacks, ignoring defaults entirely.
Restore the original i18n.t and clear chain state. Safe to call multiple times.
A dict[str, list[str]] containing all 75 built-in fallback chains. Importable for inspection or as a base for custom maps.
Merge two fallback maps, returning a new dict. Entries in overrides replace same-key entries in base. Neither input is mutated.
| Locale | Fallback Chain |
|---|---|
| zh-Hant-HK | zh-Hant-TW -> zh-Hant -> (default) |
| zh-Hant-MO | zh-Hant-HK -> zh-Hant-TW -> zh-Hant -> (default) |
| zh-Hant-TW | zh-Hant -> (default) |
| Locale | Fallback Chain |
|---|---|
| zh-Hans-SG | zh-Hans -> (default) |
| zh-Hans-MY | zh-Hans -> (default) |
| Locale | Fallback Chain |
|---|---|
| pt-BR | pt-PT -> pt -> (default) |
| pt-PT | pt -> (default) |
| pt-AO | pt-PT -> pt -> (default) |
| pt-MZ | pt-PT -> pt -> (default) |
| Locale | Fallback Chain |
|---|---|
| es-419 | es -> (default) |
| es-MX | es-419 -> es -> (default) |
| es-AR | es-419 -> es -> (default) |
| es-CO | es-419 -> es -> (default) |
| es-CL | es-419 -> es -> (default) |
| es-PE | es-419 -> es -> (default) |
| es-VE | es-419 -> es -> (default) |
| es-EC | es-419 -> es -> (default) |
| es-GT | es-419 -> es -> (default) |
| es-CU | es-419 -> es -> (default) |
| es-BO | es-419 -> es -> (default) |
| es-DO | es-419 -> es -> (default) |
| es-HN | es-419 -> es -> (default) |
| es-PY | es-419 -> es -> (default) |
| es-SV | es-419 -> es -> (default) |
| es-NI | es-419 -> es -> (default) |
| es-CR | es-419 -> es -> (default) |
| es-PA | es-419 -> es -> (default) |
| es-UY | es-419 -> es -> (default) |
| es-PR | es-419 -> es -> (default) |
| Locale | Fallback Chain |
|---|---|
| fr-CA | fr -> (default) |
| fr-BE | fr -> (default) |
| fr-CH | fr -> (default) |
| fr-LU | fr -> (default) |
| fr-MC | fr -> (default) |
| fr-SN | fr -> (default) |
| fr-CI | fr -> (default) |
| fr-ML | fr -> (default) |
| fr-CM | fr -> (default) |
| fr-MG | fr -> (default) |
| fr-CD | fr -> (default) |
| Locale | Fallback Chain |
|---|---|
| de-AT | de -> (default) |
| de-CH | de -> (default) |
| de-LU | de -> (default) |
| de-LI | de -> (default) |
| Locale | Fallback Chain |
|---|---|
| it-CH | it -> (default) |
| Locale | Fallback Chain |
|---|---|
| nl-BE | nl -> (default) |
| Locale | Fallback Chain |
|---|---|
| en-GB | en -> (default) |
| en-AU | en-GB -> en -> (default) |
| en-NZ | en-AU -> en-GB -> en -> (default) |
| en-IN | en-GB -> en -> (default) |
| en-CA | en -> (default) |
| en-ZA | en-GB -> en -> (default) |
| en-IE | en-GB -> en -> (default) |
| en-SG | en-GB -> en -> (default) |
| Locale | Fallback Chain |
|---|---|
| ar-SA | ar -> (default) |
| ar-EG | ar -> (default) |
| ar-AE | ar -> (default) |
| ar-MA | ar -> (default) |
| ar-DZ | ar -> (default) |
| ar-IQ | ar -> (default) |
| ar-KW | ar -> (default) |
| ar-QA | ar -> (default) |
| ar-BH | ar -> (default) |
| ar-OM | ar -> (default) |
| ar-JO | ar -> (default) |
| ar-LB | ar -> (default) |
| ar-TN | ar -> (default) |
| ar-LY | ar -> (default) |
| ar-SD | ar -> (default) |
| ar-YE | ar -> (default) |
| Locale | Fallback Chain |
|---|---|
| nb | no -> (default) |
| nn | nb -> no -> (default) |
| Locale | Fallback Chain |
|---|---|
| ms-MY | ms -> (default) |
| ms-SG | ms -> (default) |
| ms-BN | ms -> (default) |
configure()saves a reference to the originali18n.t()function and replaces it with a chain-aware wrapper.- When
i18n.t("key", locale="pt-BR")is called, the wrapper first tries the requested locale directly. - If the key is missing, it walks the fallback chain for that locale (e.g.,
pt-PT->pt). - If no chain locale has the key, it tries the configured default locale (e.g.,
"en"). - If the key is still not found, it falls through to the original
i18n.t()behaviour (which produces a missing-key marker). reset()restores the originali18n.t()and clears all chain state.
Is this production-ready?
Yes. The library wraps i18n.t() with a thin chain-aware resolver. It uses python-i18n's public translations.has() API to check key existence and delegates all actual translation to the original i18n.t().
Performance impact?
Minimal. For each call, the wrapper checks translations.has() for the requested locale and then for each fallback in the chain. These are dictionary lookups into python-i18n's in-memory translation store. No file I/O or parsing happens during lookup.
Does it work with YAML and JSON translation files? Yes. This library operates on python-i18n's in-memory translation store, which is populated from YAML, JSON, or programmatically added translations. Any format that python-i18n supports will work.
Can I use a non-English default locale?
Yes. Pass default_locale="de" (or any locale) to configure(). The default locale is only used as a last resort when neither the requested locale nor any fallback in its chain has the key.
Can I deactivate it?
Yes. Call reset() to restore the original i18n.t() and remove all chain configuration.
Does it work with python-i18n's built-in fallback?
Yes. The chain-aware wrapper temporarily disables python-i18n's own fallback setting during each lookup to prevent conflicts, then restores it. This ensures the chain order is respected without interfering with python-i18n's internals.
Minimum Python version? Python 3.8. No additional dependencies beyond python-i18n itself.
- Open issues for bugs or feature requests.
- PRs welcome, especially for adding new locale fallback chains.
- Run tests with:
pytest
MIT License - see LICENSE file.
Built by i18nagent.ai