翻译:UiBinder with I18N

原文地址

http://www.gwtproject.org/doc/latest/DevGuideUiBinderI18n.html

翻译时间

2013年9月10日至2013年9月14日

译者注
这篇文章是我边看边译的,方便自己的同时也方便他人。内容不完全直译,未必100%准确,只能翻译出大概意思,本人之前毫无翻译经验,如有错漏之处,敬请指教。

译文

本文主要探讨UiBinder模板的I18N功能,想了解更多UiBinder的功能,请浏览Declarative Layout with UiBinder(译者注:该文章的中文版,click here

目录

  1. 背景;
  2. Bonjour, Tout Le Monde(Hello,world)!
  3. Message内的简易HTML标记
  4. 隐藏(保护)部分消息
  5. Messages在运行期的值运算
  6. 将Widgets植入message当中(只适用于HTMLPanel)
  7. 翻译HTML属性
  8. 具有多重含义的词组

背景

UiBinder模板可以用于本地化标记,使用<ui:msg><ui:attribute>元素表示该模板的哪些部分应进行翻译,在构建应用程序时提供版本化本地化消息的properties文件。

原文:then provide properties files with localized versions of the messages when building your app.

前文中(click here)通过一系列的典型应用例子部分介绍了如何本地化你的UI模板。
注意:你将看到UiBinder的本地化与GWTMessages有很多相似之处,原因是:UiBinder的I18N功能是通过为每个模板生成一个隐藏的com.google.gwt.i18n.client.Messages接口而实现的。除了复数形式(原文:plural forms),你能通过模板实现Messages所实现的。

Bonjour, Tout Le Monde(Hello,world)

这里将展示如何开启这个功能。

Original:

1
2
3
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'>
<div>Hello, world.</div>
</ui:UiBinder>

Tagged:

1
2
3
4
5
6
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
ui:generateFormat='com.google.gwt.i18n.rebind.format.PropertiesFormat'
ui:generateKeys="com.google.gwt.i18n.rebind.keygen.MD5KeyGenerator"
ui:generateLocales="default">
<div><ui:msg description="Greeting">Hello, world.</ui:msg></div>
</ui:UiBinder>

在这里我们做了两件事。一、通过在根元素<ui:UiBinder>里面添加一些属性来配置UiBinder的I18N功能;二、单独标记需要实现本地化的文本。
先来看看“Hello,World”这个文本。我们将放进<ui:msg>“Hello,World”标记为一条可翻译的文本,一个Message.description属性是用来描述该消息的用途。虽然只是个可选属性,但可能是翻译者在上下文中能看到的为数不多的信息,所以你应该习惯性地为消息添加描述,就像为代码添加注释一样。
现在我们有了需要翻译的东西,我们通过一些配置在<ui:UiBinder>里面的属性告诉UiBinder如何完成翻译工作。这些ui:generate*属性相当于LocalizableResource@Generate标注。详见如下:

  • ui:generateFormat
    使用java properties格式。

  • ui:generateKeys
    使用消息内容的MD5哈希值作为键集合,因此消息翻译才能在模板内生效。(如果我们留意一下properties是如何被管理的,你会发现在多个模板使用同一个message只需要翻译一次)

  • ui:generateLocales
    将生成的properties作为default locale的一部分。(关于default localefallback properties的更多内容,请浏览Locales in GWT
    编译应用程序时,给gwt compiler传递一个-extras参数,告诉编译器生成应用程序的附属文件。然后就会为每个模板生成一个包含所有message入口标记位置的properties文件。

    1
    2
    3
    4
    5
    # Generated from my.app.HelloWorldMyBinderImplGenMessages
    # for locale default
    # Description: Greeting
    022A824F26735ED0582324BE34F3CAE1=Hello, world.

生成的文件名是根据你定义的UiBinder接口而定的,有点惨不忍睹。
举个例子:
模板名称:com.example.app.client.Hello.Binder
module名称:App
运行带有-extra /tmp/gwt-extras参数的gwt compiler
然后你会发现文件的路径是这个样子:/tmp/gwt-extras/com.example.app.App/com.example.app.client.HelloBinderImplGenMessages.properties

就如包大人所说的:这,并不好笑嘛。希望在GWT以后的发行版本里面能有更简洁的解决方案。

然后,你可以在ui.xml文件的同一目录下创建该文件的本地化版本。继续上面的例子,一个Mexican Spanish版本的本地化properties应命名为HelloBinderImplGenMessages_es_MX.properties(注意将com.example...前缀咔擦掉)。
你不一定需要使用MD5 keys。如果你想自定义键名,可以在<ui:msg>里面添加一个key属性并指定你想要设置的键名。另一方面,如果你坚持使用MD5 keys,你可以将应用程序所有的翻译集合成一个指定特定语言环境的文件,而不是让他们遍布你的代码。这个properties文件的文件名格式是:LocalizableResource_<locale>.properties,而且你必须把这个文件放在com/google/gwt/i18n/client包下面。(原理:i18n properties文件会沿着类继承树往上搜寻,而所有Messages接口都继承于LocalizableResource

译者注:这个功能看上去挺有趣,有兴趣的可以去了解以下。

有些项目会使用脚本来维护这些统一的翻译文件。这里有个来自puzzlebazar项目的例子(wiki entry)。

还有其他I18N属性可以配置在<ui:UiBinder>元素下,其实相当于Localizable的其他注解,但你很少会改变他们的默认值。

  • ui:defaultLocale
    See @DefaultLocale for details

  • ui:generateFilename
    See @Generate(fileName = “…”) for details

  • ui:baseMessagesInterface
    设置用于生成Messages的基类接口,值必须是全路径类名,且该类需继承于Messages接口。你可以添加任何注解,以便公司及项目范围内的设置可以在一个地方修改即可。如有必要,你也可以覆盖该接口其他属性的默认值。

Message内的简易HTML标记

<ui:msg>可以适当地使用HTML标记。

Original:

1
We <b>strongly</b> urge you to reconsider.

Tagged:

1
<ui:msg>We <b>strongly</b> urge you to reconsider.</ui:msg>

UiBinder中的Messages支持HTML,不只是文本。

隐藏(保护)部分消息

译者注:这部分内容翻译起来有点难,可能看代码反而更容易理解。

有时候你需要将message的某些部分隐藏起来,以防翻译者破坏你的应用程序。

Original:

1
2
3
<!-- Uh oh, don't want translator to mess up brand CSS or the trademark span -->
<div><span class="brand">Colgate</span>, with MFP!<span class="tm">TM</span></div>

Tagged:

1
2
3
4
5
6
7
8
<div>
<ui:msg description="blurb"><span class="brand" ui:ph="brandedSpan">Colgate</span>,
with MFP!<ui:ph name="trademark"><span class="tm">TM</span></ui:ph></ui:msg>
</div>
Generated:
# Description: blurb
# 0=arg0 (Example: <span class='brand'>), 1=arg1 (Example: </span>), 2=arg2 (Example: <span class='tm'>TM</span>)
6E8B421C6A7C1FEAE23FAA9D43C90D5E={0}Colgate{1}, with MFP\!{2}

在这里例子中有两个地方。首先,你可以在<ui:msg>里面的任何子元素添加ui:ph属性,表明这里应该创建占位符免受翻译。在element的开始和结束标记位置会创建两个占位符(在这个例子里,分别是brandedSpanOpenbrandedSpanClose)。
然后,我们同样看到一个名为ui:ph的子元素,可以将任何被保护的元素置于其包围之中(在这个例子里,就是tradermark占位符)。
所以,你既可以将被保护的元素置于<ui:ph>包围之下,就像<ui:ph>don't translate</ui:ph>,也可以使用ui:ph属性隐藏元素的开始和结束标记(相对于翻译者而言),只保留元素的内容允许翻译,就像<span ui:ph>attribute</span>。注意:你可以将ui:ph属性置于任何DOM element,而不只是<span>

译者注:其实上面这段话的意思就是:ui:ph既可以作为element的属性使用,也可以将<ui:ph>作为element封装需要被保护的元素。
用法1:
<span class="brand" ui:ph="brandedSpan">Colgate</span>

用法2:
<ui:ph name="trademark"><span class="tm">TM</span></ui:ph>

Messages在运行期的值运算

当你想在运行期间修改模板的某一部分,你通常会将ui:field放入一个span或者其他element并且置于其HTML当中。当你在<ui:msg>里面这样做,它会自动保护免受翻译。

Original:

1
2
<!-- Java code will make calls like getClosingDate().setInnerText(closingDate()) -->
(closed <span ui:field="closingDate" /> through <span ui:field="reopeningDate"/>)

Tagged:

1
2
3
<ui:msg description='closed for business message'>
(closed <span ui:field='closingDate' /> through <span ui:field='reopeningDate'/>)
</ui:msg>

Generated:

1
2
3
# Description: closed for business message
# 0=arg0 (Example: <span id=''>), 1=arg1 (Example: </span>), 2=arg2 (Example: <span id=''>), 3=arg3 (Example: </span>)
E30D43242E1AD2AC2EFA1AEEEFDFCC33=(closed {0}{1} through {2}{3})

这里有一个好消息和坏消息。好消息就是你不需要在标记有ui:filed属性的span里额外添加任何ui:ph属性或用<ui:ph>包围去保护开始和结束标记。坏消息就是无法限制翻译者在这些开始和结束标记之间添加任何东西。Notice:{0}{1} and {2}{3}
如果这是个问题,你需要将spans放入<ui:ph>里面以被保护,就像这样:

Tagged:

1
2
3
4
<ui:msg>
(closed <ui:ph name='closingDate' example="7/12/2008"><span ui:field="closingDate"/></ui:ph>
through <ui:ph name='reopeningDate' example="7/12/2008"><span ui:field="reopeningDate"/></ui:ph>)
</ui:msg>

Generated:

1
2
# 0=arg0 (Example: 7/12/2008), 1=arg1 (Example: 7/12/2008)
53B9CF65553DFAA091435791E5C731E7=(closed {0} through {1})

example属性是可选的,这个属性允许你提供翻译者更多关于占位符的值和用途用法的信息,方便翻译者。

译者注:这一节同样看代码能更易理解,对比两段taggedgenerated就很容易明白,大概意思就是说如果你需要在<ui:msg>里面使用ui:field的话,需要使用<ui:ph>包围封装带有ui:field属性的element

将Widgets植入message当中(只适用于HTMLPanel

当使用<g:HTMLPanel>时,你可能会想将widgets植入messages当中。这个可以有!

Original:

1
2
3
4
5
6
<g:HTMLPanel>
Meeting starts at
<my:TimePicker ui:field="startPicker"/>
and ends at
<my:TimePicker ui:field="endPicker"/>.
</g:HTMLPanel>

Tagged:

1
2
3
4
5
6
7
<g:HTMLPanel>
<ui:msg>Meeting starts at
<my:TimePicker ui:field="startPicker"/>
and ends at
<my:TimePicker ui:field="endPicker"/>.
</ui:msg>
</g:HTMLPanel>

Generated:

1
2
# 0=arg0 (Example: <span>), 1=arg1 (Example: </span>), 2=arg2 (Example: <span>), 3=arg3 (Example: </span>)
23CBEA252C9901BF84D757FAD4968289=Meeting starts at {0}{1} and ends at {2}{3}.

注意:widgets是没有ui:ph属性的,因为不需要。当一个widget出现在message中的时候,widget需要做什么是毫无歧义的。需要留意的是你只能在HTMLPanel里面做这种事情(widgets in messages),因为HTMLPanelGWT里面唯一一个widget能混合html标记和子widget。

更多:你可能注意到在生成的properties文件里每个widget拥有太多的占位符,这意味着翻译者可能会引入一些不需要的文本到在运行期被widgets取代的spans里面。出现这样的事情,除了浪费翻译者的时间,不会造成任何危害。widgets还在那里,而文本则会丢失——你的用户不会看到这些文本。

当你把一个包含在message文本主体里的widget放入HTMLPanel,事情会变得更加有趣(widget implements HasText, HasHTML)。

Original:

1
2
3
4
<g:HTMLPanel>
To do the thing, <g:Hyperlink targetHistoryToken="/doThe#thing">click here</g:Hyperlink>
and massage vigorously.
</g:HTMLPanel>

Tagged:

1
2
3
4
5
6
<g:HTMLPanel>
<ui:msg>
To do the thing, <g:Hyperlink targetHistoryToken="/doThe#thing">click here</g:Hyperlink>
and massage vigorously.
</ui:msg>
</g:HTMLPanel>

Generated:

1
2
# 0=arg0 (Example: <span>), 1=arg1 (Example: </span>)
8EFBF967A3FEFE78C41C8A298562A094=To do the thing, {0}click here{1} and massage vigorously.

译者注:但我觉得这种做法很恶心……

翻译HTML属性

可以翻译的并不只是主体文本——属性同样也可以。比较常见的有title属性(tool-tip文本)和alt`标记。

Original:

1
<th title="Gross recipts">Gross</th>

Tagged:

1
2
3
4
<th title="Gross receipts">
<ui:attribute ui:name='title' ui:description='Tooltip text for gross column'/>
<ui:msg description='name of gross column'>Gross</ui:msg>
</th>

具有多重含义的词组

需要注意的是,words在不同的上下文中有时候会表达不同的意思。

Original:

1
2
3
Favorite Color:
<ui:RadioButton name="color">Red</ui:RadioButton>
<ui:RadioButton name="color">Orange</ui:RadioButton>

1
2
3
Favorite Fruit:
<ui:RadioButton name="fruit">Apple</ui:RadioButton>
<ui:RadioButton name="fruit">Orange</ui:RadioButton>

Tagged:

1
2
3
Favorite Color:
<ui:RadioButton name="color"><ui:msg>Red</ui:msg></ui:RadioButton>
<ui:RadioButton name="color"><ui:msg meaning="the color"/>Orange</ui:msg></ui:RadioButton>

1
2
3
Favorite Fruit:
<ui:RadioButton name="fruit"><ui:msg>Apple</ui:msg></ui:RadioButton>
<ui:RadioButton name="fruit"><ui:msg meaning="the fruit">Orange<ui:msg></ui:RadioButton>

Generated:

1
2
3
4
5
6
7
8
9
# Meaning: the color
4404BE8C34552617D633271BBC1FAB07=Orange
# Meaning: the fruit
7A6DCA1ACC86B4A7D7574CD6BDD4E0C1=Orange
9F6290F4436E5A2351F12E03B6433C3C=Apple
EE38E4D5DD68C4E440825018D549CB47=Red

The punchline here is that a translator may well be working with no more context than the attributes you set on an individual message. (译者注:这句不懂得翻译,但也不大影响对本节内容的理解)

如果你将所有的翻译都放入一个大池当中使用,而它们之间仅仅通过MD5哈希值来区别区分的话,你在这里就不能提供“两种口味”的“Organge”了。

所以,你可以通过使用meaning属性来解决这个问题。不同description的是,meaning属性是会影响生成的哈希值的。

译者注:这一节大意就是说当一个properties里有同一个值的时候,需要增加一个meaning属性来区分彼此。

结尾语

自己在做翻译的过程中也觉得翻译不好做,也不知道自己的理解和表达是否准确,希望对学习uibinder的人有一点点帮助吧。
这里附上一个有关UiBinder的PDF下载地址。gwt-ui-overhaul