Java国际化及Spring国际化解决⽅法
假设我们正在开发⼀个⽀持多国语⾔的Web应⽤程序,要求系统能够根据客户端的系统的语⾔类型返回对应的界⾯:英⽂的操作系统返回英⽂界⾯,⽽中⽂的操作系统则返回中⽂界⾯——这便是典型的i18n国际化问题。
国际化(internationalization)⼜称为 i18n(读法为i 18 n,据说是因为internationalization(国际化)这个单词从i到n之间有18个英⽂字母,i18n的名字由此⽽来)。
对于有国际化要求的应⽤系统,我们不能简单地采⽤硬编码的⽅式编写⽤户界⾯信息、报错信息等内容,⽽必须为这些需要国际化的信息进⾏特殊处理。简单来说,就是为每种语⾔提供⼀套相应的资源⽂件,并以规范化命名的⽅式保存在特定的⽬录中,由系统⾃动根据客户端语⾔选择适合的资源⽂件。
基础知识
“国际化信息”也称为“本地化信息”,⼀般需要两个条件才可以确定⼀个特定类型的本地化信息,它们分别是“语⾔类型”和“国家/地区的类型”。如中⽂本地化信息既有中国⼤陆地区的中⽂,⼜有、中国⾹港地区的中⽂,还有新加坡地区的中⽂。Java通过java.util.Locale类表⽰⼀个本地化对象,它允许通过语⾔参数和国家/地区参数创建⼀个确定的本地化对象。
语⾔参数使⽤ISO标准语⾔代码表⽰,这些代码是由ISO-639标准定义的,每⼀种语⾔由两个⼩写字母表⽰。在许多⽹站上都可以到这些代码的完整列表,下⾯的⽹址是提供了标准语⾔代码的信息:。
国家/地区参数也由标准的ISO国家/地区代码表⽰,这些代码是由ISO-3166标准定义的,每个国家/地区由两个⼤写字母表⽰。⽤户可以从以下⽹址查看ISO-3166的标准代码:。
表5-2给出了⼀些语⾔和国家/地区的标准代码:
Locale
java.util.Locale是表⽰语⾔和国家/地区信息的本地化类,它是创建国际化应⽤的基础。下⾯给出⼏个创建本地化对象的⽰例:
/
/①带有语⾔和国家/地区信息的本地化对象
Locale locale1 = new Locale("zh","CN");
//②只有语⾔信息的本地化对象
Locale locale2 = new Locale("zh");
//③等同于Locale("zh","CN")
Locale locale3 = Locale.CHINA;
//④等同于Locale("zh")
Locale locale4 = Locale.CHINESE;
//⑤获取本地系统默认的本地化对象
Locale locale5= Default();
⽤户既可以同时指定语⾔和国家/地区参数定义⼀个本地化对象①,也可以仅通过语⾔参数定义⼀个泛
本地化对象②。Locale类中通过静态常量定义了⼀些常⽤的本地化对象,③和④处就直接通过引⽤常量返回本地化对象。此外,⽤户还可以获取系统默认的本地化对象,如⑤所⽰。
在测试时,如果希望改变系统默认的本地化设置,可以在启动JVM时通过命令参数指定:java -Duser.language=en -
本地化⼯具类
JDK的java.util包中提供了⼏个⽀持本地化的格式化操作⼯具类:NumberFormat、DateFormat、MessageFormat。下⾯,我们分别通过实例了解它们的⽤法:
NumberFormat:
Locale locale = new Locale("zh", "CN");
NumberFormat currFmt = CurrencyInstance(locale);
double amt = 123456.78;
System.out.println(currFmt.format(amt));
上⾯的实例通过NumberFormat按本地化的⽅式对货币⾦额进⾏格式化操作,运⾏实例,输出以下信息:
¥123,456.78
DateFormat:
Locale locale = new Locale("en", "US");
Date date = new Date();
DateFormat df = DateInstance(DateFormat.MEDIUM, locale);
System.out.println(df.format(date));
通过DateFormat#getDateInstance(int style,Locale locale)⽅法按本地化的⽅式对⽇期进⾏格式化操作。该⽅法第⼀个⼊参为时间样式,第⼆个⼊参为本地化对象。运⾏以上代码,输出以下信息:
Jan 8, 2007
MessageFormat在NumberFormat和DateFormat的基础上提供了强⼤的占位符字符串的格式化功能,它⽀持时间、货币、数字以及对象属性的格式化操作。下⾯的实例演⽰了⼀些常见的格式化功能:
MessageFormat:
//①信息格式化串
String pattern1 = "{0},你好!你于 {1} 在⼯商银⾏存⼊ {2} 元。";
String pattern2 = "At {1,time,short} On {1,date,long},{0} paid {2,number, currency}.";
//②⽤于动态替换占位符的参数
Object[] params = {"John", new GregorianCalendar().getTime(), 1.0E3};
//③使⽤默认本地化对象格式化信息
String msg1 = MessageFormat.format(pattern1, params);
//④使⽤指定的本地化对象格式化信息
MessageFormat mf = new MessageFormat(pattern2, Locale.US);
String msg2 = mf.format(params);
System.out.println(msg1);
System.out.println(msg2);
pattern1是简单形式的格式化信息串,通过{n}占位符指定动态参数的替换位置索引,{0}表⽰第⼀个参数,{1}表⽰第⼆个参数,以此类推。
pattern2格式化信息串⽐较复杂⼀些,除参数位置索引外,还指定了参数的类型和样式。从pattern2中可以看出格式化信息串的语法是很灵活的,⼀个参数甚⾄可以出现在两个地⽅:如 {1,time,short}表⽰从第⼆个⼊参中获取时间部分的值,显⽰为短样式时间;⽽
{1,date,long}表⽰从第⼆个⼊参中获取⽇期部分的值,显⽰为长样式时间。关于MessageFormat更详细的使⽤⽅法,请参见JDK的Javadoc。
在②处,定义了⽤于替换格式化占位符的动态参数,这⾥,我们使⽤到了JDK5.0⾃动装包的语法,否则必须采⽤封装类表⽰基本类型的参数值。
在③处,通过MessageFormat的format()⽅法格式化信息串。它使⽤了系统默认的本地化对象,由于
我们是中⽂平台,因此默认为Locale.CHINA。⽽在④处,我们显式指定MessageFormat的本地化对象。
运⾏上⾯的代码,输出以下信息:
John,你好!你于 14-7-7 下午11:29 在⼯商银⾏存⼊ 1,000 元。
At 11:29 PM On July 7, 2014,John paid $1,000.00.
如果应⽤系统中某些信息需要⽀持国际化功能,则必须为希望⽀持的不同本地化类型分别提供对应的资源⽂件,并以规范的⽅式进⾏命名。国际化资源⽂件的命名规范规定资源名称采⽤以下的⽅式进⾏命名:
<;资源名><;语⾔代码><;国家/地区代码>.properties
其中,语⾔代码和国家/地区代码都是可选的。<;资源名>.properties命名的国际化资源⽂件是默认的资源⽂件,即某个本地化类型在系统中不到对应的资源⽂件,就采⽤这个默认的资源⽂件。
<;资源名>_<;语⾔代码>.properties命名的国际化资源⽂件是某⼀语⾔默认的资源⽂件,即某个本地化类型在系统中不到精确匹配的资源⽂件,将采⽤相应语⾔默认的资源⽂件。
举⼀个例⼦:假设资源名为resource,则语⾔为英⽂,国家为美国,则与其对应的本地化资源⽂件命名为resource_en_US.properties。信息在资源⽂件以属性名/值的⽅式表⽰:
greetingmon=How are you!
greeting.afternoon = Good Afternoon!
对应语⾔为中⽂,国家/地区为中国⼤陆的本地化资源⽂件则命名为resource_zh_ CN.properties,资源⽂件内容如下:
greetingmon=\u60a8\u597d\uff01
greeting.afternoon=\u4e0b\u5348\u597d\uff01
本地化不同的同⼀资源⽂件,虽然属性值各不相同,但属性名却是相同的,这样应⽤程序就可以通过Locale对象和属性名精确调⽤到某个具体的属性值了。
读者可能已经注意到,上⾯中⽂的本地化资源⽂件内容采⽤了特殊的编码表⽰中⽂字符,这是因为资源⽂件对⽂件内容有严格的要求:只能包含ASCII字符。所以必须将⾮ASCII字符的内容转换为Unicode代码的表⽰⽅式。如上⾯中⽂的resource_zh_CN.properties资源⽂件的三个属性值分别是“您好!”、“早上好!”和“下午好!”三个中⽂字符串对应的Unicode代码串。
如果在应⽤开发时,直接采⽤Unicode代码编辑资源⽂件是很不⽅便的,所以,通常我们直接使⽤正常的⽅式编写资源⽂件,在测试或部署时再采⽤⼯具进⾏转换。JDK在bin⽬录下为我们提供了⼀个完成此项功能的native2ascii⼯具,它可以将中⽂字符的资源⽂件转换为Unicode代码格式的⽂件,命令格式如下:
native2ascii [-reverse] [-encoding 编码] [输⼊⽂件 [输出⽂件]]
resource_zh_CN.properties包含中⽂字符并且以UTF-8进⾏编码,假设将该资源⽂件放到d:\⽬录下,通过下⾯的命令就可以将其转换为Unicode代码的形式:
D:\>native2ascii -encoding utf-8 d:\resource_zh_CN.properties
d:\resource_zh_CN_1.properties
由于原资源⽂件采⽤UTF-8编码,所以必须显式通过-encoding指定编码格式。
通过native2ascii命令⼿⼯转换资源⽂件,不但在操作上不⽅便,转换后资源⽂件中的属性内容由于采⽤了ASCII编码,阅读起来也不⽅便。很多IDE开发⼯具都有属性编辑器的插件,插件会⾃动将资源⽂件内容转换为ASCII形式的编码,同时以正常的⽅式阅读和编辑资源⽂件的内容,这给开发和维护带来了很⼤的便利。
对于MyEclipse来说,使⽤MyEclipse Properties Editor编辑资源属性⽂件;
对于Intellij IDEA来说,⽆须安装任何插件就⾃然⽀持资源属性⽂件的这种编辑⽅式了。
如果应⽤程序中拥有⼤量的本地化资源⽂件,直接通过传统的File操作资源⽂件显然太过笨拙。Java为我们提供了⽤于加载本地化资源⽂件的⽅便类java.util.ResourceBoundle。
ResourceBoundle为加载及访问资源⽂件提供便捷的操作,下⾯的语句从相对于类路径的⽬录中加载⼀个名为resource的本地化资源⽂件:
ResourceBundle rb = Bundle("com/baobaotao/i18n/resource", locale)
通过以下的代码即可访问资源⽂件的属性值:
来看下⾯的实例:
ResourceBundle rb1 = Bundle("com/baobaotao/i18n/resource", Locale.US);
ResourceBundle rb2 = Bundle("com/baobaotao/i18n/resource", Locale.CHINA);
System.out.println("us:"+String("greetingmon"));
System.out.println("cn:"+String("greetingmon"));
rb1加载了对应美国英语本地化的resource_en_US.properties资源⽂件;⽽rb2加载了对应中国⼤陆中⽂的resource_zh_CN.properties 资源⽂件。运⾏上⾯的代码,将输出以下信息:
us:How are you!
cn:你好!
加载资源⽂件时,如果不指定本地化对象,将使⽤本地系统默认的本地化对象。所以,在中⽂系统
中,Bundle(“com/baobaotao/i18n/resource”)语句也将返回和代码清单5-14中rb2相同的本地化资源。
ResourceBundle在加载资源时,如果指定的本地化资源⽂件不存在,它按以下顺序尝试加载其他的资源:本地系统默认本地化对象对应的资源→默认的资源。
上⾯的例⼦中,假设我们使⽤Bundle(“com/baobaotao/i18n/resource”,Locale.CANADA)加载资源,由于不存在resource_en_CA.properties资源⽂件,它将尝试加载resource_zh_CN.properties的资源⽂件,假设resource_zh_CN.properties 资源⽂件也不存在,它将继续尝试加载resource.properties的资源⽂件,如果这些资源都不存在,将抛出
springmvc常用标签java.util.MissingResourceException异常。
在资源⽂件中使⽤格式化串
在上⾯的资源⽂件中,属性值都是⼀般的字符串,它们不能结合运⾏时的动态参数构造出灵活的信息,⽽这种需求是很常见的。要解决这个问题很简单,只须使⽤带占位符的格式化串作为资源⽂件的属性值并结合使⽤MessageFormat就可以满⾜要求了。
上⾯的例⼦中,我们仅向⽤户提供⼀般性问候,下⾯我们对资源⽂件进⾏改造,通过格式化串让问候语更具个性化:
greetingmon=How are you!{0},today is {1}
greeting.afternoon = Good Afternoon!{0} now is {1 date long}
将该资源⽂件保存在fmt_resource_en_US.properties中,按照同样的⽅式编写对应的中⽂本地化资源⽂件
fmt_resource_zh_CN.properties。
下⾯,我们联合使⽤ResourceBoundle和MessageFormat得到美国英⽂的本地化问候语:
//①加载本地化资源
ResourceBundle rb1 =
ResourceBundle rb2 =
Object[] params = {"John", new GregorianCalendar().getTime()};
String str1 = new String("greetingmon"),Locale.US).format(params);
String str2 =new String("ing"),Locale.CHINA).format(params);
String str3 =new String("greeting.afternoon"),Locale.CHINA).format(params);
System.out.println(str1);
System.out.println(str2);
System.out.println(str3);
运⾏以上的代码,将输出以下信息:
How are you!John,today is 1/9/07 4:11 PM
早上好!John,现在是下午4:11
下午好!John,现在是2007年1⽉9⽇
MessageSource
Spring定义了访问国际化信息的MessageSource接⼝,并提供了⼏个易⽤的实现类。⾸先来了解⼀下该接⼝的⼏个重要⽅法:
String getMessage(String code, Object[] args, String defaultMessage, Locale locale) code表⽰国际化资源中的属性名;args⽤于传递格式化串占位符所⽤的运⾏期参数;当在资源不到对应属性名时,返回defaultMessage参数所指定的默认信息;locale表⽰本地化对象;
String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException
与上⾯的⽅法类似,只不过在不到资源中对应的属性名时,直接抛出NoSuchMessageException异常;
String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException
MessageSourceResolvable 将属性名、参数数组以及默认信息封装起来,它的功能和第⼀个接⼝⽅法相同。
MessageSource的类结构
MessageSource分别被HierarchicalMessageSource和ApplicationContext接⼝扩展,这⾥我们主要看⼀下HierarchicalMessageSource接⼝的⼏个实现类,如图5-7所⽰:
HierarchicalMessageSource接⼝添加了两个⽅法,建⽴⽗⼦层级的MessageSource结构,类似于前
⾯我们所介绍的HierarchicalBeanFactory。该接⼝的setParentMessageSource (MessageSource parent)⽅法⽤于设置⽗MessageSource,⽽getParentMessageSource()⽅法⽤于返回⽗MessageSource。
HierarchicalMessageSource接⼝最重要的两个实现类是ResourceBundleMessageSource和ReloadableResourceBundleMessageSource。它们基于Java的ResourceBundle基础类实现,允许仅通过资源名加载国际化资源。ReloadableResourceBundleMessageSource提供了定时刷新功能,允许在不重启系统的情况下,更新资源的信息。StaticMessageSource主要⽤于程序测试,它允许通过编程的⽅式提供国际化信息。⽽DelegatingMessageSource是为⽅便操作⽗MessageSource⽽提供的代理类。
ResourceBundleMessageSource
该实现类允许⽤户通过beanName指定⼀个资源名(包括类路径的全限定资源名),或通过beanNames指定⼀组资源名。在前⾯的代码清单中,我们通过JDK的基础类完成了本地化的操作,下⾯我们使⽤ResourceBundleMessageSource来完成相同的任务。读者可以⽐较两者的使⽤差别,并体会Spring所提供的国际化处理功能所带给我们的好处:
通过ResourceBundleMessageSource配置资源
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论