# 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` 檔給外部翻譯商
---
## 小結

* 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/)