Internationalization (i18n)¶
Revel supports multiple languages with full translation coverage across API responses, emails, and notifications.
Supported Languages¶
| Code | Language | Style |
|---|---|---|
en | English | Default |
de | German | Informal ("du") |
it | Italian | Informal ("tu") |
fr | French | Informal ("tu") |
How It Works¶
flowchart LR
A[User.language field] --> B[I18nJWTAuth middleware]
B --> C[django.utils.translation.activate]
C --> D[All _() strings resolve in user's language]
D --> E[API response / Email / Notification] - User preference: The
User.languagefield stores each user's language preference - Automatic activation:
I18nJWTAuthreads the user's language and activates it on every authenticated request - Lazy translation: All user-facing strings are wrapped with
gettext_lazy(imported as_) - Email templates: Use
{% trans %}and{% blocktrans %}Django template tags
Developer Workflows¶
Adding a New Language¶
- Add the language to
LANGUAGESin Django settings (and toLANGUAGESinscripts/check_translations.py) - Extract translatable strings:
- Translate the new
.pofile atsrc/locale/{lang}/LC_MESSAGES/django.po(fill everymsgstr; clear everyfuzzyflag) - Commit the
.pofile only —.mois built automatically (see ADR-0011)
Updating Existing Translations¶
# 1. Extract new/changed strings (deterministic: no POT-Creation-Date churn)
make makemessages
# 2. Translate new strings in .po files
# (look for empty msgstr entries and `fuzzy` markers)
# 3. Verify the catalog is complete (keys extracted + translated)
make i18n-check
# 4. Commit the .po files (no .mo — they are git-ignored, built in the image)
git add src/locale/
git commit -m "chore(i18n): update translations"
Don't commit .mo files
Compiled .mo binaries are git-ignored and built during the Docker image build (and before tests). Only the .po sources are committed. See ADR-0011.
File Structure¶
src/locale/
├── de/
│ └── LC_MESSAGES/
│ └── django.po # German translations (source; .mo built, not committed)
├── it/
│ └── LC_MESSAGES/
│ └── django.po # Italian translations (source; .mo built, not committed)
└── fr/
└── LC_MESSAGES/
└── django.po # French translations (source; .mo built, not committed)
No English locale directory
English is the source language. Translatable strings are written in English directly in the code, so there is no en/ locale directory.
Rules & Best Practices¶
Do
- Always use
I18nJWTAuthfor authenticated endpoints. It activates the user's language automatically - Always wrap user-facing strings with
_() - Always call
str()on lazy translations when used in API responses (e.g., in serializers or plain dicts) - Always translate every new key (no empty
msgstr, nofuzzy) — CI enforces it - Use
.format()for string interpolation with translations:
Don't
- Don't translate log messages or developer-facing content
- Don't hardcode user-facing strings; always use
_() - Don't use f-strings with
_(). The variable parts won't be extracted:
Why .mo Files Are NOT in Git
Revel does not commit compiled .mo files. They are built from the .po sources during the Docker image build (and before tests), while translation completeness is enforced statically on the .po sources by scripts/check_translations.py. See ADR-0011: Build Compiled Translations, Don't Commit Them for the full rationale (it reverts the earlier ADR-0006).
Troubleshooting¶
Translations Not Showing¶
| Check | Fix |
|---|---|
| Catalogs compiled locally | Run make compilemessages (.mo is not committed) |
| User has correct language set | Verify User.language field |
Endpoint uses I18nJWTAuth | Switch from plain JWTAuth to I18nJWTAuth |
String is wrapped with _() | Add the _() wrapper |
| Lazy translation resolved to string | Call str() on the value |
CI Failing on i18n Check¶
The CI pipeline runs make i18n-check (scripts/check_translations.py), which verifies the .po sources are complete — every code string is extracted and every entry is translated (no empty msgstr, no fuzzy). If it fails:
# 1. Extract any missing keys, then translate the empty/fuzzy entries it reports
make makemessages
# ...edit src/locale/{lang}/LC_MESSAGES/django.po...
# 2. Re-check and commit the .po sources
make i18n-check
git add src/locale/
git commit -m "chore(i18n): complete translations"