Translations
Table of contents
Introduction
Your product speaks the language its .env names, and every feature ships the catalogue of its own strings. A translation that goes wrong raises no error. The page renders and the label reads in English, so this page shows where that happens.
BABEL_DEFAULT_LOCALE=es
BABEL_SUPPORTED_LOCALES=es,en
Choosing which language a product speaks
The framework reads both variables, and the order of BABEL_SUPPORTED_LOCALES is the order a language switcher shows. Each request gets
- the reader’s choice from the switcher, when it is supported,
- else your
BABEL_DEFAULT_LOCALE, - else the browser’s
Accept-Language, only withBABEL_NEGOTIATE_FROM_HEADER=true.
The product’s language comes before the browser’s, since a site built for an institution speaks its language. Outside a request, in a mail or a command, the answer is the default.
Writing a translatable string
In templates and request-time code, use _().
from flask_babel import gettext as _
flash(_("Saved %(name)s.", name=page.name), "success")
In a form class, use _l() and nothing else.
from flask_babel import lazy_gettext as _l
class PageForm(FlaskForm):
name = StringField(_l("Title"), validators=[DataRequired()])
A form class is built once, when it is imported, before any request asks for a language. gettext there would freeze the startup language into every label. lazy_gettext resolves when the label renders.
feature:translate --extractknows_,gettext,ngettext,_l,lazy_gettextand_n. Strings behind any other alias never reach a catalogue.
Translating a feature
splent feature:translate option. The two notes below the catalogue are where a translation silently goes missing.splent feature:translate notes --extract # source -> messages.pot
splent feature:translate notes --init es # .pot -> es/messages.po
# edit the .po
splent feature:translate notes --compile # .po -> .mo
--init on a locale that exists merges the new strings and keeps the translated ones.
Reading every fuzzy entry
The merge guesses new strings from similar old ones and marks the guesses #, fuzzy. A fuzzy entry is left out of the compiled catalogue, so it shows in English. The guesses are rarely close, so read each one before clearing the mark.
| Written | Guessed |
|---|---|
Compare |
Curso |
Body |
Por |
History of %(name)s |
Se ha eliminado %(name)s. |
Translating text the product writes
A string your product writes in its settings cannot be in a feature’s catalogue. Write one variant per language in your [tool.splent.config].
SEARCH_PLACEHOLDER = "es:Buscar en esta wiki|en:Search this wiki"
placeholder="{{ localized(config.SEARCH_PLACEHOLDER, _('Search this site')) }}"
localized tries the exact locale, then the base language (pt-BR finds pt), then the first variant. A value without language tags serves every language. A value is split only when every piece carries a language tag Babel knows, so a URL or a time of day is left alone.
Shipping the catalogue
gettext reads the compiled .mo, so commit it and package it. Without it, every release ships untranslated while development looks fine. feature:create sets this up. A feature without it needs this line in MANIFEST.in, and no *.mo line in .gitignore.
recursive-include src/<org>/<feature>/translations *.mo *.po *.pot
Every failure on this page renders a good page in the wrong language. The tests pass and the logs are clean, so look at your pages in each language.