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_namehelp_textchoices 也常遇到同音異義問題:

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. 短字串(1–2 個單詞):按鈕文字、標籤、菜單項
  2. 同一字串在 UI 中有多種意義: * "Open""Close""Save" 等動作 * "Book""Order" 等名詞/動詞
  3. 翻譯者無法直接看到畫面: * 使用翻譯平台 * 只交 .po 檔給外部翻譯商

小結

開發者因同音異義翻譯而困惑的畫面

  • Django 只允許同一 msgid 對應一個翻譯,導致同音異義詞常出錯。
  • 透過 {% translate "…" context "…" %}(模板)或 pgettext 系列(Python)加入語境,.po 檔案會自動產生 msgctxt
  • 這樣既不改變原始程式碼,又能在不同語境下提供獨立翻譯,提升翻譯品質與維護性。

在多語言日益重要的時代,Contextual Markers 是 Django 開發者必備的 i18n 工具。


相關文章