翻译

概述

为了使 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 支持的语法。特别是,Python f-strings 尚不受 xgettext 支持,并且 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中所做的一样,您应该为关系提供小写的冗长名称文本,因为 Django 在需要时会自动将其大写。

模型冗长名称值

建议始终提供明确的verbose_nameverbose_name_plural选项,而不是依赖 Django 通过查看模型的类名来执行的以英语为中心且有些幼稚的冗长名称的备用确定

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参数向 Django 和管理站点提供翻译,该参数用于display()装饰器

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

使用惰性翻译对象

可以在使用字符串(其他 Django 代码中的 str 对象)的任何地方使用 gettext_lazy() 调用的结果,但它可能无法与任意 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_stringstr.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 本身用于字符串中(通常在模板渲染时)时才转换为字符串。

延迟翻译中延迟的其它用法

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

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

mark_safe_lazy = lazy(mark_safe, str)

然后稍后

lazy_string = mark_safe_lazy(_("<p>My <strong>string!</strong></p>"))

语言的本地化名称

get_language_info(lang_code)

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 %}

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

如果解析块参数之一失败,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 变量的说明 适用。

反向 URL 查找不能在 blocktranslate 中进行,应事先检索(并存储)

{% 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 }}">

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

在 Django 4.2 中更改

在较早版本中,asvar 实例未标记为安全,以用于(HTML)输出目的。

{% 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 中指定的活动语言)。请参阅 有关 set_language 重定向视图的部分,了解如何使用 {% get_language_info_list %} 显示语言选择器。

除了 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 }}(“německy”,当活动语言为捷克语时)

国际化:在 JavaScript 代码中

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

  • JavaScript 代码无法访问 gettext 实现。
  • JavaScript 代码无法访问 .po.mo 文件;它们需要由服务器提供。
  • JavaScript 的翻译目录应尽可能小。

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

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

JavaScriptCatalog 视图

class JavaScriptCatalog

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

属性

domain

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

packages

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

具有默认值示例:

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

在 Python 代码中,gettext 函数的行为与标准 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

JSONCatalog

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

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

响应格式如下

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

关于性能的说明

各种 JavaScript/JSON i18n 视图在每次请求时都会从 .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)

此函数可用于根 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/'

警告

在大多数情况下,最好仅在模式的语言代码前缀块中使用已翻译的 URL(使用 i18n_patterns()),以避免不小心翻译的 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 正常工作,请参阅 gettext on Windows 以获取更多信息。

每个 .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

在某些情况下,例如带有百分号后跟空格和 字符串转换类型的字符串(例如 _("10% interest")),gettext() 会错误地将带有 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-admin makemessages 工具,以与其他 Django 消息文件相同的方式创建和更新消息文件。唯一的区别是,你需要明确指定在 gettext 术语中称为域的内容,在本例中为 djangojs 域,方法是提供 -d djangojs 参数,如下所示

django-admin makemessages -d djangojs -l de

这将为德语创建或更新 JavaScript 的消息文件。在更新消息文件后,以与处理普通 Django 消息文件相同的方式运行 django-admin compilemessages

gettext 在 Windows 上

这仅适用于想要提取消息 ID 或编译消息文件 (.po) 的人员。翻译工作本身涉及编辑此类型的现有文件,但如果你想创建自己的消息文件,或者想测试或编译已更改的消息文件,请下载 预编译二进制安装程序

你还可以使用从其他地方获取的 gettext 二进制文件,只要 xgettext --version 命令正常工作即可。如果在 Windows 命令提示符下输入的命令 xgettext --version 导致弹出窗口显示“xgettext.exe 已生成错误,将由 Windows 关闭”,请不要尝试将 Django 翻译实用程序与 gettext 包一起使用。

自定义 makemessages 命令

如果你想将其他参数传递给 xgettext,你需要创建一个自定义 makemessages 命令并覆盖其 xgettext_options 属性

from django.core.management.commands import makemessages


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

如果你需要更大的灵活性,你还可以向自定义 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)

为了方便起见,Django 附带了一个视图 django.views.i18n.set_language(),它设置用户的语言首选项并重定向到给定的 URL,或者默认情况下重定向到前一页。

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

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

(请注意,此示例将视图显示在 /i18n/setlang/。)

警告

确保不要将上述 URL 包含在 i18n_patterns() 中 - 它本身需要与语言无关才能正常工作。

该视图期望通过 POST 方法进行调用,其中请求中设置了 language 参数。如果启用了会话支持,则该视图会将语言选择保存在用户的会话中。它还会将语言选择保存在默认名为 django_language 的 Cookie 中。(可以通过 LANGUAGE_COOKIE_NAME 设置来更改名称。)

在设置语言选择后,Django 会在 POSTGET 数据中查找 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()。它仅适用于当前线程。要在 Cookie 中为整个会话保留语言,请在响应中设置 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() 更改此线程的语言,而设置 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' 调用此函数将为您提供 "Willkommen",无论 LANGUAGE_CODE 和中间件设置的语言如何。

特别感兴趣的函数是 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() 以避免循环导入。

    这是一个示例设置文件

    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/conf/locale 中提供的 Django 基本翻译用作后备。

另请参阅

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 设置为西班牙语且原始字符串用俄语编写的网站的英语用户将看到俄语文本,而不是西班牙语文本。
返回顶部