Skip to main content

Flask i18n: creați o aplicație multilingvă cu Flask-Babel

De la noțiunile de bază gettext până la implementarea în producție: internaționalizați aplicația Flask cu Flask-Babel, fișiere PO și traducere automată cu IA.

1

Instalați Flask-Babel

Flask-Babel este extensia standard de internaționalizare pentru Flask. Aceasta integrează GNU gettext cu Flask și șabloanele Jinja2, oferind din start funcții de traducere, selectarea setărilor regionale și compatibilitate cu fusurile orare.

Flask-Babel încapsulează Babel (biblioteca i18n pentru Python) și îl integrează în ciclul de prelucrare a solicitărilor Flask. Acesta vă oferă funcțiile gettext(), ngettext() și lazy_gettext(), precum și detectarea automată a setărilor regionale din antetele browserului.
Terminal
pip install Flask-Babel
2

Configurați Babel

Creați un fișier babel.cfg pentru a-i indica lui pybabel unde să caute șirurile traductibile, apoi inițializați Flask-Babel cu o funcție de selectare a setării regionale care stabilește limba furnizată pentru fiecare solicitare.

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)
Funcția locale_selector este apelată la fiecare solicitare. Dacă returnează o setare regională fără un fișier .mo compilat, Flask-Babel revine fără avertisment la setarea regională implicită. Nu este generată nicio eroare — șirurile apar pur și simplu netraduse.
3

Marcați șirurile pentru traducere

Încadrați fiecare șir destinat utilizatorilor cu gettext() în codul Python și cu _() în șabloanele Jinja2. Utilizați lazy_gettext() pentru șirurile definite la încărcarea modulului (precum etichetele formularelor și configurările) care trebuie traduse ulterior, la prelucrarea solicitării.

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>
Utilizați _() în șabloanele Jinja2 în loc de gettext() — este abrevierea gettext standard și menține șabloanele ordonate. Flask-Babel înregistrează automat _() ca element global Jinja2.
4

Extrageți mesajele

Executați pybabel extract pentru a scana codul sursă și șabloanele după șiruri traductibile. Comanda creează un fișier .pot (Portable Object Template). Apoi inițializați cataloagele pentru fiecare limbă-țintă sau actualizați-le pe cele existente când se modifică șirurile sursă.

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
Executați întotdeauna pybabel update (nu init) după prima extragere. Executarea init într-un director de limbă existent suprascrie toate traducerile existente. Comanda update îmbină șirurile noi și păstrează traducerile existente.
5

Traduceți fișierele PO

Deschideți fișierele .po generate și completați valorile msgstr pentru fiecare msgid. Fișierele PO sunt text simplu — le puteți edita direct, puteți utiliza un editor PO precum Poedit sau puteți automatiza traducerea cu instrumente bazate pe 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"
Fișierele PO includ comentarii contextuale (linii care încep cu #:) ce indică locul unde este utilizat fiecare șir în codul sursă. Traducătorii le folosesc pentru a înțelege contextul. Păstrați-le — sunt generate automat de pybabel extract.
6

Compilați traducerile

Compilați fișierele .po în fișiere binare .mo utilizând pybabel compile. Flask-Babel citește fișiere .mo în timpul execuției — nu poate citi direct fișiere .po. Trebuie să recompilați după fiecare actualizare a traducerilor.

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.
Dacă traducerile nu apar după editarea unui fișier .po, aproape sigur ați uitat să executați pybabel compile. Aceasta este cea mai frecventă problemă Flask-Babel. Adăugați etapa de compilare în scriptul de implementare pentru a o evita în producție.
7

Gestionați formele de plural și variabilele

Utilizați ngettext() pentru șirurile care depind de plural. Funcția primește o formă de singular, una de plural și numărul. Babel utilizează automat regula de plural corectă pentru fiecare limbă — engleza are 2 forme, rusa 3, araba 6, iar japoneza una singură.

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 товаров в вашей корзине"
Nu utilizați niciodată if count == 1 pentru logica pluralului. Limbi precum franceza tratează 0 ca singular. Rusa și araba au forme care nu există în engleză. Lăsați ngettext() și regulile de plural CLDR din Babel să selecteze forma.
8

Adăugați schimbarea setării regionale

Creați un selector de limbă care stochează alegerea utilizatorului în sesiunea Flask. Actualizați funcția locale_selector pentru a verifica mai întâi sesiunea, apoi pentru a reveni la detectarea din 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>
După modificarea setării regionale a sesiunii, apelați flask_babel.refresh() pentru a obliga Flask-Babel să citească din nou setarea regională a solicitării curente. Fără refresh(), vechea setare regională persistă până la următoarea solicitare.
9

Automatizați traducerile

După finalizarea configurării Flask-Babel, traduceți fișierele PO cu IA. Automatizați ciclul extragere-traducere-compilare în fluxul CI/CD pentru a păstra traducerile sincronizate cu codul sursă.

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
Traduceți incremental — când adăugați șiruri noi și executați pybabel update, traduceți numai intrările noi netraduse (valorile msgstr goale), fără să regenerați totul. Astfel păstrați traducerile verificate de persoane.

Bonus: mecanisme inteligente de rezervă cu flask-babel-locale-chain

În mod implicit, Flask-Babel revine direct la setarea regională implicită când setarea regională preferată a utilizatorului nu este disponibilă. Un utilizator pt-BR care are disponibile numai traduceri pt-PT vede engleză în loc de portugheză. flask-babel-locale-chain adaugă lanțuri de rezervă configurabile, astfel încât setările regionale înrudite să fie parcurse firesc.

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 este un pachet Python cu sursă deschisă. Îl puteți consulta pe GitHub la github.com/i18n-agent/flask-babel-locale-chain.

Automatizați verificarea calității traducerilor

Detectați cheile lipsă și substituenții nevalizi înainte de lansare cu i18n-validate. Testați interfața cu pseudotraduceri folosind i18n-pseudo înainte de sosirea traducerilor reale.

Capcane frecvente

Ați uitat să compilați .po în .mo

Flask-Babel citește fișiere .mo compilate, nu fișiere .po. Dacă traducerile nu apar după editarea fișierelor .po, executați pybabel compile -d translations. Adăugați această etapă în scriptul de implementare.

Utilizarea gettext() la nivel de modul

gettext() necesită un context de solicitare. Dacă definiți șiruri traduse la nivel de modul (atribute de clasă, constante), utilizați lazy_gettext(). Acesta amână traducerea până când șirul este randat efectiv într-o solicitare.

La extragere lipsesc șiruri

pybabel extract scanează numai fișierele care corespund modelelor din babel.cfg. Dacă șirurile nu sunt extrase: verificați dacă modelele din babel.cfg corespund structurii fișierelor, adăugați -k lazy_gettext pentru extragerea apelurilor cu evaluare întârziată și asigurați-vă că șabloanele Jinja2 utilizează extensia corectă (.html, .jinja2).

Erori de codificare în fișierele PO

Fișierele PO trebuie să fie codificate UTF-8. Dacă întâlniți UnicodeDecodeError, verificați antetul Content-Type din fișierul .po: acesta trebuie să specifice charset=UTF-8. Unele editoare salvează cu alte codificări — verificați întotdeauna după editare.

Structura recomandată a fișierelor

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/

Încercați acum i18n Agent

Plasați aici fișierul de traducere

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

sau faceți clic pentru a-l selecta

Limbi țintă

Nu este necesară înregistrareaEstimare instantanee

Întrebări frecvente despre Flask i18n