Skip to main content

Flask i18n: Buat Aplikasi Multibahasa dengan Flask-Babel

Dari dasar gettext hingga deployment produksi: internasionalisasikan aplikasi Flask dengan Flask-Babel, file PO, dan penerjemahan AI otomatis.

1

Instal Flask-Babel

Flask-Babel adalah ekstensi internasionalisasi standar untuk Flask. Ekstensi ini mengintegrasikan GNU gettext dengan templat Flask dan Jinja2 serta langsung menyediakan fungsi terjemahan, pemilihan bahasa, dan dukungan zona waktu.

Flask-Babel membungkus Babel (library i18n Python) dan mengintegrasikannya dengan siklus permintaan Flask. Anda mendapatkan fungsi gettext(), ngettext(), dan lazy_gettext(), ditambah deteksi bahasa otomatis dari header browser.
Terminal
pip install Flask-Babel
2

Konfigurasikan Babel

Buat file babel.cfg untuk memberi tahu pybabel tempat memindai string yang dapat diterjemahkan, lalu inisialisasi Flask-Babel dengan fungsi pemilih bahasa yang menentukan bahasa bagi setiap permintaan.

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)
Fungsi locale_selector dipanggil pada setiap permintaan. Jika mengembalikan bahasa tanpa file .mo terkompilasi, Flask-Babel beralih ke bahasa default tanpa pesan. Tidak ada kesalahan—string hanya tampil belum diterjemahkan.
3

Tandai String untuk Diterjemahkan

Bungkus setiap string yang terlihat pengguna dengan gettext() dalam kode Python dan _() dalam templat Jinja2. Gunakan lazy_gettext() untuk string yang ditentukan saat modul dimuat (seperti label formulir dan konfigurasi) dan perlu diterjemahkan kemudian saat permintaan.

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>
Gunakan _() dalam templat Jinja2 alih-alih gettext()—ini singkatan gettext standar dan menjaga templat tetap rapi. Flask-Babel mendaftarkan _() sebagai global Jinja2 secara otomatis.
4

Ekstrak Pesan

Jalankan pybabel extract untuk memindai kode sumber dan templat bagi string yang dapat diterjemahkan. Ini membuat file .pot (Portable Object Template). Lalu inisialisasi katalog untuk setiap bahasa target atau perbarui katalog yang ada saat string sumber berubah.

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
Selalu jalankan pybabel update (bukan init) setelah ekstraksi pertama. Menjalankan init pada direktori bahasa yang ada akan menimpa semua terjemahan. Perintah update menggabungkan string baru sekaligus mempertahankan terjemahan yang ada.
5

Terjemahkan File-file PO

Buka file .po yang dibuat dan isi nilai msgstr untuk setiap msgid. File PO berupa teks biasa—Anda dapat mengeditnya langsung, menggunakan editor PO seperti Poedit, atau mengotomatiskan penerjemahan dengan alat 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"
File PO menyertakan komentar konteks (baris yang diawali dengan #:) yang menunjukkan tempat setiap string digunakan dalam kode sumber. Penerjemah menggunakannya untuk memahami konteks. Pertahankan komentar tersebut—dibuat secara otomatis oleh pybabel extract.
6

Kompilasi Terjemahan

Kompilasi file .po menjadi file biner .mo menggunakan pybabel compile. Flask-Babel membaca file .mo saat runtime—tidak dapat membaca file .po secara langsung. Anda harus mengompilasi ulang setelah setiap pembaruan terjemahan.

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.
Jika terjemahan tidak tampil setelah file .po diedit, Anda hampir pasti lupa menjalankan pybabel compile. Ini masalah Flask-Babel paling umum. Tambahkan langkah kompilasi ke skrip deployment untuk menghindarinya di produksi.
7

Tangani Bentuk Jamak dan Variabel

Gunakan ngettext() untuk string yang peka terhadap bentuk jamak. Fungsi menerima bentuk tunggal, bentuk jamak, dan jumlah. Babel menggunakan aturan bentuk jamak yang benar untuk setiap bahasa secara otomatis—Inggris memiliki 2 bentuk, Rusia 3, Arab 6, dan Jepang 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 товаров в вашей корзине"
Jangan gunakan if count == 1 untuk logika bentuk jamak. Bahasa seperti Prancis menganggap 0 sebagai tunggal. Rusia dan Arab memiliki bentuk yang tidak ada dalam bahasa Inggris. Biarkan ngettext() dan aturan bentuk jamak CLDR Babel menangani pemilihan.
8

Tambahkan Pengalihan Bahasa

Buat pemilih bahasa yang menyimpan pilihan pengguna dalam sesi Flask. Perbarui fungsi locale_selector untuk memeriksa sesi terlebih dahulu, lalu beralih ke deteksi browser.

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>
Setelah mengubah bahasa sesi, panggil flask_babel.refresh() untuk memaksa Flask-Babel membaca ulang bahasa bagi permintaan saat ini. Tanpa refresh(), bahasa lama bertahan hingga permintaan berikutnya.
9

Otomatiskan Penerjemahan

Setelah penyiapan Flask-Babel selesai, terjemahkan file PO dengan AI. Otomatiskan siklus ekstrak-terjemahkan-kompilasi dalam pipeline CI/CD agar terjemahan tetap sinkron dengan kode sumber.

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
Terjemahkan secara bertahap—saat menambahkan string baru dan menjalankan pybabel update, terjemahkan hanya entri baru yang belum diterjemahkan (nilai msgstr kosong), bukan membuat ulang semuanya. Ini mempertahankan terjemahan yang telah ditinjau manusia.

Bonus: Fallback Bahasa Cerdas dengan flask-babel-locale-chain

Secara default, Flask-Babel langsung beralih ke bahasa default saat bahasa pilihan pengguna tidak tersedia. Pengguna pt-BR yang hanya memiliki terjemahan pt-PT melihat bahasa Inggris, bukan Portugis. flask-babel-locale-chain menambahkan rantai fallback yang dapat dikonfigurasi agar bahasa terkait beralih secara alami.

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 adalah paket Python sumber terbuka. Lihat di GitHub pada github.com/i18n-agent/flask-babel-locale-chain.

Otomatiskan Kualitas Terjemahan

Temukan kunci hilang dan placeholder rusak sebelum dirilis dengan i18n-validate. Uji UI dengan terjemahan semu menggunakan i18n-pseudo sebelum terjemahan asli tersedia.

Kesalahan Umum

Lupa Mengompilasi .po menjadi .mo

Flask-Babel membaca file .mo terkompilasi, bukan file .po. Jika terjemahan tidak tampil setelah file .po diedit, jalankan pybabel compile -d translations. Tambahkan langkah ini ke skrip deployment.

Menggunakan gettext() di Tingkat Modul

gettext() memerlukan konteks permintaan. Jika Anda menentukan string terjemahan di tingkat modul (atribut kelas, konstanta), gunakan lazy_gettext(). Fungsi ini menunda penerjemahan hingga string benar-benar dirender dalam permintaan.

Ekstraksi Melewatkan String

pybabel extract hanya memindai file yang cocok dengan pola dalam babel.cfg. Jika string tidak diekstrak: pastikan pola babel.cfg cocok dengan struktur file, tambahkan -k lazy_gettext untuk mengekstrak panggilan lazy, dan pastikan templat Jinja2 menggunakan ekstensi yang benar (.html, .jinja2).

Kesalahan Pengodean File PO

File PO harus dikodekan sebagai UTF-8. Jika Anda melihat UnicodeDecodeError, periksa header Content-Type dalam file .po: harus tertulis charset=UTF-8. Beberapa editor menyimpan dengan pengodean berbeda—selalu verifikasi setelah mengedit.

Struktur File yang Disarankan

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/

Coba i18n Agent Sekarang

Lepaskan file terjemahan Anda di sini

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

atau klik untuk menjelajahi

Bahasa target

Tidak perlu mendaftarEstimasi instan

FAQ Flask i18n