国际化和本地化的目标是让同一站点为不同的用户提供定制化的语言和格式服务。
Django支持文本、格式化日期、时间、数字以及时区的翻译。
实际上,Django 做了两件事:
基本原理是:
LANGUAGE_CODE
在HTTP请求头中告诉网站后台服务器,用户所需要的页面语言;LANGUAGE_CODE
查询每个需要翻译成对应语言的文本字符串,并将其替换到网页内,最后将网页返回给用户浏览器。下面是一些术语:
为本地化准备软件。通常由开发者完成。
编写翻译和本地格式化。通常由翻译者完成。
翻译和格式化分别由 USE_I18N
和 USE_L10N
控制。但是这两个功能都涉及国际化和本地化。这两个配置的名字是 Django 的历史原因所导致。
本地化名字
要么是ll
表中列出的语言,要么是ll_CC
表中列出的国家。比如 it
, de_AT
, es
, pt_BR
, sr_Latn
。这些名称具有特定的规范,不了解的可以百度。
语言的代码。比如:it
, de-at
, es
, pt-br
。语言代码一般用小写表示,但是 HTTP Accept-Language
header 不区分大小写。用破折号来间隔。
·消息文件。纯文本文件。代表一种语言,包含所有可用的 translation strings
,以及它们如何在给定的语言里表示。消息文件的文件扩展名是 .po
。
可以翻译的文字。
格式文件是一个 Python 模块,用于定义本地数据格式。
要使用国际化和本地化功能,需要开启'django.middleware.locale.LocaleMiddleware'
中间件,它需要位于session中间件之后,Common中间件之前。
为了使 Django 项目可以翻译,你需要在 Python 代码和HTML模板中添加少量钩子。这些钩子被称为 translation strings
。它们告诉Django,如果在终端用户语言里,这个文本有对应的翻译,那么应该使用翻译后的文本。(标记字符串是你的职责,系统只会翻译它知道的字符串。)
然后, Django 提供工具将翻译字符串提取到 message file
中。这个文件让翻译者方便地提供翻译字符串。一旦翻译者完成了 message file
,就必须编译它。这个过程依赖 GNU gettext
工具集。
完成后,Django 会根据用户的语言偏好,对网页进行即时翻译。
Django 的国际化钩子默认是开启的,这意味着在框架的某些位置存在一些 i18n
相关的开销。如果你不提供国际化能力,你应该在配置文件里设置 USE_I18N = False
。然后 Django 将进行优化,以免加载国际化机制,较少开销,提高性能。
所以,国际化的第一步是在视图和HTML模板中标识要翻译的文本。
通过gettext()
函数指定需要翻译的字符串,按照惯例,将其作为下划线( _
)导入,以保存输入。如下所示:
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)
传递给 _()
or gettext()
的字符串可以使用占位符,这是 Python 标准命名字符串插值语法指定的。:
def my_view(request, m, d): output = _('Today is %(month)s %(day)s.') % {'month': m, 'day': d} return HttpResponse(output)
如果你想给翻译人员一些提示,可以添加一个以Translators为前缀的注释,例如:
def my_view(request): # Translators: This message appears on the home page only output = ugettext("Welcome to my site.")
这个注释将出现在 .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()
带有三个参数:单数翻译字符串,复数翻译字符串和对象的数量。
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)
这个方法给你提供了一种简便的方式,但不是那么可靠,可能会出现各种坑,随便用用就行。
一些词有很多不同含义,比如 "May"
,它指五月或者表示一个动词。
为了使翻译者在不同上下文中正确翻译这些词组,可以使用 django.utils.translation.pgettext()
函数,或者如果字符串需要复数形式的话,可以使用 django.utils.translation.npgettext()
函数,来帮助翻译者,提供语境。
两者都使用上下文字符串作为第一个变量。
例如:
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 ""
有时候,我们需要对一些字符串进行惰性翻译,而不是立刻翻译。这需要使用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')) class AnotherThing(models.Model): kind = models.ForeignKey( ThingKind, on_delete=models.CASCADE, related_name='kinds', verbose_name=_('kind'), ) def is_mouse(self): return self.kind.type == MOUSE_TYPE is_mouse.short_description = _('Is it a mouse?') class MoreThing(models.Model): name = models.CharField(_('name'), help_text=_('This is the help text')) class Meta: verbose_name = _('my thing') verbose_name_plural = _('my things')
在模版文件中,要标识一个待翻译的文本,需要使用{% translate %}
模板标签,但首先你要在模版的顶部加载{% load i18n %}
。比如:
{% load i18n %} <title>{% translate "This is the title." %}</title> <title>{% translate myvar %}</title>
注意:从Django3.1开始原有的trans标签更名为translate,但为了向后兼容,trans依然可用!
要注意的是translate标签内部不可以有内嵌的模板变量
如果你想提前翻译字符串但是不显示出来,可以使用下面的方法:
{% 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" %}
与{% translate %}
模板标签不同,blocktranslate标签允许你通过使用占位符来标记由文字和可变内容组成的复杂句子进行翻译,如下例所示:
{% blocktranslate %}This string will have {{ value }} inside.{% endblocktranslate %}
原blocktrans标签更名为blocktranslate,向后兼容。
还可以像下面一样使用:
{% blocktranslate with amount=article.price %} That will cost $ {{ amount }}. {% endblocktranslate %} {% blocktranslate with myvar=value|filter %} This will have {{ myvar }} inside. {% endblocktranslate %}
甚至在一个blocktrans标签内内使用多个表达式:
{% blocktranslate with book_t=book|title author_t=author|title %} This is {{ book_t }} by {{ author_t }} {% 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 %}
就像 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 %}
一旦标记好需要翻译的文本(也就是国际化)后,就需要进行本地化,也就是创建翻译用的消息文件。
消息文件(Message File)是Django用于保存翻译关系的文件,你的网站应该为每种支持的语言建立一个消息文件。
建立消息文件是通过django-admin makemessages
命令完成的。
在项目的根目录下,也就是包含manage.py的目录下,运行下面的命令:
django-admin makemessages -l de
其中的de
表示你要本地化的国家,例如pt_BR
表示巴西葡萄牙语,奥地利德语为de_AT
,印尼语为id。
或者使用下面的方式:
python manage.py makemessages -l zh-cn //中文简体 python manage.py makemessages -l en //英文
执行命令后,Django会在根目录及其子目录下搜集所有需要翻译的字符串,默认情况下它会搜索.html、.txt和.py文件,然后在根目录的locale/LANG/LC_MESSAGES
目录下创建一个django.po
文件。对于上面的例子,目录就是locale/de/LC_MESSAGES/
,文件就是locale/de/LC_MESSAGES/django.po
。
否则会弹出下面的错误:
CommandError: Can't find msguniq. Make sure you have GNU gettext tools 0.15 or newer installed.
.po
文件的格式非常简单!
每个.po
文件首先包含一小部分元数据,例如翻译维护者的联系信息,但文件的大部分是翻译对照:被翻译字符串和特定语言的实际翻译文本之间的简单映射。
例如,有一个像下面这样的待翻译字符串:
_("Welcome to my site.")
在.po
文件中将包含一条下面样子的条目:
#: path/to/python/module.py:23 msgid "Welcome to my site." msgstr ""
这三行内容各自代表下面的意思:
这是一个文本文件,需要专业的翻译人员将所有的msgstr空白‘填写’齐全。如果你的项目比较大,这可能是个磨人的事。
当完成消息文件的创建和翻译工作后,或者对文件修改后,需要将其编译成对应的*.mo
文件,Django在运行时将使用*.mo
文件对网站进行国际化翻译。
进入项目根目录,运行下面的命令,进行语言文件编译:
django-admin compilemessages
Django将自动搜索所有的.po
文件,将它们都翻译成.mo
文件。
至此,国际化和本地化就完成了。你的网站页面将根据访问者使用语言的不同,展示为不同的语言版本,比如中文、英文、法文、德文之类。
下面列出了所有可用于各种系统设置的语言代码对照表:
用的django-rest-framwork,需要在settings中配置LOCALE_PATHS,不然找不到翻译文件,还有台湾的话前端accept-languae设定zh-tw或zh-hant都可以,后端文件需要zh_Hant,踩过的坑。。。
博主,有个问题,如果一个正在开发中的项目,生成过相应的po文件,但是又更改了,行号变了,需要重新生成吗?重新生成,之前写好的翻译文件还能重用吗?
有个坑是makemessages -l zh-cn 时应该用下划线既是-l zh_cn。然后settings.py的LANGUAGES应该是 ('zh-cn','简体中文') 这样设置,但是一定要用短横线 - ,而不是下划线 _ ,可谓巨坑无比。
当初看到 for _, item in xxx 不要在国际化相关场景下使用 完全一头雾水 今天拨开云雾 非常感谢
unbelievable and pretty good
HAO