Django 多语言处理:"Polish" 被误译为 "波兰语" 的悲剧(上下文标记)
当你真正支持多语言(i18n)时,往往会遇到类似的情况。
- 按钮上写着 “Polish”(意为“润色”),但翻译结果却是 “波兰语”
- 日期选择器里的 “May” 在某些语言中被翻译成了人名
- 菜单里的 “Book” 只被翻译成 “书”,而没有被翻译成 “预订”
问题的根源很简单。
计算机不懂 “上下文”。 当字符串相同时,它们会被视为完全相同。
本文将介绍如何利用 Django 的 上下文标记,在保持源代码不变的前提下,为同一字符串提供不同的翻译。
为什么会出现这种情况?gettext 的基本行为
Django 的翻译系统内部使用 GNU gettext。gettext 的工作原理非常简单:
- 原文字符串 →
msgid - 翻译字符串 →
msgstr - 如果
msgid相同,则始终使用同一个msgstr
# django.po
msgid "Polish"
msgstr "波兰语"
一旦在 .po 文件中出现了上述映射,
无论在何处使用 “Polish”,都会得到同样的翻译。
这导致以下两种 UI 无法区分:
- “Polish” = 动词(润色)
- “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 %}
这样,即使是同样的 "Hello %(username)s" 句子,也能在不同上下文中使用多次。
在 Python 代码中解决:pgettext
在模板之外(如视图、模型、表单等)使用 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 都是 "May" 或 "may",
但不同的 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,
)
如上所示:
- 同一
"Open"也能被区分为 状态 与 动作。
3) 同时处理复数:npgettext
当同一词在复数形式时意义不同,可使用 npgettext 同时处理上下文和复数。
from django.utils.translation import npgettext
def get_notification(count):
# 假设 "Message" 用于通知
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 不同,
翻译工具(Poedit、Weblate、Crowdin 等)会将它们视为不同条目。
翻译者可以更直观地了解每个字符串在 UI 中的用途。
关键点:
- 源代码中的字符串完全不变(仍为
"Polish") - 仅
.po文件结构更智能。
编写优质 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 仅为说明。若把翻译结果写进去:
- 随着语言增多,维护成本大幅上升
- 自动翻译系统可能会混淆
始终以原文(通常是英文)为基准,简短说明概念/角色。
何时使用上下文标记?
以下情况几乎一定要考虑使用 context/pgettext:
- 短字符串(1–2 词) * 按钮文本 * 选项卡名称 * 菜单项
- 在 UI 中以不同含义复用的字符串
*
"Open"、"Close"、"Save"等通用动作 *"Book"、"Order"、"Post"等既可作名词又可作动词 - 翻译者无法直接查看 UI
* 使用协作翻译平台
* 仅向外部翻译公司提供
.po文件
小结

- Django 默认将同一
msgid只映射一次翻译。 - 因此,
"Polish"、"May"、"Book"等同音异义词经常导致翻译冲突。 - 解决方案:
- 模板中使用
{% translate "…" context "…" %} - Python 代码中使用
pgettext/pgettext_lazy/npgettext - 通过添加
msgctxt,.po文件中同一原文可拥有多种翻译
这样既保持了代码可读性,又显著提升了翻译质量和维护性。
在多语言支持日益重要的时代,上下文标记 是 Django 开发者必备的 i18n 工具。
值得一读的相关文章
目前没有评论。