Skip to main content

Flask i18n: kurkite daugiakalbę programą su Flask-Babel

Nuo gettext pagrindų iki diegimo gamybinėje aplinkoje: internacionalizuokite Flask programą naudodami Flask-Babel, PO failus ir automatizuotą DI vertimą.

1

Įdiegti Flask-Babel

Flask-Babel yra standartinis Flask internacionalizavimo plėtinys. Jis integruoja GNU gettext su Flask ir Jinja2 šablonais, iškart suteikia vertimo funkcijas, lokalių parinkimą ir laiko juostų palaikymą.

Flask-Babel apgaubia Babel (Python i18n biblioteką) ir integruoja ją į Flask užklausos gyvavimo ciklą. Jis suteikia gettext(), ngettext() bei lazy_gettext() funkcijas ir automatinį lokalės aptikimą pagal naršyklės antraštes.
Terminal
pip install Flask-Babel
2

Sukonfigūruoti Babel

Sukurkite babel.cfg failą, kuris nurodo pybabel, kur ieškoti verstinų eilučių, tada inicializuokite Flask-Babel su lokalės parinkimo funkcija, nustatančia kiekvienai užklausai pateikiamą kalbą.

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)
Funkcija locale_selector iškviečiama kiekvienai užklausai. Jei ji grąžina lokalę, neturinčią sukompiliuoto .mo failo, Flask-Babel tyliai grįžta prie numatytosios lokalės. Klaida nepateikiama – eilutės tiesiog rodomos neišverstos.
3

Pažymėti verstinas eilutes

Kiekvieną naudotojams skirtą eilutę Python kode apgaubkite gettext(), o Jinja2 šablonuose – _(). Modulio įkėlimo metu apibrėžtoms eilutėms (pvz., formų etiketėms ir konfigūracijai), kurios turi būti išverstos vėliau užklausos metu, naudokite lazy_gettext().

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>
Jinja2 šablonuose naudokite _(), o ne gettext(): tai standartinis gettext trumpinys, išlaikantis šablonus tvarkingus. Flask-Babel automatiškai užregistruoja _() kaip visuotinę Jinja2 funkciją.
4

Išskirti pranešimus

Paleiskite pybabel extract, kad nuskaitytumėte pirminį kodą ir šablonus, ieškodami verstinų eilučių. Taip sukuriamas .pot (Portable Object Template) failas. Tada inicializuokite kiekvienos tikslinės kalbos katalogus arba atnaujinkite esamus, kai pasikeičia šaltinio eilutės.

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
Po pirmojo išskyrimo visada paleiskite pybabel update, o ne init. Paleidus init esamame kalbos kataloge perrašomi visi esami vertimai. Komanda update sujungia naujas eilutes ir išsaugo esamus vertimus.
5

Versti PO failus

Atidarykite sugeneruotus .po failus ir užpildykite kiekvieno msgid reikšmes msgstr. PO failai yra paprastas tekstas: galite juos redaguoti tiesiogiai, naudoti tokį PO redaktorių kaip Poedit arba automatizuoti vertimą DI įrankiais.

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 failuose yra konteksto komentarų (eilutės, prasidedančios #:), nurodančių, kur kiekviena eilutė naudojama pirminiame kode. Vertėjai jais naudojasi kontekstui suprasti. Išsaugokite juos – juos automatiškai sugeneruoja pybabel extract.
6

Sukompiliuoti vertimus

Sukompiliuokite .po failus į dvejetainius .mo failus naudodami pybabel compile. Vykdymo metu Flask-Babel nuskaito .mo failus – jis negali tiesiogiai nuskaityti .po failų. Po kiekvieno vertimo naujinio turite sukompiliuoti iš naujo.

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.
Jei redagavus .po failą vertimai nepasirodo, beveik neabejotinai pamiršote paleisti pybabel compile. Tai dažniausia Flask-Babel problema. Pridėkite kompiliavimo veiksmą prie diegimo scenarijaus, kad išvengtumėte jos gamybinėje aplinkoje.
7

Apdoroti daugiskaitą ir kintamuosius

Daugiskaitai jautrioms eilutėms naudokite ngettext(). Ji priima vienaskaitos formą, daugiskaitos formą ir skaičių. Babel automatiškai naudoja tinkamą kiekvienos kalbos daugiskaitos taisyklę: anglų kalboje yra 2 formos, rusų – 3, arabų – 6, japonų – 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 товаров в вашей корзине"
Daugiskaitos logikai niekada nenaudokite if count == 1. Tokiose kalbose kaip prancūzų 0 laikomas vienaskaita. Rusų ir arabų kalbos turi anglų kalboje neegzistuojančių formų. Leiskite parinkimą atlikti ngettext() ir Babel CLDR daugiskaitos taisyklėms.
8

Pridėti lokalės keitimą

Sukurkite kalbos parinkiklį, kuris saugo naudotojo pasirinkimą Flask seanse. Atnaujinkite funkciją locale_selector, kad ji pirmiausia tikrintų seansą, o tada grįžtų prie naršyklės aptikimo.

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>
Pakeitę seanso lokalę iškvieskite flask_babel.refresh(), kad Flask-Babel iš naujo nuskaitytų dabartinės užklausos lokalę. Be refresh() senoji lokalė išlieka iki kitos užklausos.
9

Automatizuoti vertimus

Baigę Flask-Babel sąranką išverskite PO failus naudodami DI. Automatizuokite išskyrimo, vertimo ir kompiliavimo ciklą CI/CD konvejeryje, kad vertimai liktų sinchronizuoti su pirminiu kodu.

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
Verskite palaipsniui – pridėję naujų eilučių ir paleidę pybabel update išverskite tik naujus neišverstus įrašus (tuščias msgstr reikšmes), o ne generuokite viską iš naujo. Taip išsaugomi žmonių peržiūrėti vertimai.

Papildomai: išmani atsarginė lokalė su flask-babel-locale-chain

Pagal numatytąją nuostatą Flask-Babel iškart grįžta prie numatytosios lokalės, kai pageidaujamos naudotojo lokalės nėra. pt-BR naudotojas, turintis tik pt-PT vertimus, mato anglų, o ne portugalų kalbą. flask-babel-locale-chain prideda konfigūruojamas atsargines grandines, kad susijusios lokalės natūraliai pereitų viena į kitą.

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 yra atvirojo kodo Python paketas. Peržiūrėkite jį GitHub adresu github.com/i18n-agent/flask-babel-locale-chain.

Automatizuoti vertimo kokybę

Naudodami i18n-validate prieš išleidimą aptikite trūkstamus raktus ir sugadintus vietos rezervavimo ženklus. Kol dar nėra tikrų vertimų, patikrinkite UI su i18n-pseudo pseudoverstimais.

Dažnos klaidos

Pamiršta sukompiliuoti .po į .mo

Flask-Babel nuskaito sukompiliuotus .mo, o ne .po failus. Jei redagavus .po failus vertimai nepasirodo, paleiskite pybabel compile -d translations. Pridėkite šį veiksmą prie diegimo scenarijaus.

gettext() naudojamas modulio lygiu

gettext() reikia užklausos konteksto. Jei išverstas eilutes apibrėžiate modulio lygiu (klasės atributus, konstantas), vietoje jo naudokite lazy_gettext(). Jis atideda vertimą, kol eilutė iš tiesų atvaizduojama užklausoje.

Išskyrimas praleidžia eilutes

pybabel extract nuskaito tik failus, atitinkančius babel.cfg šablonus. Jei eilutės neišskiriamos, patikrinkite, ar babel.cfg šablonai atitinka failų struktūrą, pridėkite -k lazy_gettext tingiesiems iškvietimams išskirti ir įsitikinkite, kad Jinja2 šablonai naudoja tinkamą plėtinį (.html, .jinja2).

PO failo koduotės klaidos

PO failai turi būti užkoduoti UTF-8. Jei matote UnicodeDecodeError, patikrinkite .po failo antraštę Content-Type: joje turi būti charset=UTF-8. Kai kurie redaktoriai išsaugo kita koduote, todėl redagavę visada patikrinkite.

Rekomenduojama failų struktūra

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/

Išbandykite i18n Agent dabar

Nuvilkite vertimo failą čia

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

arba spustelėkite norėdami pasirinkti

Tikslinės kalbos

Registruotis nereikiaMomentinis įvertis

DUK apie Flask i18n