Skip to main content

i18n di Flask: creare un'app multilingue con Flask-Babel

Dalle nozioni di base di gettext alla distribuzione in produzione: internazionalizzi l'app Flask con Flask-Babel, file PO e traduzione automatizzata tramite IA.

1

Installare Flask-Babel

Flask-Babel è l'estensione standard di internazionalizzazione per Flask. Integra GNU gettext con i modelli Flask e Jinja2, offrendo funzioni di traduzione, selezione della lingua e supporto del fuso orario senza configurazione aggiuntiva.

Flask-Babel racchiude Babel, la biblioteca i18n Python, e la integra nel ciclo di vita delle richieste Flask. Offre le funzioni gettext(), ngettext() e lazy_gettext(), oltre al rilevamento automatico della lingua dalle intestazioni del browser.
Terminal
pip install Flask-Babel
2

Configurare Babel

Crei un file babel.cfg per indicare a pybabel dove cercare le stringhe traducibili, quindi inizializzi Flask-Babel con una funzione di selezione della lingua che stabilisca quale lingua servire per ogni richiesta.

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 funzione locale_selector viene chiamata a ogni richiesta. Se restituisce una lingua priva di un file .mo compilato, Flask-Babel passa silenziosamente alla lingua predefinita. Non viene generato alcun errore: le stringhe appaiono semplicemente non tradotte.
3

Contrassegnare le stringhe per la traduzione

Racchiuda ogni stringa rivolta agli utenti con gettext() nel codice Python e _() nei modelli Jinja2. Usi lazy_gettext() per le stringhe definite al caricamento del modulo, come le etichette dei moduli e la configurazione, che devono essere tradotte successivamente al momento della richiesta.

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>
Nei modelli Jinja2 usi _() anziché gettext(): è l'abbreviazione gettext standard e mantiene ordinati i modelli. Flask-Babel registra automaticamente _() come funzione globale Jinja2.
4

Estrarre i messaggi

Esegua pybabel extract per esaminare il codice sorgente e i modelli alla ricerca di stringhe traducibili. Viene creato un file .pot (Portable Object Template). Quindi inizializzi i cataloghi per ogni lingua di destinazione oppure aggiorni quelli esistenti quando cambiano le stringhe di origine.

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
Dopo la prima estrazione, esegua sempre pybabel update, non init. Eseguire init su una directory di lingua esistente sovrascrive tutte le traduzioni. Il comando update unisce le nuove stringhe preservando le traduzioni esistenti.
5

Tradurre file PO

Apra i file .po generati e compili i valori msgstr per ogni msgid. I file PO sono semplici file di testo: può modificarli direttamente, usare un editor PO come Poedit oppure automatizzare la traduzione con strumenti di 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"
I file PO includono commenti di contesto (righe che iniziano con #:) che indicano dove viene usata ogni stringa nel codice sorgente. I traduttori li usano per comprendere il contesto. Li conservi: vengono generati automaticamente da pybabel extract.
6

Compilare le traduzioni

Compili i file .po in file binari .mo tramite pybabel compile. Flask-Babel legge i file .mo durante l'esecuzione e non può leggere direttamente i file .po. Deve ricompilare dopo ogni aggiornamento delle traduzioni.

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.
Se le traduzioni non appaiono dopo la modifica di un file .po, quasi certamente non è stato eseguito pybabel compile. È il problema più comune di Flask-Babel. Aggiunga il passaggio di compilazione allo script di distribuzione per evitarlo in produzione.
7

Gestire plurali e variabili

Usi ngettext() per le stringhe sensibili al plurale. Accetta una forma singolare, una forma plurale e il conteggio. Babel usa automaticamente la regola del plurale corretta per ogni lingua: l'inglese ha 2 forme, il russo 3, l'arabo 6 e il giapponese 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 товаров в вашей корзине"
Non usi mai if count == 1 per la logica del plurale. Lingue come il francese considerano 0 singolare. Il russo e l'arabo hanno forme assenti in inglese. Lasci che ngettext() e le regole del plurale CLDR di Babel effettuino la selezione.
8

Aggiungere il cambio di lingua

Crei un selettore di lingua che archivi la scelta dell'utente nella sessione Flask. Aggiorni la funzione locale_selector affinché controlli prima la sessione e poi passi al rilevamento del browser.

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>
Dopo aver cambiato la lingua della sessione, chiami flask_babel.refresh() per costringere Flask-Babel a rileggere la lingua della richiesta corrente. Senza refresh(), la lingua precedente rimane fino alla richiesta successiva.
9

Automatizzare le traduzioni

Dopo aver completato la configurazione di Flask-Babel, traduca i file PO con l'IA. Automatizzi il ciclo estrazione-traduzione-compilazione nella pipeline CI/CD per mantenere le traduzioni sincronizzate con il codice sorgente.

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
Traduca in modo incrementale: quando aggiunge nuove stringhe ed esegue pybabel update, traduca soltanto le nuove voci non tradotte, ossia i valori msgstr vuoti, anziché rigenerare tutto. In questo modo preserva le traduzioni revisionate da persone.

In più: fallback intelligente con flask-babel-locale-chain

Per impostazione predefinita, Flask-Babel passa direttamente alla lingua predefinita quando quella preferita dall'utente non è disponibile. Un utente pt-BR con sole traduzioni pt-PT vede l'inglese anziché il portoghese. flask-babel-locale-chain aggiunge catene di fallback configurabili, affinché le lingue correlate vengano applicate in sequenza in modo naturale.

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 è un pacchetto Python open source. Lo può visualizzare su GitHub all'indirizzo github.com/i18n-agent/flask-babel-locale-chain.

Automatizzare la qualità

Con i18n-validate, rilevi chiavi mancanti e segnaposto non validi prima del rilascio. Testi l'interfaccia con le pseudotraduzioni di i18n-pseudo prima che arrivino le traduzioni reali.

Problemi comuni

Mancata compilazione da .po a .mo

Flask-Babel legge i file .mo compilati, non i file .po. Se le traduzioni non appaiono dopo la modifica dei file .po, esegua pybabel compile -d translations. Aggiunga questo passaggio allo script di distribuzione.

Uso di gettext() a livello di modulo

gettext() richiede un contesto di richiesta. Se definisce stringhe tradotte a livello di modulo, ad esempio attributi di classe o costanti, usi invece lazy_gettext(). Rinvia la traduzione fino al rendering effettivo della stringa in una richiesta.

Stringhe non rilevate durante l'estrazione

pybabel extract esamina soltanto i file che corrispondono ai pattern in babel.cfg. Se le stringhe non vengono estratte, controlli che i pattern di babel.cfg corrispondano alla struttura dei file, aggiunga -k lazy_gettext per estrarre le chiamate lazy e verifichi che i modelli Jinja2 usino l'estensione corretta (.html, .jinja2).

Errori di codifica dei file PO

I file PO devono usare la codifica UTF-8. Se compare UnicodeDecodeError, controlli l'intestazione Content-Type del file .po: deve indicare charset=UTF-8. Alcuni editor salvano con codifiche diverse; verifichi sempre dopo ogni modifica.

Struttura dei file consigliata

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/

Provi subito i18n Agent

Trascinare qui il file di traduzione

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

oppure fare clic per selezionarlo

Lingue di destinazione

Nessuna registrazione richiestaPreventivo immediato

Domande frequenti sull'i18n di Flask