Anda sudah menandai string untuk diterjemahkan, membuat file .po, menjalankan compilemessages, dan aplikasi Anda masih menampilkan bahasa Inggris. Anda tidak sendirian. Framework i18n Django sangat powerful, tetapi memiliki jebakan yang menangkap bahkan developer berpengalaman.
Panduan ini mencakup 10 alasan paling umum mengapa terjemahan Django gagal secara diam-diam, dengan gejala dan perbaikan yang tepat untuk masing-masing.
1. Lupa Menjalankan compilemessages Setelah Mengedit File .po
Anda mengedit file .po (secara manual atau dengan alat), tetapi teks terjemahan tidak pernah muncul. Aplikasi terus menampilkan string bahasa Inggris asli.
Django tidak membaca file .po saat runtime. Django membaca file biner .mo (machine object) yang sudah dikompilasi. Jika Anda mengedit file .po tanpa mengompilasi ulang, Django tidak tahu ada yang berubah.
Jalankan compilemessages setelah setiap perubahan file .po:
python manage.py compilemessages
Jika Anda mengotomatiskan terjemahan dengan TranslateBot, tambahkan compilemessages sebagai langkah terakhir dalam alur kerja Anda:
python manage.py makemessages -a --no-obsolete
python manage.py translate
python manage.py compilemessages
2. {% load i18n %} Tidak Ada di Template
Anda menggunakan {% trans "Hello" %} di template, tetapi Django memunculkan TemplateSyntaxError. Atau lebih buruk, tag tersebut diam-diam tidak melakukan apa-apa jika engine template Anda salah konfigurasi.
Tag {% trans %} dan {% blocktrans %} ada di library tag template i18n Django. Tanpa memuatnya, engine template tidak mengenalinya.
Tambahkan {% load i18n %} di bagian atas setiap template yang menggunakan tag terjemahan:
{% load i18n %}
<h1>{% trans "Welcome to our site" %}</h1>
<p>{% blocktrans with name=user.name %}Hello, {{ name }}!{% endblocktrans %}</p>
Ini adalah persyaratan per-template. Meskipun template induk memuat i18n, template anak yang menggunakan tag terjemahan memerlukan deklarasi {% load i18n %} mereka sendiri.
3. LocaleMiddleware Tidak Ada di MIDDLEWARE atau di Posisi yang Salah
Django selalu menyajikan konten dalam bahasa default tanpa memperhatikan header Accept-Language browser, prefix URL, atau pengaturan sesi.
LocaleMiddleware menentukan bahasa aktif untuk setiap request. Tanpanya, Django default ke LANGUAGE_CODE dan mengabaikan semua mekanisme pemilihan bahasa. Posisinya di stack middleware juga penting, karena memerlukan akses ke data sesi dan resolusi URL.
Tambahkan LocaleMiddleware ke pengaturan MIDDLEWARE Anda, setelah SessionMiddleware dan CommonMiddleware:
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.locale.LocaleMiddleware", # Must be after SessionMiddleware
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
]
Pastikan juga django.conf.urls.i18n disertakan dalam konfigurasi URL Anda jika Anda menggunakan penggantian bahasa berbasis URL:
from django.conf.urls.i18n import i18n_patterns
urlpatterns = i18n_patterns(
path("", include("myapp.urls")),
)
4. Ketidakcocokan Kode Bahasa (misalnya pt-br vs pt_BR)
Terjemahan ada di file .po Anda, compilemessages berhasil, tetapi Django mengabaikan terjemahan untuk locale tertentu.
Django mengharapkan direktori locale mengikuti format <language>_<COUNTRY> dengan pemisah underscore dan kode negara huruf besar. Misalnya, pt_BR untuk Portugis Brasil. Jika direktori Anda bernama pt-br, pt-BR, atau ptBR, Django tidak akan menemukannya. Hal yang sama berlaku untuk pengaturan LANGUAGES: kode di sana menggunakan tanda hubung (pt-br), tetapi filesystem menggunakan underscore (pt_BR).
Pastikan struktur direktori Anda sesuai dengan ekspektasi Django:
locale/
pt_BR/
LC_MESSAGES/
django.po
django.mo
Dan di pengaturan Anda, gunakan bentuk dengan tanda hubung:
LANGUAGES = [
("en", "English"),
("pt-br", "Brazilian Portuguese"),
("zh-hans", "Simplified Chinese"),
]
Saat menjalankan makemessages, gunakan bentuk underscore untuk flag locale:
python manage.py makemessages -l pt_BR
5. Entri Fuzzy Dilewati Secara Diam-diam Saat Kompilasi
Terjemahan ada di file .po, tetapi Django menampilkan string bahasa Inggris asli saat runtime untuk entri tersebut. Ini sangat membuat frustrasi karena terjemahannya ada di sana dalam file.
Ketika makemessages Django mendeteksi string sumber yang sedikit berubah, ia menandai terjemahan yang ada sebagai "fuzzy" (artinya ini adalah tebakan yang memerlukan tinjauan manusia). Perintah compilemessages melewati semua entri fuzzy, memperlakukannya sebagai tidak diterjemahkan. Jadi entri tersebut terlihat diterjemahkan di file .po, tetapi file .mo mengecualikannya sepenuhnya.
Entri fuzzy terlihat seperti ini:
#, fuzzy
msgid "Welcome to our website!"
msgstr "Welkom op onze website!"
Tinjau terjemahan, perbarui msgstr jika diperlukan, lalu hapus flag #, fuzzy:
msgid "Welcome to our website!"
msgstr "Welkom op onze website!"
Lalu kompilasi ulang:
python manage.py compilemessages
Di proyek yang lebih besar, entri fuzzy menumpuk dan mudah terlewat. Perintah check_translations TranslateBot menangkap ini secara otomatis:
python manage.py check_translations
locale/nl/LC_MESSAGES/django.po: 0 untranslated, 3 fuzzy
CommandError: Translation check failed
Tambahkan ke pipeline CI Anda dengan check_translations --makemessages dan Anda tidak akan pernah mengirimkan entri fuzzy lagi.
6. LOCALE_PATHS Tidak Dikonfigurasi atau Menunjuk ke Direktori yang Salah
makemessages membuat file .po di satu lokasi, tetapi Django mencarinya di tempat lain. Terjemahan ada di disk tetapi tidak pernah dimuat.
Django mencari file terjemahan dalam urutan tertentu: direktori LOCALE_PATHS terlebih dahulu, lalu direktori locale/ setiap aplikasi, dan terakhir direktori locale/ proyek. Jika LOCALE_PATHS tidak diatur atau menunjuk ke path yang salah, Django mungkin tidak pernah menemukan file .po Anda.
Atur LOCALE_PATHS di pengaturan Anda ke path absolut:
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
LOCALE_PATHS = [
BASE_DIR / "locale",
]
Verifikasi bahwa direktori ada dan berisi struktur yang diharapkan:
locale/
de/
LC_MESSAGES/
django.po
django.mo
nl/
LC_MESSAGES/
django.po
django.mo
Kesalahan umum adalah mengatur LOCALE_PATHS ke locale/ (relatif) alih-alih path absolut. Django tidak menyelesaikan path relatif dari root proyek Anda. Itu bergantung pada direktori kerja proses, yang sering kali bukan yang Anda harapkan.
7. Cache Menyajikan Terjemahan yang Sudah Usang
Anda memperbarui dan mengompilasi terjemahan, tetapi teks lama terus muncul. Memulai ulang server memperbaikinya.
Katalog terjemahan Django dimuat sekali per proses. Di produksi, server WSGI/ASGI seperti Gunicorn atau Uvicorn menjaga proses worker tetap hidup untuk waktu yang lama. File .mo mungkin sudah berubah di disk, tetapi proses yang berjalan masih memiliki terjemahan lama di memori. Selain itu, jika Anda menggunakan framework cache Django atau reverse proxy seperti Nginx atau Cloudflare, respons yang di-cache akan menyajikan konten lama sampai kedaluwarsa.
Mulai ulang server aplikasi Anda setelah men-deploy terjemahan baru:
# Gunicorn
kill -HUP $(cat /tmp/gunicorn.pid)
# Systemd
sudo systemctl restart myapp
# Docker
docker compose restart web
Untuk framework cache Django, bersihkan cache setelah memperbarui terjemahan:
from django.core.cache import cache
cache.clear()
Di pengembangan, runserver otomatis memuat ulang ketika file Python berubah tetapi tidak memantau file .mo. Anda perlu memulai ulang secara manual setelah menjalankan compilemessages.
8. String Tidak Dibungkus dalam gettext (_() atau {% trans %})
makemessages tidak mengekstrak string tertentu, sehingga tidak pernah muncul di file .po Anda dan tidak pernah diterjemahkan. Ini adalah masalah paling dasar dan juga paling mudah terlewat di codebase besar.
Perintah makemessages Django menggunakan xgettext untuk memindai kode sumber Anda mencari penanda terjemahan. Jika string tidak dibungkus dalam gettext() (biasanya dialiaskan sebagai _()), gettext_lazy(), {% trans %}, atau {% blocktrans %}, string tersebut tidak terlihat oleh proses ekstraksi.
Bungkus setiap string yang menghadap pengguna:
# Python code
from django.utils.translation import gettext_lazy as _
class Article(models.Model):
class Meta:
verbose_name = _("article")
verbose_name_plural = _("articles")
# Views
from django.utils.translation import gettext as _
def my_view(request):
message = _("Your changes have been saved.")
return HttpResponse(message)
<!-- Templates -->
{% load i18n %}
<h1>{% trans "Welcome" %}</h1>
<p>{% blocktrans with count=items|length %}You have {{ count }} items.{% endblocktrans %}</p>
Gunakan flag --dry-run TranslateBot untuk melihat pratinjau string yang saat ini belum diterjemahkan, sehingga Anda dapat menangkap string yang terlewat selama ekstraksi:
python manage.py translate --target-lang de --dry-run
Ini menampilkan semua entri yang belum diterjemahkan di file .po Anda tanpa melakukan panggilan API atau perubahan apa pun.
9. f-string Tidak Dapat Diterjemahkan (Keterbatasan Django)
Anda membungkus f-string dalam _() dan mendapatkan syntax error, atau makemessages mengekstrak string yang rusak/parsial yang tidak dapat diterjemahkan.
F-string Python dievaluasi saat runtime. Alat ekstraksi xgettext mengurai kode sumber secara statis, sehingga tidak dapat mengevaluasi ekspresi Python di dalam kurung kurawal {}. Ini berarti _(f"Hello, {name}") diekstrak sebagai string yang berisi ekspresi literal {name} (atau gagal diekstrak sepenuhnya), dan entri .po yang dihasilkan tidak akan pernah cocok dengan string saat runtime.
Gunakan format % Django atau .format() dengan placeholder bernama sebagai gantinya:
# Wrong -- f-string cannot be extracted
message = _(f"Hello, {user.name}! You have {count} new messages.")
# Correct -- named placeholders
message = _("Hello, %(name)s! You have %(count)d new messages.") % {
"name": user.name,
"count": count,
}
# Also correct -- .format() with positional args
message = _("Hello, {0}! You have {1} new messages.").format(user.name, count)
Ini bukan keterbatasan TranslateBot atau alat. Ini fundamental terhadap cara kerja gettext. String sumber harus berupa literal statis agar dapat diekstrak dan dicari saat runtime.
TranslateBot mempertahankan semua format placeholder ini (%(name)s, {0}, %s, tag HTML) selama terjemahan, sehingga string yang diterjemahkan tetap berfungsi penuh.
10. Error Format String Placeholder Merusak Kompilasi .po
compilemessages gagal dengan error, atau file .po memiliki ketidakcocokan flag #, python-format, dan entri diam-diam dibuang.
Ketika string sumber berisi placeholder format Python seperti %(name)s, Django menandai entri .po dengan #, python-format. Jika terjemahan memiliki placeholder yang berbeda (typo seperti %(nome)s, placeholder yang hilang, atau yang tambahan), alat gettext mungkin menolak entri tersebut atau compilemessages mungkin gagal. Ini biasa terjadi dengan terjemahan manual atau dengan alat terjemahan AI yang tidak memahami semantik placeholder.
Entri yang rusak terlihat seperti ini:
#, python-format
msgid "Hello, %(name)s! You have %(count)d new messages."
msgstr "Hallo, %(naam)s! Je hebt %(count)d nieuwe berichten."
Di sini %(naam)s seharusnya %(name)s. Placeholder harus cocok dengan sumber secara persis.
Pastikan string yang diterjemahkan berisi placeholder yang sama persis dengan sumber. Periksa typo, placeholder yang hilang, dan placeholder tambahan.
Ini adalah area di mana TranslateBot memberikan keuntungan nyata. Logika pelestarian placeholder-nya memastikan bahwa semua format string (%(name)s, {0}, %s) di output terjemahan cocok dengan sumber secara persis. Penanganan placeholder dicakup oleh 100% cakupan tes, sehingga error format string dari terjemahan dieliminasi di tingkat alat, bukan ditangkap saat kompilasi.
Jika Anda menerjemahkan secara manual atau dengan alat yang tidak menangani placeholder, validasi file .po Anda dengan:
msgfmt --check-format locale/de/LC_MESSAGES/django.po
Ini menjalankan validasi format string gettext dan melaporkan ketidakcocokan apa pun.
Menyatukan Semuanya: Alur Kerja Defensif
Sebagian besar masalah ini berbagi akar penyebab: langkah manual yang mudah dilupakan. Berikut alur kerja yang mencegah semua 10 masalah:
# 1. Extract strings (catches #8 -- any new gettext-wrapped strings)
python manage.py makemessages -a --no-obsolete
# 2. Translate (catches #1, #5, #8, #9, #10 -- handles untranslated,
# fuzzy, and placeholder issues automatically)
python manage.py translate
# 3. Compile (catches #1 -- generates .mo files)
python manage.py compilemessages
# 4. Verify in CI (catches everything that slipped through)
python manage.py check_translations --makemessages
Tambahkan langkah 4 ke pipeline CI Anda dan string yang belum diterjemahkan, entri fuzzy, dan error format akan menggagalkan build sebelum mencapai produksi.
Tabel Referensi Cepat
| Penyebab | Gejala | Perbaikan Satu Baris |
|---|---|---|
Tidak ada compilemessages |
Terjemahan ada tapi tidak muncul | python manage.py compilemessages |
{% load i18n %} hilang |
TemplateSyntaxError pada {% trans %} |
Tambahkan {% load i18n %} ke template |
| LocaleMiddleware hilang | Bahasa selalu default ke Inggris | Tambahkan django.middleware.locale.LocaleMiddleware ke MIDDLEWARE |
| Kode bahasa tidak cocok | Direktori locale tidak ditemukan | Gunakan pt_BR (underscore) untuk direktori, pt-br (tanda hubung) untuk pengaturan |
| Entri fuzzy dilewati | Terjemahan di .po tapi tidak di aplikasi |
Hapus flag #, fuzzy setelah ditinjau |
| LOCALE_PATHS salah | File .po ada tapi Django mengabaikannya |
Atur LOCALE_PATHS ke path absolut |
| Terjemahan di-cache | Teks lama muncul setelah pembaruan | Mulai ulang server aplikasi |
| String tidak di gettext | String hilang dari file .po |
Bungkus dalam _() atau {% trans %} |
| f-string di gettext | Ekstraksi rusak atau ketidakcocokan runtime | Ganti dengan placeholder % atau .format() |
| Placeholder tidak cocok | compilemessages gagal atau entri dibuang |
Cocokkan placeholder secara persis antara sumber dan terjemahan |
Sebagian besar masalah ini hilang ketika Anda mengotomatiskan langkah terjemahan dan menerapkan pemeriksaan di CI. Perintah translate TranslateBot menangani pelestarian placeholder dan terjemahan inkremental, sementara check_translations menangkap apa pun yang lolos (entri yang belum diterjemahkan, flag fuzzy, dan masalah format string) sebelum mencapai produksi.