Skip to main content

Flask i18n : créez une application multilingue avec Flask-Babel

Des bases de gettext au déploiement en production : internationalisez votre application Flask avec Flask-Babel, les fichiers PO et la traduction automatisée par IA.

1

Installer Flask-Babel

Flask-Babel est l'extension d'internationalisation standard pour Flask. Elle intègre GNU gettext à Flask et aux modèles Jinja2, en fournissant nativement des fonctions de traduction, la sélection de la locale et la prise en charge des fuseaux horaires.

Flask-Babel encapsule Babel (la bibliothèque d'i18n Python) et l'intègre au cycle de vie des requêtes de Flask. Il vous donne accès aux fonctions gettext(), ngettext() et lazy_gettext(), ainsi qu'à la détection automatique de la locale à partir des en-têtes du navigateur.
Terminal
pip install Flask-Babel
2

Configurer Babel

Créez un fichier babel.cfg pour indiquer à pybabel où rechercher les chaînes traduisibles, puis initialisez Flask-Babel avec une fonction de sélection de la locale qui détermine quelle langue servir pour chaque requête.

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)
La fonction locale_selector est appelée à chaque requête. Si elle renvoie une locale sans fichier .mo compilé, Flask-Babel se replie silencieusement sur la locale par défaut. Aucune erreur n'est levée — les chaînes apparaissent simplement non traduites.
3

Marquer les chaînes à traduire

Encadrez chaque chaîne destinée à l'utilisateur avec gettext() dans le code Python et _() dans les modèles Jinja2. Utilisez lazy_gettext() pour les chaînes définies au chargement du module (comme les libellés de formulaires et la configuration) qui doivent être traduites plus tard, au moment de la requête.

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>
Utilisez _() dans les modèles Jinja2 plutôt que gettext() — c'est le raccourci standard de gettext, et il garde vos modèles épurés. Flask-Babel enregistre automatiquement _() en tant que variable globale Jinja2.
4

Extraire les messages

Exécutez pybabel extract pour analyser votre code source et vos modèles à la recherche de chaînes traduisibles. Cela crée un fichier .pot (Portable Object Template). Initialisez ensuite les catalogues pour chaque langue cible, ou mettez à jour les catalogues existants lorsque les chaînes source changent.

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
Exécutez toujours pybabel update (et non init) après la première extraction. Exécuter init sur un répertoire de langue existant écrase toutes les traductions existantes. La commande update fusionne les nouvelles chaînes tout en préservant les traductions existantes.
5

Traduire des fichiers PO

Ouvrez les fichiers .po générés et renseignez la valeur msgstr pour chaque msgid. Les fichiers PO sont du texte brut — vous pouvez les modifier directement, utiliser un éditeur PO comme Poedit, ou automatiser la traduction avec des outils d'IA.

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"
Les fichiers PO incluent des commentaires de contexte (lignes commençant par #:) indiquant où chaque chaîne est utilisée dans votre code source. Les traducteurs s'en servent pour comprendre le contexte. Conservez-les — ils sont générés automatiquement par pybabel extract.
6

Compiler les traductions

Compilez vos fichiers .po en fichiers binaires .mo à l'aide de pybabel compile. Flask-Babel lit les fichiers .mo à l'exécution — il ne peut pas lire directement les fichiers .po. Vous devez recompiler après chaque mise à jour de traduction.

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.
Si les traductions n'apparaissent pas après la modification d'un fichier .po, vous avez presque certainement oublié d'exécuter pybabel compile. C'est le problème Flask-Babel le plus courant. Ajoutez l'étape de compilation à votre script de déploiement pour éviter ce problème en production.
7

Gérer les pluriels et les variables

Utilisez ngettext() pour les chaînes sensibles au pluriel. Elle prend une forme singulière, une forme plurielle et le nombre. Babel applique automatiquement la règle de pluriel correcte pour chaque langue — l'anglais compte 2 formes, le russe 3, l'arabe 6 et le japonais 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 товаров в вашей корзине"
N'utilisez jamais if count == 1 pour la logique de pluriel. Des langues comme le français traitent 0 comme un singulier. Le russe et l'arabe ont des formes que l'anglais n'a pas. Laissez ngettext() et les règles de pluriel CLDR de Babel gérer la sélection.
8

Ajouter le changement de locale

Créez un sélecteur de langue qui stocke le choix de l'utilisateur dans la session Flask. Mettez à jour votre fonction locale_selector pour qu'elle vérifie d'abord la session, puis se replie sur la détection du navigateur.

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>
Après avoir changé la locale de session, appelez flask_babel.refresh() pour forcer Flask-Babel à relire la locale de la requête en cours. Sans refresh(), l'ancienne locale persiste jusqu'à la requête suivante.
9

Automatiser les traductions

Une fois votre configuration Flask-Babel terminée, traduisez vos fichiers PO à l'aide de l'IA. Automatisez le cycle extraction-traduction-compilation dans votre pipeline CI/CD pour maintenir vos traductions synchronisées avec votre code source.

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
Traduisez de manière incrémentale — lorsque vous ajoutez de nouvelles chaînes et exécutez pybabel update, traduisez uniquement les nouvelles entrées non traduites (valeurs msgstr vides) plutôt que de tout régénérer. Cela préserve les traductions relues par des humains.

Bonus : repli intelligent de locale avec flask-babel-locale-chain

Par défaut, Flask-Babel se replie directement sur la locale par défaut lorsque la locale préférée de l'utilisateur n'est pas disponible. Un utilisateur pt-BR ne disposant que de traductions pt-PT voit l'anglais s'afficher au lieu du portugais. flask-babel-locale-chain ajoute des chaînes de repli configurables afin que les locales apparentées s'enchaînent naturellement.

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 est un package Python open source. Consultez-le sur GitHub à l'adresse github.com/i18n-agent/flask-babel-locale-chain.

Automatiser la qualité des traductions

Détectez les clés manquantes et les espaces réservés cassés avant leur mise en production grâce à i18n-validate. Testez votre interface avec des pseudo-traductions grâce à i18n-pseudo avant l'arrivée des traductions réelles.

Pièges courants

Oubli de compiler les .po en .mo

Flask-Babel lit les fichiers .mo compilés, pas les fichiers .po. Si les traductions n'apparaissent pas après la modification des fichiers .po, exécutez pybabel compile -d translations. Ajoutez cette étape à votre script de déploiement.

Utilisation de gettext() au niveau du module

gettext() nécessite un contexte de requête. Si vous définissez des chaînes traduites au niveau du module (attributs de classe, constantes), utilisez plutôt lazy_gettext(). Elle diffère la traduction jusqu'à ce que la chaîne soit réellement affichée dans une requête.

L'extraction manque des chaînes

pybabel extract n'analyse que les fichiers correspondant aux motifs définis dans babel.cfg. Si des chaînes ne sont pas extraites : vérifiez que les motifs de babel.cfg correspondent à la structure de vos fichiers, ajoutez -k lazy_gettext pour extraire les appels différés, et assurez-vous que vos modèles Jinja2 utilisent la bonne extension (.html, .jinja2).

Erreurs d'encodage des fichiers PO

Les fichiers PO doivent être encodés en UTF-8. Si vous voyez une erreur UnicodeDecodeError, vérifiez l'en-tête Content-Type de votre fichier .po : il doit indiquer charset=UTF-8. Certains éditeurs enregistrent avec des encodages différents — vérifiez toujours après modification.

Structure de fichiers recommandée

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/

Essayez i18n Agent maintenant

Déposez votre fichier de traduction ici

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

ou cliquez pour parcourir

Langues cibles

Aucune inscription requiseEstimation instantanée

FAQ Flask i18n