翻译 — Django 文档

来自菜鸟教程
Django/docs/3.2.x/topics/i18n/translation
跳转至:导航、​搜索

翻译

概览

为了使 Django 项目可翻译,您必须向 Python 代码和模板添加最少数量的钩子。 这些钩子被称为翻译字符串。 他们告诉 Django:“如果该文本有该语言的翻译版本,则该文本应该被翻译成最终用户的语言。” 标记可翻译的字符串是您的责任; 系统只能翻译它知道的字符串。

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

完成此操作后,Django 会根据用户的语言偏好将 Web 应用程序即时翻译为每种可用语言。

Django 的国际化钩子默认是打开的,这意味着在框架的某些地方有一些与 i18n 相关的开销。 如果你不使用国际化,你应该花两秒钟来设置 :设置:`USE_I18N = False ` 在您的设置文件中。 然后Django会做一些优化,以免加载国际化机制。

笔记

还有一个独立但相关的 :setting:`USE_L10N` 设置,用于控制 Django 是否应该实现格式本地化。 有关更多详细信息,请参阅 格式本地化


笔记

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


国际化:在 Python 代码中

标准翻译

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

笔记

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

  1. 有时,您应该使用 gettext_lazy() 作为特定文件的默认翻译方法。 如果全局命名空间中没有 _(),开发者就不得不考虑哪个是最合适的翻译功能。
  2. 下划线字符 (_) 用于表示 Python 交互式 shell 和 doctest 测试中的“上一个结果”。 安装全局 _() 功能会导致干扰。 将 gettext() 显式导入为 _() 可避免此问题。


哪些函数可以别名为 _

由于 xgettext(由 :djadmin:`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 的翻译字符串检测实用程序, :djadmin:`django-admin makemessages ` , 将无法找到这些字符串。 更多关于 :djadmin:`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,
}

运行时会报错 :djadmin:`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 ""

:ttag:`translate`:ttag:`blocktranslate` 模板标签也支持上下文标记。


懒惰的翻译

使用 django.utils.translation 中的翻译函数的惰性版本(通过名称中的 lazy 后缀很容易识别)来延迟翻译字符串——当值被访问时而不是当它们被访问时重新调用。

这些函数存储对字符串的惰性引用——而不是实际的翻译。 当字符串在字符串上下文中使用时,翻译本身将完成,例如在模板渲染中。

当对这些函数的调用位于在模块加载时执行的代码路径中时,这是必不可少的。

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

模型字段和关系 verbose_name 和 help_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'))

您可以使用 verbose_name 选项将 ForeignKeyManyToManyFieldOneToOneField 关系的名称标记为可翻译:

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')

@display 装饰器的模型方法 description 参数

对于模型方法,您可以使用 display() 装饰器的 description 参数为 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() 调用的结果可用于在其他 Django 代码中使用字符串(str 对象)的任何地方,但它可能不适用于任意 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 本身用于字符串时(通常在模板渲染时)转换为字符串。


延迟翻译中lazy的其他用途

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

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()

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 %} 内的字符串中混合模板变量。 如果您的翻译需要带有变量(占位符)的字符串,请使用 :ttag:`{% 块翻译 %} ` 反而。

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

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

3.1 版更改: trans 标签更名为 translatetrans 标签仍然支持作为向后兼容的别名。


blocktranslate 模板标签

:ttag:`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 的计数器值。 该值将用于选择正确的复数形式。
  • {% blocktranslate %}{% endblocktranslate %} 标签中的 {% plural %} 标签指定单数和复数形式。

一个例子:

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

如果未指定 trimmed 选项,与 "\n  First sentence.\n  Second paragraph.\n" 相比,将导致 PO 文件中的条目 "First sentence. Second paragraph."

3.1 版更改: blocktrans 标签更名为 blocktranslateblocktrans 标签仍然支持作为向后兼容的别名。


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

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

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

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

笔记

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


对模板中译员的评论

就像使用 Python 代码 一样,这些翻译人员的注释可以使用注释指定,或者使用 :ttag:`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 %} 模板标签来检索语言列表的信息(例如 :setting:`LANGUAGES` 中指定的活动语言)。 有关如何使用 {% get_language_info_list %} 显示语言选择器的示例,请参阅 有关 set_language 重定向视图 的部分。

除了 :setting:`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 %}

模板过滤器

为方便起见,还有一些过滤器可用:


国际化:在 JavaScript 代码中

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

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

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

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

JavaScriptCatalog 视图

class JavaScriptCatalog

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

属性

domain

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

packages

已安装应用程序中的 应用程序名称 列表。 这些应用程序应该包含一个 locale 目录。 所有这些目录加上在 :setting:`LOCALE_PATHS` 中找到的所有目录(总是包括在内)被合并到一个目录中。 默认为 None,这意味着来自所有 :setting:`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

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 Array 对象,其元素值然后按照它们出现的相同顺序依次插入到相应的 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 函数的工作方式与 :tfilter:`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

为了使用另一个客户端库来处理翻译,您可能需要利用 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='js18n-%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 模式:

警告

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


URL 模式中的语言前缀

i18n_patterns(*urls, prefix_default_language=True)

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

prefix_default_language 设置为 False 会从默认语言 (:setting:`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 模式

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

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,请使用 :ttag:`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 %}

:ttag:`language` 标签需要语言代码作为唯一的参数。


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

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

消息文件

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

Django 自带一个工具, :djadmin:`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 如何发现翻译 并确保 :setting:`LOCALE_PATHS` 已配置正确)。 它在目录 locale/LANG/LC_MESSAGES 中创建(或更新)一个消息文件。 在 de 示例中,文件将为 locale/de/LC_MESSAGES/django.po

当您从项目的根目录运行 makemessages 时,提取的字符串将自动分发到正确的消息文件中。 也就是说,从包含 locale 目录的应用程序文件中提取的字符串将进入该目录下的消息文件中。 从没有任何 locale 目录的应用程序文件中提取的字符串将出现在 :setting:`LOCALE_PATHS` 中首先列出的目录下的消息文件中,或者如果 [ X204X]:setting:`LOCALE_PATHS` 为空。

默认情况下 :djadmin:`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 模板?

:djadmin:`makemessages` 不理解 Jinja2 模板的语法。 要从包含 Jinja2 模板的项目中提取字符串,请改用 Message Extracting from Babel

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

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

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

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

Babel 提供了类似 :djadmin:`makemessages` 的特性,一般可以替换,不依赖于 gettext。 有关更多信息,请阅读有关 使用消息目录 的文档。


没有gettext?

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


在 Windows 上工作?

如果您使用的是 Windows 并且需要安装 GNU gettext 实用程序以便 :djadmin:`makemessages` 工作,请参阅 Windows 上的 gettext 以获取更多信息。


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

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

_("Welcome to my site.")

…然后 :djadmin:`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 文件时很重要。


模糊条目

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


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

django-admin makemessages -a

编译消息文件

创建消息文件后——每次对其进行更改时——您都需要将其编译为更有效的形式,以供 gettext 使用。 用 :djadmin:`django-admin compilemessages ` 公用事业。

该工具运行所有可用的 .po 文件并创建 .mo 文件,这些文件是为 gettext 使用而优化的二进制文件。 在您运行的同一目录中 :djadmin:`django-admin makemessages ` , 跑 :djadmin:`django-admin compilemessages ` 像这样:

django-admin compilemessages

就是这样。 您的翻译已准备就绪。

在 Windows 上工作?

如果您使用的是 Windows 并且需要安装 GNU gettext 实用程序,那么 :djadmin:`django-admin compilemessages ` 作品见在 Windows 上获取文本想要查询更多的信息。


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

Django 仅支持以 UTF-8 编码的 .po 文件,并且没有任何 BOM(字节顺序标记),因此如果您的文本编辑器默认将此类标记添加到文件的开头,那么您将需要重新配置它。


故障排除: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 消息文件相同——使用 :djadmin:`django-admin makemessages ` 工具。 唯一的区别是您需要通过提供 -d djangojs 参数明确指定在这种情况下在 gettext 说法中称为域的域 djangojs 域,如下所示:

django-admin makemessages -d djangojs -l de

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


gettext 在 Windows 上

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

您也可以使用在别处获得的 gettext 二进制文件,只要 xgettext --version 命令正常工作即可。 如果在 Windows 命令提示符下输入的命令 xgettext --version 导致弹出窗口显示“xgettext.exe 已生成错误并将被 Windows 关闭”,请不要尝试使用带有 gettext 包的 Django 翻译实用程序”。


自定义 makemessages 命令

如果你想向 xgettext 传递额外的参数,你需要创建一个自定义的 :djadmin:`makemessages` 命令并覆盖它的 xgettext_options 属性:

from django.core.management.commands import makemessages

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

如果您需要更大的灵活性,您还可以向您的自定义 :djadmin:`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/ 处可用。)

警告

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


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

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

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

3.1 版本变更: 在旧版本中,回退的区别是根据 X-Requested-With 标头是否设置为值 XMLHttpRequest。 这是由 jQuery ajax() 方法设置的。


这是示例 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 中,请在响应中设置 :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() 更改此线程的语言,设置 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",无论 :setting:`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 有一个非常灵活的模型来决定应该使用哪种语言——安装范围内,特定用户,或两者兼而有之。

要设置安装范围的语言首选项,请设置 :setting:`LANGUAGE_CODE`。 Django 使用这种语言作为默认翻译——如果没有找到更好的匹配翻译,那么最后的尝试是通过语言环境中间件采用的方法之一(见下文)。

如果你只想用你的母语运行 Django,你需要做的就是设置 :setting:`LANGUAGE_CODE` 并确保相应的 消息文件 及其编译版本 ([ X203X]) 存在。

如果您想让每个单独的用户指定他们喜欢哪种语言,那么您还需要使用 LocaleMiddlewareLocaleMiddleware 启用基于请求数据的语言选择。 它为每个用户定制内容。

要使用 LocaleMiddleware,请将 'django.middleware.locale.LocaleMiddleware' 添加到您的 :setting:`MIDDLEWARE` 设置。 由于中间件顺序很重要,请遵循以下准则:

  • 确保它是第一个安装的中间件之一。
  • 它应该在 SessionMiddleware 之后,因为 LocaleMiddleware 使用会话数据。 它应该在 CommonMiddleware 之前,因为 CommonMiddleware 需要激活语言才能解析请求的 URL。
  • 如果你使用CacheMiddleware,在它后面加上LocaleMiddleware

例如,您的 :setting:`MIDDLEWARE` 可能如下所示:

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

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

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

  • 首先,它在请求的 URL 中查找语言前缀。 这仅在您在根 URLconf 中使用 i18n_patterns 函数时执行。 有关语言前缀和如何国际化 URL 模式的更多信息,请参阅 国际化:在 URL 模式中

  • 如果失败,它会寻找一个 cookie。

    使用的 cookie 名称由 :setting:`LANGUAGE_COOKIE_NAME` 设置设置。 (默认名称为 django_language。)

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

  • 如果失败,它使用全局 :setting:`LANGUAGE_CODE` 设置。

注意事项:

  • 在这些地方的每一个中,语言首选项都应该是标准的 语言格式 ,作为一个字符串。 例如,巴西葡萄牙语是 pt-br

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

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

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

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

  • 如果你定义了一个自定义的 :setting:`LANGUAGES` 设置,如上一个项目符号中所述,你可以将语言名称标记为翻译字符串——但使用 gettext_lazy() 而不是 [ X199X]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. :setting:`LOCALE_PATHS` 中列出的目录具有最高优先级,首先出现的目录的优先级高于后面出现的目录。
  2. 然后,它会查找并使用 :setting:`INSTALLED_APPS` 中列出的每个已安装应用程序中是否存在 locale 目录。 先出现的优先级高于后出现的优先级。
  3. 最后,Django 提供的 django/conf/locale 中的基本翻译用作后备。

也可以看看

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

如果您还设置了 :setting:`FORMAT_MODULE_PATH`,也可以将 自定义格式文件 放在 :setting:`LOCALE_PATHS` 目录中。


在所有情况下,包含翻译的目录名称应使用 语言环境名称 符号命名。 例如 dept_BRes_AR等 地域语言变体的未翻译字符串使用通用语言的翻译。 例如,未翻译的 pt_BR 字符串使用 pt 翻译。

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

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

  • 将在您的设置文件中的 :setting:`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)

要创建消息文件,您可以使用 :djadmin:`django-admin makemessages ` 工具。 你用 :djadmin:`django-admin compilemessages ` 产生二进制.mo使用的文件gettext .

你也可以运行 :djadmin:`django-admin compilemessages --settings=path.to.settings ` 使编译器处理您的所有目录 :设置:`LOCALE_PATHS` 环境。


使用非英语基础语言

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

  • gettext 仅提供原始消息的两种复数形式,因此如果基础语言的复数规则与英语不同,您还需要提供基础语言的翻译以包括所有复数形式。
  • 当激活英文变体并且缺少英文字符串时,回退语言将不是项目的 :setting:`LANGUAGE_CODE`,而是原始字符串。 例如,一个英语用户访问一个 :setting:`LANGUAGE_CODE` 设置为西班牙语的网站,并且用俄语书写的原始字符串将看到俄语文本而不是西班牙语。