4.5 国际化与本地化基本实践

为Java程序添加国际化的支持,只需要遵循一定的模式即可,更多的是执行按部就班的过程。下面对这个过程进行具体介绍,并说明其中一些需要注意的地方。

第一步是提取出程序中需要本地化的内容。这些内容包括前面介绍的用户界面上的消息文本、日期和时间、数字和货币,以及字符串比较操作等。这一步可以在程序开发的早期开始进行,即在代码编写过程中就进行提取;也可以在程序开发的后期进行。比较推荐的做法是在程序开发的后期进行。这主要是因为在程序开发过程中,本地化的内容可能还在不断变化,过早进行相关内容的提取,可能造成工作量的浪费。

由于消息文本是最常见的需要本地化的内容,很多集成开发环境(IDE),如Eclipse和NetBeans,都提供了相应的功能来实现快速提取。利用IDE的支持,可以提高提取工作的效率。在源代码中,某些字符串字面量并不需要进行本地化。应该把这些字符串字面量重构为Java类中的字符串常量,以区别于其他需要本地化的字符串字面量。对于除消息文本之外的其他需要本地化的内容,需要开发人员手动识别。

第二步是添加国际化的能力。这一步需要对程序的代码进行修改。对于消息文本,把直接使用字符串字面量的方式替换成从资源包中根据指定的键来读取对应的值。从源代码中提取出来的文本内容被保存到一个属性文件中。一般来说,一个程序使用一个属性文件即可。对于复杂的程序,其中的每个组件可以有属于自己的独立的属性文件。开发人员需要负责为每条文本指定一个有意义的键的名称。在源代码中有可能使用了字符串拼接的方式来生成一段长文本,对于这种情况,需要把多个字符串字面量提取成资源包中的单条记录。代码清单4-24给出了一个未经本地化处理的Java类。

代码清单4-24 未经本地化处理的Java类


public class NormalGreetings{

public String greet(String name){

return"Hello,"+name+".Today is"+new Date().toString()+".";

}

}


由于代码清单4-24中实际上只包含一条消息,在进行提取时,需要对多个字符串字面量进行合并。在属性文件中只包含一条记录,即“GREETINGS=Hello,{0}.Today is{1}.”。下一步需要创建相关的辅助Java类来负责从资源包中读取消息文本。代码清单4-25中给出了辅助Java类的示例。在Messages类的静态代码块中,调用ResourceBundle类的getBundle方法来加载指定基本名称的资源包。如果找不到,则用一个空的ListResourceBundle类的对象来代替。Messages类的get方法用来根据消息文本的键和进行格式化的实际参数值来得到最终显示的文本。如果在资源包中找不到给定的键,则返回一个特殊形式的文本内容来进行声明。

代码清单4-25 从资源包中读取消息文本的辅助Java类


public class Messages{

private static ResourceBundle bundle;

static{

try{

bundle=ResourceBundle.getBundle("com.java7book.chapter4.demo.Messages",LocaleHolder.get());

}catch(MissingResourceException e){

e.printStackTrace();

bundle=new ListResourceBundle(){

protected Object[][]getContents(){

return new Object[0][0];

}

};

}

}

public static String get(String key, Object……args){

try{

String value=bundle.getString(key);

return MessageFormat.format(value, args);

}

catch(MissingResourceException e){

return"!"+key;

}

}

}


在调用ResourceBundle类的getBundle方法时,总是应该显式地指定一个Locale类的对象,而不是依靠Java平台的默认区域设置。这是因为默认区域设置可能随着底层操作系统的变化而发生改变,可能会对程序的行为产生影响。一个比较好的做法是把应该使用的Locale类的对象保存在ThreadLocal类的对象中,与当前的运行线程绑定在一起。这种方式尤其适合于Web应用。

对于除消息文本之外的其他需要本地化的内容,在代码中使用对应的Format类的子类对象来进行格式化。具体的使用方式参考前面对DateFormat和NumberFormat类的介绍。

在选择程序使用的区域设置时,通常依次考虑三种情况。第一种情况是用户显式指定的区域设置。程序应该提供相关的功能供用户手动设置使用的区域设置。如果用户进行了选择,那么应该使用用户所选择的区域设置。第二种情况是自动检测区域设置。这情况通常发生在Web应用中。在HTTP请求中,可以通过“Accept-Language”头来指定所要求使用的区域设置。Web服务器需要处理“Accept-Language”头,并返回正确的响应。第三种情况是使用虚拟机的默认设置。使用Locale类的getDefault方法可以获取默认设置。

第三步是进行内容的本地化。这主要是针对消息文本的。这一步通常需要由专门的翻译人员对包含消息文本的属性文件的内容进行翻译。翻译之后的属性文件的名称需要进行修改,以表明所对应的区域设置,如“Messages_en_US.properties”文件名表示的是针对区域设置“en_US”的属性文件。

最后一步是进行相关的测试。首先把程序的区域设置修改成待测试的值,再查看用户界面上的内容。检查用户界面上的消息文本、日期和时间,以及数字和货币的显示方式是否正确。对于Web应用来说,通过修改浏览器的设置可以改变所发送的“Accept-Language”HTTP头的值。通过这种方式来测试Web页面的展示结果是否正确。如果程序在选择区域设置时使用了虚拟机的默认区域设置,可以通过虚拟机的启动参数“user.language”、“user.country”、“user.script”和“user.variant”来修改默认的区域设置,比如,使用“-Duser.country=US-Duser.language=en”可以把默认的区域设置修改为“en_US”。