Django 多語言處理:避免「Polish」被誤譯為「波蘭語」的悲劇(Contextual Markers)
當你正確支援多語言(i18n)時,往往會遇到以下情況。
- 按鈕上寫著 “Polish”(修飾 UI),但翻譯結果卻是 “波蘭語”
- 日期選擇器中的 “May” 在某些語言中被譯成像人名一樣的詞
- 菜單中的 “Book” 只被譯成「書」,卻無法翻譯成「預約」
問題的根源很簡單:
電腦不懂「語境(context)」;相同字串會被視為同一個。
本文將說明如何利用 Django 的 Contextual Markers,在不改變原始程式碼的前提下,為同一字串提供不同的翻譯。
為什麼會發生衝突?gettext 的基本行為
Django 的翻譯系統內部使用 GNU gettext。gettext 的規則非常簡單:
- 原文字串 →
msgid - 翻譯字串 →
msgstr - 相同
msgid必須使用相同msgstr
# django.po
msgid "Polish"
msgstr "波蘭語"
一旦 .po 檔案中出現這樣的映射,無論在哪裡使用「Polish」,都會得到同樣的翻譯。
這就導致以下兩個 UI 無法區分:
- 「Polish」= 修飾 UI 的動詞
- 「Polish」= 語言/國家名稱
為了避免衝突,有時會改寫程式碼:
<!-- 不建議的寫法 -->
{% trans "Polish (verb)" %}
{% trans "Polish (language)" %}
雖然翻譯能正確,但顯示的字串會變得怪異,且難以在其他地方重複使用。
我們想要的是:
- 原始字串保持
"Polish" - 透過額外說明告訴翻譯系統「現在是什麼語境」
這正是 Contextual Marker(語境資訊) 的用途。
在模板中解決:{% translate %} + context
在 Django 模板中,可以在 {% translate %}(或舊版 {% trans %})標籤後加上 context 選項,將同一字串按語境區分。
1) 原始程式碼(衝突)
{% load i18n %}
<button>{% translate "Polish" %}</button>
<span>{% translate "Polish" %}</span>
在 .po 檔案中,兩者皆為 msgid "Polish",只能選擇一種翻譯。
2) 改進後的程式碼(使用 context)
{% load i18n %}
<button>
{% translate "Polish" context "verb: to refine UI" %}
</button>
<span>
{% translate "Polish" context "language name" %}
</span>
重點:
context後的字串不會顯示給使用者。- 只作為翻譯系統與翻譯者的元資料。
-
建議簡短、明確地說明語境。
-
"verb: to refine UI" "language name""menu label""button text"等
3) {% blocktranslate %} 也可使用
對於較長句子,使用 {% blocktranslate %}(或 {% blocktrans %})時也能加上 context:
{% 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) 基本範例
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 也常遇到同音異義問題:
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
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 後,執行
python manage.py makemessages -l ko
.po 檔案會加入 msgctxt 欄位:
# 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–2 個單詞):按鈕文字、標籤、菜單項
- 同一字串在 UI 中有多種意義:
*
"Open"、"Close"、"Save"等動作 *"Book"、"Order"等名詞/動詞 - 翻譯者無法直接看到畫面:
* 使用翻譯平台
* 只交
.po檔給外部翻譯商
小結

- Django 只允許同一
msgid對應一個翻譯,導致同音異義詞常出錯。 - 透過
{% translate "…" context "…" %}(模板)或pgettext系列(Python)加入語境,.po檔案會自動產生msgctxt。 - 這樣既不改變原始程式碼,又能在不同語境下提供獨立翻譯,提升翻譯品質與維護性。
在多語言日益重要的時代,Contextual Markers 是 Django 開發者必備的 i18n 工具。
相關文章
目前沒有評論。