Kembali ke blog

Mengapa Terjemahan Django Rosak (dan Cara Membetulkan 10 Punca Paling Biasa)

2026-02-04 9 min baca
Mengapa Terjemahan Django Rosak (dan Cara Membetulkan 10 Punca Paling Biasa)

Anda sudah menandakan string untuk terjemahan, menjana fail .po, menjalankan compilemessages, dan aplikasi anda masih menunjukkan Bahasa Inggeris. Anda tidak bersendirian. Rangka kerja i18n Django berkuasa, tetapi mempunyai tepi tajam yang menangkap walaupun pembangun berpengalaman.

Panduan ini merangkumi 10 sebab paling biasa terjemahan Django gagal secara senyap, dengan simptom tepat dan pembetulan untuk setiap satu.

1. Lupa Menjalankan compilemessages Selepas Menyunting Fail .po

Anda menyunting fail .po (secara manual atau dengan alat), tetapi teks yang diterjemahkan tidak pernah muncul. Aplikasi terus menunjukkan string Bahasa Inggeris asal.

Django tidak membaca fail .po semasa runtime. Ia membaca fail binari .mo (machine object) yang dikompilasi. Jika anda menyunting fail .po tanpa mengompilasi semula, Django tidak tahu apa-apa berubah.

Jalankan compilemessages selepas setiap perubahan fail .po:

python manage.py compilemessages

Jika anda mengautomasikan terjemahan dengan TranslateBot, tambahkan compilemessages sebagai langkah terakhir dalam aliran kerja anda:

python manage.py makemessages -a --no-obsolete
python manage.py translate
python manage.py compilemessages

2. Tiada {% load i18n %} dalam Templat

Anda menggunakan {% trans "Hello" %} dalam templat, tetapi Django mengeluarkan TemplateSyntaxError. Atau lebih teruk, tag tersebut secara senyap tidak melakukan apa-apa jika anda mempunyai enjin templat yang salah konfigurasi.

Tag {% trans %} dan {% blocktrans %} berada dalam pustaka tag templat i18n Django. Tanpa memuatnya, enjin templat tidak mengenalinya.

Tambahkan {% load i18n %} di bahagian atas setiap templat 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 keperluan bagi setiap templat. Walaupun templat induk memuat i18n, templat anak yang menggunakan tag terjemahan memerlukan deklarasi {% load i18n %} mereka sendiri.

3. LocaleMiddleware Tiada dalam MIDDLEWARE atau di Posisi Salah

Django sentiasa menyajikan kandungan dalam bahasa lalai tanpa mengira header Accept-Language pelayar, awalan URL, atau tetapan sesi.

LocaleMiddleware menentukan bahasa aktif untuk setiap permintaan. Tanpanya, Django lalai kepada LANGUAGE_CODE dan mengabaikan semua mekanisme pemilihan bahasa. Kedudukannya dalam timbunan middleware juga penting, kerana ia memerlukan akses kepada data sesi dan resolusi URL.

Tambahkan LocaleMiddleware pada tetapan MIDDLEWARE anda, selepas 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",
]

Juga pastikan django.conf.urls.i18n disertakan dalam konfigurasi URL anda jika anda menggunakan penukaran bahasa berdasarkan URL:

from django.conf.urls.i18n import i18n_patterns

urlpatterns = i18n_patterns(
    path("", include("myapp.urls")),
)

4. Ketidakpadanan Kod Bahasa (cth., pt-br vs pt_BR)

Terjemahan wujud dalam fail .po anda, compilemessages berjaya, tetapi Django mengabaikan terjemahan untuk locale tertentu.

Django menjangkakan direktori locale mengikut format <language>_<COUNTRY> dengan pemisah garis bawah dan kod negara huruf besar. Contohnya, pt_BR untuk Bahasa Portugis Brazil. Jika direktori anda dinamakan pt-br, pt-BR, atau ptBR, Django tidak akan menemuinya. Perkara yang sama berlaku untuk tetapan LANGUAGES: kod di sana menggunakan sempang (pt-br), tetapi sistem fail menggunakan garis bawah (pt_BR).

Pastikan struktur direktori anda sepadan dengan jangkaan Django:

locale/
    pt_BR/
        LC_MESSAGES/
            django.po
            django.mo

Dan dalam tetapan anda, gunakan bentuk bersempang:

LANGUAGES = [
    ("en", "English"),
    ("pt-br", "Brazilian Portuguese"),
    ("zh-hans", "Simplified Chinese"),
]

Apabila menjalankan makemessages, gunakan bentuk garis bawah untuk flag locale:

python manage.py makemessages -l pt_BR

5. Entri Fuzzy Dilangkau Secara Senyap Semasa Kompilasi

Terjemahan wujud dalam fail .po, tetapi Django menunjukkan string Bahasa Inggeris asal semasa runtime untuk entri tertentu itu. Ini sangat mengecewakan kerana terjemahan ada tepat di situ dalam fail.

Apabila makemessages Django mengesan string sumber yang berubah sedikit, ia menandakan terjemahan sedia ada sebagai "fuzzy" (bermaksud ia adalah tekaan yang memerlukan semakan manusia). Perintah compilemessages melangkau semua entri fuzzy, menganggapnya sebagai tidak diterjemah. Jadi entri kelihatan diterjemah dalam fail .po, tetapi fail .mo mengecualikannya sepenuhnya.

Entri fuzzy kelihatan seperti ini:

#, fuzzy
msgid "Welcome to our website!"
msgstr "Welkom op onze website!"

Semak terjemahan, kemas kini msgstr jika perlu, kemudian buang bendera #, fuzzy:

msgid "Welcome to our website!"
msgstr "Welkom op onze website!"

Kemudian kompilasi semula:

python manage.py compilemessages

Dalam projek yang lebih besar, entri fuzzy bertimbun dan mudah terlepas pandang. Perintah check_translations TranslateBot menangkap ini secara automatik:

python manage.py check_translations
locale/nl/LC_MESSAGES/django.po: 0 untranslated, 3 fuzzy
CommandError: Translation check failed

Tambahkan check_translations --makemessages pada saluran paip CI anda dan anda tidak akan pernah menghantar entri fuzzy lagi.

6. LOCALE_PATHS Tidak Dikonfigurasi atau Menghala ke Direktori Salah

makemessages mencipta fail .po di satu lokasi, tetapi Django mencarinya di tempat lain. Terjemahan wujud pada cakera tetapi tidak pernah dimuatkan.

Django mencari fail terjemahan dalam susunan tertentu: direktori LOCALE_PATHS dahulu, kemudian direktori locale/ setiap aplikasi, dan akhirnya direktori locale/ projek. Jika LOCALE_PATHS tidak ditetapkan atau menghala ke laluan yang salah, Django mungkin tidak akan pernah menemui fail .po anda.

Tetapkan LOCALE_PATHS dalam tetapan anda kepada laluan mutlak:

from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent

LOCALE_PATHS = [
    BASE_DIR / "locale",
]

Sahkan bahawa direktori wujud dan mengandungi struktur yang dijangka:

locale/
    de/
        LC_MESSAGES/
            django.po
            django.mo
    nl/
        LC_MESSAGES/
            django.po
            django.mo

Kesilapan biasa ialah menetapkan LOCALE_PATHS kepada locale/ (relatif) dan bukannya laluan mutlak. Django tidak menyelesaikan laluan relatif dari akar projek anda. Ia bergantung pada direktori kerja proses, yang selalunya bukan apa yang anda jangka.

7. Cache Menyajikan Terjemahan Lama

Anda mengemas kini dan mengompilasi terjemahan, tetapi teks lama terus muncul. Memulakan semula pelayan membetulkannya.

Katalog terjemahan Django dimuatkan sekali bagi setiap proses. Dalam pengeluaran, pelayan WSGI/ASGI seperti Gunicorn atau Uvicorn mengekalkan proses pekerja untuk tempoh yang lama. Fail .mo mungkin telah berubah pada cakera, tetapi proses yang sedang berjalan masih mempunyai terjemahan lama dalam memori. Selain itu, jika anda menggunakan rangka kerja cache Django atau proksi terbalik seperti Nginx atau Cloudflare, respons yang dicache akan menyajikan kandungan lama sehingga ia tamat tempoh.

Mulakan semula pelayan aplikasi anda selepas menggunakan terjemahan baharu:

# Gunicorn
kill -HUP $(cat /tmp/gunicorn.pid)

# Systemd
sudo systemctl restart myapp

# Docker
docker compose restart web

Untuk rangka kerja cache Django, kosongkan cache selepas mengemas kini terjemahan:

from django.core.cache import cache
cache.clear()

Dalam pembangunan, runserver memuat semula secara automatik apabila fail Python berubah tetapi tidak memantau fail .mo. Anda perlu memulakannya semula secara manual selepas menjalankan compilemessages.

8. String Tidak Dibalut dalam gettext (_() atau {% trans %})

makemessages tidak mengekstrak string tertentu, jadi ia tidak pernah muncul dalam fail .po anda dan tidak pernah diterjemahkan. Ini adalah isu paling asas dan juga yang paling mudah untuk terlepas pandang dalam pangkalan kod yang besar.

Perintah makemessages Django menggunakan xgettext untuk mengimbas kod sumber anda untuk penanda terjemahan. Jika string tidak dibalut dalam gettext() (biasanya dialiaskan sebagai _()), gettext_lazy(), {% trans %}, atau {% blocktrans %}, ia tidak kelihatan kepada proses pengekstrakan.

Balut 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 bendera --dry-run TranslateBot untuk melihat pratonton string yang belum diterjemah, supaya anda boleh menangkap string yang terlepas semasa pengekstrakan:

python manage.py translate --target-lang de --dry-run

Ini menunjukkan semua entri yang tidak diterjemah merentas fail .po anda tanpa membuat sebarang panggilan API atau perubahan.

9. f-string Tidak Boleh Diterjemahkan (Had Django)

Anda membalut f-string dalam _() dan sama ada mendapat ralat sintaks, atau makemessages mengekstrak string yang rosak/separa yang tidak boleh diterjemahkan.

f-string Python dinilai semasa runtime. Alat pengekstrakan xgettext mengurai kod sumber secara statik, jadi ia tidak boleh menilai ekspresi Python dalam kurungan {}. Ini bermaksud _(f"Hello, {name}") diekstrak sebagai string yang mengandungi ekspresi literal {name} (atau gagal diekstrak sepenuhnya), dan entri .po yang terhasil tidak akan pernah sepadan dengan string runtime.

Gunakan pemformatan % Django atau .format() dengan placeholder bernama sebaliknya:

# 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 had TranslateBot atau alat. Ia adalah asas kepada cara gettext berfungsi. String sumber mestilah literal statik supaya ia boleh diekstrak dan dicari semasa runtime.

TranslateBot mengekalkan semua format placeholder ini (%(name)s, {0}, %s, tag HTML) semasa terjemahan, jadi string yang diterjemahkan kekal berfungsi sepenuhnya.

10. Ralat Format String Placeholder Merosakkan Kompilasi .po

compilemessages gagal dengan ralat, atau fail .po mempunyai ketidakpadanan bendera #, python-format, dan entri digugurkan secara senyap.

Apabila string sumber mengandungi placeholder format Python seperti %(name)s, Django menandakan entri .po dengan #, python-format. Jika terjemahan mempunyai placeholder yang berbeza (kesilapan taip seperti %(nome)s, placeholder yang hilang, atau yang berlebihan) alat gettext mungkin menolak entri atau compilemessages mungkin gagal. Ini biasanya berlaku dengan terjemahan manual atau dengan alat terjemahan AI yang tidak memahami semantik placeholder.

Entri yang rosak kelihatan 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 sepatutnya %(name)s. Placeholder mesti sepadan dengan sumber dengan tepat.

Pastikan string yang diterjemah mengandungi placeholder yang sama tepat seperti sumber. Periksa kesilapan taip, placeholder yang hilang, dan placeholder yang berlebihan.

Ini adalah satu bidang di mana TranslateBot memberikan kelebihan sebenar. Logik pemeliharaan placeholder memastikan bahawa semua format string (%(name)s, {0}, %s) dalam output yang diterjemah sepadan dengan sumber dengan tepat. Pengendalian placeholder dilindungi oleh 100% liputan ujian, jadi ralat format string daripada terjemahan dihapuskan di peringkat alat dan bukannya ditangkap semasa kompilasi.

Jika anda menterjemah secara manual atau dengan alat yang tidak mengendalikan placeholder, sahkan fail .po anda dengan:

msgfmt --check-format locale/de/LC_MESSAGES/django.po

Ini menjalankan pengesahan format string gettext dan melaporkan sebarang ketidakpadanan.

Menggabungkan Semuanya: Aliran Kerja Pertahanan

Kebanyakan isu ini berkongsi punca utama yang sama: langkah manual yang mudah dilupakan. Berikut ialah aliran kerja yang mencegah kesemua 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 pada saluran paip CI anda dan string yang tidak diterjemah, entri fuzzy, dan ralat format akan gagalkan binaan sebelum sampai ke pengeluaran.

Jadual Rujukan Pantas

Punca Simptom Pembetulan Satu Baris
Tiada compilemessages Terjemahan wujud tetapi tidak muncul python manage.py compilemessages
Tiada {% load i18n %} TemplateSyntaxError pada {% trans %} Tambah {% load i18n %} pada templat
Tiada LocaleMiddleware Bahasa sentiasa lalai kepada Inggeris Tambah django.middleware.locale.LocaleMiddleware pada MIDDLEWARE
Ketidakpadanan kod bahasa Direktori locale tidak ditemui Guna pt_BR (garis bawah) untuk direktori, pt-br (sempang) untuk tetapan
Entri fuzzy dilangkau Terjemahan dalam .po tetapi tiada dalam app Buang bendera #, fuzzy selepas semakan
LOCALE_PATHS salah Fail .po wujud tetapi Django abaikan Tetapkan LOCALE_PATHS kepada laluan mutlak
Terjemahan dicache Teks lama muncul selepas kemas kini Mulakan semula pelayan aplikasi
String tiada dalam gettext String hilang daripada fail .po Balut dalam _() atau {% trans %}
f-string dalam gettext Pengekstrakan rosak atau ketidakpadanan runtime Ganti dengan placeholder % atau .format()
Ketidakpadanan placeholder compilemessages gagal atau entri digugurkan Padankan placeholder dengan tepat antara sumber dan terjemahan

Kebanyakan masalah ini hilang apabila anda mengautomasikan langkah terjemahan dan menguatkuasakan semakan dalam CI. Perintah translate TranslateBot mengendalikan pemeliharaan placeholder dan terjemahan bertahap, manakala check_translations menangkap apa sahaja yang terlepas (entri tidak diterjemah, bendera fuzzy, dan isu format string) sebelum ia sampai ke pengeluaran.

Hentikan pengeditan fail .po secara manual

TranslateBot mengautomasikan terjemahan Django dengan AI. Satu arahan, semua bahasa anda, sen sahaja setiap terjemahan.