翻译

概述

为了使 Django 项目可翻译,您需要在 Python 代码和模板中添加最少的几个钩子。这些钩子称为翻译字符串。它们告诉 Django:“如果该语言中存在此文本的翻译,则应将此文本翻译成最终用户的语言。”标记可翻译字符串是您的责任;系统只能翻译已知的字符串。

然后,Django 提供实用程序将翻译字符串提取到消息文件中。此文件是翻译人员以目标语言提供翻译字符串等价物的一种便捷方式。翻译人员填写消息文件后,必须对其进行编译。此过程依赖于 GNU gettext 工具集。

完成后,Django 会根据用户的语言偏好,在每个可用语言中动态地翻译 Web 应用程序。

Django 的国际化钩子默认启用,这意味着框架的某些地方存在一些与 i18n 相关的开销。如果您不使用国际化,则应花两秒钟的时间在您的设置文件中设置USE_I18N = False。然后 Django 将进行一些优化,以便不加载国际化机制。

注意

确保您已为项目激活了翻译(最快的检查方法是查看MIDDLEWARE是否包含django.middleware.locale.LocaleMiddleware)。如果您尚未激活,请参阅Django 如何发现语言偏好

国际化:在 Python 代码中

标准翻译

使用函数gettext()指定翻译字符串。习惯上将其导入为较短的别名_,以节省输入。

注意

Python 的标准库gettext模块将_()安装到全局命名空间中,作为gettext()的别名。在 Django 中,我们选择不遵循此做法,原因如下:

  1. 有时,您应该使用gettext_lazy()作为特定文件的默认翻译方法。如果没有全局命名空间中的_(),开发人员必须考虑哪种翻译函数最合适。

  2. 下划线字符(_)用于在 Python 的交互式 shell 和 doctest 测试中表示“前一个结果”。安装全局_()函数会导致干扰。显式地将gettext()导入为_()可以避免此问题。

哪些函数可以作为_别名?

由于xgettext(由makemessages使用)的工作方式,只有接受单个字符串参数的函数才能作为_导入。

在此示例中,文本"Welcome to my site."被标记为翻译字符串。

from django.http import HttpResponse
from django.utils.translation import gettext as _


def my_view(request):
    output = _("Welcome to my site.")
    return HttpResponse(output)

您可以编写不使用别名的代码。此示例与前一个示例相同。

from django.http import HttpResponse
from django.utils.translation import gettext


def my_view(request):
    output = gettext("Welcome to my site.")
    return HttpResponse(output)

翻译适用于计算值。此示例与前两个示例相同。

def my_view(request):
    words = ["Welcome", "to", "my", "site."]
    output = _(" ".join(words))
    return HttpResponse(output)

翻译适用于变量。同样,这里还有一个相同的示例。

def my_view(request):
    sentence = "Welcome to my site."
    output = _(sentence)
    return HttpResponse(output)

(使用变量或计算值(如前两个示例中所示)的注意事项是,Django 的翻译字符串检测实用程序django-admin makemessages将无法找到这些字符串。稍后将详细介绍makemessages。)

传递给_()gettext()的字符串可以包含占位符,这些占位符使用 Python 的标准命名字符串插值语法指定。示例:

def my_view(request, m, d):
    output = _("Today is %(month)s %(day)s.") % {"month": m, "day": d}
    return HttpResponse(output)

此技术允许特定语言的翻译重新排序占位符文本。例如,英语翻译可能是"Today is November 26.",而西班牙语翻译可能是"Hoy es 26 de noviembre."——其中月份和日期占位符已交换。

因此,当您有多个参数时,应使用命名字符串插值(例如%(day)s),而不是位置插值(例如%s%d)。如果您使用位置插值,则翻译将无法重新排序占位符文本。

由于字符串提取由xgettext命令完成,因此 Django 仅支持gettext支持的语法。特别是,xgettext尚不支持 Python f-字符串,JavaScript 模板字符串需要gettext 0.21+。

供翻译人员参考的注释

如果您想向翻译人员提供有关可翻译字符串的提示,可以在字符串前面的行添加以Translators关键字为前缀的注释,例如:

def my_view(request):
    # Translators: This message appears on the home page only
    output = gettext("Welcome to my site.")

然后,注释将出现在与位于其下方的可翻译结构关联的生成的.po文件中,并且大多数翻译工具也应该显示该注释。

注意

仅供完整起见,这是生成的.po文件的相应片段。

#. Translators: This message appears on the home page only
# path/to/python/file.py:123
msgid "Welcome to my site."
msgstr ""

这在模板中也适用。有关更多详细信息,请参阅模板中的供翻译人员参考的注释

将字符串标记为无操作

使用函数django.utils.translation.gettext_noop()将字符串标记为翻译字符串,但不进行翻译。稍后将从变量翻译字符串。

如果您有应存储在源语言中的常量字符串(例如数据库中的字符串),因为它们是在系统或用户之间交换的,但应在最后可能的时刻进行翻译(例如,当字符串呈现给用户时),则可以使用此方法。

复数形式

使用函数django.utils.translation.ngettext()指定复数形式的消息。

ngettext()接受三个参数:单数翻译字符串、复数翻译字符串和对象数。

当您需要将 Django 应用程序本地化为复数形式数量和复杂度大于英语中使用的两种形式的语言时,此函数很有用(“object”表示单数,“objects”表示count与 1 不同的所有情况,无论其值如何)。

例如:

from django.http import HttpResponse
from django.utils.translation import ngettext


def hello_world(request, count):
    page = ngettext(
        "there is %(count)d object",
        "there are %(count)d objects",
        count,
    ) % {
        "count": count,
    }
    return HttpResponse(page)

在此示例中,对象数作为count变量传递给翻译语言。

请注意,复数形式很复杂,并且在每种语言中的工作方式都不同。将count与 1 进行比较并不总是正确的规则。此代码看起来很复杂,但对于某些语言会产生错误的结果。

from django.utils.translation import ngettext
from myapp.models import Report

count = Report.objects.count()
if count == 1:
    name = Report._meta.verbose_name
else:
    name = Report._meta.verbose_name_plural

text = ngettext(
    "There is %(count)d %(name)s available.",
    "There are %(count)d %(name)s available.",
    count,
) % {"count": count, "name": name}

不要尝试实现您自己的单数或复数逻辑;它不会是正确的。在这种情况下,请考虑以下内容:

text = ngettext(
    "There is %(count)d %(name)s object available.",
    "There are %(count)d %(name)s objects available.",
    count,
) % {
    "count": count,
    "name": Report._meta.verbose_name,
}

注意

使用ngettext()时,请确保为包含在文字中的每个外推变量使用一个名称。在上面的示例中,请注意我们如何在两个翻译字符串中都使用了name Python 变量。除了如上所述在某些语言中不正确之外,此示例还会失败。

text = ngettext(
    "There is %(count)d %(name)s available.",
    "There are %(count)d %(plural_name)s available.",
    count,
) % {
    "count": Report.objects.count(),
    "name": Report._meta.verbose_name,
    "plural_name": Report._meta.verbose_name_plural,
}

运行django-admin compilemessages时会发生错误。

a format specification for argument 'name', as in 'msgstr[0]', doesn't exist in 'msgid'

上下文标记

有时,单词有多种含义,例如英语中的"May",它既指月份名称,也指动词。为了使翻译人员能够在不同的上下文中正确翻译这些单词,您可以使用django.utils.translation.pgettext()函数,或者如果字符串需要复数形式,则使用django.utils.translation.npgettext()函数。两者都将上下文字符串作为第一个变量。

在生成的 .po 文件中,字符串将根据相同字符串的不同上下文标记出现的次数而重复出现(上下文将出现在 msgctxt 行),从而允许翻译人员为每个上下文提供不同的翻译。

例如:

from django.utils.translation import pgettext

month = pgettext("month name", "May")

或者

from django.db import models
from django.utils.translation import pgettext_lazy


class MyThing(models.Model):
    name = models.CharField(
        help_text=pgettext_lazy("help text for MyThing model", "This is the help text")
    )

将在 .po 文件中显示为

msgctxt "month name"
msgid "May"
msgstr ""

上下文标记也受 translateblocktranslate 模板标签的支持。

延迟翻译

django.utils.translation 中使用翻译函数的延迟版本(从其名称中的 lazy 后缀可以轻松识别)来延迟翻译字符串 - 在访问值时而不是在调用它们时进行翻译。

这些函数存储对字符串的延迟引用 - 而不是实际的翻译。当字符串在字符串上下文中使用时(例如在模板渲染中),将执行翻译本身。

当对这些函数的调用位于模块加载时执行的代码路径中时,这一点至关重要。

在定义模型、表单和模型表单时,这种情况很容易发生,因为 Django 的实现方式使得它们的字段实际上是类级属性。因此,请确保在以下情况下使用延迟翻译

模型字段和关系的 verbose_namehelp_text 选项值

例如,要翻译以下模型中 *name* 字段的帮助文本,请执行以下操作

from django.db import models
from django.utils.translation import gettext_lazy as _


class MyThing(models.Model):
    name = models.CharField(help_text=_("This is the help text"))

您可以使用 ForeignKeyManyToManyFieldOneToOneField 关系的名称标记为可翻译,方法是使用它们的 verbose_name 选项

class MyThing(models.Model):
    kind = models.ForeignKey(
        ThingKind,
        on_delete=models.CASCADE,
        related_name="kinds",
        verbose_name=_("kind"),
    )

就像在 verbose_name 中一样,您应该为关系提供一个小写的 verbose name 文本,因为 Django 会在需要时自动将其转换为标题大小写。

模型 verbose name 值

建议始终提供明确的 verbose_nameverbose_name_plural 选项,而不是依赖于 Django 通过查看模型的类名执行的回退以英语为中心的且有些简单的 verbose name 确定。

from django.db import models
from django.utils.translation import gettext_lazy as _


class MyThing(models.Model):
    name = models.CharField(_("name"), help_text=_("This is the help text"))

    class Meta:
        verbose_name = _("my thing")
        verbose_name_plural = _("my things")

模型方法 description 参数传递给 @display 装饰器

对于模型方法,您可以使用 description 参数传递给 display() 装饰器来为 Django 和管理站点提供翻译

from django.contrib import admin
from django.db import models
from django.utils.translation import gettext_lazy as _


class MyThing(models.Model):
    kind = models.ForeignKey(
        ThingKind,
        on_delete=models.CASCADE,
        related_name="kinds",
        verbose_name=_("kind"),
    )

    @admin.display(description=_("Is it a mouse?"))
    def is_mouse(self):
        return self.kind.type == MOUSE_TYPE

使用延迟翻译对象

gettext_lazy() 调用的结果可以在任何使用字符串(str 对象)的其他 Django 代码中使用,但它可能不适用于任意 Python 代码。例如,以下代码将无法工作,因为 requests 库不处理 gettext_lazy 对象

body = gettext_lazy("I \u2764 Django")  # (Unicode :heart:)
requests.post("https://example.com/send", data={"body": body})

您可以通过在将 gettext_lazy() 对象传递给非 Django 代码之前将其转换为文本字符串来避免此类问题

requests.post("https://example.com/send", data={"body": str(body)})

如果您不喜欢冗长的 gettext_lazy 名称,您可以将其别名为 _(下划线),如下所示

from django.db import models
from django.utils.translation import gettext_lazy as _


class MyThing(models.Model):
    name = models.CharField(help_text=_("This is the help text"))

使用 gettext_lazy()ngettext_lazy() 在模型和实用程序函数中标记字符串是一种常见的操作。当您在代码的其他地方使用这些对象时,您应该确保您不会意外地将其转换为字符串,因为它们应该尽可能晚地转换(以便生效正确的语言环境)。这需要使用接下来描述的辅助函数。

延迟翻译和复数

当对复数字符串使用延迟翻译(n[p]gettext_lazy)时,您通常在字符串定义时不知道 number 参数。因此,您被授权传递键名而不是整数作为 number 参数。然后,在字符串插值期间,将在该键下的字典中查找 number。这是一个示例

from django import forms
from django.core.exceptions import ValidationError
from django.utils.translation import ngettext_lazy


class MyForm(forms.Form):
    error_message = ngettext_lazy(
        "You only provided %(num)d argument",
        "You only provided %(num)d arguments",
        "num",
    )

    def clean(self):
        # ...
        if error:
            raise ValidationError(self.error_message % {"num": number})

如果字符串恰好包含一个未命名的占位符,则可以使用 number 参数直接进行插值

class MyForm(forms.Form):
    error_message = ngettext_lazy(
        "You provided %d argument",
        "You provided %d arguments",
    )

    def clean(self):
        # ...
        if error:
            raise ValidationError(self.error_message % number)

格式化字符串:format_lazy()

format_string 或传递给 str.format() 的任何参数包含延迟翻译对象时,Python 的 str.format() 方法将无法工作。相反,您可以使用 django.utils.text.format_lazy(),它创建一个延迟对象,该对象仅在结果包含在字符串中时才运行 str.format() 方法。例如

from django.utils.text import format_lazy
from django.utils.translation import gettext_lazy

...
name = gettext_lazy("John Lennon")
instrument = gettext_lazy("guitar")
result = format_lazy("{name}: {instrument}", name=name, instrument=instrument)

在这种情况下,result 中的延迟翻译仅在 result 本身在字符串中使用时(通常在模板渲染时)才会转换为字符串。

延迟翻译中的其他 lazy 用法

对于任何其他您希望延迟翻译但必须将可翻译字符串作为参数传递给另一个函数的情况,您可以自己将此函数包装在延迟调用中。例如

from django.utils.functional import lazy
from django.utils.translation import gettext_lazy as _


def to_lower(string):
    return string.lower()


to_lower_lazy = lazy(to_lower, str)

然后稍后

lazy_string = to_lower_lazy(_("My STRING!"))

语言的本地化名称

get_language_info(lang_code)[source]

get_language_info() 函数提供有关语言的详细信息

>>> from django.utils.translation import activate, get_language_info
>>> activate("fr")
>>> li = get_language_info("de")
>>> print(li["name"], li["name_local"], li["name_translated"], li["bidi"])
German Deutsch Allemand False

字典的 namename_localname_translated 属性分别包含英语、语言本身和您当前活动语言中的语言名称。只有双向语言的 bidi 属性为 True。

语言信息的来源是 django.conf.locale 模块。模板代码可以类似地访问此信息。见下文。

国际化:在模板代码中

Django 模板 中的翻译使用两个模板标签,并且语法与 Python 代码略有不同。要使您的模板能够访问这些标签,请将 {% load i18n %} 放在模板的顶部附近。与所有模板标签一样,此标签需要在使用翻译的所有模板中加载,即使这些模板扩展自其他已经加载了 i18n 标签的其他模板。

警告

翻译后的字符串在模板中渲染时不会被转义。这允许您在翻译中包含 HTML,例如用于强调,但潜在的危险字符(例如 ")也将保持不变地渲染。

translate 模板标签

{% translate %} 模板标签翻译常量字符串(用单引号或双引号括起来)或变量内容

<title>{% translate "This is the title." %}</title>
<title>{% translate myvar %}</title>

如果存在 noop 选项,则变量查找仍然会发生,但翻译将被跳过。当“存根”将来需要翻译的内容时,这很有用

<title>{% translate "myvar" noop %}</title>

在内部,内联翻译使用 gettext() 调用。

如果将模板变量(上面 myvar)传递给标签,则标签首先会在运行时将该变量解析为字符串,然后在消息目录中查找该字符串。

无法在 {% translate %} 中的字符串内混合使用模板变量。如果您的翻译需要包含变量(占位符)的字符串,请改用 {% blocktranslate %}

如果您想检索翻译后的字符串而不显示它,可以使用以下语法

{% translate "This is the title" as the_title %}

<title>{{ the_title }}</title>
<meta name="description" content="{{ the_title }}">

在实践中,您将使用此方法获取可以在模板中多个位置使用的字符串,或者可以使用输出作为其他模板标签或过滤器的参数。

{% translate "starting point" as start %}
{% translate "end point" as end %}
{% translate "La Grande Boucle" as race %}

<h1>
  <a href="/" title="{% blocktranslate %}Back to '{{ race }}' homepage{% endblocktranslate %}">{{ race }}</a>
</h1>
<p>
{% for stage in tour_stages %}
    {% cycle start end %}: {{ stage }}{% if forloop.counter|divisibleby:2 %}<br>{% else %}, {% endif %}
{% endfor %}
</p>

{% translate %} 也支持使用 context 关键字的 上下文标记

{% translate "May" context "month name" %}

blocktranslate 模板标签

translate 标签相反,blocktranslate 标签允许您通过使用占位符标记由字面量和变量内容组成的复杂句子以进行翻译。

{% blocktranslate %}This string will have {{ value }} inside.{% endblocktranslate %}

要翻译模板表达式(例如,访问对象属性或使用模板过滤器),您需要将表达式绑定到局部变量以在翻译块中使用。示例

{% blocktranslate with amount=article.price %}
That will cost $ {{ amount }}.
{% endblocktranslate %}

{% blocktranslate with myvar=value|filter %}
This will have {{ myvar }} inside.
{% endblocktranslate %}

您可以在单个 blocktranslate 标签中使用多个表达式。

{% blocktranslate with book_t=book|title author_t=author|title %}
This is {{ book_t }} by {{ author_t }}
{% endblocktranslate %}

注意

仍然支持之前更详细的格式:{% blocktranslate with book|title as book_t and author|title as author_t %}

不允许在 blocktranslate 标签内使用其他块标签(例如 {% for %}{% if %})。

如果解析块参数之一失败,blocktranslate 将通过使用 deactivate_all() 函数暂时停用当前活动语言,回退到默认语言。

此标签还提供复数形式。要使用它

  • 指定并使用名称 count 绑定计数器值。此值将用于选择正确的复数形式。

  • 使用 {% plural %} 标签在 {% blocktranslate %}{% endblocktranslate %} 标签内指定单数和复数形式,并用它们分隔。

一个例子

{% blocktranslate count counter=list|length %}
There is only one {{ name }} object.
{% plural %}
There are {{ counter }} {{ name }} objects.
{% endblocktranslate %}

一个更复杂的例子

{% blocktranslate with amount=article.price count years=i.length %}
That will cost $ {{ amount }} per year.
{% plural %}
That will cost $ {{ amount }} per {{ years }} years.
{% endblocktranslate %}

当您同时使用复数形式功能并将值绑定到除计数器值之外的局部变量时,请记住 blocktranslate 结构在内部被转换为 ngettext 调用。这意味着相同的 关于 ngettext 变量的说明 适用。

无法在 blocktranslate 中执行反向 URL 查找,应事先检索(并存储)它们。

{% url 'path.to.view' arg arg2 as the_url %}
{% blocktranslate %}
This is a URL: {{ the_url }}
{% endblocktranslate %}

如果您想检索翻译后的字符串而不显示它,可以使用以下语法

{% blocktranslate asvar the_title %}The title is {{ title }}.{% endblocktranslate %}
<title>{{ the_title }}</title>
<meta name="description" content="{{ the_title }}">

在实践中,您将使用此方法获取可以在模板中多个位置使用的字符串,或者可以使用输出作为其他模板标签或过滤器的参数。

{% blocktranslate %} 也支持使用 context 关键字的 上下文标记

{% blocktranslate with name=user.username context "greeting" %}Hi {{ name }}{% endblocktranslate %}

{% blocktranslate %} 支持的另一个功能是 trimmed 选项。此选项将删除 {% blocktranslate %} 标签内容开头和结尾的换行符,替换每行开头和结尾的任何空格,并将所有行合并为一行,使用空格字符分隔它们。这对于缩进 {% blocktranslate %} 标签的内容非常有用,而无需将缩进字符放入 .po 文件中的相应条目中,这使得翻译过程更容易。

例如,以下 {% blocktranslate %} 标签

{% blocktranslate trimmed %}
  First sentence.
  Second paragraph.
{% endblocktranslate %}

将在 .po 文件中生成条目 "First sentence. Second paragraph.",而如果没有指定 trimmed 选项,则会生成 "\n  First sentence.\n  Second paragraph.\n"

传递给标签和过滤器的字符串字面量

您可以通过使用熟悉的 _() 语法翻译作为参数传递给标签和过滤器的字符串字面量。

{% some_tag _("Page not found") value|yesno:_("yes,no") %}

在这种情况下,标签和过滤器都将看到翻译后的字符串,因此它们不需要了解翻译。

注意

在此示例中,翻译基础结构将传递字符串 "yes,no",而不是单独的字符串 "yes""no"。翻译后的字符串需要包含逗号,以便过滤器解析代码知道如何拆分参数。例如,德语翻译人员可能会将字符串 "yes,no" 翻译为 "ja,nein"(保持逗号不变)。

模板中的翻译人员注释

Python 代码 一样,这些翻译人员的注释可以使用注释指定,可以使用 comment 标签

{% comment %}Translators: View verb{% endcomment %}
{% translate "View" %}

{% comment %}Translators: Short intro blurb{% endcomment %}
<p>{% blocktranslate %}A multiline translatable
literal.{% endblocktranslate %}</p>

或使用 {##} 单行注释结构

{# Translators: Label of a button that triggers search #}
<button type="submit">{% translate "Go" %}</button>

{# Translators: This is a text of the base template #}
{% blocktranslate %}Ambiguous translatable block of text{% endblocktranslate %}

注意

仅供完整起见,以下是生成的 .po 文件的相应片段。

#. Translators: View verb
# path/to/template/file.html:10
msgid "View"
msgstr ""

#. Translators: Short intro blurb
# path/to/template/file.html:13
msgid ""
"A multiline translatable"
"literal."
msgstr ""

# ...

#. Translators: Label of a button that triggers search
# path/to/template/file.html:100
msgid "Go"
msgstr ""

#. Translators: This is a text of the base template
# path/to/template/file.html:103
msgid "Ambiguous translatable block of text"
msgstr ""

在模板中切换语言

如果要在模板中选择语言,可以使用 language 模板标签。

{% load i18n %}

{% get_current_language as LANGUAGE_CODE %}
<!-- Current language: {{ LANGUAGE_CODE }} -->
<p>{% translate "Welcome to our page" %}</p>

{% language 'en' %}
    {% get_current_language as LANGUAGE_CODE %}
    <!-- Current language: {{ LANGUAGE_CODE }} -->
    <p>{% translate "Welcome to our page" %}</p>
{% endlanguage %}

虽然第一次出现的“欢迎访问我们的页面”使用当前语言,但第二次将始终为英语。

其他标签

这些标签也需要 {% load i18n %}

get_available_languages

{% get_available_languages as LANGUAGES %} 返回一个元组列表,其中第一个元素是 语言代码,第二个元素是语言名称(翻译成当前活动的区域设置)。

get_current_language

{% get_current_language as LANGUAGE_CODE %} 返回当前用户的首选语言(作为字符串)。例如:en-us。请参阅 Django 如何发现语言偏好

get_current_language_bidi

{% get_current_language_bidi as LANGUAGE_BIDI %} 返回当前区域设置的方向。如果为 True,则为从右到左的语言,例如希伯来语、阿拉伯语。如果为 False,则为从左到右的语言,例如英语、法语、德语等。

i18n 上下文处理器

如果启用了 django.template.context_processors.i18n 上下文处理器,则每个 RequestContext 都可以访问上面定义的 LANGUAGESLANGUAGE_CODELANGUAGE_BIDI

get_language_info

您还可以使用提供的模板标签和过滤器检索有关任何可用语言的信息。要获取有关单个语言的信息,请使用 {% get_language_info %} 标签

{% get_language_info for LANGUAGE_CODE as lang %}
{% get_language_info for "pl" as lang %}

然后您可以访问信息

Language code: {{ lang.code }}<br>
Name of language: {{ lang.name_local }}<br>
Name in English: {{ lang.name }}<br>
Bi-directional: {{ lang.bidi }}
Name in the active language: {{ lang.name_translated }}

get_language_info_list

您还可以使用 {% get_language_info_list %} 模板标签检索一组语言的信息(例如,在 LANGUAGES 中指定的活动语言)。有关如何使用 {% get_language_info_list %} 显示语言选择器的示例,请参阅 关于 set_language 重定向视图的部分

除了 LANGUAGES 样式的元组列表之外,{% get_language_info_list %} 还支持语言代码列表。如果您在视图中这样做

context = {"available_languages": ["en", "es", "fr"]}
return render(request, "mytemplate.html", context)

您可以在模板中遍历这些语言

{% get_language_info_list for available_languages as langs %}
{% for lang in langs %} ... {% endfor %}

模板过滤器

还有一些过滤器可供方便使用

  • {{ LANGUAGE_CODE|language_name }} (“德语”)

  • {{ LANGUAGE_CODE|language_name_local }} (“德语”)

  • {{ LANGUAGE_CODE|language_bidi }} (False)

  • {{ LANGUAGE_CODE|language_name_translated }} (“德语”,当活动语言为捷克语时)

国际化:在 JavaScript 代码中

向 JavaScript 添加翻译会带来一些问题

  • JavaScript 代码无法访问 gettext 实现。

  • JavaScript 代码无法访问 .po.mo 文件;它们需要由服务器提供。

  • JavaScript 的翻译目录应尽可能小。

Django 为这些问题提供了一个集成解决方案:它将翻译传递到 JavaScript 中,因此您可以在 JavaScript 中调用 gettext 等。

解决这些问题的主要方案是以下 JavaScriptCatalog 视图,它生成一个 JavaScript 代码库,其中包含模拟 gettext 接口的函数,以及一个翻译字符串数组。

JavaScriptCatalog 视图

class JavaScriptCatalog[source]

一个生成 JavaScript 代码库的视图,该库包含模拟 gettext 接口的函数,以及一个翻译字符串数组。

属性

domain

包含要在视图输出中添加的字符串的翻译域。默认为 'djangojs'

packages

已安装应用程序中的一组 application names。这些应用应包含一个 locale 目录。所有这些目录以及在 LOCALE_PATHS 中找到的所有目录(始终包含)都合并到一个目录中。默认为 None,这意味着 JavaScript 输出中提供了所有来自所有 INSTALLED_APPS 的可用翻译。

使用默认值的示例:

from django.views.i18n import JavaScriptCatalog

urlpatterns = [
    path("jsi18n/", JavaScriptCatalog.as_view(), name="javascript-catalog"),
]

使用自定义包的示例:

urlpatterns = [
    path(
        "jsi18n/myapp/",
        JavaScriptCatalog.as_view(packages=["your.app.label"]),
        name="javascript-catalog",
    ),
]

如果您的根 URLconf 使用 i18n_patterns(),则 JavaScriptCatalog 也必须用 i18n_patterns() 包裹,才能正确生成目录。

使用 i18n_patterns() 的示例

from django.conf.urls.i18n import i18n_patterns

urlpatterns = i18n_patterns(
    path("jsi18n/", JavaScriptCatalog.as_view(), name="javascript-catalog"),
)

翻译的优先级是这样的:packages 参数中后面出现的包比前面出现的包具有更高的优先级。这在同一字面量的翻译冲突的情况下很重要。

如果您在一个站点上使用多个 JavaScriptCatalog 视图,并且其中一些视图定义了相同的字符串,则最后加载的目录中的字符串将具有优先级。

使用 JavaScript 翻译目录

要使用该目录,请像这样引入动态生成的脚本

<script src="{% url 'javascript-catalog' %}"></script>

这使用反向 URL 查找来查找 JavaScript 目录视图的 URL。加载目录后,您的 JavaScript 代码可以使用以下方法

  • gettext

  • ngettext

  • interpolate

  • get_format

  • gettext_noop

  • pgettext

  • npgettext

  • pluralidx

gettext

gettext 函数的行为类似于 Python 代码中的标准 gettext 接口

document.write(gettext("this is to be translated"))

ngettext

ngettext 函数提供了一个用于复数化单词和短语的接口

const objectCount = 1 // or 0, or 2, or 3, ...
const string = ngettext(
    'literal for the singular case',
    'literal for the plural case',
    objectCount
);

interpolate

interpolate 函数支持动态填充格式字符串。插值语法借鉴于 Python,因此 interpolate 函数支持位置插值和命名插值

  • 位置插值:obj 包含一个 JavaScript 数组对象,其元素值随后按顺序插入到它们在 fmt 中对应的位置,顺序与它们出现的顺序相同。例如

    const formats = ngettext(
      'There is %s object. Remaining: %s',
      'There are %s objects. Remaining: %s',
      11
    );
    const string = interpolate(formats, [11, 20]);
    // string is 'There are 11 objects. Remaining: 20'
    
  • 命名插值:通过将可选布尔 named 参数作为 true 传递来选择此模式。obj 包含一个 JavaScript 对象或关联数组。例如

    const data = {
      count: 10,
      total: 50
    };
    
    const formats = ngettext(
        'Total: %(total)s, there is %(count)s object',
        'there are %(count)s of a total of %(total)s objects',
        data.count
    );
    const string = interpolate(formats, data, true);
    

但是,您不应该过度使用字符串插值:这仍然是 JavaScript,因此代码必须进行重复的正则表达式替换。这不像 Python 中的字符串插值那样快,因此请将其限制在确实需要它的那些情况下(例如,与 ngettext 结合使用以生成正确的复数形式)。

get_format

get_format 函数可以访问已配置的 i18n 格式设置,并可以检索给定设置名称的格式字符串

document.write(get_format('DATE_FORMAT'));
// 'N j, Y'

它可以访问以下设置

这对于维护与 Python 呈现的值的格式一致性很有用。

gettext_noop

这模拟了 gettext 函数,但什么也不做,返回传递给它的任何内容

document.write(gettext_noop("this will not be translated"))

这对于存根将来需要翻译的代码部分很有用。

pgettext

pgettext 函数的行为类似于 Python 变体 (pgettext()),提供了一个上下文相关的翻译词

document.write(pgettext("month name", "May"))

npgettext

npgettext 函数的行为也类似于 Python 变体 (npgettext()),提供了一个复数上下文相关的翻译词

document.write(npgettext('group', 'party', 1));
// party
document.write(npgettext('group', 'party', 2));
// parties

pluralidx

pluralidx 函数的工作方式类似于 pluralize 模板过滤器,用于确定给定的 count 是否应该使用单词的复数形式。

document.write(pluralidx(0));
// true
document.write(pluralidx(1));
// false
document.write(pluralidx(2));
// true

在最简单的情况下,如果不需要自定义复数化,则对于整数 1 返回 false,对于所有其他数字返回 true

但是,并非所有语言的复数化都如此简单。如果语言不支持复数化,则提供一个空值。

此外,如果复数化规则比较复杂,目录视图将呈现一个条件表达式。该表达式将计算结果为 true(应复数化)或 false(不应复数化)。

JSONCatalog 视图

class JSONCatalog[source]

为了使用另一个客户端库来处理翻译,您可能希望利用 JSONCatalog 视图。它类似于 JavaScriptCatalog,但返回 JSON 响应。

请参阅 JavaScriptCatalog 的文档,以了解 domainpackages 属性的可能值和用法。

响应格式如下所示

{
    "catalog": {
        # Translations catalog
    },
    "formats": {
        # Language formats for date, time, etc.
    },
    "plural": "..."  # Expression for plural forms, or null.
}

关于性能的说明

各种 JavaScript/JSON 国际化视图在每次请求时都会从 .mo 文件生成目录。由于其输出对于给定版本的站点来说是恒定的,因此它是缓存的理想选择。

服务器端缓存将减少 CPU 负载。它可以使用 cache_page() 装饰器轻松实现。当您的翻译发生更改时,若要触发缓存失效,请提供一个版本相关的键前缀(如下面的示例所示),或将视图映射到版本相关的 URL。

from django.views.decorators.cache import cache_page
from django.views.i18n import JavaScriptCatalog

# The value returned by get_version() must change when translations change.
urlpatterns = [
    path(
        "jsi18n/",
        cache_page(86400, key_prefix="jsi18n-%s" % get_version())(
            JavaScriptCatalog.as_view()
        ),
        name="javascript-catalog",
    ),
]

客户端缓存将节省带宽并使您的网站加载速度更快。如果您使用的是 ETags(ConditionalGetMiddleware),那么您已经涵盖了。否则,您可以应用 条件装饰器。在下面的示例中,只要重新启动应用程序服务器,缓存就会失效。

from django.utils import timezone
from django.views.decorators.http import last_modified
from django.views.i18n import JavaScriptCatalog

last_modified_date = timezone.now()

urlpatterns = [
    path(
        "jsi18n/",
        last_modified(lambda req, **kw: last_modified_date)(
            JavaScriptCatalog.as_view()
        ),
        name="javascript-catalog",
    ),
]

您甚至可以在部署过程中预生成 JavaScript 目录,并将其作为静态文件提供服务。这种激进的技术在 django-statici18n 中实现。

国际化:在 URL 模式中

Django 提供了两种机制来国际化 URL 模式。

警告

使用这两种功能中的任何一种都需要为每个请求设置活动语言;换句话说,您需要在 MIDDLEWARE 设置中包含 django.middleware.locale.LocaleMiddleware

URL 模式中的语言前缀

i18n_patterns(*urls, prefix_default_language=True)[source]

此函数可以在根 URLconf 中使用,Django 会自动将当前活动语言代码添加到 i18n_patterns() 中定义的所有 URL 模式之前。

prefix_default_language 设置为 False 会从默认语言(LANGUAGE_CODE)中删除前缀。在向现有站点添加翻译时,这可能很有用,因为当前 URL 不会更改。

URL 模式示例

from django.conf.urls.i18n import i18n_patterns
from django.urls import include, path

from about import views as about_views
from news import views as news_views
from sitemap.views import sitemap

urlpatterns = [
    path("sitemap.xml", sitemap, name="sitemap-xml"),
]

news_patterns = (
    [
        path("", news_views.index, name="index"),
        path("category/<slug:slug>/", news_views.category, name="category"),
        path("<slug:slug>/", news_views.details, name="detail"),
    ],
    "news",
)

urlpatterns += i18n_patterns(
    path("about/", about_views.main, name="about"),
    path("news/", include(news_patterns, namespace="news")),
)

定义这些 URL 模式后,Django 会自动将语言前缀添加到 i18n_patterns 函数添加的 URL 模式。示例

>>> from django.urls import reverse
>>> from django.utils.translation import activate

>>> activate("en")
>>> reverse("sitemap-xml")
'/sitemap.xml'
>>> reverse("news:index")
'/en/news/'

>>> activate("nl")
>>> reverse("news:detail", kwargs={"slug": "news-slug"})
'/nl/news/news-slug/'

如果 prefix_default_language=FalseLANGUAGE_CODE='en',则 URL 将为

>>> activate("en")
>>> reverse("news:index")
'/news/'

>>> activate("nl")
>>> reverse("news:index")
'/nl/news/'

警告

i18n_patterns() 仅允许在根 URLconf 中使用。在包含的 URLconf 中使用它将引发 ImproperlyConfigured 异常。

警告

确保您没有可能与自动添加的语言前缀冲突的无前缀 URL 模式。

翻译 URL 模式

URL 模式也可以使用 gettext_lazy() 函数标记为可翻译。示例

from django.conf.urls.i18n import i18n_patterns
from django.urls import include, path
from django.utils.translation import gettext_lazy as _

from about import views as about_views
from news import views as news_views
from sitemaps.views import sitemap

urlpatterns = [
    path("sitemap.xml", sitemap, name="sitemap-xml"),
]

news_patterns = (
    [
        path("", news_views.index, name="index"),
        path(_("category/<slug:slug>/"), news_views.category, name="category"),
        path("<slug:slug>/", news_views.details, name="detail"),
    ],
    "news",
)

urlpatterns += i18n_patterns(
    path(_("about/"), about_views.main, name="about"),
    path(_("news/"), include(news_patterns, namespace="news")),
)

创建翻译后,reverse() 函数将返回活动语言中的 URL。示例

>>> from django.urls import reverse
>>> from django.utils.translation import activate

>>> activate("en")
>>> reverse("news:category", kwargs={"slug": "recent"})
'/en/news/category/recent/'

>>> activate("nl")
>>> reverse("news:category", kwargs={"slug": "recent"})
'/nl/nieuws/categorie/recent/'

警告

在大多数情况下,最好仅在语言代码前缀模式块(使用 i18n_patterns())中使用翻译后的 URL,以避免粗心翻译的 URL 与未翻译的 URL 模式发生冲突的可能性。

在模板中反转

如果本地化的 URL 在模板中反转,它们始终使用当前语言。要链接到另一种语言的 URL,请使用 language 模板标签。它在封闭的模板部分中启用给定的语言。

{% load i18n %}

{% get_available_languages as languages %}

{% translate "View this category in:" %}
{% for lang_code, lang_name in languages %}
    {% language lang_code %}
    <a href="{% url 'category' slug=category.slug %}">{{ lang_name }}</a>
    {% endlanguage %}
{% endfor %}

language 标签期望语言代码作为唯一参数。

本地化:如何创建语言文件

一旦应用程序的字符串文字被标记为以后翻译,就需要编写(或获取)翻译本身。以下是其工作原理。

消息文件

第一步是为新的语言创建一个 消息文件。消息文件是一个纯文本文件,表示一种语言,其中包含所有可用的翻译字符串以及如何在给定语言中表示它们。消息文件具有 .po 文件扩展名。

Django 带有一个工具 django-admin makemessages,用于自动化这些文件的创建和维护。

Gettext 实用程序

makemessages 命令(以及稍后讨论的 compilemessages)使用来自 GNU gettext 工具集的命令:xgettextmsgfmtmsgmergemsguniq

支持的gettext工具的最低版本为0.15。

要创建或更新消息文件,请运行以下命令

django-admin makemessages -l de

…其中de是您要创建的消息文件的区域设置名称。例如,巴西葡萄牙语为pt_BR,奥地利德语为de_AT,印度尼西亚语为id

脚本应从以下两个位置之一运行

  • Django 项目的根目录(包含manage.py的目录)。

  • 您其中一个 Django 应用程序的根目录。

该脚本遍历您的项目源代码树或应用程序源代码树,并提取所有标记为需要翻译的字符串(请参阅Django 如何发现翻译,并确保LOCALE_PATHS已正确配置)。它在locale/LANG/LC_MESSAGES目录中创建(或更新)一个消息文件。在de示例中,文件将为locale/de/LC_MESSAGES/django.po

当您从项目的根目录运行makemessages时,提取的字符串将自动分发到相应的消息文件。也就是说,从包含locale目录的应用程序的文件中提取的字符串将放置在该目录下的消息文件中。从任何不包含locale目录的应用程序的文件中提取的字符串将放置在LOCALE_PATHS中列出的第一个目录下的消息文件中,或者如果LOCALE_PATHS为空,则会生成错误。

默认情况下,django-admin makemessages会检查每个具有.html.txt.py文件扩展名的文件。如果要覆盖此默认值,请使用--extension-e选项指定要检查的文件扩展名。

django-admin makemessages -l de -e txt

用逗号分隔多个扩展名,或多次使用-e--extension

django-admin makemessages -l de -e html,txt -e xml

警告

从 JavaScript 源代码创建消息文件时,您需要使用特殊的djangojs域,**而不是**-e js

使用 Jinja2 模板?

makemessages不理解 Jinja2 模板的语法。要从包含 Jinja2 模板的项目中提取字符串,请改用Babel 的消息提取

这是一个示例babel.cfg配置文件

# Extraction from Python source files
[python: **.py]

# Extraction from Jinja2 templates
[jinja2: **.jinja]
extensions = jinja2.ext.with_

确保列出您正在使用的所有扩展名!否则,Babel 将无法识别这些扩展名定义的标签,并将完全忽略包含它们的 Jinja2 模板。

Babel 提供与makemessages类似的功能,可以总体上替换它,并且不依赖于gettext。有关更多信息,请阅读其关于使用消息目录的文档。

没有 gettext?

如果您没有安装gettext工具,makemessages将创建空文件。如果是这种情况,请安装gettext工具,或复制英文消息文件(locale/en/LC_MESSAGES/django.po)(如果可用)并将其用作起点,它是一个空的翻译文件。

在 Windows 上工作?

如果您使用的是 Windows 并且需要安装 GNU gettext 工具才能使makemessages工作,请参阅Windows 上的 gettext以获取更多信息。

每个.po文件包含少量元数据,例如翻译维护者的联系信息,但文件的大部分内容是消息列表——翻译字符串与特定语言的实际翻译文本之间的映射。

例如,如果您的 Django 应用程序包含一个用于文本"Welcome to my site."的翻译字符串,如下所示

_("Welcome to my site.")

…那么django-admin makemessages将创建一个包含以下代码段(一条消息)的.po文件

#: path/to/python/module.py:23
msgid "Welcome to my site."
msgstr ""

快速说明

  • msgid是出现在源代码中的翻译字符串。不要更改它。

  • msgstr是您放置特定于语言的翻译的地方。它最初为空,因此您有责任更改它。确保在翻译周围保留引号。

  • 为了方便起见,每条消息都包含从提取翻译字符串的文件名和行号中获取的信息,以#开头的注释行形式,位于msgid行上方。

长消息是一种特殊情况。在那里,msgstr(或msgid)之后的第一行字符串是空字符串。然后内容本身将在接下来的几行中逐行写入。这些字符串将直接连接起来。不要忘记字符串中的尾随空格;否则,它们将被拼接到一起而没有空格!

注意字符集

由于gettext工具在内部的工作方式,并且因为我们希望允许在 Django 的核心和您的应用程序中使用非 ASCII 源字符串,所以您**必须**使用 UTF-8 作为.po文件的编码(创建.po文件时的默认编码)。这意味着每个人都将使用相同的编码,这在 Django 处理.po文件时非常重要。

模糊条目

makemessages有时会生成标记为模糊的翻译条目,例如,当从先前翻译的字符串推断翻译时。默认情况下,compilemessages不会处理模糊条目。

要重新检查所有源代码和模板以查找新的翻译字符串并更新所有语言的所有消息文件,请运行以下命令

django-admin makemessages -a

编译消息文件

创建消息文件后(以及每次对其进行更改后),您都需要将其编译成更有效的形式,以便gettext使用。使用django-admin compilemessages工具执行此操作。

此工具遍历所有可用的.po文件,并创建.mo文件,这些文件是针对gettext使用而优化的二进制文件。在您运行django-admin makemessages的同一目录中,运行django-admin compilemessages,如下所示

django-admin compilemessages

就是这样。您的翻译已准备好使用。

在 Windows 上工作?

如果您使用的是 Windows 并且需要安装 GNU gettext 工具才能使django-admin compilemessages工作,请参阅Windows 上的 gettext以获取更多信息。

.po 文件:编码和 BOM 使用。

Django 仅支持使用 UTF-8 编码且不包含任何 BOM(字节顺序标记)的 .po 文件,因此如果您的文本编辑器默认会在文件开头添加此类标记,则需要重新配置它。

故障排除:gettext() 在包含百分号的字符串中错误地检测到 python-format

在某些情况下,例如百分号后跟空格和 [字符串转换类型](https://docs.pythonlang.cn/3/library/stdtypes.html#old-string-formatting) 的字符串(例如 _("10% interest")),[django.utils.translation.gettext](../../../ref/utils/#django.utils.translation.gettext) 会错误地将包含 python-format 的字符串标记为 python-format。

如果尝试使用错误标记的字符串编译消息文件,您将收到类似于 "number of format specifications in 'msgid' and 'msgstr' does not match" 或 "'msgstr' is not a valid Python format string, unlike 'msgid'" 的错误消息。

要解决此问题,您可以通过添加第二个百分号来转义百分号。

from django.utils.translation import gettext as _

output = _("10%% interest")

或者,您可以使用 no-python-format,以便所有百分号都被视为字面量。

# xgettext:no-python-format
output = _("10% interest")

从 JavaScript 源代码创建消息文件

您可以像其他 Django 消息文件一样创建和更新消息文件 - 使用 [django-admin makemessages](../../../ref/django-admin/#django-admin-makemessages) 工具。唯一的区别是您需要明确指定在 gettext 中称为域的内容,在本例中为 djangojs 域,方法是提供 -d djangojs 参数,如下所示:

django-admin makemessages -d djangojs -l de

这将为德语创建或更新 JavaScript 的消息文件。更新消息文件后,像处理普通 Django 消息文件一样运行 [django-admin compilemessages](../../../ref/django-admin/#django-admin-compilemessages)。

gettext 在 Windows 上

这仅适用于想要提取消息 ID 或编译消息文件 (.po) 的用户。翻译工作本身涉及编辑此类类型的现有文件,但如果您想创建自己的消息文件,或者想要测试或编译已更改的消息文件,请下载 [预编译的二进制安装程序](https://mlocati.github.io/articles/gettext-iconv-windows.html)。

您也可以使用从其他地方获得的 gettext 二进制文件,只要 xgettext --version 命令能够正常工作即可。如果在 Windows 命令提示符下输入 xgettext --version 命令导致弹出窗口显示“xgettext.exe has generated errors and will be closed by Windows”,则不要尝试将 Django 翻译实用程序与 gettext 包一起使用。

自定义 makemessages 命令

如果要将其他参数传递给 xgettext,则需要创建一个自定义的 [makemessages](../../../ref/django-admin/#django-admin-makemessages) 命令并覆盖其 xgettext_options 属性。

from django.core.management.commands import makemessages


class Command(makemessages.Command):
    xgettext_options = makemessages.Command.xgettext_options + ["--keyword=mytrans"]

如果您需要更多灵活性,还可以向您的自定义 [makemessages](../../../ref/django-admin/#django-admin-makemessages) 命令添加一个新参数。

from django.core.management.commands import makemessages


class Command(makemessages.Command):
    def add_arguments(self, parser):
        super().add_arguments(parser)
        parser.add_argument(
            "--extra-keyword",
            dest="xgettext_keywords",
            action="append",
        )

    def handle(self, *args, **options):
        xgettext_keywords = options.pop("xgettext_keywords")
        if xgettext_keywords:
            self.xgettext_options = makemessages.Command.xgettext_options[:] + [
                "--keyword=%s" % kwd for kwd in xgettext_keywords
            ]
        super().handle(*args, **options)

其他

set_language 重定向视图

set_language(request)[源代码](https://github.com/django/django/blob/stable/5.1.x/django/views/i18n.py#L30)

为了方便起见,Django 提供了一个视图 [django.views.i18n.set_language()](#django.views.i18n.set_language),该视图设置用户的语言偏好并重定向到给定的 URL,或者默认情况下重定向回上一页。

通过将以下行添加到您的 URLconf 来激活此视图。

path("i18n/", include("django.conf.urls.i18n")),

(请注意,此示例使该视图在 /i18n/setlang/ 可用。)

警告

确保您不要将上述 URL 包含在 [i18n_patterns()](#django.conf.urls.i18n.i18n_patterns) 中 - 它本身需要与语言无关才能正常工作。

该视图期望通过 POST 方法被调用,并且在请求中设置了 language 参数。如果启用了会话支持,该视图会将语言选择保存在用户的会话中。它还会将语言选择保存在名为 django_language 的 Cookie 中(默认情况下)。(名称可以通过 [LANGUAGE_COOKIE_NAME](../../../ref/settings/#std-setting-LANGUAGE_COOKIE_NAME) 设置更改。)

设置语言选择后,Django 会在 POST 或 GET 数据中查找 next 参数。如果找到该参数并且 Django 认为它是一个安全的 URL(即它不指向不同的主机并使用安全的方案),则将执行到该 URL 的重定向。否则,Django 可能会回退到将用户重定向到 Referer 标头中的 URL,或者如果未设置该标头,则重定向到 /,具体取决于请求的性质。

  • 如果请求接受 HTML 内容(基于其 Accept HTTP 标头),则始终会执行回退。

  • 如果请求不接受 HTML,则仅当设置了 next 参数时才会执行回退。否则,将返回 204 状态代码(无内容)。

以下是一个 HTML 模板代码示例。

{% load i18n %}

<form action="{% url 'set_language' %}" method="post">{% csrf_token %}
    <input name="next" type="hidden" value="{{ redirect_to }}">
    <select name="language">
        {% get_current_language as LANGUAGE_CODE %}
        {% get_available_languages as LANGUAGES %}
        {% get_language_info_list for LANGUAGES as languages %}
        {% for language in languages %}
            <option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected{% endif %}>
                {{ language.name_local }} ({{ language.code }})
            </option>
        {% endfor %}
    </select>
    <input type="submit" value="Go">
</form>

在此示例中,Django 在 redirect_to 上下文变量中查找用户将被重定向到的页面的 URL。

明确设置活动语言

您可能希望明确设置当前会话的活动语言。例如,用户的语言偏好可能是从另一个系统检索到的。您已经了解了 [django.utils.translation.activate()](../../../ref/utils/#django.utils.translation.activate)。这仅适用于当前线程。要在 Cookie 中为整个会话持久化语言,请在响应中设置 [LANGUAGE_COOKIE_NAME](../../../ref/settings/#std-setting-LANGUAGE_COOKIE_NAME) Cookie。

from django.conf import settings
from django.http import HttpResponse
from django.utils import translation

user_language = "fr"
translation.activate(user_language)
response = HttpResponse(...)
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, user_language)

您通常需要同时使用两者:[django.utils.translation.activate()](../../../ref/utils/#django.utils.translation.activate) 更改此线程的语言,而设置 Cookie 使此偏好在未来的请求中持久化。

在视图和模板之外使用翻译

虽然 Django 为在视图和模板中使用提供了一套丰富的 i18n 工具,但它并没有将使用限制在 Django 特定的代码中。Django 翻译机制可用于将任意文本翻译成 Django 支持的任何语言(当然,前提是存在相应的翻译目录)。您可以加载翻译目录,激活它并将文本翻译成您选择的语言,但请记住切换回原始语言,因为激活翻译目录是在每个线程的基础上完成的,并且此类更改将影响在同一线程中运行的代码。

例如:

from django.utils import translation


def welcome_translated(language):
    cur_language = translation.get_language()
    try:
        translation.activate(language)
        text = translation.gettext("welcome")
    finally:
        translation.activate(cur_language)
    return text

如果使用值'de'调用此函数,无论LANGUAGE_CODE和中间件设置的语言是什么,你都会得到"Willkommen"

一些特别需要注意的函数包括:django.utils.translation.get_language(),它返回当前线程使用的语言;django.utils.translation.activate(),它为当前线程激活翻译目录;以及django.utils.translation.check_for_language(),它检查Django是否支持给定的语言。

为了帮助编写更简洁的代码,还有一个上下文管理器django.utils.translation.override(),它在进入时存储当前语言,并在退出时恢复。使用它,上面的例子变成

from django.utils import translation


def welcome_translated(language):
    with translation.override(language):
        return translation.gettext("welcome")

实现说明

Django 翻译的特性

Django 的翻译机制使用 Python 自带的标准 gettext 模块。如果你熟悉 gettext,你可能会注意到 Django 在翻译方面的一些特性

  • 字符串域为 djangodjangojs。此字符串域用于区分将数据存储在公共消息文件库(通常为 /usr/share/locale/)中的不同程序。django 域用于 Python 和模板翻译字符串,并加载到全局翻译目录中。djangojs 域仅用于 JavaScript 翻译目录,以确保这些目录尽可能小。

  • Django 不仅使用 xgettext。它使用围绕 xgettextmsgfmt 的 Python 包装器。这主要出于方便考虑。

Django 如何发现语言偏好

一旦你准备好了你的翻译——或者,如果你想使用 Django 自带的翻译——你需要为你的应用激活翻译。

在幕后,Django 拥有一个非常灵活的模型来决定应该使用哪种语言——在整个安装范围内、针对特定用户或两者兼而有之。

要设置安装范围内的语言偏好,请设置 LANGUAGE_CODE。Django 使用此语言作为默认翻译——如果通过区域设置中间件使用的其中一种方法找不到更匹配的翻译,则作为最后尝试。(参见下文)。

如果你只想用你的母语运行 Django,你只需要设置 LANGUAGE_CODE 并确保相应的 消息文件 及其编译版本(.mo)存在。

如果你想让每个用户指定他们喜欢的语言,那么你还需要使用 LocaleMiddlewareLocaleMiddleware 允许根据请求中的数据选择语言。它为每个用户定制内容。

要使用 LocaleMiddleware,请将 'django.middleware.locale.LocaleMiddleware' 添加到你的 MIDDLEWARE 设置中。由于中间件的顺序很重要,请遵循以下指南

  • 确保它是安装的第一个中间件之一。

  • 它应该放在 SessionMiddleware 之后,因为 LocaleMiddleware 使用会话数据。它应该放在 CommonMiddleware 之前,因为 CommonMiddleware 需要激活的语言才能解析请求的 URL。

  • 如果你使用 CacheMiddleware,请将 LocaleMiddleware 放置在它之后。

例如,你的 MIDDLEWARE 可能如下所示

MIDDLEWARE = [
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.locale.LocaleMiddleware",
    "django.middleware.common.CommonMiddleware",
]

(有关中间件的更多信息,请参阅 中间件文档。)

LocaleMiddleware 尝试通过以下算法确定用户的语言偏好

  • 首先,它在请求的 URL 中查找语言前缀。仅当你在根 URLconf 中使用 i18n_patterns 函数时才会执行此操作。有关语言前缀以及如何国际化 URL 模式的更多信息,请参见 国际化:在 URL 模式中

  • 如果失败,它会查找 Cookie。

    使用的 Cookie 的名称由 LANGUAGE_COOKIE_NAME 设置。(默认名称为 django_language。)

  • 如果失败,它会查看 Accept-Language HTTP 标头。此标头由你的浏览器发送,并告诉服务器你优先选择的语言(按优先级排序)。Django 会尝试标头中的每种语言,直到找到一种具有可用翻译的语言。

  • 如果失败,它将使用全局 LANGUAGE_CODE 设置。

注意

  • 在这些位置中的每一个位置,都期望语言偏好在标准 语言格式 中,作为字符串。例如,巴西葡萄牙语为 pt-br

  • 如果基本语言可用但指定的子语言不可用,Django 将使用基本语言。例如,如果用户指定 de-at(奥地利德语),但 Django 只有 de 可用,Django 将使用 de

  • 只能选择 LANGUAGES 设置中列出的语言。如果你想将语言选择限制为提供的语言的子集(因为你的应用程序没有提供所有这些语言),请将 LANGUAGES 设置为语言列表。例如

    LANGUAGES = [
        ("de", _("German")),
        ("en", _("English")),
    ]
    

    此示例将可用于自动选择的语言限制为德语和英语(以及任何子语言,如 de-chen-us)。

  • 如果你定义了一个自定义的 LANGUAGES 设置,如上一条中所述,你可以将语言名称标记为翻译字符串——但使用 gettext_lazy() 而不是 gettext() 以避免循环导入。

    这是一个示例 settings 文件

    from django.utils.translation import gettext_lazy as _
    
    LANGUAGES = [
        ("de", _("German")),
        ("en", _("English")),
    ]
    

一旦 LocaleMiddleware 确定了用户的偏好,它就会将此偏好作为 request.LANGUAGE_CODE 提供给每个 HttpRequest。请随时在你的视图代码中读取此值。这是一个示例

from django.http import HttpResponse


def hello_world(request, count):
    if request.LANGUAGE_CODE == "de-at":
        return HttpResponse("You prefer to read Austrian German.")
    else:
        return HttpResponse("You prefer to read another language.")

请注意,对于静态(无中间件)翻译,语言位于 settings.LANGUAGE_CODE 中,而对于动态(中间件)翻译,语言位于 request.LANGUAGE_CODE 中。

Django 如何发现翻译

在运行时,Django 会构建一个内存中的统一文字翻译目录。为了实现这一点,它会按照以下算法查找翻译,该算法规定了检查不同文件路径以加载已编译的消息文件 (.mo) 的顺序以及同一文字的多个翻译的优先级。

  1. LOCALE_PATHS 中列出的目录具有最高优先级,其中先出现的目录优先级高于后出现的目录。

  2. 然后,它会在INSTALLED_APPS 中列出的每个已安装应用中查找并使用(如果存在)一个名为 locale 的目录。其中先出现的应用优先级高于后出现的应用。

  3. 最后,Django 提供的基本翻译(位于 django/conf/locale)用作回退。

另请参阅

JavaScript 资产中包含的文字的翻译是按照类似但并不完全相同的算法查找的。有关更多详细信息,请参阅 JavaScriptCatalog

如果您还设置了 FORMAT_MODULE_PATH,则还可以将 自定义格式文件 放入 LOCALE_PATHS 目录中。

在所有情况下,都期望包含翻译的目录的名称使用 区域设置名称 表示法命名。例如 dept_BRes_AR 等。地区语言变体的未翻译字符串使用通用语言的翻译。例如,未翻译的 pt_BR 字符串使用 pt 翻译。

这样,您可以编写包含自身翻译的应用程序,并且可以在项目中覆盖基本翻译。或者,您可以从多个应用程序构建一个大型项目,并将所有翻译放入一个特定于您正在组合的项目的大型通用消息文件中。选择权由您决定。

所有消息文件存储库的结构相同。它们是

  • 搜索设置文件中 LOCALE_PATHS 中列出的所有路径,查找 <language>/LC_MESSAGES/django.(po|mo)

  • $APPPATH/locale/<language>/LC_MESSAGES/django.(po|mo)

  • $PYTHONPATH/django/conf/locale/<language>/LC_MESSAGES/django.(po|mo)

要创建消息文件,请使用 django-admin makemessages 工具。并使用 django-admin compilemessages 生成由 gettext 使用的二进制 .mo 文件。

您还可以运行 django-admin compilemessages --settings=path.to.settings 以使编译器处理 LOCALE_PATHS 设置中的所有目录。

使用非英语基本语言

Django 通常假设可翻译项目中的原始字符串是用英语编写的。您可以选择其他语言,但必须注意某些限制。

  • gettext 仅为原始消息提供两种复数形式,因此如果基本语言的复数规则与英语不同,则还需要为基本语言提供翻译以包含所有复数形式。

  • 当激活英语变体且缺少英语字符串时,回退语言将不是项目的 LANGUAGE_CODE,而是原始字符串。例如,访问 LANGUAGE_CODE 设置为西班牙语且原始字符串用俄语编写的网站的英语用户将看到俄语文本而不是西班牙语文本。

返回顶部