# Django 多語言處理:避免「Polish」被誤譯為「波蘭語」的悲劇(Contextual Markers) 當你正確支援多語言(i18n)時,往往會遇到以下情況。 * 按鈕上寫著 **“Polish”**(修飾 UI),但翻譯結果卻是 **“波蘭語”** * 日期選擇器中的 **“May”** 在某些語言中被譯成像人名一樣的詞 * 菜單中的 **“Book”** 只被譯成「書」,卻無法翻譯成「預約」 問題的根源很簡單: > 電腦不懂「語境(context)」;相同字串會被視為同一個。 本文將說明如何利用 Django 的 **Contextual Markers**,在不改變原始程式碼的前提下,為同一字串提供不同的翻譯。 --- ## 為什麼會發生衝突?gettext 的基本行為 Django 的翻譯系統內部使用 GNU gettext。gettext 的規則非常簡單: * 原文字串 → `msgid` * 翻譯字串 → `msgstr` * **相同 `msgid` 必須使用相同 `msgstr`** ```po # django.po msgid "Polish" msgstr "波蘭語" ``` 一旦 `.po` 檔案中出現這樣的映射,無論在哪裡使用「Polish」,都會得到同樣的翻譯。 這就導致以下兩個 UI 無法區分: * 「Polish」= 修飾 UI 的動詞 * 「Polish」= 語言/國家名稱 為了避免衝突,有時會改寫程式碼: ```html {% trans "Polish (verb)" %} {% trans "Polish (language)" %} ``` 雖然翻譯能正確,但顯示的字串會變得怪異,且難以在其他地方重複使用。 我們想要的是: * 原始字串保持 `"Polish"` * 透過額外說明告訴翻譯系統「現在是什麼語境」 這正是 **Contextual Marker(語境資訊)** 的用途。 --- ## 在模板中解決:`{% translate %}` + `context` 在 Django 模板中,可以在 `{% translate %}`(或舊版 `{% trans %}`)標籤後加上 `context` 選項,將同一字串按語境區分。 ### 1) 原始程式碼(衝突) ```html {% load i18n %} {% translate "Polish" %} ``` 在 `.po` 檔案中,兩者皆為 `msgid "Polish"`,只能選擇一種翻譯。 ### 2) 改進後的程式碼(使用 context) ```html {% load i18n %} {% translate "Polish" context "language name" %} ``` 重點: * `context` 後的字串**不會顯示給使用者**。 * 只作為翻譯系統與翻譯者的**元資料**。 * 建議簡短、明確地說明語境。 * `"verb: to refine UI"` * `"language name"` * `"menu label"` * `"button text"` 等 ### 3) `{% blocktranslate %}` 也可使用 對於較長句子,使用 `{% blocktranslate %}`(或 `{% blocktrans %}`)時也能加上 `context`: ```html {% load i18n %} {% blocktranslate context "greeting message" with username=user.username %} Hello {{ username }} {% endblocktranslate %} ``` 這樣同一樣式的句子也能在不同語境下使用。 --- ## 在 Python 程式碼中解決:`pgettext` 在模板之外(如 views、models、forms)使用 `gettext` 的同時,應改用 `pgettext` 系列函式。 常見函式: * `pgettext(context, message)` * `pgettext_lazy(context, message)` – 延遲評估(模型欄位、模組層級) * `npgettext(context, singular, plural, number)` – 同時處理複數與語境 ### 1) 基本範例 ```python from django.utils.translation import pgettext def my_view(request): # 1. 月份 "May" month = pgettext("month name", "May") # 2. 人名 "May" person = pgettext("person name", "May") # 3. 助動詞 "may"(可能) verb = pgettext("auxiliary verb", "may") ``` 雖然 `msgid` 相同,但不同 `context` 讓翻譯能各不相同。 ### 2) 在模型中使用 `pgettext_lazy` 模型欄位的 `verbose_name`、`help_text` 或 `choices` 也常遇到同音異義問題: ```python from django.db import models from django.utils.translation import pgettext_lazy class Order(models.Model): # "Order" = 訂單 type = models.CharField( verbose_name=pgettext_lazy("order model field", "Order type"), max_length=20, ) STATUS_CHOICES = [ # "Open" = 狀態 ("open", pgettext_lazy("order status", "Open")), # "Open" = 動作(打開) ("opened", pgettext_lazy("log action", "Open")), ] status = models.CharField( max_length=20, choices=STATUS_CHOICES, ) ``` ### 3) 同時處理複數:`npgettext` ```python from django.utils.translation import npgettext def get_notification(count): return npgettext( "user notification", # context "You have %(count)d message", # singular "You have %(count)d messages", # plural count ) % {"count": count} ``` --- ## `.po` 檔案會長什麼樣? 使用 `context` 後,執行 ```bash python manage.py makemessages -l ko ``` `.po` 檔案會加入 `msgctxt` 欄位: ```po # django.po # 1) "Polish" = UI 修飾動詞 msgctxt "verb: to refine UI" msgid "Polish" msgstr "修飾" # 2) "Polish" = 語言/國家名稱 msgctxt "language name" msgid "Polish" msgstr "波蘭語" ``` `msgid` 相同,但 `msgctxt` 不同,翻譯工具會將它們視為獨立條目,翻譯者也能更清楚地了解語境。 --- ## 如何撰寫好的 Context Context 字串雖不會顯示給使用者,但對翻譯者而言是關鍵提示。建議遵循以下原則: ### 1) 以「角色」說明 * `"button label"` * `"menu item"` * `"tooltip"` * `"error message"` * `"form field label"` 說明 UI 角色能讓多語言翻譯保持一致。 ### 2) 再加上「概念」 同一單字在不同概念下使用: * `"File"` * `"file menu item"` * `"uploaded file object"` * `"Order"` * `"e-commerce order"` * `"sorting order"` 這樣能大幅提升翻譯穩定性。 ### 3) 切勿把翻譯結果寫進 Context Context 只作說明,切勿放入翻譯文字,否則管理困難且會干擾自動翻譯。 --- ## 何時使用 Contextual Markers 以下情況幾乎必須使用 `context`/`pgettext`: 1. **短字串(1–2 個單詞)**:按鈕文字、標籤、菜單項 2. **同一字串在 UI 中有多種意義**: * `"Open"`、`"Close"`、`"Save"` 等動作 * `"Book"`、`"Order"` 等名詞/動詞 3. **翻譯者無法直接看到畫面**: * 使用翻譯平台 * 只交 `.po` 檔給外部翻譯商 --- ## 小結 ![開發者因同音異義翻譯而困惑的畫面](/media/editor_temp/6/14112b82-d049-4b32-9275-02429e9305c0.png) * Django 只允許同一 `msgid` 對應一個翻譯,導致同音異義詞常出錯。 * 透過 `{% translate "…" context "…" %}`(模板)或 `pgettext` 系列(Python)加入語境,`.po` 檔案會自動產生 `msgctxt`。 * 這樣既不改變原始程式碼,又能在不同語境下提供獨立翻譯,提升翻譯品質與維護性。 在多語言日益重要的時代,Contextual Markers 是 Django 開發者必備的 i18n 工具。 --- **相關文章** - [Django 中 gettext vs gettext_lazy 的區別:從評估時點說明](/ko/whitedec/2026/1/5/django-gettext-vs-gettext-lazy/) - [將 gettext_lazy 用於 JSON 鍵時的問題與解決方案](/ko/whitedec/2025/4/26/gettext-lazy-json-key-problem-solution/)