Zope3宝典/国际化软件包
Chapter 18: Internationalizing a Package(第 18 章:国际化软件包)
原文出处:The Zope 3 Developers Book - An Introduction for Python Programmers
原文作者:StephanRichter, zope.org
授权许可:创作共用协议
翻译人员:
- dugan <[email protected]>
校对人员:Leal, FireHare
适用版本:Zope 3
文章状态:校正阶段
Difficulty(难度)
Sprinter(进阶者)
Skills(技能)
- You should be familiar with the previous chapters of the “Content Components” section.
您应该熟悉前面的“内容组件”章节的内容; - Familiarity with Page Templates is desired.
需要熟悉页面模板; - Basic knowledge of the gettext format and tools is also a plus. Optional.
需要具备Gettext工具的基础知识(可选)。
Problem/Task(问题/任务)
Now that we have a working message board package, it is time to think about our friends overseas and the fact that not everyone can speak English. Therefore it is our task now to internationalize and localize the code to ...let’s say German.
现在我们有一个可以正常工作的软件包,如今我们需要替我们的海外朋友想想了,事实上不是每个人都能讲英语。因此我们现在的任务就把代码国际化和本地化...让我们试试德语。
Solution(解决方案)
Before we can start coding, it is important to cover some of the basics. You might already have wondered about the difference between the terms internationalization and localization.
在我们开始编码以前,对一些基础概念做一些了解是很重要的,您可能已经想知道国际化和本地化条款之间的所存在的差异。
- Internationalization (I18n) is the process of making a package translatable, basically the programmer’s task of inserting the necessary code so that human-readable strings can be translated and dates/times be formatted, respecting the users “locale” settings.
国际化(I18n)就是制作可翻译软件包的过程,程序员的基本任务就是插入必要的代码,把字符串翻译成可理解的格式并且日期/时间被格式化,这与用户的本地设置(“locale”)有关。 - Localization (L10n) is the process of actually creating a translation for a particular language. Often translations are not done by programmers but by translators (or formally, localization vendors).
本地化(L10n)实际上就是转换成一种特殊语言的过程。翻译常常不是有程序员来做,而是由翻译者来做(或者由正式的本地供货商来做)。
But what is a so-called “locale”? Locales are objects that contain information about a particular physical/abstract region in the world, such as language, dialect, monetary unit, date/time/number formats and so on. An example of a locale would be “de_DE_PREEURO” (language, country/region, variant), which describes Germany before the Euro was introduced. However, “de” is also a valid locale, referring to all German speaking regions. So you can imagine that there is a locale hierarchy. “de_DE_PREEURO” is more specific than “de_DE”, which is in turn more specific than “de”. So if the user’s locale setting is “de_DE_PREEURO” and we want to look for the date format template, the system will look up the path in “de_DE_PREEURO”, then “de_DE” and finally in “de”, where it will find it.
但是一个所谓的“locale(本地)”是什么? Locales是一个包含世界上一个特定物理区域/抽象区域的信息对象,例如语言、方言、货币单位、日期/时间/数字 格式等。例子中的locale将是“de_DE_PREEURO”(语言、国家/地区,差异),在Euro被介绍之前以德国来描述。然而,“de”也是一个有效的locale,涉及到所有讲德语的地区。因此您可以想象的到这儿有一个locale体系。“de_DE_PREEURO”是比“de_DE”更加特殊,当然更特殊于“de”。因而如果用户的locale设置是“de_DE_PREEURO”并且我们想寻找一个日期格式模板,系统将在“de_DE_PREEURO”这个路径里寻找该模板,然后依次是“de_DE”,最后才是“de”。
Note that this chapter has little to do with Python development, but is still useful to know, since all Zope 3 core components are required to be internationalized.
注意这章与Python开发完全无关,但知道一些仍然是有用的,因为所有Zope3核心组件都要求被国际化。
18.1 Step I: Internationalizing Python code(18.1 步骤 I:国际化 Python 代码)
There should be only a few spots where internationalizing is necessary, since translatable strings are used for views, which are usually coded in Page Templates. One of the big exceptions are schemas, since we always define human readable titles, descriptions and default text values for the declared fields.
这儿有几处必须国际化,视图中的可翻译的字符串,它通常在页面模板中编码。另外就是schema,因为它总是为声明的字段定义一个可理解的标题(title)、描述(description)和缺省的文本值(text value)。
Zope uses message ids to mark strings as translatable. Translatable strings must always carry a domain, so that we know to which translation domain to pick.
Zope用message id标识string为可翻译。可翻译字符串必须携带一个域,因而我们就知道哪个翻译域我们可以挑选。
We use message id factories to create message ids:
我们使用消息id工厂(factory)创建一个消息id:
#!python from zope.i18n import MessageIDFactory _ = MessageIDFactory('messageboard')
- Line 1-2: Every Python file containing translatable strings must contain this small boiler plate. Note that for Zope 3 core code we have a short cut:
第1-2行:每个包含可翻译字符串Python文件都必须包含这个样板文件。
#!python from zope.app.i18n import ZopeMessageIDFactory as _
This import creates a message id factory that uses the “zope” domain.
Import用"zope"域创建一个消息id工厂。
- Line 2: The underscore character is commonly used to represent the “translation function” (from gettext). In our case it is used as message id constructor/factory. The argument of the MessageIDFactory is the domain, which is in our case messageboard.
第2行:底线字符普遍用于表示"翻译功能(translation function)"(来自gettext)。当前情况下它被用作消息id constructor/factory。MessageIDFactory参数就是我们当前的messageboard域。
But why do we need domains in the first place? My favorite example for the need of domains is the word Sun. This word really represents three different meanings in English: (1) our star the Sun, (2) an abbreviation for Sunday and (3) the company Sun Microsystems. All of these meanings have different translations in German for example. So you can distinguish between them by specifying domains, such as “astronomy”, “calendar” and “companies”, respectively. Domains also allow us to organize and reuse translations; they are almost like libraries. For example, not every single package needs to collect its own “calendar” translations, but all packages could benefit from one cohesive domain.
但是为什么我们在开始位置需要域呢,比如说单词Sun对域有什么需求呢。Sun在英语有三种不同的意思:(1)太阳星球 (2)星期天的缩写 (3)Sun微系统公司。所有的这些意思在德语中都对应着不同的翻译。因此您能通过指定域来区分他们,例如"astronomy(天文学)"、"calendar(日历)"和"companies(公司)",域的作用几乎类似库,它也允许我们组织和重用翻译。例如,不是每一个单独地软件包都需要把它自己的"calendar(日历)"翻译收集到它的库中,所有的软件包能受益于一个结合的域。
Another way of categorizing translations is by creating somewhat abstract message strings. So for example the value of an add button becomes add-button instead of the usual Add and translations for this string would then insert the human readable string, such as Add for English or Hinzufgen for German. We will see this usage specifically in Page Templates (see next section). These “abstract message strings” are known as “explicit message ids”.
分类翻译的另一个方法就是通过创建抽象message字符串。例如,Add button的value变成了add-button替代通常的Add,翻译这个字符串然后插入这个可理解串,例如英语中Add或德语中的Hinzufgen。我们将在页面模板中看到这个特定的用法(看下一章)。这些"抽象message字符串"被认为是"明确的message ID"。
You might also wonder why we have to use the message id concept, instead of using a translation function directly, like other desktop applications do. Here we should recall that Zope is an Application Server and has multiple users that are served over the network. So at the time a piece of code is called, we often do not know anything about the user or the desired language. Only views (be it Python code or Page Templates) have information about the user and therefore the desired locale, which contains the language, so that the translation has to be prolonged as long as possible. As a rule of thumb, I always say that translating is the last task the application should do before providing the final end-user output. Zope 3 honors this rule in every aspect.
您也可能想知道我们为什么必须使用message id概念,为什么用message id来代替直接使用翻译功能。这里我们需要回顾一下Zope,由于Zope是一个应用程序服务器并且有很多用户通过网络接受它的服务,因此当一个程序块被调用后,我们不会知道用户究竟想要使用哪种语言。通过分析,我们发现只有视图(Python代码和页面模板)有关于用户和用户本地设置的信息,当然按照惯例,本地设置中就包括了我们需要的语言信息,为了获得相应的语言信息,我们就不得不对翻译做尽可能的扩展。根据以往经验,我们认为翻译是在提供给最终用户输出应用程序应该做的最后一个任务。当然,Zope 3也会遵从这个规则
But let’s get back to translating Python code. Since the interfaces have the most translatable strings, we start with them. Open the interfaces.py module and add the above mentioned boiler plate. Now, we internationalize each field. For example, the `IMessageBoard` schema’s description field is changed from
让我们回到翻译Python代码。既然接口有大多数可翻译的字符串,我们就从它们开始。打开interfaces.py模块并且添加我们上面提到的样板文件。现在我们国际化每个字段。例如,`ImessageBoard` 架构中的description字段由:
1 description = Text( 2 title=u"Description", 3 description=u"A detailed description of the content of the board.", 4 default=u"", 5 required=False)
to
改变成:
1 description = Text( 2 title=_("Description"), 3 description=_("A detailed description of the content of the board."), 4 default=u"", 5 required=False)
Note how the underscore message id factory simply functions like a translating message. Do the same transformation for all schemas in the interfaces module. Also, note that while title and description require unicode strings, we can simply pass a regular string into the message id factory, since the message id uses unicode as its base class, making the message id look like a unicode object. Another minor translation is the doc attribute of the `ForbiddenTags` class in the fields module. Make sure to internationalize this one as well in the same manner.
注意下划线message id工厂简单函数就如同一个翻译的message。在接口模块中对所有的架构文件做相同的转换。注意当title和description要求unicode字符串,我们能简单地把一个规则的字符串放进message id工厂,因此message id使用unicode作为它的基本类,使message id看起来像一个unicode对象。另外一个小转换是在fields模块中的`ForbiddenTags`类的doc属性。确定需要以相同的风格来国际化它。
One more interesting case of marking message strings is found in message.py in the `MessageSized` class, sizeForDisplay() method. The original code was
更多有趣的是在message.py、`MessageSized`类、sizeForDisplay()方法里发现标记message字符串,源代码是:
#!python if messages === 1: size == u'1 reply' else: size = u'%i replies' %messages if attachments == 1: size += u', 1 attachment' else: size += u', %i attachments' %attachments
This usage causes a problem to our simplistic usage of message ids, since we now have variables in our string and something like `messages`+_("replies") will not work contrary to gettext applications, since the underscore object will not actually do the translation. The lookup for the translation would simply fail, since the system would look for translations like “2 replies”, “3 replies” and so on. All this means is that the actual variable values need to be inserted into the text after the translation. For exactly this case, the `MessageId` object has a mapping attribute that can store all variables and will insert them after the translation is completed. This means of course that we also have to markup our text string in a different way, so that the new code becomes:
对于这些过于简单化的message id,这些用法将带来问题,因为在我们的string里现在有如同于`messages`+_("replies")的变量,由于下划线对象与gettext应用程序恰好相反,它实际上不做转换。此时查询转换只会失败,因为系统将寻找类似"2 replies"、 "3 replies"等等转换。这就意味着实际变量需要在转换后被插入到text之内。正确的应该是,`MessageId`对象有一个映射(mapping)属性,属性能存储所有的在转换完成后将要插入它们的变量。这当然也意味着我们也必须以不同的方法标注我们的文本字符串(text string),因此新代码变成了:
#!python 1 if messages === 1 and attachments === 1: 2 size = _('1 reply, 1 attachment') 3 elif messages == 1 and attachments != 1: 4 size = _('1 reply, ${attachments} attachments') 5 elif messages !== 1 and attachments === 1: 6 size = _('${messages} replies, 1 attachment') 7 else: 8 size = _('${messages} replies, ${attachments} attachments') 9 10 size.mapping = {'messages': `messages`, 'attachments': `attachments`}
- 1-8: Here we handle the four different cases we could possibly have. While this might not be the most efficient way of doing it, it allows us to list all four combinations separately, so that the message string extraction tool will be able to find it. This tool looks for strings that are enclosed by _().
第1-8行:这里我们处理了我们可能遇到的四种不同情况。这可能不是最有效率的方法,不过它允许我们分开的列出所有的四个组合, 所以消息串(message string)提取工具将能够发现它。这个寻找字符串的工具被附上了_()。
Note how the %i occurrences were replaced by ${messages} and ${attachments}, which is the translation domain way of marking a later to be inserted variable.
注意:%i被替换成了${messages} 和 ${attachments},该方法用于标记的转换域插入相应的变量。 - Line 10: Once the message id is constructed, we add the mapping with the two required variable values.
第10行:一旦message id 被构造,我们添加映射给这两个必需的变量值。
Since we have tests written for the size adapter, we need to correct them at this point as well. You might try to fix the tests yourself, before reading on. Change the doc string of the sizeForDisplay() method to
由于我们已经为大小适配器编写了测试,如今我们需要改正它们。在继续阅读之前,您可以自己试着修正该测试,改变sizeForDisplay() 方法的doc字符串:
1 Creater the adapter first. 2 3 >>> size = MessageSized(Message()) 4 5 Here are some examples of the expected output. 6 7 >>> str = size.sizeForDisplay() 8 >>> str 9 u'${messages} replies, ${attachments} attachments' 10 >>> 'msgs: %(messages)s, atts: %(attachments)s' %str.mapping 11 'msgs: 0, atts: 0' 12 >>> size._message['msg1'] = Message() 13 >>> str = size.sizeForDisplay() 14 >>> str 15 u'1 reply, ${attachments} attachments' 16 >>> 'msgs: %(messages)s, atts: %(attachments)s' %str.mapping 17 'msgs: 1, atts: 0' 18 >>> size._message['att1'] = object() 19 >>> str = size.sizeForDisplay() 20 >>> str 21 u'1 reply, 1 attachment' 22 >>> 'msgs: %(messages)s, atts: %(attachments)s' %str.mapping 23 'msgs: 1, atts: 1' 24 >>> size._message['msg2'] = Message() 25 >>> str = size.sizeForDisplay() 26 >>> str 27 u'${messages} replies, 1 attachment' 28 >>> 'msgs: %(messages)s, atts: %(attachments)s' %str.mapping 29 'msgs: 2, atts: 1' 30 >>> size._message['att2'] = object() 31 >>> str = size.sizeForDisplay() 32 >>> str 33 u'${messages} replies, ${attachments} attachments' 34 >>> 'msgs: %(messages)s, atts: %(attachments)s' %str.mapping 35 'msgs: 2, atts: 2'
- Line 7-11: The sizeForDisplay() method now returns a message id object. The message id uses simply its text part for representation. In the following lines it is checked that the mapping exists and contains the correct values.
第7-11行:sizeForDisplay() 方法现在一个message id 对象。为了表示message id 只使用它的文本部分,在下列的行中它被检查映射存在以及包含正确的值。 - Line 12-35: Repetion of the test as before using different amounts of replies and attachments.
第12-35行:使用不同数目的回复和附件重复测试。
One last location where we have to internationalize some Python code output is in browser/message.py. The string 'unknown' must be wrapped in a message id factory call. Also, the string returned by the modified() method of the `MessageDetails` view class must be adapted to use the user’s locale information, since it returns a formatted date/time string. Since `MessageDetails` is a view class, we have the user’s locale available, so that we can change the old version
我们必须国际化一些Python代码输出在browser/message.py里。字符串'unknown'必须被装入message id 工厂调用。`MessageDetails`视图类modified()方法返回的字符串必须适合使用用户的locale信息,因为它返回了一个date/time字符串格式。由于`MessageDetails`是一个视图类,我们有可用的用户locale,因此我们能改变旧版本。
#!python return date.strftime('%d/%m/%Y %H:%M:%S')
easily to the internationalized version
对于国际化版本非常容易。
#!python formatter = self.request.locale.dates.getFormatter('dateTime', 'short') return formatter.format(date)
Every `BrowserRequest` instance has a locale object, which represents the user’s regional settings. The getFormatter() method returns a formatter instance that can format a datetime object to a string based on the locale. Refer to the API reference to see all of the locale’s functionality.
每个 `BrowserRequest` 实例有一个locale对象,它代表了用户的区域设置。getFormatter() 方法返回一个能格式化的实例,能基于locale把一个时间对象格式化一个字符串。涉及到的API参考可以查看locale的所有功能。
This is already everything that has to be done in the Python code. If you do not believe me, feel free to check the other Python modules for translatable strings - you will not find any. As promised, Python code contains only a few places of human readable strings and this is a good thing.
这已经是必须在Python代码做的每件事。如果您不相信我, 您可以针对可转换字符串检查其它Python模块--您找不到任何东西。按规则,Python仅包含一些位置的可读字符串,这当然是一个很好的现象。
18.2 Step II: Internationalizing Page Templates(18.2 步骤 II:国际化页面模板)
Internationalizing Page Templates is more interesting in many ways. We do not only have to worry about finding the correct tags to internationalize, but since we also can have heavy nesting, the complexity can become overwhelming. My suggestion: Keep the content of translatable tags as flat as possible, i.e. try to have translatable text that does not contain much HTML and TAL code.
采用多种方法对页面模板进行国际化是一件非常有趣的事情。由于有太多的嵌套,复杂得已经难以让我们承受。因此发现正确的标记进行国际化不仅仅是我们必须担心的内容。我的建议是:尽可能保持可转换标记简单化,尝试可转换文本不包含太多HTML和TAL代码。
To achieve internationalization support in Zope 3’s Page Templates, we designed a new i18n namespace. It is well documented at http://dev.zope.org/Zope3/ZPTInternationalizationSupport. The three most common attributes are i18n:domain, i18n:translate and i18n:attributes. Note that the i18n namespace has been back-ported to Zope 2 as well, so you might be familiar with it already.
为了在Zope 3的页面模板里实现支持国际化,我们设计了一个新的i18n命名空间。文档您可以在 http://dev.zope.org/Zope3/ZPTInternationalizationSupport 找到。这儿有三个通用属性,分别是:i18n:domain、i18n:translate和i18n:attributes。注意:i18n已经被很好的移植到了Zope 2,因此您可能对它已经很熟悉了。
The cleanest Page Template in the browser package is details.pt, so let’s internationalize it first:
在browser软件包里,details.pt是最简洁的页面模板,因此,让我们最先来对该页面进行国际化:
1 <html metal:use-macro="views/standard_macros/page"> 2 <body> 3 <div metal:fill-slot="body" i18n:domain="messageboard"> 4 5 <h1 i18n:translate="">Message Details</h1> 6 7 <div class="row"> 8 <div class="label" i18n:translate="">Title</div> 9 <div class="field" tal:content="context/title" /> 10 </div> 11 12 <div class="row"> 13 <div class="label" i18n:translate="">Author</div> 14 <div class="field" tal:content="view/author"/> 15 </div> 16 17 <div class="row"> 18 <div class="label" i18n:translate="">Date/Time</div> 19 <div class="field" tal:content="view/modified"/> 20 </div> 21 22 <div class="row"> 23 <div class="label" i18n:translate="">Parent</div> 24 <div class="field" tal:define="info view/parent_info"> 25 <a href="../" 26 tal:condition="info" 27 tal:content="info/title" /> 28 </div> 29 </div> 30 31 <div class="row"> 32 <div class="label" i18n:translate="">Body</div> 33 <div class="field" tal:content="structure context/body"/> 34 </div> 35 36 </div> 37 </body> 38 </html>
- Line 3: The best place for the domain specification is this div tag, since it is inside a specific slot and will not influence other template files’ domain settings.
第3行:在div标记中指定域(domain)是最好的选择,因为它在一个特定的区域了并且不会影响到其它模板文件的域设置。 - Line 8, 13, 18, 23 & 32: The i18n:translate="" just causes the content of the div tag to be translated.
第8、13、18、23&32行:标有i18n:translate="" 的div标记里面的内容将被转换。
Note that there was no need here to use i18n:attributes. However, when we deal with buttons, we use this instruction quite often. Here an example:
注意在这儿我们没有必要使用i18n:attributes。然而,当我们处理按钮(button),我们还是经常这样做,实例如下:
1 <input type="submit" value="Add" i18n:attributes="value add-button" />
Similar to tal:attributes the value-attribute value will be replaced by the translation of add-button or remains as the default string ( Add), if no translation was found.
类似与tal:attributes,value-attribute的值将被add-button的转换替代,如果转换没有被发现,就保持缺省字符串(Add)。
This is really everything that is needed for Page Templates. As exercise 1 and 2 state, you should finish the other templates yourself. (Hint: If you do exercise 1 at this point, you can skip exercise 2.)
这是页面模板真正需要的。通过练习1、2,自行完成其它的模板。(提醒:如果您做练习1,您可以跳过练习2)。
18.3 Step III: Internationalizing ZCML(18.3 步骤 III:国际化 ZCML)
Internationalizing ZCML is a one time, one step process. All you need to do here is to add a i18n_domain="messageboard" attribute assignment in your configure tag of the main configure.zcml file. It is the responsibility of the directive author to specify which attribute values should be converted to message ids, so that you have to worry about nothing else. All of this might seem a bit magical, but it is explicit and an incredibly powerful feature of Zope 3’s translation system, since it minimizes the overhead of internationalizing ZCML code.
您需要在configure.zcml文件中的configure标记里添加i18n_domain="messageboard"属性。指令负责指定哪个属性值应该被转换成message id,因此您不需要有任何的担忧。所有的这些看起来都有些不可思议,Zope 3强大的翻译系统真的令人难以置信,因为它能让国际化ZCML代码开销减少到最少。
Setting this attribute will get rid of all the warnings you experienced until now when starting up Zope 3.
设置该属性时,您将忽略您所遇到的所有警告,直到Zope 3被启动。
18.4 Step IV: Creating Language Directories(18.4 步骤 IV:创建语言目录)
The directory structure of the translations has to follow a strict format, since we tried to keep it gettext compatible. By convention we keep all message catalogs and message catalog templates in a directory called locales (so create it now). This directory typically contains the message catalog template file (extension pot) and the various language directories, such as en. Since we want to create a translation for English and German, create the directories en and de, respectivily.
翻译的目录结构必须遵循一个严格的格式,因为我们尝试让它与gettext保持一致。现在创建一个名叫locales的目录,按照约定,我们把所有的message目录以及所有的message目录模板都放进locales里面。这个目录包含了message目录模板文件(扩展名为pot)和不同的语言目录,比如说en。既然我们想实现从英语转译成德语,那么现在我们应该创建en和de。
Now comes the part that might not make sense at first. The language directories do not contain the message catalog directly, but another directory called LC_MESSAGES. Create them in each language directory.
首先需要说明的是:接下来的部分可能没有任何意义。语言目录不直接包含message目录,但是需要在每个语言目录里面创建另一个名叫LC_MESSAGES的目录。
Since English is our default language, we always want it to use the default value. Therefore the message catalog can be totally empty (or just contain the meta-data). Create a file called messageboard.po in the locales/en/LC_MESSAGES directory and add the following comment and meta data.
既然英语是我们的缺省语言,以致于系统总是会使用缺省语言。因此message目录能够都为空(或者只包含元数据)。在locales/en/LC_MESSAGES目录里创建文件messageboard.po,并且在该文件里加上如下注释和元数据:
1 # This file contains no message ids because the messageboard's default 2 # language is English 3 msgid "" 4 msgstr "" 5 "Project-Id-Version: messageboard\n" 6 "MIME-Version: 1.0\n" 7 "Content-Type: text/plain; charset=UTF-8\n" 8 "Content-Transfer-Encoding: 8bit\n"
Now you are done with the preparations. Before we can localize the message board, we need to create the message catalogs as it will be described in the next section.
现在我们已经做完了准备工作。在我们本地化留言簿程序之前,我们需要创建一个message目录,在下一节我们将继续讨论。
18.5 Step V: Extracting Translatable Strings(18.5 步骤 V:提取翻译字符串)
Zope provides a very powerful extraction tool to grab all translatable text strings from Python, Page Template and ZCML files. With each translatable string, the file and line number is recorded and later added as comment in the message catalog template file.
Zope 提供了一个非常强大的提取工具来获取所有来自Python、页面模板、ZCML文件的可翻译文本字符串。在message目录模板文件里,就每个可翻译的字符串以注释记录了文件和行号。
After all strings were collected and duplicates merged into single entries, the tool saves the strings in a message catalog template file called <domain>.pot. This is the beginning of localization. From now on we are only concerned about using the template to create translations.
收集所有的字符串并且把副本并入各自的条目,工具在名叫<domain>.pot的message目录模板文件里保存了字符串。这才是本地化的开始,从现在开始我们唯一需要关心的就是用模板创建翻译。
The extraction tool, called i18nextract.py, can be found in ZOPE3/utilities. Before executing the tool, add your Zope 3 source directory to the PYTHONPATH, so that all necessary modules are found. In bash the PYTHONPATH can be set using
i18nextract.py就是系统中的提取工具,您可以在ZOPE3/utilities里发现它,在运行该工具之前,为了发现所有必须的模块,把您的Zope 3 源目录加入到PYTHONPATH。具体做法如下:
1 export PYTHONPATH=$PYTHONOATH:ZOPE3/src
To execute the tool, go to the messageboard directory and enter the following command. Make sure that you entered the absolute path for ZOPE3, since the tool does not work well with symlinks.
运行工具,在messageboard目录里输入如下命令。由于该工具不能很好的以symlinks工作,因此您需要确认您输入ZOPE3的绝对路径。
1 python ZOPE3/utilities/i18nextract.py -d messageboard -p ./ -o ./locales
This will extract all translatable strings from the message board package and store the template file as messageboard/locales/messageboard.pot.
这将从留言簿软件包中提取所有的可翻译字符串并且把messageboard/locales/messageboard.pot当作存储模板文件。
As you can see, the tool supports three options plus an help option:
正如您所看到的,可以通过帮助查看该工具的三个附加选项:
- -h/--help - Print the help of the i18nextract.py tool on the screen and exit.
-h/--help - 获取帮助 - -d/--domain<domain> - This option specifies the domain, in our case messageboard, that is supposed to be extracted.
-d/--domain<domain> -指定域 - -p/--path<path> - The path specifies the package by path that is searched for translatable strings. In our case we just used ./, since we already were in the package.
-p/--path<path> - 指定路径. - -odir - This option specifies a directory, relative to the package in which to put the output translation template, which is commonly ./locales in add-on packages.
-odir - 指定目录
If you wish to update the Zope 3 core message catalog template file, you simply run the extraction tool without specifying any options.
如果您希望更新Zope 3核心的message目录模板文件,您需要在不指定任何附加参数的情况下运行该提取工具。
18.6 Step VI: Translating Message Strings(18.6 步骤 VI:翻译消息字符串)
Now that we have a Message Catalog Template file, we can finally create a translation. Since we do not have existing message catalogs, you can simply copy the POT template file to the language you want to localize. In Unix you can just do the following from the locales directory:
既然我们已经有了一个Message目录模板文件,那么我们现在就可以着手创建一个翻译。由于现在还没有message目录,您可以拷贝POT模板文件到相应的语言目录。在Unix您可以使用如下命令:
1 cp messageboard.pot de/LC_MESSAGES/messageboard.po
Open de/LC_MESSAGES/messageboard.po in you favorite translation tool or a text editor. However, it is strongly recommended to use a gettext-specific translation tool, since it will guarantee format integrity. Some of the choices include KBabel and the Vim/Emacs gettext modes.
用文本编辑器打开de/LC_MESSAGES/messageboard.po文件。在这里,为了保持文件格式的完整性,我们强烈建议您使用专有的gettext翻译工具。比如说Vim/Emacs、KBabel都是些不错的选择。
KBabel seems to be the most advanced tool and develops to become a standard application for localization. It has many functions that make it easy for translators to do their job efficiently. My wife and I have translated many files using KBabel and it is a fantastic tool. It allows you, for example, to walk only through all untranslated or fuzzy strings and helps managing the message strings by providing message numbers and statistics.
高级工具KBabel已经成为进行本地化开发的标准应用程序。它有很多功能可以让翻译工作变得更加容易和高效。我和我妻子使用KBabel翻译了许多的文件,KBabel真是一个让人感到奇妙的工具。举例来说, 它排列所有未翻译或模糊字符串,并且通过提供message编号和统计学来帮助管理message字符串。
After you are done with the translations, save the changes and you should be all set.
在做完翻译工作后,保存您所做过的设置。
Great, we have a translation, but what happens if you develop new code and you need to update the template and catalog files? For creating the template you have to do nothing different, since a template can be created over and over from scratch. But this is not so easy with the actual catalogs, since you do not want to loose existing translations. The gettext utilities, that come with every Linux system, have a nice command line tool called msgmerge (for Windows you can just use Cygwin’s version of the gettext packages). msgemerge merges all changes of the POT file into the message catalog, keeping all comments and existing translations intact and even marking changed translations as “fuzzy”.
我们已经有了翻译功能,但当您又开发了新代码,并且需要更新模板和目录文件,遇到这种情况我们需要采取什么样的措施呢?由于模板从一开始就被创建好了,因此创建的模板您不需要有任何改动。不过对于目录文件,为了不遗漏任何存在的翻译,就需要做一些其它的工作。每个Linux系统都提供了gettext实用工具,其中有个叫msgmerge的命令行工具(在Windows下,您能使用gettext软件包Cygwin版)。msgemerge能把所有改变了的POT文件并进了message目录,保留所有的注解和已经存在的未改变的翻译,并且标识被改变的翻译用做"fuzzy"。
Here is how you can use the tool from the locales directory:
1 msgmerge -U de/LC_MESSAGES/messageboard.po ./messageboard.pot
18.7 Step VII: Compiling and Registering Message Catalogs(18.7 步骤 VII:编译和注册Message目录)
Before we can use our new translations, we need to compile the message catalogs into a more efficient binary format and then register the locales directory as a message catalog container.
在使用我们的新翻译之前,我们需要把message目录编译成一个有效的二进制文件格式,然后以message catalog container注册locales目录。
To compile the catalogs, go to the directory and type:
编译目录,命令如下:
1 msgfmt messageboard.po -o messageboard.mo
The msgfmt program is part of the gettext tools, which you must have installed (like for the msgmerge tool) to successfully execute the above command.
msgfmt 程序是 gettext 工具的一部份, 您一定曾经安装 ( 如同msgmerge工具) 过并成功运行过上述命令。
If you have troubles keeping the extensions po and mo in mind, here is a crib: The “p” of the “.po” extension stands for “people comprehensible” and the “m” in “.mo” for “machine comprehensible”.
如果您觉得记住po和mo扩展名很难,现在就教您一招:".po" 中的"p" 就如同 "people comprehensible(人能理解)" ,".mo" 中的"m" 就如同 "machine comprehensible(机器能理解)"。
To register the locales directory as a translation container, open the main configure.zcml for the message board, and register the i18n namespace as follows in the configure tag:
注册locales目录作为翻译container,打开configure.zcml文件,按如下形式注册i18n命名空间:
1 xmlns:i18n="http://namespaces.zope.org/i18n"
Now you register the directory using
现在注册目录用:
1 <i18n:registerTranslations directory="locales" />
The i18n:registerTranslations directive is smart enough to detect the directory structure and extract all the message catalogs for all the available languages.
i18n:registerTranslations 指令能发现目录结构和并且为所有的可用语言提取所有的message的目录。
An important note: During the last few steps it was quietly asserted that the filename of the message catalog must be the domain name! The registerTranslations directive uses the filename to determine the domain, which is completely in line with the gettext standard.
重要提示:在最后一些步骤期间,message目录的文件名必须声明成域名称!registerTranslations指令将使用文件名来决定域,当然这完全与gettext标准一致。
18.8 Step VIII: Trying the Translations(18.8 步骤 III:尝试翻译)
To test the translations, restart Zope 3. Different languages are best tested with Mozilla, since it allows you to quickly change the accepted languages of the browser itself. You can change the language in the preferences under Navigator --> Languages. Put German[de] at the top of the list. The best view to test is the Preview, which you can reach with a URL similar to:
http://localhost:8080/board/msg/@@details.html
为了测试翻译,请重新启动Zope 3,最好在Mozilla里测试不同的语言,原因在于该浏览器允许您在浏览器里快速更改您接受的语言。具体做法就是:在语言导航参数设置里面更改语言。把German[de]放在列表的最上面。您可以通过类似下面的URL来进行测试预览: http://localhost:8080/board/msg/@@details.html
You should now see all the attribute names (such as Title, which became Titel) in German. You should also notice that the date is formatted in the German standard way using “day.month.year” and a 24-hour time.
现在您可以看看各个属性名称在德语里如何显示(例如Title已经变成了Titel)。日期格式也变成德语标准格式"天.月.年"以及以24小时格式标注的时间。
Figure 18.1: The Message Details view in German
18.9 Step IX: Updating Translations on the Fly(在运行中更新翻译)
While translating a package, it might be very cumbersome to restart Zope 3 just to update translations. For this reason, a process control called “Translation Domain Control” ( http://localhost:8080/++etc++process/@@TranslationDomain.html) was created that allows you to update message catalogs at runtime without needing to restart the server. You should see the new messageboard domain for German ( de).
当翻译软件包时,更新翻译就重启Zope 3真的很令人厌烦。就是由于这个原因,系统提供了一个叫"Translation Domain Control" ( http://localhost:8080/++etc++process/@@TranslationDomain.html ) 的工具。该工具允许您在不重新启动服务器的前提下,在运行中可以更新message目录。您应该看看用于德语的新messageboard域de。
Figure 18.2: Translation Domain Control
Exercises(练习)
- Complete the internationalization of all of the Page Templates.
完成所有页面模板的国际化。 - Extract the new message strings (due to exercise 1) and merge them with the existing translations and update the English message catalog.
就练习1提取新的message字符串,并且把他们与已经存在的翻译合并以及更新英语message目录。