Skip to main content

Flask i18n: създайте многоезично приложение с Flask-Babel

От основите на gettext до внедряването в производствена среда: интернационализирайте своето Flask приложение с Flask-Babel, PO файлове и автоматизиран превод с ИИ.

1

Инсталирайте Flask-Babel

Flask-Babel е стандартното разширение за интернационализация на Flask. То интегрира GNU gettext с шаблоните на Flask и Jinja2 и предоставя готови функции за превод, избор на локал и поддръжка на часови зони.

Flask-Babel обвива Babel (библиотеката за i18n на Python) и го интегрира с жизнения цикъл на заявките във Flask. Така получавате функциите gettext(), ngettext() и lazy_gettext(), както и автоматично определяне на локала от заглавките на браузъра.
Terminal
pip install Flask-Babel
2

Конфигурирайте Babel

Създайте файл babel.cfg, за да укажете на pybabel къде да търси низове за превод, след което инициализирайте Flask-Babel с функция за избор на локал, която определя езика за всяка заявка.

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)
Функцията locale_selector се извиква при всяка заявка. Ако върне локал без компилиран .mo файл, Flask-Babel без предупреждение преминава към локала по подразбиране. Не възниква грешка — низовете просто остават непреведени.
3

Маркирайте низовете за превод

Обвийте всеки видим за потребителя низ с gettext() в кода на Python и с _() в шаблоните на Jinja2. Използвайте 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>
Използвайте _() вместо gettext() в шаблоните на Jinja2 — това е стандартното съкратено означение на gettext и поддържа шаблоните Ви прегледни. Flask-Babel автоматично регистрира _() като глобална функция на Jinja2.
4

Извлечете съобщенията

Изпълнете pybabel extract, за да претърсите изходния код и шаблоните за низове за превод. Така се създава .pot файл (Portable Object Template). След това инициализирайте каталози за всеки целеви език или актуализирайте съществуващите, когато изходните низове се променят.

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
След първото извличане винаги изпълнявайте pybabel update (а не init). Изпълнението на init за съществуваща езикова директория презаписва всички налични преводи. Командата update обединява новите низове, като запазва съществуващите преводи.
5

Преведете PO файловете

Отворете създадените .po файлове и попълнете стойностите msgstr за всеки msgid. PO файловете са обикновен текст — можете да ги редактирате директно, да използвате редактор за PO файлове като Poedit или да автоматизирате превода с инструменти на базата на ИИ.

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 файловете съдържат контекстни коментари (редове, започващи с #:), които показват къде се използва всеки низ в изходния код. Преводачите ги използват, за да разберат контекста. Запазете ги — те се създават автоматично от pybabel extract.
6

Компилирайте преводите

Компилирайте своите .po файлове в двоични .mo файлове чрез pybabel compile. Flask-Babel чете .mo файлове по време на изпълнение — не може да чете директно .po файлове. Трябва да компилирате отново след всяка актуализация на преводите.

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.
Ако преводите не се показват след редактиране на .po файл, почти сигурно сте пропуснали да изпълните pybabel compile. Това е най-често срещаният проблем при Flask-Babel. Добавете стъпката за компилиране към скрипта за внедряване, за да го избегнете в производствена среда.
7

Обработвайте множествени форми и променливи

Използвайте ngettext() за низове, чиито форми зависят от броя. Функцията приема форма за единствено число, форма за множествено число и брой. Babel автоматично използва правилното правило за множествено число за всеки език — английският има 2 форми, руският 3, арабският 6, а японският 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 товаров в вашей корзине"
Никога не използвайте if count == 1 за логиката на множественото число. Езици като френския третират 0 като единствено число. Руският и арабският имат форми, които не съществуват в английския. Оставете ngettext() и правилата за множествено число от CLDR в Babel да направят избора.
8

Добавете превключване на локала

Създайте селектор за език, който съхранява избора на потребителя в сесията на Flask. Актуализирайте функцията locale_selector така, че първо да проверява сесията, а след това да използва автоматичното определяне от браузъра.

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>
След промяна на локала в сесията извикайте flask_babel.refresh(), за да принудите Flask-Babel да прочете отново локала за текущата заявка. Без refresh() старият локал се запазва до следващата заявка.
9

Автоматизирайте преводите

След като конфигурирате Flask-Babel, преведете своите PO файлове с помощта на ИИ. Автоматизирайте цикъла извличане–превод–компилиране във Вашия CI/CD пайплайн, за да поддържате преводите синхронизирани с изходния код.

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
Превеждайте поетапно — когато добавите нови низове и изпълните pybabel update, преведете само новите непреведени записи (празните стойности msgstr), вместо да създавате всичко отначало. Така се запазват преводите, които вече са прегледани от човек.

Допълнение: интелигентни резервни локали с flask-babel-locale-chain

По подразбиране Flask-Babel преминава директно към локала по подразбиране, когато предпочитаният локал на потребителя не е наличен. Потребител с pt-BR, за когото има само преводи на pt-PT, вижда английски вместо португалски. flask-babel-locale-chain добавя конфигурируеми вериги от резервни локали, така че сродните локали да се използват в естествена последователност.

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 е Python пакет с отворен код. Разгледайте го в GitHub на github.com/i18n-agent/flask-babel-locale-chain.

Автоматизирайте контрола на качеството на превода

Откривайте липсващи ключове и повредени запазени места с i18n-validate, преди да достигнат до потребителите. Тествайте интерфейса си с псевдопреводи чрез i18n-pseudo, преди да получите истинските преводи.

Често срещани затруднения

Пропуснато компилиране от .po в .mo

Flask-Babel чете компилирани .mo файлове, а не .po файлове. Ако преводите не се показват след редактиране на .po файловете, изпълнете pybabel compile -d translations. Добавете тази стъпка към скрипта за внедряване.

Използване на gettext() на ниво модул

gettext() изисква контекст на заявка. Ако дефинирате преведени низове на ниво модул (атрибути на класове или константи), използвайте lazy_gettext(). Тази функция отлага превода до момента, в който низът действително се визуализира в рамките на заявка.

При извличането се пропускат низове

pybabel extract претърсва само файловете, които съответстват на шаблоните в babel.cfg. Ако дадени низове не се извличат, проверете дали шаблоните в babel.cfg съответстват на файловата Ви структура, добавете -k lazy_gettext за извличане на отложените извиквания и се уверете, че шаблоните на Jinja2 използват правилното разширение (.html, .jinja2).

Грешки в кодирането на PO файловете

PO файловете трябва да бъдат кодирани с UTF-8. Ако виждате UnicodeDecodeError, проверете заглавката Content-Type във Вашия .po файл: тя трябва да съдържа charset=UTF-8. Някои редактори записват файловете с различно кодиране — винаги проверявайте след редактиране.

Препоръчителна структура на файловете

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/

Изпробвайте i18n Agent сега

Пуснете тук Вашия файл за превод

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

или натиснете, за да изберете файл

Целеви езици

Не се изисква регистрацияНезабавна оценка

Често задавани въпроси за i18n във Flask