Skip to main content

Flask i18n: zbuduj wielojęzyczną aplikację z Flask-Babel

Od podstaw gettext po wdrożenie produkcyjne: internacjonalizuj aplikację Flask za pomocą Flask-Babel, plików PO i automatycznych tłumaczeń AI.

1

Zainstaluj Flask-Babel

Flask-Babel to standardowe rozszerzenie internacjonalizacyjne dla Flaska. Integruje GNU gettext z Flaskiem i szablonami Jinja2, zapewniając gotowe funkcje tłumaczeniowe, wybór języka oraz obsługę stref czasowych.

Flask-Babel opakowuje Babel (bibliotekę i18n dla Pythona) i integruje ją z cyklem obsługi żądań Flaska. Udostępnia funkcje gettext(), ngettext() oraz lazy_gettext(), a także automatyczne wykrywanie języka z nagłówków przeglądarki.
Terminal
pip install Flask-Babel
2

Skonfiguruj Babel

Utwórz plik babel.cfg, aby wskazać narzędziu pybabel miejsca skanowania tekstów przeznaczonych do tłumaczenia, a następnie zainicjalizuj Flask-Babel z funkcją wybierającą język dla każdego żądania.

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)
Funkcja locale_selector jest wywoływana przy każdym żądaniu. Jeśli zwróci ustawienia regionalne bez skompilowanego pliku .mo, Flask-Babel po cichu wróci do języka domyślnego. Nie zostanie zgłoszony błąd — teksty będą po prostu nieprzetłumaczone.
3

Oznacz teksty do tłumaczenia

Otocz każdy tekst widoczny dla użytkownika funkcją gettext() w kodzie Python oraz _() w szablonach Jinja2. Używaj lazy_gettext() dla tekstów zdefiniowanych podczas wczytywania modułu (takich jak etykiety formularzy i konfiguracja), które mają być przetłumaczone później podczas żądania.

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>
W szablonach Jinja2 używaj _() zamiast gettext() — to standardowy skrót gettext, który pozwala zachować przejrzystość szablonów. Flask-Babel automatycznie rejestruje _() jako funkcję globalną Jinja2.
4

Wyodrębnij wiadomości

Uruchom pybabel extract, aby przeskanować kod źródłowy i szablony w poszukiwaniu tekstów do tłumaczenia. Powstanie plik .pot (Portable Object Template). Następnie zainicjalizuj katalogi dla każdego języka docelowego albo zaktualizuj istniejące po zmianie tekstów źródłowych.

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 pierwszym wyodrębnieniu zawsze uruchamiaj pybabel update, a nie init. Uruchomienie init w istniejącym katalogu językowym nadpisuje wszystkie dotychczasowe tłumaczenia. Polecenie update scala nowe teksty z zachowaniem istniejących tłumaczeń.
5

Przetłumacz pliki PO

Otwórz wygenerowane pliki .po i uzupełnij wartości msgstr dla każdego msgid. Pliki PO to zwykły tekst — możesz edytować je bezpośrednio, użyć edytora PO takiego jak Poedit albo zautomatyzować tłumaczenie za pomocą narzędzi AI.

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"
Pliki PO zawierają komentarze kontekstowe (wiersze zaczynające się od #:) wskazujące miejsca użycia każdego tekstu w kodzie źródłowym. Tłumacze korzystają z nich, aby zrozumieć kontekst. Zachowaj je — są automatycznie generowane przez pybabel extract.
6

Skompiluj tłumaczenia

Skompiluj pliki .po do binarnych plików .mo za pomocą pybabel compile. Flask-Babel podczas działania odczytuje pliki .mo — nie potrafi bezpośrednio odczytać plików .po. Po każdej aktualizacji tłumaczeń trzeba przeprowadzić ponowną kompilację.

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.
Jeśli po edycji pliku .po tłumaczenia się nie pojawiają, prawie na pewno pominięto pybabel compile. To najczęstszy problem z Flask-Babel. Dodaj etap kompilacji do skryptu wdrożeniowego, aby uniknąć go w wersji produkcyjnej.
7

Obsłuż liczbę mnogą i zmienne

Używaj ngettext() dla tekstów zależnych od liczby. Funkcja przyjmuje formę pojedynczą, formę mnogą oraz liczbę. Babel automatycznie stosuje właściwą regułę dla każdego języka — angielski ma 2 formy, rosyjski 3, arabski 6, a japoński 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 товаров в вашей корзине"
Nigdy nie używaj if count == 1 jako logiki liczby mnogiej. Języki takie jak francuski traktują 0 jak liczbę pojedynczą. Rosyjski i arabski mają formy nieobecne w angielskim. Pozwól ngettext() oraz regułom liczby mnogiej CLDR w Babel dokonać wyboru.
8

Dodaj zmianę języka

Utwórz selektor języka, który zapisuje wybór użytkownika w sesji Flask. Zaktualizuj funkcję locale_selector tak, aby najpierw sprawdzała sesję, a następnie korzystała z wykrywania przez przeglądarkę.

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>
Po zmianie języka sesji wywołaj flask_babel.refresh(), aby zmusić Flask-Babel do ponownego odczytania języka dla bieżącego żądania. Bez refresh() poprzedni język pozostaje aktywny do następnego żądania.
9

Zautomatyzuj tłumaczenia

Po skonfigurowaniu Flask-Babel tłumacz pliki PO za pomocą AI. Zautomatyzuj cykl wyodrębniania, tłumaczenia i kompilowania w pipeline CI/CD, aby tłumaczenia pozostawały zsynchronizowane z kodem źródłowym.

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
Tłumacz przyrostowo — po dodaniu nowych tekstów i uruchomieniu pybabel update tłumacz tylko nowe nieprzetłumaczone wpisy (puste wartości msgstr), zamiast ponownie generować wszystko. Pozwala to zachować tłumaczenia sprawdzone przez człowieka.

Bonus: inteligentny język rezerwowy z flask-babel-locale-chain

Domyślnie Flask-Babel przechodzi bezpośrednio do języka domyślnego, gdy preferowany język użytkownika jest niedostępny. Użytkownik pt-BR mający do dyspozycji tylko tłumaczenia pt-PT zobaczy angielski zamiast portugalskiego. flask-babel-locale-chain dodaje konfigurowalne łańcuchy rezerwowe, które sprawdzają powiązane języki w naturalnej kolejności.

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 to pakiet Python o otwartym kodzie źródłowym. Zobacz go na GitHubie: github.com/i18n-agent/flask-babel-locale-chain.

Zautomatyzuj kontrolę jakości tłumaczeń

Wykrywaj brakujące klucze i uszkodzone symbole zastępcze przed wydaniem za pomocą i18n-validate. Testuj interfejs z pseudotłumaczeniami przy użyciu i18n-pseudo, zanim pojawią się prawdziwe tłumaczenia.

Typowe pułapki

Pominięcie kompilacji .po do .mo

Flask-Babel odczytuje skompilowane pliki .mo, a nie pliki .po. Jeśli po edycji plików .po tłumaczenia się nie pojawiają, uruchom pybabel compile -d translations. Dodaj ten etap do skryptu wdrożeniowego.

Używanie gettext() na poziomie modułu

gettext() wymaga kontekstu żądania. Jeśli definiujesz przetłumaczone teksty na poziomie modułu (atrybuty klas, stałe), użyj zamiast niego lazy_gettext(). Odkłada tłumaczenie do chwili faktycznego wyrenderowania tekstu w żądaniu.

Wyodrębnianie pomija teksty

pybabel extract skanuje tylko pliki pasujące do wzorców w babel.cfg. Jeśli teksty nie są wyodrębniane, sprawdź, czy wzorce babel.cfg odpowiadają strukturze plików, dodaj -k lazy_gettext, aby wykrywać leniwe wywołania i upewnij się, że szablony Jinja2 używają właściwego rozszerzenia (.html, .jinja2).

Błędy kodowania pliku PO

Pliki PO muszą być zakodowane w UTF-8. Jeśli widzisz UnicodeDecodeError, sprawdź nagłówek Content-Type w pliku .po: powinien zawierać charset=UTF-8. Niektóre edytory zapisują pliki w innym kodowaniu — zawsze sprawdzaj je po edycji.

Zalecana struktura plików

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/

Wypróbuj i18n Agent

Upuść tutaj plik tłumaczenia

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

lub kliknij, aby go wybrać

Języki docelowe

Rejestracja nie jest wymaganaNatychmiastowa wycena

Najczęstsze pytania o Flask i18n