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>
Використовуйте _() у шаблонах Jinja2 замість gettext() — це стандартне скорочення 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 за допомогою ШІ. Автоматизуйте цикл видобування, перекладу та компіляції у своєму pipeline 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

або натисніть, щоб вибрати

Цільові мови

Реєстрація не потрібнаМиттєвий розрахунок

Поширені запитання про Flask i18n