Django诞生于美国,和许多其他的开源软件一样,Django社区发展中得到了全球范围的支持。所以Djano社区的国际化应用变得非常重要。由于大量开发者对本章内容比较困惑,所以我们将详细介绍。
国际化是指为了在任何其它地区使用该软件而进行设计的过程。它包括为了以后的翻译而标记文本(比如用户界面控件和错误信息等),提取出日期和时间的显示以保证显示遵循不同地区的标准,为不同时区提供支持,并且在一般情况下确保代码中不会有关于使用者所在地区的假设。您可以经常看到国际化被缩写为“I18N”(18表示Internationlization这个单词首字母I和结尾字母N之间的字母有18个)。
本地化是指使一个国际化的程序为了在某个特定地区使用而进行翻译的过程。有时,本地化缩写为L10N 。
Django本身是完全国际化的;所有的字符串均被标记为需要翻译,而且在选项中可以设置区域选项(如时间和日期)的显示。Django是带着40个不同的本地化文件发行的。即使您不是以英语作为母语的,也很有可能Django已经被翻译为您的母语了。
这些本地化文件所使用的国际化框架同样也可以被用在您自己的代码和模板中。
简要地说,您只需要添加少量的hook到您的Python代码和模板中。这些hook被称为“翻译字符串”。它们告诉Django,如果这段文本可以被翻译为终端用户语言,那么就翻译这段文本。
Django根据用户的语言偏好来使用这些hook去翻译Web应用程序。
本质上来说,Django做这两件事情:
§ 由开发者和模板的作者指定他们的应用程序的哪些部分是需要被翻译的。
§ Django根据用户的语言偏好来翻译Web应用程序。
备注:
Django的翻译机制是使用 GNUgettext(http://www.gnu.org/software/gettext/),具体为Python标准模块gettext。
如果您不需要国际化:
Django国际化的hook默认是开启的,这可能会给Django增加一点点负担。如果您不需要国际化支持,那么您可以在您的设置文件中设置USE_I18N = False。如果USE_I18N被设为False,那么Django会进行一些优化,而不加载国际化支持机制。
您也可以从您的TEMPLATE_CONTEXT_PROCESSORS设置中移除'django.core.context_processors.i18n'。
在Python代码中指定翻译字符串
翻译字符串指定这段文本需要被翻译。这些字符串出现在您的Python代码和模板中。您需要做的是标记出这些翻译字符串;而系统只会翻译出它所知道的东西。
标准的翻译函数
Django通过使用函数_()来指定翻译字符串(是的,函数名是一个下划线字符)。这个函数是全局有效的(也就是说,相当于它是一个内置函数);您不需要import任何东西。
在下面这个例子中,这段文本"Welcome to my site"被标记为翻译字符串:
def my_view(request):
output = _("Welcome to my site.")
return HttpResponse(output)
函数django.utils.translation.gettext()与_()是相同的。下面这个例子与前一个例子没有区别:
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的翻译字符串检测工具,make-messages.py,不能找到这些字符串。在后面的内容中会继续讨论这个问题。)
传递给_()或gettext()的字符串可以接受由Python标准字典型对象的格式化字符串表达式指定的占位符,比如:
def my_view(request, n):
output = _('%(name)s is my name.') % {'name': n}
return HttpResponse(output)
这项技术使得特定语言的译文可以对这段文本进行重新排序。比如,一段文本的英语翻译为"Adrian is my name.",而西班牙语翻译为"Me llamo Adrian.",此时,占位符(即name)实在被翻译的文本之前而不是之后。
正因为如此,您应该使用字典型对象的格式化字符串(比如,%(name)s),而不是针对位置的格式化字符串(比如,%s或%d)。如果您使用针对位置的格式化字符串,翻译机制将无法重新安排包含占位符的文本。
标记字符串为不操作
使用django.utils.translation.gettext_noop()函数来标记一个不需要立即翻译的字符串。而被标记的字符串只在最终显示出来时才被翻译。
使用这种方法的环境是,有字符串必须以原始语言的形式存储(如储存在数据库中的字符串)而在最后需要被翻译出来,如当其在用户前显示出来时。
惰性翻译
使用django.utils.translation.gettext_lazy()函数,使得其中的值只有在访问时才会被翻译,而不是在gettext_lazy()被调用时翻译。
比如,要标记help_text列是需要翻译的,可以这么做:
from django.utils.translation import gettext_lazy
class MyThing(models.Model):
name = models.CharField(help_text=gettext_lazy('This is the help text'))
在这个例子中,gettext_lazy()将字符串作为惰性翻译字符串存储,此时并没有进行翻译。翻译工作将在字符串在字符串上下文中被用到时进行,比如在Django管理页面提交模板时。
如果觉得gettext_lazy太过冗长,可以用_(下划线)作为别名,就像这样:
from django.utils.translation import gettext_lazy as _
class MyThing(models.Model):
name = models.CharField(help_text=_('This is the help text'))
在Django模型中最好一直使用惰性翻译(除非这样翻译的结果无法正确地显示)。同时,对于列名和表名最好也能进行翻译。这需要在Meta中明确verbose_name和verbose_name_plural的值:
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 = _('mythings')
复数的处理
使用django.utils.translation.ngettext()函数来指定有单数和复数形式之分的信息,比如:
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)
ngettext函数包括三个参数:单数形式的翻译字符串,复数形式的翻译字符串,和对象的个数(将以count变量传递给需要翻译的语言)。
在模板中指定翻译字符串
Django模板使用两种模板标签,且语法格式与Python代码有些许不同。为了使得模板访问到标签,需要将{% load i18n %}放在模板最前面。
{% trans %}模板标签标记需要翻译的字符串:
<title>{% trans "This is the title." %}</title>
如果只需要标记字符串而以后再翻译,可以使用noop选项:
<title>{% trans "value" noop %}</title>
在{% trans %}中不允许使用模板中的变量,只能使用单引号或双引号中的字符串。如果翻译时需要用到变量(占位符),可以使用{% blocktrans %},比如:
{% blocktrans %}This 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 %}
其内在机制是,所有的块和内嵌翻译调用相应的gettext或ngettext。
使用RequestContext(见Django深入模板引擎)时,模板可以访问三个针对翻译的变量:
§ {{ LANGUAGES }}是一系列元组组成的列表,每个元组的第一个元素是语言代码,第二个元素是用该语言表示的语言名称。
§ {{ LANGUAGE_CODE }}是以字符串表示的当前用户偏好语言(例如,en-us)。
§ {{ LANGUAGE_BIDI }}是当前语言的书写方式。若设为True,则该语言书写方向为从右到左(如希伯来语和阿拉伯语);若设为False,则该语言书写方向为从左到右(如英语、法语和德语)。
你也可以通过使用模板标签来加载这些变量:
{% load i18n %}
{% get_current_language as LANGUAGE_CODE %}
{% get_available_languages as LANGUAGES %}
{% get_current_language_bidi as LANGUAGE_BIDI %}
翻译的hook在任何接受常量字符串的模板块标签内也是可以使用的。此时,使用_()表达式来指定翻译字符串,例如:
{% some_special_tag _("Page not found") value|yesno:_("yes,no") %}
这种情况下,标签和过滤器都将看到已翻译的字符串(也就是说,字符串是在传递给标签处理函数之前翻译的),所以它们(标签和过滤器)不必处理相关的东西。
创建语言文件
当你标记了翻译字符串,你就需要写出(或获取已有的)对应的语言翻译信息。在这一节中我们将解释如何使它起作用。
创建信息文件
第一步,就是为一种语言创建一个信息文件。一个信息文件是包含了某一语言翻译字符串和对这些字符串的翻译的一个文本文件。信息文件以.po为后缀名。
Django中带有一个工具,bin/make-messages.py,它完成了这些文件的创建和维护工作。
运行以下命令来创建或更新一个信息文件:
bin/make-messages.py -l de
其中de是所创建的信息文件的语言代码。在这里,语言代码是以本地格式给出的。例如,巴西地区的葡萄牙语为pt_BR,澳大利亚地区的德语为de_AT。可查看django/conf/locale目录获取Django所支持的语言代码。
这段脚本应该在三处之一运行:
§ django根目录(不是Subversion检出目录,而是通过$PYTHONPATH链接或位于该路径的某处)
§ Django项目根目录
§ Django应用程序根目录
该脚本作用于所在的整个目录树,并且抽取所有被标记的字符串以进行翻译。它在conf/locale目录下创建(或更新)了一个信息文件。在上面这个例子中,这个信息文件是conf/locale/de/LC_MESSAGES/django.po。
运行于项目源码树或应用程序源码树下时,该脚本完成同样的功能,但是此时locale目录的位置为locale/LANG/LC_MESSAGES(注意没有``conf``前缀)。在第一次运行时需要创建locale目录。
没有gettext?
如果没有安装gettext组件,make-messages.py将会创建空白文件。这种情况下,安装gettext组件或只是复制英语信息文件(conf/locale/en/LC_MESSAGES/django.po)来作为一个起点;只是一个空白的翻译信息文件而已。
.po文件格式很直观。每个.po文件包含一小部分的元数据,比如翻译维护人员的联系信息,而文件的大部分内容是简单的翻译字符串和对应语言翻译结果的映射关系的列表。
举个例子,如果Django应用程序包括一个"Welcome to my site."的翻译字符串,像这样:
_("Welcome to my site.")
make-message.py将创建一个包含以下片段的.po文件:
#: path/to/python/module.py:23
msgid "Welcome to my site."
msgstr ""
按顺序简单解释一下:
§ msgid是在源文件中出现的翻译字符串。不要做改动。
§ msgstr是相应语言的翻译结果。刚创建时它只是空字符串,此时就需要你来完成它。注意不要丢掉语句前后的引号。
§ 方便起见,每一条信息包含了翻译字符串所在文件的文件名和行数。
对于比较长的信息也有其处理方法。msgstr(或msgid)后紧跟着的字符串为一个空字符串。然后真正的内容在其下面的几行。这些字符串会被直接连在一起。同时,不要忘了字符串末尾的空格,因为它们会不加空格地连到一起。
比如,以下是一个多行翻译(取自随Django发行的西班牙本地化文件):
msgid ""
"There's been an error. It's been reported to the site administrators via e-"
"mail and should be fixed shortly. Thanks for your patience."
msgstr ""
"Ha ocurrido un error. Se ha informado a los administradores del sitio "
"mediante correo electrnico y debera arreglarse en breve. Gracias por su "
"paciencia."
注意每一行结尾的空格。
注意字符集
当你使用喜爱的文本编辑器创建.po文件时,首先请编辑字符集行(搜索"CHARSET"),并将其设为你将使用的字符集。一般说来,UTF-8对绝大多数语言有效,不过gettext会处理任何你所使用的字符集。
若要对新创建的翻译字符串校验所有的源代码和模板中,并且更新所有语言的信息文件,可以运行以下命令:
make-messages.py -a
编译信息文件
创建信息文件之后,每次对其做了修改,都需要将它重新编译成一种更有效率的形式,供gettext使用。使用 `` bin/compile-messages.py ``来完成这项工作。
这个工具作用于所有有效的.po文件,创建优化过的二进制.mo文件供gettext使用。在运行make-messages.py的同一目录下,运行compile-messages.py:
bin/compile-messages.py
就是这样了。你的翻译成果已经可以使用了。
Django如何处理语言偏好
一旦你准备好了翻译,如果希望在Django中使用,那么只需要激活这些翻译即可。
在这些功能背后,Django拥有一个灵活的模型来确定在安装和使用应用程序的过程中选择使用的语言。
若要在整个安装和使用过程中确定语言偏好,就要在设置文件中设置LANGUAGE_CODE。Django将用指定的语言来进行翻译,如果没有其它的翻译器发现要进行翻译的语句,这就是最后一步了。
如果你只是想要用本地语言来运行Django,并且该语言的语言文件存在,只需要简单地设置LANGUAGE_CODE即可。
如果要让每一个使用者各自指定语言偏好,就需要使用LocaleMiddleware。LocaleMiddleware使得Django基于请求的数据进行语言选择,从而为每一位用户定制内容。
使用LocaleMiddleware需要在MIDDLEWARE_CLASSES设置中增加'django.middleware.locale.LocaleMiddleware'。中间件的顺序是有影响的,最好按照依照以下要求:
§ 保证它是第一批安装的中间件类。
§ 因为LocalMiddleware要用到session数据,所以需要放在SessionMiddleware之后。
§ 如果使用了CacheMiddleware,将LocaleMiddleware放在CacheMiddleware之后(否则用户可能会从错误的本地化文件中取得缓冲数据)。
例如,MIDDLE_CLASSES可能会是如此:
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware'
)
LocaleMiddleware按照如下算法确定用户的语言:
§ 首先,在当前用户 session的中查找键django_language的值;
§ 如果失败的话,接着查找名为django_language的 cookie;
§ 还是失败的话,就在 HTTP请求头部查找Accept-Language关键字的值,该关键字是你的浏览器发送的,按优先顺序告诉服务器你的语言偏好。Django会根据这些语言的顺序逐一搜索直到发现可用的翻译;
§ 以上都失败了的话,就使用全局的LANGUAGE_CODE设定值。
在上述每一处,语言偏好应作为字符串,以标准的语言格式出现。比如,巴西地区的葡萄牙语表示为pt-br。如果Django中只有基本语言而没有其衍生的字语言的话,Django将只是用基本语言。比如,如果用户指定了de-at(澳式德语)但Django只有针对de的翻译,那么de会被选用。
只有在LANGUAGES设置中列出的语言才能被选用。若希望将语言限制为所提供语言中的某些(因为应用程序并不提供所有语言的表示),则将LANGUAGES设置为所希望提供语言的列表,例如:
LANGUAGES = (
('de', _('German')),
('en', _('English')),
)
上面这个例子限制了语言偏好只能是德语和英语(包括它们的子语言,如de-ch和en-us)。
如果自定义了LANGUAGES,将语言标记为翻译字符串是可以的,但是,请不要使用django.utils.translation中的gettext()(决不要在settings文件中导入django.utils.translation,因为这个模块本身是依赖于settings,这样做会导致无限循环),而是使用一个“虚构的”gettext()。
解决方案就是使用一个“虚假的”gettext()。以下是一个settings文件的例子:
_ = lambda s: s
LANGUAGES = (
('de', _('German')),
('en', _('English')),
)
这样做的话,make-messages.py仍会寻找并标记出将要被翻译的这些字符串,但翻译不会再运行时进行,故而需要在任何使用LANGUAGES的代码中用“真实的”gettext()来修饰这些语言。
LocaleMiddleware只能选择那些Django已经提供了基础翻译的语言。如果想要在应用程序中对Django中还没有基础翻译的语言提供翻译,那么必须至少先提供该语言的基本的翻译。例如,Django使用特定的信息ID来翻译日期和时间格式,故要让系统正常工作,至少要提供这些基本的翻译。
以英语的.po文件为基础,翻译其中的技术相关的信息,可能还包括一些使之生效的信息。这会是一个好的开始。
技术相关的信息ID很容易被人出来:它们都是大写的。这些信息ID的翻译与其他信息不同:你需要提供其对应的本地化内容。例如,对于DATETIME_FORMAT(或DATE_FORMAT、TIME_FORMAT),应该提供希望在该语言中使用的格式化字符串。格式和now模板标签中使用的格式化字符串一样。
一旦LocalMiddleware确定了用户的使用偏好,就将其以request.LANGUAGE_CODE的形式提供给每个请求对象。如此,在视图代码中就可以自由使用了。以下是一个简单的例子:
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。
set_language重定向视图
方便起见,Django自带了一个django.views.i18n.set_language视图,作用是设置用户语言偏好并重定向返回到前一页面。
在URLconf中加入下面这行代码来激活这个视图:
(r'^i18n/', include('django.conf.urls.i18n')),
(注意这个例子使得这个视图在/i18n/setlang/中有效。)
这个视图是通过GET方法调用的,在Query String中包含了language参数。如果session已启用,这个视图会将语言选择保存在用户的session中。否则,语言选择将被保存在名为django_language的cookie中。
保存了语言选择后,Django根据以下算法来重定向页面:
§ Django在提交的Query String中寻找next参数。
§ 如果next参数不存在或为空,Django尝试重定向页面为HTML头部信息中Referer的值。
§ 如果Referer也是空的,即该用户的浏览器并不发送Referer头信息,则页面将重定向到/(页面根目录)。
这是一个HTML模板代码的例子:
<form action="/i18n/setlang/" method="get">
<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>
在你自己的项目中使用翻译
Django使用以下算法寻找翻译:
§ 首先,Django在该视图所在的应用程序文件夹中寻找locale目录。若找到所选语言的翻译,则加载该翻译。
§ 第二步,Django在项目目录中寻找locale目录。若找到翻译,则加载该翻译。
§ 最后,Django使用django/conf/locale目录中的基本翻译。
以这种方式,你可以创建包含独立翻译的应用程序,可以覆盖项目中的基本翻译。或者,你可以创建一个包含几个应用程序的大项目,并将所有需要的翻译放在一个大的项目信息文件中。决定权在你手中。
注意
如果是使用手动配置的settings文件,因Django无法获取项目目录的位置,所以项目目录下的locale目录将不会被检查。(Django一般使用settings文件的位置来确定项目目录,而若手动配置settings文件,则settings文件不会在该目录中。)
所有的信息文件库都是以同样方式组织的:
§ $APPPATH/locale/<language>/LC_MESSAGES/django.(po|mo)
§ $PROJECTPATH/locale/<language>/LC_MESSAGES/django.(po|mo)
§ 所有在settings文件中LOCALE_PATHS中列出的路径以其列出的顺序搜索<language>/LC_MESSAGES/django.(po|mo)
§ $PYTHONPATH/django/conf/locale/<language>/LC_MESSAGES/django.(po|mo)
要创建信息文件,也是使用make-messages.py工具,和Django信息文件一样。需要做的就是改变到正确的目录下——conf/locale(在源码树的情况下)或者locale/(在应用程序信息或项目信息的情况下)所在的目录下。同样地,使用compile-messages.py生成gettext需要使用的二进制django.mo文件。
应用程序信息文件稍微难以发现——因为它们需要LocaleMiddle。如果不使用中间件,Django只会处理Django的信息文件和项目的信息文件。
最后,需要考虑一下翻译文件的结构。若应用程序要发放给其他用户,应用到其它项目中,可能需要使用应用程序相关的翻译。但是,使用应用程序相关的翻译和项目翻译在使用make-messages时会产生古怪的问题。make-messages会遍历当前路径下的所有目录,所以可能会将应用程序信息文件已有的信息ID放在项目信息文件中。
最容易的解决方法就是将不属于项目的应用程序(因此附带着本身的翻译)存储在项目树之外。这样做的话,项目级的make-messages将只会翻译与项目精确相关的,而不包括那些独立发布的应用程序中的字符串。
翻译与JavaScript
将翻译添加到JavaScript会引起一些问题:
§ JavaScript代码无法访问一个gettext的实现。
§ JavaScript代码无法访问.po或.mo文件,它们需要由服务器分发。
§ 针对JavaScript的翻译目录应尽量小。
Django已经提供了一个集成解决方案:它会将翻译传递给JavaScript,因此就可以在JavaScript中调用gettext之类的代码。
javascript_catalog视图
这些问题的主要解决方案就是javascript_catalog视图。该视图生成一个JavaScript代码库,包括模仿gettext接口的函数,和翻译字符串的数组。这些翻译字符串来自应用程序,项目,或者Django核心,具体由info_dict或URL来确定。
像这样使用:
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.conf或INSTALLED_APPS设置中的包。
使用JavaScript翻译目录
要使用这个目录,只要这样引入动态生成的脚本:
<script type="text/javascript" src="/path/to/jsi18n/"></script>
这就是管理页面如何从服务器获取翻译目录。当目录加载后,JavaScript代码就能通过标准的gettext接口进行访问:
document.write(gettext('this is to be translated'));
甚至有一个ngettext接口和一个字符串查补函数:
d = {
count: 10
};
s = interpolate(ngettext('this is %(count)s object', 'this are %(count)s objects', d.count), d);
interpolate函数支持位置插补和名字查补。因此前面的代码也可以写成这样:
s = interpolate(ngettext('this is %s object', 'this are %s objects', 11), [11]);
插补的语法是借鉴了Python。但不应该超过字符串插补的能力,这仍然还是JavaScript,因此代码将不得不做重复的正则替换。它不会和Python中的字符串插补一样快,因此只有真正需要的时候再使用它(例如,利用ngettext生成合适的复数形式)。
创建JavaScript翻译目录
用和其它Django翻译目录相同的方法来创建和更新JavaScript翻译目录:用`make-messages.py`工具。唯一的差别是需要提供一个-d djangojs的参数,就像这样:
make-messages.py -d djangojs -l de
这样来创建或更新JavaScript的德语翻译目录。和普通的Django翻译目录一样,更新了翻译目录后,运行compile-messages.py即可。
熟悉gettext用户的注意事项
如果你了解gettext,你可能会发现Django进行翻译时的一些特殊的东西:
§ 字符串域为django或djangojs。字符串域是用来区别将数据存储在同一信息文件库(一般是/usr/share/locale/)的不同程序。django域是为Python和模板翻译字符串服务的,被加载到全局翻译目录。djangojs域用在JavaScript翻译目录中,以确保其足够小。
§ Django仅适用gettext和gettext_noop。这是因为Django总是内在地使用DEFAULT_CHARSET字符串。使用ugettext并没有什么好处,因为总是需要生成UTF-8。
§ Django不单独使用xgettext,而是经过Python包装后的xgettext和msgfmt。这主要是为了方便。
转载请注明文章出处:http://blog.csdn.net/wolaiye320/article/details/52241352