
Flask i18n: Xây dựng ứng dụng đa ngôn ngữ với Flask-Babel
Từ kiến thức gettext cơ bản đến triển khai thực tế: quốc tế hóa ứng dụng Flask bằng Flask-Babel, tệp PO và bản dịch AI tự động.
Cài đặt Flask-Babel
Flask-Babel là tiện ích mở rộng quốc tế hóa tiêu chuẩn cho Flask. Nó tích hợp GNU gettext với Flask và mẫu Jinja2, cung cấp sẵn hàm dịch, lựa chọn locale và hỗ trợ múi giờ.
pip install Flask-BabelCấu hình Babel
Tạo tệp babel.cfg để cho pybabel biết vị trí cần quét chuỗi có thể dịch, rồi khởi tạo Flask-Babel với hàm chọn locale nhằm xác định ngôn ngữ phục vụ từng yêu cầu.
# babel.cfg — tells pybabel where to find translatable strings
[python: **.py]
[jinja2: **/templates/**.html]
extensions=jinja2.ext.autoescape,jinja2.ext.with_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)Đánh dấu chuỗi cần dịch
Bọc mọi chuỗi dành cho người dùng bằng gettext() trong mã Python và _() trong mẫu Jinja2. Dùng lazy_gettext() cho chuỗi được xác định lúc tải mô-đun (như nhãn biểu mẫu và cấu hình) nhưng cần dịch sau khi xử lý yêu cầu.
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')){# 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>Trích xuất thông báo
Chạy pybabel extract để quét mã nguồn và mẫu nhằm tìm chuỗi có thể dịch. Lệnh này tạo tệp .pot (Portable Object Template). Sau đó, khởi tạo danh mục cho từng ngôn ngữ đích hoặc cập nhật danh mục hiện có khi chuỗi nguồn thay đổi.
# 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 translationsDịch tệp PO
Mở các tệp .po đã tạo và điền giá trị msgstr cho từng msgid. Tệp PO là văn bản thuần; bạn có thể sửa trực tiếp, dùng trình biên tập PO như Poedit hoặc tự động dịch bằng công cụ AI.
# 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"Biên dịch bản dịch
Dùng pybabel compile để biên dịch tệp .po thành tệp .mo nhị phân. Khi chạy, Flask-Babel đọc tệp .mo và không thể đọc trực tiếp tệp .po. Bạn phải biên dịch lại sau mỗi lần cập nhật bản dịch.
# 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.Xử lý dạng số nhiều và biến
Dùng ngettext() cho chuỗi phụ thuộc vào số lượng. Hàm nhận dạng số ít, dạng số nhiều và số lượng. Babel tự động dùng đúng quy tắc số nhiều cho từng ngôn ngữ: tiếng Anh có 2 dạng, tiếng Nga 3, tiếng Ả Rập 6 và tiếng Nhật 1.
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)# 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 товаров в вашей корзине"Thêm chức năng chuyển locale
Tạo bộ chọn ngôn ngữ lưu lựa chọn của người dùng trong phiên Flask. Cập nhật hàm locale_selector để kiểm tra phiên trước, rồi dự phòng bằng cơ chế phát hiện của trình duyệt.
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']
){# 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>Tự động dịch
Sau khi hoàn tất thiết lập Flask-Babel, hãy dùng AI để dịch tệp PO. Tự động hóa chu trình trích xuất–dịch–biên dịch trong quy trình CI/CD để đồng bộ bản dịch với mã nguồn.
# 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 translationsBổ sung: Dự phòng locale thông minh với flask-babel-locale-chain
Theo mặc định, Flask-Babel chuyển thẳng về locale mặc định khi locale ưu tiên của người dùng không khả dụng. Người dùng pt-BR chỉ có bản dịch pt-PT sẽ thấy tiếng Anh thay vì tiếng Bồ Đào Nha. flask-babel-locale-chain bổ sung chuỗi dự phòng có thể cấu hình để các locale liên quan chuyển tiếp tự nhiên.
# 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)Tự động kiểm soát chất lượng bản dịch
Lỗi thường gặp
Quên biên dịch .po thành .mo
Dùng gettext() ở cấp mô-đun
Trích xuất bỏ sót chuỗi
Lỗi bảng mã tệp PO
Cấu trúc tệp đề xuất
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/Dùng thử i18n Agent ngay
Thả tệp bản dịch của bạn vào đây
JSON, YAML, PO, XML, CSV, Markdown, Properties
hoặc nhấp để duyệt
Ngôn ngữ đích