第十九章: 国际化

Django诞生于美国中部堪萨斯的劳伦斯,距美国的地理中心不到40英里。 像大多数开源项目一样,Djano社区逐渐开始包括来自全球各地的许多参与者。 鉴于Django社区逐渐变的多样性,国际化本地化逐渐变得很重要。 由于很多开发者对这些措辞比较困惑,所以我们将简明的定义一下它们。

  • 国际化* 是指为了该软件在任何地区的潜在使用而进行程序设计的过程。 它包括了为将来翻译而标记的文本(比如用户界面要素和错误信息等)、日期和时间的抽象显示以便保证不同地区的标准得到遵循、为不同时区提供支持,并且一般确保代码中不会存在关于使用者所在地区的假设。 您会经常看到国际化被缩写为“I18N”(18表示Internationlization这个单词首字母I和结尾字母N之间的字母有18个)。

  • 本地化* 是指使一个国际化的程序为了在某个特定地区使用而进行实际翻译的过程。 有时,本地化缩写为 L10N

Django本身是完全国际化了的,所有的字符串均因翻译所需而被标记,并且设定了与地域无关的显示控制值,如时间和日期。 Django是带着50个不同的本地化文件发行的。 即使您的母语不是英语,Django也很有可能已经被翻译为您的母语了。

这些本地化文件所使用的国际化框架同样也可以被用在您自己的代码和模板中。

您只需要添加少量的挂接代码到您的Python代码和模板中。 这些挂接代码被称为 翻译字符串 。它们告诉Django:如果这段文本的译文可用的话,它应被翻译为终端用户指定的语言。

Django会根据用户的语言偏好,在线地运用这些挂接指令去翻译Web应用程序。

本质上来说,Django做两件事情:

  • 它让开发者和模板的作者指定他们的应用程序的哪些部分应该被翻译。

  • Django根据用户的语言偏好来翻译Web应用程序。

备注:

Django的翻译机制是使用 GNU gettext (http://www.gnu.org/software/gettext/),具体为Python自带的标准模块 gettext

如果您不需要国际化:

Django的国际化挂接是默认开启的,这可能会给Django的运行增加一点点开销。 如果您不需要国际化支持,那么您可以在您的设置文件中设置 USE_I18N = False 。 如果 USE_I18N 被设为 False ,那么Django会进行一些优化,而不加载国际化支持机制。

您也可以从您的 TEMPLATE_CONTEXT_PROCESSORS 设置中移除 'django.core.context_processors.i18n'

对你的Django应用进行国际化的三个步骤:

  1. 第一步:在你的Python代码和模板中嵌入待翻译的字符串。

  2. 第二步:把那些字符串翻译成你要支持的语言。

  3. 第三步:在你的Django settings文件中激活本地中间件。

我们将详细地对以上步骤逐一进行描述。

1、如何指定待翻译字符串

翻译字符串指定这段需要被翻译的文本。 这些字符串可以出现在您的Python代码和模板中。 而标记出这些翻译字符串则是您的责任;系统仅能翻译出它所知道的东西。

在Python 代码中

标准翻译

使用函数 ugettext() 来指定一个翻译字符串。 作为惯例,使用短别名 _ 来引入这个函数以节省键入时间.

在下面这个例子中,文本 "Welcome to my site" 被标记为待翻译字符串:

from django.utils.translation import ugettext as _

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

显然,你也可以不使用别名来编码。 下面这个例子和前面两个例子相同:

from django.utils.translation import ugettext

def my_view(request):
    output = ugettext("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的待翻译字符串检测工具, make-messages.py ,将不能找到这些字符串。 稍后,在 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 or %d )。 如果你使用位置插入的话,翻译动作将不能重新排序占位符文本。

标记字符串为不操作

使用 django.utils.translation.gettext_noop() 函数来标记一个不需要立即翻译的字符串。 这个串会稍后从变量翻译。

使用这种方法的环境是,有字符串必须以原始语言的形式存储(如储存在数据库中的字符串)而在最后需要被翻译出来(如显示给用户时)。

惰性翻译

使用 django.utils.translation.gettext_lazy() 函数,使得其中的值只有在访问时才会被翻译,而不是在 gettext_lazy() 被调用时翻译。

例如:要翻译一个模型的 help_text,按以下进行:

from django.utils.translation import ugettext_lazy

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

在这个例子中, ugettext_lazy() 将字符串作为惰性参照存储,而不是实际翻译。 翻译工作将在字符串在字符串上下文中被用到时进行,比如在Django管理页面提交模板时。

在Python中,无论何处你要使用一个unicode 字符串(一个unicode 类型的对象),您都可以使用一个 ugettext_lazy() 调用的结果。 一个ugettext_lazy()对象并不知道如何把它自己转换成一个字节串。如果你尝试在一个需要字节串的地方使用它,事情将不会如你期待的那样。 同样,你也不能在一个字节串中使用一个 unicode 字符串。所以,这同常规的Python行为是一致的。 例如:

# This is fine: putting a unicode proxy into a unicode string.
u"Hello %s" % ugettext_lazy("people")

# This will not work, since you cannot insert a unicode object
# into a bytestring (nor can you insert our unicode proxy there)
"Hello %s" % ugettext_lazy("people")

如果你曾经见到到像"hello"这样的输出,你就可能在一个字节串中插入了ugettext_lazy()的结果。 在您的代码中,那是一个漏洞。

如果觉得 gettext_lazy 太过冗长,可以用 _ (下划线)作为别名,就像这样:

from django.utils.translation import ugettext_lazy as _

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

在Django模型中总是无一例外的使用惰性翻译。 为了翻译,字段名和表名应该被标记。(否则的话,在管理界面中它们将不会被翻译) 这意味着在Meta类中显式地编写verbose_naneverbose_name_plural选项,而不是依赖于Django默认的verbose_nameverbose_name_plural(通过检查model的类名得到)。

from django.utils.translation import ugettext_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 = _('mythings')

复数的处理

使用django.utils.translation.ungettext()来指定以复数形式表示的消息。 例如:

from django.utils.translation import ungettext

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

ngettext 函数包括三个参数: 单数形式的翻译字符串,复数形式的翻译字符串,和对象的个数(将以 count 变量传递给需要翻译的语言)。

模板代码

Django模板使用两种模板标签,且语法格式与Python代码有些许不同。 为了使得模板访问到标签,需要将 {% load i18n %} 放在模板最前面。

这个{% trans %}模板标记翻译一个常量字符串 (括以单或双引号) 或 可变内容:

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

如果有noop 选项,变量查询还是有效但翻译会跳过。 当空缺内容要求将来再翻译时,这很有用。

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

在一个带 {% trans %} 的字符串中,混进一个模板变量是不可能的。如果你的译文要求字符串带有变量(占位符placeholders),请使用 {% blocktrans %}

{% blocktrans %}This string will have {{ value }} inside.{% endblocktrans %}

使用模板过滤器来翻译一个模板表达式,需要在翻译的这段文本中将表达式绑定到一个本地变量中:

{% blocktrans with value|filter as myvar %}
This will have {{ myvar }} inside.
{% endblocktrans %}

如果需要在 blocktrans 标签内绑定多个表达式,可以用 and 来分隔:

{% blocktrans with book|title as book_t and author|title as author_t %}
This is {{ book_t }} by {{ author_t }}
{% endblocktrans %}

为了表示单复数相关的内容,需要在 {% blocktrans %}{% endblocktrans %} 之间使用 {% plural %} 标签来指定单复数形式,例如:

{% blocktrans count list|length as counter %}
There is only one {{ name }} object.
{% plural %}
There are {{ counter }} {{ name }} objects.
{% endblocktrans %}

其内在机制是,所有的块和内嵌翻译调用相应的 gettextngettext

每一个RequestContext可以访问三个指定翻译变量:

  • {{ LANGUAGES }} 是一系列元组组成的列表,每个元组的第一个元素是语言代码,第二个元素是用该语言表示的语言名称。

  • 作为一二字符串,LANGUAGE_CODE是当前用户的优先语言。 例如: en-us。(请参见下面的Django如何发现语言偏好)

  • LANGUAGE_BIDI就是当前地域的说明。 如果为真(True),它就是从右向左书写的语言,例如: 希伯来语,阿拉伯语。 如果为假(False),它就是从左到右书写的语言,如: 英语,法语,德语等。

如果你不用这个RequestContext扩展,你可以用3个标记到那些值:

{% get_current_language as LANGUAGE_CODE %}
{% get_available_languages as LANGUAGES %}
{% get_current_language_bidi as LANGUAGE_BIDI %}

这些标记亦要求一个 {% load i18n %}

翻译的hook在任何接受常量字符串的模板块标签内也是可以使用的。 此时,使用 _() 表达式来指定翻译字符串,例如:

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

在这种情况下,标记和过滤器两个都会看到已经翻译的字符串,所有它们并不需要提防翻译操作。

备注:

在这个例子中,翻译结构将放过字符串"yes,no",而不是单独的字符串"yes""no"。翻译的字符串将需要包括逗号以便过滤器解析代码明白如何分割参数。 例如, 一个德语翻译器可能会翻译字符串 "yes,no""ja,nein" (保持逗号原封不动)。

与惰性翻译对象一道工作

在模型和公用函数中,使用ugettext_lazy()ungettext_lazy()来标记字符串是很普遍的操作。 当你在你的代码中其它地方使用这些对象时,你应当确定你不会意外地转换它们成一个字符串,因为它们应被尽量晚地转换(以便正确的地域生效) 这需要使用及个帮助函数。

拼接字符串: string_concat()

标准Python字符串拼接(''.join([...]) ) 将不会工作在包括惰性翻译对象的列表上。 作为替代,你可以使用django.utils.translation.string_concat(), 这个函数创建了一个惰性对象,其连接起它的内容 并且 仅当结果被包括在一个字符串中时转换它们为字符串 。 例如:

from django.utils.translation import string_concat
# ...
name = ugettext_lazy(u'John Lennon')
instrument = ugettext_lazy(u'guitar')
result = string_concat([name, ': ', instrument])

System Message: ERROR/3 (&lt;string&gt;, line 519)

Error in “cnid” directive: no content permitted.

.. cnid:: 109

  在这种情况下,当

System Message: WARNING/2 (&lt;string&gt;, line 523)

Explicit markup ends without a blank line; unexpected unindent.

result 自己被用与一个字符串时, result 中的惰性翻译将仅被转换为字符串(通常在模板渲染时间)。

allow_lazy() 修饰符

Django提供很多功能函数(如:取一个字符串作为他们的第一个参数并且对那个字符串做些什么)。(尤其在 django.utils 中) 这些函数被模板过滤器像在其他代码中一样直接使用。

如果你写你自己的类似函数并且与翻译打交道,当第一个参数是惰性翻译对象时,你会面临“做什么”的难题。 因为你可能在视图之外使用这个函数(并且因此当前线程的本地设置将会不正确),所以你不想立即转换其为一个字符串。

象这种情况,请使用 django.utils.functional.allow_lazy() 修饰符。 它修改这个函数以便 假如第一个参数是一个惰性翻译, 这个函数的赋值会被延后直到它需要被转化为一个字符串为止。

例如:

from django.utils.functional import allow_lazy

def fancy_utility_function(s, ...):
    # Do some conversion on string 's'
    # ...
fancy_utility_function = allow_lazy(fancy_utility_function, unicode)

allow_lazy() 装饰符 采用了另外的函数来装饰,以及一定量的,原始函数可以返回的特定类型的额外参数 (*args ) 。 通常,在这里包括 unicode 就足够了并且确定你的函数将仅返回Unicode字符串。

使用这个修饰符意味着你能写你的函数并且假设输入是合适的字符串,然后在末尾添加对惰性翻译对象的支持。

2、如何创建语言文件

当你标记了翻译字符串,你就需要写出(或获取已有的)对应的语言翻译信息。 这里就是它如何工作的。

地域限制

Django不支持把你的应用本地化到一个连它自己都还没被翻译的地域。 在这种情况下,它将忽略你的翻译文件。 如果你想尝试这个并且Django支持它,你会不可避免地见到这样一个混合体––参杂着你的译文和来自Django自己的英文。 如果你的应用需要你支持一个Django中没有的地域,你将至少需要做一个Django core的最小翻译。

消息文件

第一步,就是为一种语言创建一个信息文件。 信息文件是包含了某一语言翻译字符串和对这些字符串的翻译的一个文本文件。 信息文件以 .po 为后缀名。

Django中带有一个工具, bin/make-messages.py ,它完成了这些文件的创建和维护工作。 运行以下命令来创建或更新一个信息文件:

django-admin.py makemessages -l de

其中 de 是所创建的信息文件的语言代码。 在这里,语言代码是以本地格式给出的。 例如,巴西地区的葡萄牙语为 pt_BR ,澳大利亚地区的德语为 de_AT

这段脚本应该在三处之一运行:

  • Django项目根目录。

  • 您Django应用的根目录。

  • django 根目录(不是Subversion检出目录,而是通过 $PYTHONPATH 链接或位于该路径的某处)。 这仅和你为Django自己创建一个翻译时有关。

这段脚本遍历你的项目源树或你的应用程序源树并且提取出所有为翻译而被标记的字符串。 它在 locale/LANG/LC_MESSAGES 目录下创建(或更新)了一个信息文件。针对上面的de,应该是locale/de/LC_MESSAGES/django.po

作为默认, django-admin.py makemessages 检测每一个有 .html 扩展名的文件。 以备你要重载缺省值,使用 --extension-e 选项指定文件扩展名来检测。

django-admin.py makemessages -l de -e txt

用逗号和(或)使用-e--extension来分隔多项扩展名:

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

当创建JavaScript翻译目录时,你需要使用特殊的Django域:not -e js

没有gettext?

如果没有安装 gettext 组件, make-messages.py 将会创建空白文件。 这种情况下,安装 gettext 组件或只是复制英语信息文件( conf/locale/en/LC_MESSAGES/django.po )来作为一个起点;只是一个空白的翻译信息文件而已。

工作在Windows上么?

如果你正在使用Windows,且需要安装GNU gettext共用程序以便 django-admin makemessages 可以工作,请参看下面Windows小节中gettext部分以获得更多信息。

.po 文件格式很直观。 每个 .po 文件包含一小部分的元数据,比如翻译维护人员的联系信息,而文件的大部分内容是简单的翻译字符串和对应语言翻译结果的映射关系的列表。

举个例子,如果Django应用程序包括一个 "Welcome to my site." 的待翻译字符串 ,像这样:

_("Welcome to my site.")

django-admin.py makemessages将创建一个 .po 文件来包含以下片段的消息:

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

快速解释:

  • msgid 是在源文件中出现的翻译字符串。 不要做改动。

  • msgstr 是相应语言的翻译结果。 刚创建时它只是空字符串,此时就需要你来完成它。 注意不要丢掉语句前后的引号。

  • 作为方便之处,每一个消息都包括:以 # 为前缀的一个注释行并且定位上边的msgid 行,文件名和行号。

对于比较长的信息也有其处理方法。 msgstr (或 msgid )后紧跟着的字符串为一个空字符串。 然后真正的内容在其下面的几行。 这些字符串会被直接连在一起。 同时,不要忘了字符串末尾的空格,因为它们会不加空格地连到一起。

若要对新创建的翻译字符串校验所有的源代码和模板,并且更新所有语言的信息文件,可以运行以下命令:

django-admin.py makemessages -a

编译信息文件

创建信息文件之后,每次对其做了修改,都需要将它重新编译成一种更有效率的形式,供 gettext 使用。可以使用django-admin.py compilemessages完成。

这个工具作用于所有有效的 .po 文件,创建优化过的二进制 .mo 文件供 gettext 使用。在你可以运行django-admin.py makemessages的目录下,运行django-admin.py compilemessages

django-admin.py compilemessages

就是这样了。 你的翻译成果已经可以使用了。

Django如何处理语言偏好

一旦你准备好了翻译,如果希望在Django中使用,那么只需要激活这些翻译即可。

在这些功能背后,Django拥有一个灵活的模型来确定在安装和使用应用程序的过程中选择使用的语言。

要设定一个安装阶段的语种偏好,请设定LANGUAGE_CODE。如果其他翻译器没有找到一个译文,Django将使用这个语种作为缺省的翻译最终尝试。

如果你只是想要用本地语言来运行Django,并且该语言的语言文件存在,只需要简单地设置 LANGUAGE_CODE 即可。

如果要让每一个使用者各自指定语言偏好,就需要使用 LocaleMiddlewareLocaleMiddleware 使得Django基于请求的数据进行语言选择,从而为每一位用户定制内容。 它为每一个用户定制内容。

使用 LocaleMiddleware 需要在 MIDDLEWARE_CLASSES 设置中增加 'django.middleware.locale.LocaleMiddleware' 。 中间件的顺序是有影响的,最好按照依照以下要求:

  • 保证它是第一批安装的中间件类。

  • 因为 LocalMiddleware 要用到session数据,所以需要放在 SessionMiddleware 之后。

  • 如果你使用CacheMiddleware,把LocaleMiddleware放在它后面。

例如, MIDDLE_CLASSES 可能会是如此:

MIDDLEWARE_CLASSES = (
   'django.contrib.sessions.middleware.SessionMiddleware',
   'django.middleware.locale.LocaleMiddleware',
   'django.middleware.common.CommonMiddleware',
)

(更多关于中间件的内容,请参阅第17章)

LocaleMiddleware 按照如下算法确定用户的语言:

  • 首先,在当前用户的 session 的中查找django_language键;

  • 如未找到,它会找寻一个cookie

  • 还找不到的话,它会在 HTTP 请求头部里查找Accept-Language, 该头部是你的浏览器发送的,并且按优先顺序告诉服务器你的语言偏好。 Django会尝试头部中的每一个语种直到它发现一个可用的翻译。

  • 以上都失败了的话, 就使用全局的 LANGUAGE_CODE 设定值。

备注:

在上述每一处,语种偏好应作为字符串,以标准的语种格式出现。 例如,巴西葡萄牙语是pt-br

如果一个基本语种存在而亚语种没有指定,Django将使用基本语种。 比如,如果用户指定了 de-at (澳式德语)但Django只有针对 de 的翻译,那么 de 会被选用。

只有在 LANGUAGES 设置中列出的语言才能被选用。 若希望将语言限制为所提供语言中的某些(因为应用程序并不提供所有语言的表示),则将 LANGUAGES 设置为所希望提供语言的列表,例如: 例如:

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

上面这个例子限制了语言偏好只能是德语和英语(包括它们的子语言,如 de-chen-us )。

如果自定义了 LANGUAGES ,将语言标记为翻译字符串是可以的,但是,请不要使用 django.utils.translation 中的 gettext() (决不要在settings文件中导入 django.utils.translation ,因为这个模块本身是依赖于settings,这样做会导致无限循环),而是使用一个“虚构的” gettext()

解决方案就是使用一个“虚假的” gettext() 。以 下是一个settings文件的例子:

ugettext = lambda s: s

LANGUAGES = (
    ('de', ugettext('German')),
    ('en', ugettext('English')),
)

这样做的话, make-messages.py 仍会寻找并标记出将要被翻译的这些字符串,但翻译不会在运行时进行,故而需要在任何使用 LANGUAGES 的代码中用“真实的” ugettext()

LocaleMiddleware 只能选择那些Django已经提供了基础翻译的语言。 如果想要在应用程序中对Django中还没有基础翻译的语言提供翻译,那么必须至少先提供该语言的基本的翻译。 例如,Django使用特定的信息ID来翻译日期和时间格式,故要让系统正常工作,至少要提供这些基本的翻译。

以英语的 .po 文件为基础,翻译其中的技术相关的信息,可能还包括一些使之生效的信息。

技术相关的信息ID很容易被认出来:它们都是大写的。 这些信息ID的翻译与其他信息不同:你需要提供其对应的本地化内容。 例如,对于 DATETIME_FORMAT (或 DATE_FORMAT 、 TIME_FORMAT ),应该提供希望在该语言中使用的格式化字符串。 格式被模板标签now用来识别格式字符串。

一旦LocaleMiddleware决定用户的偏好,它会让这个偏好作为request.LANGUAGE_CODE对每一个HttpRequest有效。请随意在你的视图代码中读一读这个值。 以下是一个简单的例子:

def hello_world(request):
    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在该视图所在的应用程序文件夹中寻找 locale 目录。 若找到所选语言的翻译,则加载该翻译。

  • 第二步,Django在项目目录中寻找 locale 目录。 若找到翻译,则加载该翻译。

  • 最后,Django使用 django/conf/locale 目录中的基本翻译。

以这种方式,你可以创建包含独立翻译的应用程序,可以覆盖项目中的基本翻译。 或者,你可以创建一个包含几个应用程序的大项目,并将所有需要的翻译放在一个大的项目信息文件中。 决定权在你手中。

所有的信息文件库都是以同样方式组织的: 它们是:

  • $APPPATH/locale/&lt;language&gt;/LC_MESSAGES/django.(po|mo)

  • $PROJECTPATH/locale/&lt;language&gt;/LC_MESSAGES/django.(po|mo)

  • 所有在settings文件中 LOCALE_PATHS 中列出的路径以其列出的顺序搜索 &lt;language&gt;/LC_MESSAGES/django.(po|mo)

  • $PYTHONPATH/django/conf/locale/&lt;language&gt;/LC_MESSAGES/django.(po|mo)

要创建信息文件,也是使用 django-admin.py makemessages.py 工具,和Django信息文件一样。 需要做的就是进入正确的目录—— conf/locale (在源码树的情况下)或者 locale/ (在应用程序信息或项目信息的情况下)所在的目录下。 同样地,使用 compile-messages.py 生成 gettext 需要使用的二进制 django.mo 文件。

您亦可运行django-admin.py compilemessages --settings=path.to.settings 来使编译器处理所有存在于您 LOCALE_PATHS 设置中的目录。

应用程序信息文件稍微难以发现——因为它们需要 LocaleMiddle 。如果不使用中间件,Django只会处理Django的信息文件和项目的信息文件。

最后,需要考虑一下翻译文件的结构。 若应用程序要发放给其他用户,应用到其它项目中,可能需要使用应用程序相关的翻译。 但是,使用应用程序相关的翻译和项目翻译在使用 make-messages 时会产生古怪的问题。它会遍历当前路径下所有的文件夹,这样可能会把应用消息文件里存在的消息ID重复放入项目消息文件中。

最容易的解决方法就是将不属于项目的应用程序(因此附带着本身的翻译)存储在项目树之外。 这样做的话,项目级的 make-messages 将只会翻译与项目精确相关的,而不包括那些独立发布的应用程序中的字符串。

set_language 重定向视图

方便起见,Django自带了一个 django.views.i18n.set_language 视图,作用是设置用户语言偏好并重定向返回到前一页面。

在URLconf中加入下面这行代码来激活这个视图:

(r'^i18n/', include('django.conf.urls.i18n')),

(注意这个例子使得这个视图在 /i18n/setlang/ 中有效。)

这个视图是通过 GET 方法调用的,在请求中包含了 language 参数。 如果session已启用,这个视图会将语言选择保存在用户的session中。 否则,它会以缺省名django_language在cookie中保存这个语言选择。(这个名字可以通过LANGUAGE_COOKIE_NAME设置来改变)

保存了语言选择后,Django根据以下算法来重定向页面:

  • Django 在 POST 数据中寻找一个 下一个 参数。

  • 如果 next 参数不存在或为空,Django尝试重定向页面为HTML头部信息中 Referer 的值。

  • 如果 Referer 也是空的,即该用户的浏览器并不发送 Referer 头信息,则页面将重定向到 / (页面根目录)。

这是一个HTML模板代码的例子:

<form action="/i18n/setlang/" method="post">
<input name="next" type="hidden" value="/next/page/" />
<select name="language">
    {% for lang in LANGUAGES %}
    <option value="{{ lang.0 }}">{{ lang.1 }}</option>
    {% endfor %}
</select>
<input type="submit" value="Go" />
</form>

翻译与JavaScript

将翻译添加到JavaScript会引起一些问题:

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

  • JavaScript 代码并不访问 .po或 .mo 文件;它们需要由服务器分发。

  • 针对JavaScript的翻译目录应尽量小。

Django已经提供了一个集成解决方案: 它会将翻译传递给JavaScript,因此就可以在JavaScript中调用 gettext 之类的代码。

javascript_catalog视图

这些问题的主要解决方案就是 javascript_catalog 视图。该视图生成一个JavaScript代码库,包括模仿 gettext 接口的函数,和翻译字符串的数组。 这些翻译字符串来自于你在info_dict或URl中指定的应用,工程或Django内核。

像这样使用:

js_info_dict = {
    'packages': ('your.app.package',),
}

urlpatterns = patterns('',
    (r'^jsi18n/$', 'django.views.i18n.javascript_catalog', js_info_dict),
)

packages 里的每个字符串应该是Python中的点分割的包的表达式形式(和在 INSTALLED_APPS 中的字符串相同的格式),而且应指向包含 locale 目录的包。 如果指定了多个包,所有的目录会合并成一个目录。 如果有用到来自不同应用程序的字符串的JavaScript,这种机制会很有帮助。

你可以动态使用视图,将包放在urlpatterns里:

urlpatterns = patterns('',
    (r'^jsi18n/(?P<packages>\S+)/$', 'django.views.i18n.javascript_catalog'),
)

这样的话,就可以在URL中指定由加号( + )分隔包名的包了。 如果页面使用来自不同应用程序的代码,且经常改变,还不想将其放在一个大的目录文件中,对于这些情况,显然这是很有用的。 出于安全考虑,这些值只能是 django.confINSTALLED_APPS 设置中的包。

使用JavaScript翻译目录

要使用这个目录,只要这样引入动态生成的脚本:

<script type="text/javascript" src="/path/to/jsi18n/"></script>

这就是管理页面如何从服务器获取翻译目录。 当目录加载后,JavaScript代码就能通过标准的 gettext 接口进行访问:

document.write(gettext('this is to be translated'));

也有一个ngettext接口:

var object_cnt = 1 // or 0, or 2, or 3, ...
s = ngettext('literal for the singular case',
        'literal for the plural case', object_cnt);

甚至有一个字符串插入函数:

function interpolate(fmt, obj, named);

插入句法是从Python借用的,所以interpolate 函数对位置和命名插入均提供支持:

位置插入 obj包括一个JavaScript数组对象,元素值在它们对应于fmt的占位符中以它们出现的相同次序顺序插值 。 例如:

fmts = ngettext('There is %s object. Remaining: %s',
        'There are %s objects. Remaining: %s', 11);
s = interpolate(fmts, [11, 20]);
// s is 'There are 11 objects. Remaining: 20'

命名插入 通过传送为真(TRUE)的布尔参数name来选择这个模式。 obj包括一个 JavaScript 对象或相关数组。 例如:

d = {
    count: 10
    total: 50
};

fmts = ngettext('Total: %(total)s, there is %(count)s object',
'there are %(count)s of a total of %(total)s objects', d.count);
s = interpolate(fmts, d, true);

但是,你不应重复编写字符串插值: 这还是JavaScript,所以这段代码不得不重复做正则表达式置换。 它不会和Python中的字符串插补一样快,因此只有真正需要的时候再使用它(例如,利用 ngettext 生成合适的复数形式)。

创建JavaScript翻译目录

你可以创建和更改翻译目录,就像其他

Django翻译目录一样,使用django-admin.py makemessages 工具。 唯一的差别是需要提供一个 -d djangojs 的参数,就像这样:

django-admin.py makemessages -d djangojs -l de

这样来创建或更新JavaScript的德语翻译目录。 和普通的Django翻译目录一样,更新了翻译目录后,运行 compile-messages.py 即可。

熟悉 gettext 用户的注意事项

如果你了解 gettext ,你可能会发现Django进行翻译时的一些特殊的东西:

  • 字符串域为 djangodjangojs 。字符串域是用来区别将数据存储在同一信息文件库(一般是 /usr/share/locale/ )的不同程序。django 域是为Python和模板翻译字符串服务的,被加载到全局翻译目录。 djangojs 域只是用来尽可能缩小JavaScript翻译的体积。

  • Django不单独使用 xgettext , 而是经过Python包装后的xgettext和msgfmt。这主要是为了方便。

Windows下的gettext

对于那些要提取消息或编译消息文件的人们来说,需要的只有这么多。翻译工作本身仅仅包含编辑这个类型的现存文件,但如果你要创建你自己的消息文件,或想要测试或编译一个更改过的消息文件,你将需要这个gettext公用程序。

  • > 从http://sourceforge.net/projects/gettext下载以下zip文件

    • gettext-runtime-X.bin.woe32.zip

    • gettext-tools-X.bin.woe32.zip

    • libiconv-X.bin.woe32.zip

  • 在同一文件夹下展开这3个文件。(也就是 C:\Program Files\gettext-utils )

  • 更新系统路径:

    • 控制面板 &gt; 系统&gt; 高级 &gt; 环境变量

    • 系统变量列表中,点击Path,点击Edit

    • ;C:\Program Files\gettext-utils\bin加到变量值字段的末尾。

只要xgettext --version命令正常工作,你亦可使用从别处获得的gettext的二进制代码。 有些版本的0.14.4二进制代码被发现不支持这个命令。 不要试图与Django公用程序一起使用一个gettext。在一个windows命令提示窗口输入命令 xgettext —version [](#id15)将导致出现一个错误弹出窗口–“xgettext.exe产生错误并且将被windows关闭”。

System Message: WARNING/2 (&lt;string&gt;, line 1346); backlink

Inline literal start-string without end-string.

下一章

未章 将关注于安全,如何帮助你的用户和网站远离恶意软件的攻击。