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