转自:http://java.chinaitlab.com/advance/719365.html
摘要:
对于许多软件开发者来说,一提到国际化(亦称为 i18n)支持就会感到害怕。 要使编写的代码能够面向外国使用者,确实需要费一翻思量,因为在现有软件的代码中添加国际化支持可不是一件轻而易举的事。对于许多软件开发者来说,一提到国际化(亦称为 i18n)支持就会感到害怕。 要使编写的代码能够面向外国使用者,确实需要费一翻思量,因为在现有软件的代码中添加国际化支持可不是一件轻而易举的事。 如果您感觉到软件需要支持不同语言和语言环境,哪怕这种可能性很小,从一开始就做国际化项目的准备,比起项目开始后再试图添加国际化支持也要明智得多。有人问“国际化”是什么意思? 国际化远不止于将用户界面消息翻译成不同的语言。 它还涉及到处理不同的字符编码、日期/时间/货币的显示形式、以及跨多区域时存在的一些其他差异。
介绍 i18nlog
本文的目的并不在于讨论那些关于国际化的琐碎的方面,而是通过研究一个称为 "I18N Messages and Logging" (简称 i18nlog) 的开源项目,来介绍引入国际化功能时需要执行的一些必要的任务
i18nlog 允许您在 Java 应用程序内集成国际化的消息,这是通过向以下内容提供 API 来完成的:
+标注 Java 类以识别国际化消息
+从所有支持的语言环境资源包中获取国际化消息
+创建特定于语言环境的异常,并在其中使用国际化的消息
+使用任何日志框架创建国际化消息日志
+自动生成特定于语言环境的资源包
+自动生成帮助及参考文档
定义国际化消息
国际化软件时一项最为乏味的工作莫过于维护资源绑定包了。 资源绑定包是包含 "name=value" 这种信息对的属性文件 (.properties),其中 "name" 是资源绑定包的关键字字符串 (key string),而 "value" 是翻译过的消息字符串本身。 习惯上为每种语言创建一个资源绑定包,在每个绑定包中关键字的设置是唯一的,而各个关键字相关的值就要翻译成各种语言了。 资源绑定文件的名称应该指明它是为哪种语言创建的,例如,mybundle_en.properties 文件中的消息是用英语写的,而 mybundle_de.properties 包含德语消息。
i18nlog 提供了一些用于定义资源绑定消息及其关键字字符串的标注--用于将资源注入到 i18nlog 的自定义 Ant 任务中,您可以自动生成资源绑定包,而不必为确保属性文件与访问属性文件的 Java 代码之间的一致性作过多的工作。
@I18NMessage 标注被放在常量上,这些常量就是资源绑定包的关键字字符串使用的常量。 使用这些常量可以迫使执行编译时检查;例如代码中引入的拼写错误(如常量名称的拼写错误)和使用过时消息或已删除消息,这些错误在编译时就可以被探测到。 以下是使用此标注的示例:
java 代码
1. @I18NMessage( "Hello, {0}. You last visited on {1,date}" )
2. public static final String MSG_WELCOME = "example.welcome-msg";
上面的示例定义了一条国际化消息。 常量的值定义了资源绑定包的关键字字符串(key string)。 标注的值是一条实际翻译好的消息。 您可以将这些标注过的常量放到应用程序的任何类或接口中。 可以将它们放在单独的类或接口中(将所有消息定义集中到一个地点),也可以放在使用到它们的类中。
@I18NResourceBundle 标注用于定义存放消息的资源绑定包文件(.properties 文件)。 它可以标注整个类或接口,也可以标注特定的部分。 如果您标注了一个类或接口,则该类或接口中的所有 @I18NMessage 标注都将被存储在该标注定义的资源绑定包中(默认情况下)。 如果只将标注放在特定的常量上,那么它就只是那个常量的绑定包。 以下是使用此标注的示例:
java 代码
1. @I18NResourceBundle( baseName = "messages",
2. defaultLocale = "en" )
以上代码的意思是,所有被 @I18NMessage 标注的相关消息都将放置在名为 messages_en.properties 绑定包文件中。 下面是一个更复杂的示例(包含一系列国际化消息的接口):
java 代码
1. @I18NResourceBundle( baseName = "messages",
2. defaultLocale = "en" )
3. public interface Messages {
4. @I18NMessage( "Hello, {0}. You last visited on {1,date}" )
5. String MSG_WELCOME = "welcome-msg";
6.
7. @I18NMessage( "An error occurred, please try again" )
8. String MSG_ERR = "error-occurred";
9.
10. @I18NMessage( "The value is {0}" )
11. String MSG_VALUE = "value";
12. }
检索国际化消息
定义了国际化常量后,就可以使用 i18nlog 的核心类提供的 API:mazz.i18n.Msg。 该 API 用于装载存放于资源绑定属性文件中的消息。 它还用于将值作为变量参数进行传递,从而替换消息中的占位符(例如, {0}, {1,date})。 另外,此 API 将您从直接使用 JDK 类进行编码的工作中解放出来,也许您需要阅读一下 Javadoc 的 java.text.MessageFormat 部分,以获得对 i18nlog 工作原理的初步认识,尤其是它如何使用本地化的数据替换占位符。
下面是 Msg 类的使用实例:
java 代码
1. // 使用静态工厂方法创建一个 Msg 对象
2. // 假设默认绑定包的名字是 "messages"
3. System.out.println( Msg.createMsg( Messages.MSG_WELCOME,
4. name,
5. date ) );
和
java 代码
1. // 使用构造函数创建一个 Msg 对象
2. Msg msg = new Msg( new Msg.BundleBaseName("messages") );
3. try {
4. String hello = msg.getMsg(Messages.MSG_WELCOME, name, date );
5. ... do something ...
6. }
7. catch (Exception e) {
8. throw new RuntimeException( msg.getMsg( Messages.MSG_ERR ) );
9. }
name 和 date 参数是传递一个任意参数列表的示例--每个对象代表各个位置上要被替换掉的占位符({0} 和 {1,date} 分别基于前面给出的 Messages.MSG_WELCOME 中的定义)。
Msg 对象会根据“绑定包的名称”及其当前的语言环境设置获知使用哪种语言环境的资源绑定包。 “绑定包的名称”是绑定包文件的名称减去语言环境符号和扩展名(在上面的示例中,绑定包的名称是 messages)。 您可以通过传递到 Msg 的构造函数或者特定的静态工厂方法的方式,来定义 Msg 对象使用的“绑定包的名称”,如果不进行明确地指定,将使用默认的 messages。 “绑定包的名称”在 Msg 对象的生命周期内将一直存在,可以通过调用 Msg 对象的 setLocale() 来切换语言环境(默认语言环境是 JVM 使用的语言环境)。 如果您使用了 Msg 对象并且要求根据(被分配了 Msg 对象的)使用者的需要来切换语言环境,这将是非常有用的。
i18nlog 提供了两个基本的异常类(分别用于已查看和未查看的异常-- LocalizedException 和 LocalizedRuntimeException),可以这两个类创建自己的本地化异常子类。 这些类都具有构造函数,其函数签名与 Msg 类非常相似。 在使用构造函数时,通过调用资源绑定包的关键字和占位符的变量参数列表的方法来指定异常消息,同时还可以选择绑定包的名称和语言环境。 这样一来,异常消息就可以使用本地化的各种语言,其原理就是利用了 Msg 可以检索本地化的消息。
创建国际化消息的日志
i18nlog 提供了一种方法,可以通过该方法记录国际化的日志信息。 日志系统的主类是 mazz.i18n.Logger。 它提供了 trace、debug、info、warn、error 和 fatal 方法等一些典型的设置。 需要指出的是,与传递一个消息本身组成的字符串不同,您为占位符传递的是资源绑定包的关键字字符串和参数列表。 它使用具有保护作用的 mazz.i18n.Msg 类来获取实际的本地化消息。
通过使用工厂类 mazz.i18n.LoggerFactory 来获得 Logger 对象,与 log4j 等获得对象的方式基本相同。 而不同的是记录日志消息的方法,该方法与通过 mazz.i18n.Msg 对象获取消息非常类似:
java 代码
1. public static final mazz.i18n.Logger LOG =
2. mazz.i18n.LoggerFactory.getLogger(MyClass.class);
3. ...
4. LOG.debug(Messages.MSG_VALUE, value);
5. ...
6. try {
7. ...
8. }
9. catch (Exception e) {
10. LOG.warn(e, Messages.MSG_ERR);
11. }
如果没有启用日志级别,则不会查找绑定包,也不会执行任何字符串连接。这一条件有效地加快了日志调用的速度。 如果日志消息与特定的表达式相关联,则需要将表达式的第一个参数传递给日志方法。 如果启用了栈倾卸设置,这将允许栈跟踪倾卸消息(请往下看)。
i18nlog 的日志框架中还添加了许多其他的功能,这些功能并非潜在的、第三方的日志框架所能媲美。 首先要介绍的功能就是,可以告诉 Logger 是否倾卸异常的栈跟踪。 您可能需要查看一个特定运行任务的所有异常的栈跟踪,也可能不想看。 请注意,对于在 FATAL 日志级别上的异常,不能禁用此功能,使用该方法记录的致命异常必需允许倾卸栈跟踪。 对于其他的日志级别,日志程序只有在系统属性 i18nlog.dump-stack-traces 设置为 true (或者在代码中调用 Logger.setDumpStackTraces(true))的时候才会倾泄栈跟踪。
i18nlog 日志框架附加的第二项功能是,在记录与一个消息关联的资源绑定包关键字的同时,记录消息本身。 资源绑定包关键字对于所有语言环境都是相同的,也就是说,无论消息是用何种语言写成的,关键字是不变的。 可以把这些关键字想象成“消息的 ID”或“错误的代码”。 这在生成涉及到这些代码的帮助文档时非常有用,用户将可以参考文档中的额外帮助文本,以得知到底要传递什么消息(关于如何生成这种文档,请查看下文)。 在默认情况下,此功能处于启用状态,要禁用此功能,只需将系统属性 i18nlog.dump-keys 设置为 false 或者在代码中调用 Logger.setDumpLogKeys(false)。
提供消息 ID、避免在已禁用日志级别上拼接字符串,这些 i18nlog 提供的日志机制也许已经足够了。 也许有人会争论说,将(除用户界面消息以外的)日志消息国际化是一种负担而且也没有必要。 我有时也感到很难说服这种观点。 如果项目没有这种需求,您当然不必使用国际化日志。 即使不使用 i18nlog 提供的日志功能,还可以使用它提供的其他功能嘛。
但是,我还是可以举出一些具体的例子来证明使用国际化消息的好处。 请注意,i18nlog 允许定义一个不同的语言环境供日志程序使用(日志语言环境),该语言环境区别于 Msg 实例使用的语言环境。 这就可以帮助使用我的开发团队的语言来记录日志消息,而我的用户界面使用用户可以阅读的语言(可能与开发团队使用的语言不同)。 例如,我的用户说德语,而软件是由说法语的法国团队开发的。 在这种情况下,德国用户碰到一个问题时,就可以正常地给法国开发团队发送日志,软件可以默认地将语言环境设置为 Locale.FRENCH。 另一方面,如果德国用户希望自行调试问题,法语的日志消息根本不会有任何帮助。 在这种情况下,德国用户简单地通过设置系统属性,将日志消息记录为德语。 关于如何切换日志语言环境的信息,请参考 mazz.i18n.LoggerLocale Javadoc。
自动生成资源绑定包
i18nlog 提供了一个 Ant 任务,用于自动生成资源绑定包属性文件,以避免开发者担负手动向属性文件中添加消息、清理过时消息的任务。
该 Ant 任务将扫描类以查找 @I18N 标注,并根据这些标注为您创建资源绑定包。 这意味着,无论添加多少 @I18NMessage 标注字段,它们都将被加入到资源绑定包中。 如果您删除了一个国际化消息常量,则该消息也将从 Ant 任务生成的资源绑定包结果中删除。 要运行该 Ant 任务,需要在 Ant 脚本中添加以下类似代码:
xml 代码
1. <taskdef name="i18n"
2. classpathref="i18nlog-jar.classpath"
3. classname="mazz.i18n.ant.I18NAntTask" />
4.
5. <i18n outputdir="${classes.dir}" verify="true" verbose="true">
6. <classpath refid="my.classpath" />
7. <classfileset dir="${classes.dir}"/>
8. i18n>
必需让 Ant 任务知道含有国际化标注类及其依赖关系的类的类路径 ,还必须给出类文件集 ,其中包含文件集的文件列表供扫描国际化标注之用。 建议您在第一次运行 Ant 任务的时候采用“冗长”模式,以便知道 Ant 任务的执行内容。 一旦得到了想要的效果,再将“冗长”模式关闭。 执行此任务之后,资源绑定包属性文件将出现在指定的输出目录中。
生成帮助文档
使用此 Ant 任务的另一个可选功能是生成帮助文档,该文档是由所有对资源绑定包关键字名称及其消息值的引用组成的,另外还包含了对这些消息的描述。 这是一个可在 @I18NMessage 标注中指定的可选属性,help 属性。 属性值可以是深入描述消息的任何字符串。 可以将文档想象成在特定情况下将传递的具体消息。 自动生成的帮助文档可以提供消息关键字、消息本身以及消息描述之间的交叉引用:
java 代码
1. @I18NMessage( value="The value is {0}",
2. help="This will show you the value of your"
3. +" current counter. If this value is over"
4. +" 1000, you should reset it.")
5. String MSG_VALUE = "value";
6.
7. @I18NMessage( value="Memory has {0} free bytes left",
8. help="The VM is very low on memory. Increase -Xmx"
9. String MSG_LOW_MEM = "low-memory";
大多数情况下,您可以将其作为“消息的 ID”或者“错误的代码”列表使用,可以将其中的资源绑定包关键字当成一个“消息 ID”或者“错误的代码”。 要生成帮助文档,需要在 任务中使用 内部标记:
xml 代码
1. <i18n outputdir="${classes.dir}">
2. <classpath refid="my.classpath" />
3. <classfileset dir="${classes.dir}"/>
4. <helpdoc outputdir="${doc.dir}/help"/>
5. i18n>
对于每个生成的资源绑定包,都可以在 <helpdoc> 中指定的目录下找到一个相应的帮助文档。 文档是根据用于描述文档样式的模板生成的。 默认情况下,模板是一个简单的 HTML 页,使用 <table> 标记消息代码,而输出就是帮助文档。 在 <helpdoc> 标记中还可以指定一些其他的属性来自定义一个模板,以满足自行定制帮助文档外观的需求。 有关更多信息,请查阅 mazz.i18n.ant.Helpdoc 类的 Javadoc。
在帮助文档的生成结束之后,您就可以得到一个(或一些)包含消息关键字代码、消息内容及任何 "help" 属性定义内容的文档了。
本地化
我们已经讨论了许多关于如何通过获取翻译的消息和本地化的消息国际化软件的内容。 嵌入国际化功能之后,剩下的工作就是手动处理那些需要被本地化的资源绑定属性文件(由 <i18n> Ant 任务生成或者自行手动编写)了。 必须确保将所有资源绑定包的消息翻译成需要支持的语言,而且这些消息包含的数据也进行相应的本地化。 通过定义占位符属性(例如,{0,date} 将使用目标语言环境的格式和语言输出日期字符串)可以完成许多本地化方面的工作。 不言而喻的是,必须找一家优秀的翻译和本地化公司。
小结
这篇文章介绍了如何通过一个新的开源项目 i18nlog 在应用程序中溶入国际化功能。 使用该开源项目提供的工具和 API 可以自动管理资源绑定包属性文件(.properties),检索和管理这些绑定包中的本地化消息,甚至还可以生成供最终用户使用的帮助文档。