Skip to main content

Flask i18n: bouw een meertalige app met Flask-Babel

Van de basis van gettext tot implementatie in productie: internationaliseer je Flask-app met Flask-Babel, PO-bestanden en geautomatiseerde AI-vertalingen.

1

Flask-Babel installeren

Flask-Babel is de standaardextensie voor internationalisatie in Flask. De extensie integreert GNU gettext met Flask- en Jinja2-sjablonen en biedt direct vertaalfuncties, taalselectie en ondersteuning voor tijdzones.

Flask-Babel verpakt Babel (de Python-bibliotheek voor i18n) en integreert deze met de levenscyclus van Flask-aanvragen. Je krijgt de functies gettext(), ngettext() en lazy_gettext(), plus automatische taalherkenning uit browserheaders.
Terminal
pip install Flask-Babel
2

Babel configureren

Maak een bestand babel.cfg om pybabel te vertellen waar het naar vertaalbare tekenreeksen moet zoeken. Initialiseer Flask-Babel vervolgens met een taalselectiefunctie die bepaalt welke taal voor elke aanvraag wordt aangeboden.

babel.cfg
# babel.cfg — tells pybabel where to find translatable strings
[python: **.py]
[jinja2: **/templates/**.html]
extensions=jinja2.ext.autoescape,jinja2.ext.with_
app.py
from flask import Flask, request
from flask_babel import Babel

app = Flask(__name__)
app.config['BABEL_DEFAULT_LOCALE'] = 'en'
app.config['BABEL_DEFAULT_TIMEZONE'] = 'UTC'
# Directory where translations live (default: "translations")
app.config['BABEL_TRANSLATION_DIRECTORIES'] = 'translations'

def get_locale():
    # 1. Check URL parameter or session
    # 2. Fall back to browser Accept-Language header
    return request.accept_languages.best_match(['en', 'de', 'ja', 'es', 'fr'])

babel = Babel(app, locale_selector=get_locale)
De functie locale_selector wordt bij elke aanvraag aangeroepen. Als deze een taal teruggeeft waarvoor geen gecompileerd .mo-bestand bestaat, valt Flask-Babel zonder melding terug op de standaardtaal. Er wordt geen fout gegenereerd — tekenreeksen verschijnen gewoon onvertaald.
3

Strings markeren voor vertaling

Omsluit elke voor gebruikers zichtbare tekenreeks in Python-code met gettext() en in Jinja2-sjablonen met _(). Gebruik lazy_gettext() voor tekenreeksen die bij het laden van een module worden gedefinieerd (zoals formulierlabels en configuratie) en later tijdens een aanvraag moeten worden vertaald.

app.py
from flask_babel import gettext, ngettext, lazy_gettext

# In views — gettext() for immediate translation
@app.route('/')
def index():
    flash(gettext('Your profile has been updated.'))
    return render_template('index.html',
        title=gettext('Home'))

# In forms/config — lazy_gettext() for deferred translation
class LoginForm(FlaskForm):
    username = StringField(lazy_gettext('Username'))
    password = PasswordField(lazy_gettext('Password'))
    submit = SubmitField(lazy_gettext('Sign In'))
templates/index.html
{# In Jinja2 templates, use _() shorthand for gettext #}
<h1>{{ _('Welcome to our app') }}</h1>

<p>{{ _('Hello, %(name)s!', name=user.name) }}</p>

<footer>
  {{ _('Copyright %(year)s Example Corp.', year=2026) }}
</footer>
Gebruik _() in Jinja2-sjablonen in plaats van gettext(). Dit is de standaardafkorting van gettext en houdt je sjablonen overzichtelijk. Flask-Babel registreert _() automatisch als globale Jinja2-functie.
4

Berichten extraheren

Voer pybabel extract uit om je broncode en sjablonen op vertaalbare tekenreeksen te doorzoeken. Hiermee wordt een .pot-bestand (Portable Object Template) gemaakt. Initialiseer vervolgens catalogi voor elke doeltaal of werk bestaande catalogi bij wanneer brontekenreeksen veranderen.

Terminal
# Extract translatable strings from source code
pybabel extract -F babel.cfg -k lazy_gettext -o messages.pot .

# Initialize a new language (first time only)
pybabel init -i messages.pot -d translations -l de
pybabel init -i messages.pot -d translations -l ja
pybabel init -i messages.pot -d translations -l es

# Update existing catalogs when source strings change
pybabel update -i messages.pot -d translations
Voer na de eerste extractie altijd pybabel update uit en niet init. Als je init voor een bestaande taalmap uitvoert, worden alle bestaande vertalingen overschreven. De opdracht update voegt nieuwe tekenreeksen samen en behoudt bestaande vertalingen.
5

PO-bestanden vertalen

Open de gegenereerde .po-bestanden en vul voor elke msgid de msgstr-waarden in. PO-bestanden zijn platte tekst: je kunt ze rechtstreeks bewerken, een PO-editor zoals Poedit gebruiken of de vertaling met AI-tools automatiseren.

translations/de/LC_MESSAGES/messages.po
# translations/de/LC_MESSAGES/messages.po
msgid "Welcome to our app"
msgstr "Willkommen in unserer App"

msgid "Hello, %(name)s!"
msgstr "Hallo, %(name)s!"

msgid "Your profile has been updated."
msgstr "Ihr Profil wurde aktualisiert."

msgid "Username"
msgstr "Benutzername"

msgid "Password"
msgstr "Passwort"

msgid "Sign In"
msgstr "Anmelden"
PO-bestanden bevatten contextopmerkingen (regels die beginnen met #:) die tonen waar elke tekenreeks in je broncode wordt gebruikt. Vertalers gebruiken deze om de context te begrijpen. Behoud ze — pybabel extract genereert ze automatisch.
6

Vertalingen compileren

Compileer je .po-bestanden met pybabel compile naar binaire .mo-bestanden. Flask-Babel leest tijdens runtime .mo-bestanden en kan .po-bestanden niet rechtstreeks lezen. Na elke vertaalupdate moet je opnieuw compileren.

Terminal
# Compile .po files to binary .mo files (required at runtime)
pybabel compile -d translations

# Flask-Babel reads .mo files, not .po files.
# You MUST compile after every translation update.
Als vertalingen na het bewerken van een .po-bestand niet verschijnen, ben je vrijwel zeker vergeten pybabel compile uit te voeren. Dit is het meest voorkomende probleem met Flask-Babel. Voeg de compilatiestap aan je implementatiescript toe om dit in productie te voorkomen.
7

Meervoudsvormen en variabelen verwerken

Gebruik ngettext() voor tekenreeksen die gevoelig zijn voor meervoudsvormen. De functie neemt een enkelvoudsvorm, een meervoudsvorm en het aantal aan. Babel gebruikt automatisch de juiste meervoudsregel voor elke taal: Engels heeft 2 vormen, Russisch 3, Arabisch 6 en Japans 1.

app.py
from flask_babel import ngettext

@app.route('/cart')
def cart():
    count = len(session.get('cart_items', []))
    message = ngettext(
        '%(num)d item in your cart',     # singular
        '%(num)d items in your cart',    # plural
        count                             # determines which form
    )
    return render_template('cart.html', message=message)
Plural forms in .po files
# English: 2 forms (nplurals=2)
msgid "%(num)d item in your cart"
msgid_plural "%(num)d items in your cart"
msgstr[0] "%(num)d item in your cart"
msgstr[1] "%(num)d items in your cart"

# German: 2 forms (nplurals=2)
msgid "%(num)d item in your cart"
msgid_plural "%(num)d items in your cart"
msgstr[0] "%(num)d Artikel in Ihrem Warenkorb"
msgstr[1] "%(num)d Artikel in Ihrem Warenkorb"

# Japanese: 1 form (nplurals=1)
msgid "%(num)d item in your cart"
msgid_plural "%(num)d items in your cart"
msgstr[0] "カートに%(num)d個の商品があります"

# Russian: 3 forms (nplurals=3)
msgid "%(num)d item in your cart"
msgid_plural "%(num)d items in your cart"
msgstr[0] "%(num)d товар в вашей корзине"
msgstr[1] "%(num)d товара в вашей корзине"
msgstr[2] "%(num)d товаров в вашей корзине"
Gebruik nooit if count == 1 voor meervoudslogica. Talen zoals het Frans behandelen 0 als enkelvoud. Het Russisch en Arabisch hebben vormen die het Engels niet kent. Laat ngettext() en de CLDR-meervoudsregels van Babel de selectie afhandelen.
8

Een taalschakelaar toevoegen

Bouw een taalselector die de keuze van de gebruiker in de Flask-sessie opslaat. Werk je functie locale_selector bij om eerst de sessie te controleren en daarna terug te vallen op browserherkenning.

app.py
from flask import session, redirect, url_for, request
from flask_babel import refresh

@app.route('/set-language/<lang>')
def set_language(lang):
    session['lang'] = lang
    refresh()  # Force Flask-Babel to re-read the locale
    return redirect(request.referrer or url_for('index'))

# Update get_locale to check session first
def get_locale():
    # 1. Explicit user choice (stored in session)
    if 'lang' in session:
        return session['lang']
    # 2. Browser Accept-Language header
    return request.accept_languages.best_match(
        ['en', 'de', 'ja', 'es', 'fr']
    )
templates/components/language_switcher.html
{# Language switcher component #}
<nav class="language-switcher">
  {% for lang, name in [('en','English'),('de','Deutsch'),
                         ('ja','日本語'),('es','Español'),
                         ('fr','Français')] %}
    <a href="{{ url_for('set_language', lang=lang) }}"
       class="{{ 'active' if get_locale() == lang }}">
      {{ name }}
    </a>
  {% endfor %}
</nav>
Roep na het wijzigen van de sessietaal flask_babel.refresh() aan om Flask-Babel de taal voor de huidige aanvraag opnieuw te laten lezen. Zonder refresh() blijft de oude taal tot de volgende aanvraag actief.
9

Vertalingen automatiseren

Nu je Flask-Babel-configuratie compleet is, kun je je PO-bestanden met AI vertalen. Automatiseer de cyclus extraheren-vertalen-compileren in je CI/CD-pipeline om vertalingen met je broncode gesynchroniseerd te houden.

Terminal
# Translate your PO files with AI directly from your IDE
# or use the CLI in CI/CD:
npx i18n-agent translate translations/de/LC_MESSAGES/messages.po \
  --source-lang en --target-lang de

# Bulk translate all languages at once:
npx i18n-agent translate messages.pot --lang de,ja,es,fr

# Then compile:
pybabel compile -d translations
Vertaal stapsgewijs: wanneer je nieuwe tekenreeksen toevoegt en pybabel update uitvoert, vertaal je alleen de nieuwe onvertaalde vermeldingen (lege msgstr-waarden) in plaats van alles opnieuw te genereren. Zo blijven door mensen beoordeelde vertalingen behouden.

Bonus: slimme locale-fallback met flask-babel-locale-chain

Standaard valt Flask-Babel rechtstreeks terug op de standaardtaal wanneer de voorkeurstaal van de gebruiker niet beschikbaar is. Een gebruiker met pt-BR en alleen pt-PT-vertalingen ziet Engels in plaats van Portugees. flask-babel-locale-chain voegt configureerbare terugvalketens toe, zodat verwante talen op natuurlijke wijze op elkaar terugvallen.

app.py
# pip install flask-babel-locale-chain
from flask_babel_locale_chain import LocaleChain

# Define fallback chains: pt-BR falls back to pt before en
locale_chain = LocaleChain({
    'pt-BR': ['pt-BR', 'pt', 'en'],
    'pt-PT': ['pt-PT', 'pt', 'en'],
    'zh-Hant': ['zh-Hant', 'zh-Hans', 'en'],
    'en-GB': ['en-GB', 'en', 'en-US'],
})

def get_locale():
    requested = request.accept_languages.best_match(
        ['en', 'pt-BR', 'pt', 'zh-Hant', 'zh-Hans']
    )
    # Returns the best available locale from the chain
    return locale_chain.resolve(requested)
flask-babel-locale-chain is een opensource-Python-pakket. Bekijk het op GitHub via github.com/i18n-agent/flask-babel-locale-chain.

Kwaliteitscontrole van vertalingen automatiseren

Vind ontbrekende sleutels en kapotte plaatsaanduidingen vóór de release met i18n-validate. Test je gebruikersinterface met pseudovertalingen uit i18n-pseudo voordat de echte vertalingen klaar zijn.

Veelvoorkomende valkuilen

Vergeten .po naar .mo te compileren

Flask-Babel leest gecompileerde .mo-bestanden en geen .po-bestanden. Als vertalingen na het bewerken van .po-bestanden niet verschijnen, voer dan pybabel compile -d translations uit. Voeg deze stap aan je implementatiescript toe.

gettext() op moduleniveau gebruiken

gettext() vereist een aanvraagcontext. Als je vertaalde tekenreeksen op moduleniveau definieert (klasseattributen, constanten), gebruik dan lazy_gettext(). Dit stelt de vertaling uit totdat de tekenreeks daadwerkelijk in een aanvraag wordt weergegeven.

Tekenreeksen ontbreken na extractie

pybabel extract doorzoekt alleen bestanden die overeenkomen met patronen in babel.cfg. Als tekenreeksen niet worden geëxtraheerd, controleer dan of de patronen in babel.cfg bij je bestandsstructuur passen, voeg -k lazy_gettext toe om uitgestelde aanroepen te extraheren en zorg dat Jinja2-sjablonen de juiste extensie (.html, .jinja2) gebruiken.

Coderingsfouten in PO-bestanden

PO-bestanden moeten UTF-8-gecodeerd zijn. Als je UnicodeDecodeError ziet, controleer dan de Content-Type-header in je .po-bestand: deze moet charset=UTF-8 vermelden. Sommige editors slaan bestanden met een andere codering op — controleer dit altijd na het bewerken.

Aanbevolen bestandsstructuur

Project Structure
my-flask-app/
├── app.py                          # Flask app with Babel config
├── babel.cfg                       # Extraction config
├── messages.pot                    # Template (extracted strings)
├── translations/
│   ├── de/
│   │   └── LC_MESSAGES/
│   │       ├── messages.po         # German translations (editable)
│   │       └── messages.mo         # Compiled binary (generated)
│   ├── ja/
│   │   └── LC_MESSAGES/
│   │       ├── messages.po
│   │       └── messages.mo
│   └── es/
│       └── LC_MESSAGES/
│           ├── messages.po
│           └── messages.mo
├── templates/
│   ├── base.html
│   ├── index.html
│   └── components/
│       └── language_switcher.html
├── requirements.txt
└── venv/

Probeer i18n Agent nu

Zet je vertaalbestand hier neer

JSON, YAML, PO, XML, CSV, Markdown, Properties

of klik om een bestand te selecteren

Doeltalen

Geen registratie nodigDirecte prijsindicatie

Veelgestelde vragen over Flask-i18n