Skip to main content

Flask i18n : สร้างแอปหลายภาษาด้วย Flask-Babel

ตั้งแต่พื้นฐาน gettext จนถึงการดีพลอยระบบจริง ทำให้แอป Flask รองรับหลายภาษาด้วย Flask-Babel ไฟล์ PO และการแปลอัตโนมัติด้วย AI

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 หรือทำให้การแปลเป็นอัตโนมัติด้วยเครื่องมือ 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"
ไฟล์ 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 ด้วย AI ทำให้วงจรแยก-แปล-คอมไพล์ในไปป์ไลน์ 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 จับคีย์ที่หายไปและตัวยึดตำแหน่งเสียหายก่อนส่งขึ้นใช้งาน แล้วทดสอบ UI ด้วยคำแปลจำลองผ่าน 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 เพื่อแยกการเรียกแบบ lazy และตรวจว่าเทมเพลต 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