服务器之家

服务器之家 > 正文

Java国际化简介_动力节点Java学院整理

时间:2020-12-04 09:00     来源/作者:stamen

        假设我们正在开发一个支持多国语言的web应用程序,要求系统能够根据客户端的系统的语言类型返回对应的界面:英文的操作系统返回英文界面,而中文的操作系统则返回中文界面——这便是典型的i18n国际化问题。对于有国际化要求的应用系统,我们不能简单地采用硬编码的方式编写用户界面信息、报错信息等内容,而必须为这些需要国际化的信息进行特殊处理。简单来说,就是为每种语言提供一套相应的资源文件,并以规范化命名的方式保存在特定的目录中,由系统自动根据客户端语言选择适合的资源文件。

基础知识

  “国际化信息”也称为“本地化信息”,一般需要两个条件才可以确定一个特定类型的本地化信息,它们分别是“语言类型”和“国家/地区的类型”。如中文本地化信息既有中国大陆地区的中文,又有中国台湾、中国香港地区的中文,还有新加坡地区的中文。java通过java.util.locale类表示一个本地化对象,它允许通过语言参数和国家/地区参数创建一个确定的本地化对象。
  语言参数使用iso标准语言代码表示,这些代码是由iso-639标准定义的,每一种语言由两个小写字母表示。
  国家/地区参数也由标准的iso国家/地区代码表示,这些代码是由iso-3166标准定义的,每个国家/地区由两个大写字母表示。     

表5-2给出了一些语言和国家/地区的标准代码:

Java国际化简介_动力节点Java学院整理

  java.util.locale是表示语言和国家/地区信息的本地化类,它是创建国际化应用的基础。下面给出几个创建本地化对象的示例:

?
1
2
3
4
5
6
7
8
9
10
//①带有语言和国家/地区信息的本地化对象
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 locale 5= locale.getdefault();

  用户既可以同时指定语言和国家/地区参数定义一个本地化对象①,也可以仅通过语言参数定义一个泛本地化对象②。locale类中通过静态常量定义了一些常用的本地化对象,③和④处就直接通过引用常量返回本地化对象。此外,用户还可以获取系统默认的本地化对象,如⑤所示。
  在测试时,如果希望改变系统默认的本地化设置,可以在启动jvm时通过命令参数指定:java -duser.language=en -duser.region=us mytest。

本地化工具类

  jdk的java.util包中提供了几个支持本地化的格式化操作工具类:numberformat、dateformat、messageformat。下面,我们分别通过实例了解它们的用法:
numberformat:

?
1
2
3
4
locale locale = new locale("zh", "cn");
numberformat currfmt = numberformat.getcurrencyinstance(locale);
double amt = 123456.78;
system.out.println(currfmt.format(amt));

  上面的实例通过numberformat按本地化的方式对货币金额进行格式化操作,运行实例,输出以下信息:

¥123,456.78 

代码清单5-14  dateformat 

?
1
2
3
4
locale locale = new locale("en", "us");
date date = new date();
dateformat df = dateformat.getdateinstance(dateformat.medium, locale);
system.out.println(df.format(date));

  通过dateformat#getdateinstance(int style,locale locale)方法按本地化的方式对日期进行格式化操作。该方法第一个入参为时间样式,第二个入参为本地化对象。运行以上代码,输出以下信息:

jan 8, 2007 

  messageformat在numberformat和dateformat的基础上提供了强大的占位符字符串的格式化功能,它支持时间、货币、数字以及对象属性的格式化操作。下面的实例演示了一些常见的格式化功能:

代码清单5-15  messageformat 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//①信息格式化串
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。信息在资源文件以属性名/值的方式表示:

引用

greeting.common=how are you! 
greeting.morning = good morning! 
greeting.afternoon = good afternoon! 

对应语言为中文,国家/地区为中国大陆的本地化资源文件则命名为resource_zh_ cn.properties,资源文件内容如下:

greeting.common=\u60a8\u597d\uff01 
greeting.morning=\u65e9\u4e0a\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 = resourcebundle.getbundle("com/baobaotao/i18n/resource", locale) 

  通过以下的代码即可访问资源文件的属性值:
rb.getstring("greeting.common")  

来看下面的实例:
代码清单5-16  resourceboundle 

?
1
2
3
4
resourcebundle rb1 = resourcebundle.getbundle("com/baobaotao/i18n/resource", locale.us);
resourcebundle rb2 = resourcebundle.getbundle("com/baobaotao/i18n/resource", locale.china);
system.out.println("us:"+rb1.getstring("greeting.common"));
system.out.println("cn:"+rb2.getstring("greeting.common"));

  rb1加载了对应美国英语本地化的resource_en_us.properties资源文件;而rb2加载了对应中国大陆中文的resource_zh_cn.properties资源文件。运行上面的代码,将输出以下信息:

引用

us:how are you! 
cn:你好! 

  加载资源文件时,如果不指定本地化对象,将使用本地系统默认的本地化对象。所以,在中文系统中,resourcebundle.getbundle("com/baobaotao/i18n/resource")语句也将返回和代码清单5-14中rb2相同的本地化资源。
  resourcebundle在加载资源时,如果指定的本地化资源文件不存在,它按以下顺序尝试加载其他的资源:本地系统默认本地化对象对应的资源→默认的资源。上面的例子中,假设我们使用resourcebundle.getbundle("com/baobaotao/i18n/resource",locale.canada)加载资源,由于不存在resource_en_ca.properties资源文件,它将尝试加载resource_zh_cn.properties的资源文件,假设resource_zh_cn.properties资源文件也不存在,它将继续尝试加载resource.properties的资源文件,如果这些资源都不存在,将抛出java.util.missingresourceexception异常。

在资源文件中使用格式化串

  在上面的资源文件中,属性值都是一般的字符串,它们不能结合运行时的动态参数构造出灵活的信息,而这种需求是很常见的。要解决这个问题很简单,只须使用带占位符的格式化串作为资源文件的属性值并结合使用messageformat就可以满足要求了。

  上面的例子中,我们仅向用户提供一般性问候,下面我们对资源文件进行改造,通过格式化串让问候语更具个性化:

引用

greeting.common=how are you!{0},today is {1} 
greeting.morning = good morning!{0},now is {1 time short} 
greeting.afternoon = good afternoon!{0} now is {1 date long} 

  将该资源文件保存在fmt_resource_en_us.properties中,按照同样的方式编写对应的中文本地化资源文件fmt_resource_zh_cn.properties。

  下面,我们联合使用resourceboundle和messageformat得到美国英文的本地化问候语:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//①加载本地化资源
resourcebundle rb1 = 
    resourcebundle.getbundle("com/baobaotao/i18n/fmt_ resource",locale.us); 
resourcebundle rb2 = 
    resourcebundle.getbundle("com/baobaotao/i18n/fmt_ resource",locale.china);
object[] params = {"john", new gregoriancalendar().gettime()};
 
 
string str1 = new messageformat(rb1.getstring("greeting.common"),locale.us).format(params);
string str2 =new messageformat(rb2.getstring("greeting.morning"),locale.china).format(params);
string str3 =new messageformat(rb2.getstring("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所示:

Java国际化简介_动力节点Java学院整理

  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配置资源

?
1
2
3
4
5
6
7
8
9
<bean id="myresource"
class="org.springframework.context.support.resourcebundlemessagesource">
 <!--①通过基名指定资源,相对于类根路径-->
 <property name="basenames">
  <list>
   <value>com/baobaotao/i18n/fmt_resource</value>
  </list>
 </property>
 </bean>

启动spring容器,并通过messagesource访问配置的国际化资源,如下代码清单所示:

代码清单5-19 访问国际化消息:resourcebundlemessagesource:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
string[] configs = {"com/baobaotao/i18n/beans.xml"};
applicationcontext ctx = new classpathxmlapplicationcontext(configs);
 
//①获取messagesource的bean
messagesource ms = (messagesource)ctx.getbean("myresource"); 
object[] params = {"john", new gregoriancalendar().gettime()};
 
//②获取格式化的国际化信息
string str1 = ms.getmessage("greeting.common",params,locale.us);
string str2 = ms.getmessage("greeting.morning",params,locale.china);
string str3 = ms.getmessage("greeting.afternoon",params,locale.china);
system.out.println(str1);
system.out.println(str2);
system.out.println(str3);

  比较代码清单中的代码,我们发现最主要的区别在于我们无须再分别加载不同语言、不同国家/地区的本地化资源文件,仅仅通过资源名就可以加载整套的国际化资源文件。此外,我们无须显式使用messageformat操作国际化信息,仅通过messagesource# getmessage()方法就可以完成操作了。这段代码的运行结果与前面的代码的运行结果完全一样。

reloadableresourcebundlemessagesource

  前面,我们提到该实现类比之于resourcebundlemessagesource的唯一区别在于它可以定时刷新资源文件,以便在应用程序不重启的情况下感知资源文件的变化。很多生产系统都需要长时间持续运行,系统重启会给运行带来很大的负面影响。这时,通过该实现类就可以解决国际化信息更新的问题。请看下面的配置:

  通过reloadableresourcebundlemessagesource配置资源:

xml代码

?
1
2
3
4
5
6
7
8
9
10
<bean id="myresource"
lass="org.springframework.context.support.reloadableresourcebundlemessagesource">
 <property name="basenames">
  <list>
  <value>com/baobaotao/i18n/fmt_resource</value>
  </list>
 </property>
 <!--① 刷新资源文件的周期,以秒为单位-->
 <property name="cacheseconds" value="5"/> 
 </bean>

  在上面的配置中,我们通过cacheseconds属性让reloadableresourcebundlemessagesource每5秒钟刷新一次资源文件(在真实的应用中,刷新周期不能太短,否则频繁的刷新将带来性能上的负面影响,一般不建议小于30分钟)。cacheseconds默认值为-1表示永不刷新,此时,该实现类的功能就蜕化为resourcebundlemessagesource的功能。
  我们编写一个测试类对上面配置的reloadableresourcebundlemessagesource进行测试:

代码清单5-21  刷新资源:reloadableresourcebundlemessagesource 

?
1
2
3
4
5
6
7
8
9
10
11
string[] configs = {"com/baobaotao/i18n/beans.xml"};
applicationcontext ctx = new classpathxmlapplicationcontext(configs);
 
messagesource ms = (messagesource)ctx.getbean("myresource");
object[] params = {"john", new gregoriancalendar().gettime()};
 
for (int i = 0; i < 2; i++) {
 string str1 = ms.getmessage("greeting.common",params,locale.us); 
 system.out.println(str1);
 thread.currentthread().sleep(20000); //①模拟程序应用,在此期间,我们更改资源文件 
}

  在①处,我们让程序睡眠20秒钟,在这期间,我们将fmt_resource_zh_cn.properties资源文件的greeting.common键值调整为:

引用

---how are you!{0},today is {1}--- 
  我们将看到两次输出的格式化信息分别对应更改前后的内容,也即本地化资源文件的调整被自动生效了:

引用

how are you!john,today is 1/9/07 4:55 pm 
---how are you!john,today is 1/9/07 4:55 pm--- 

容器级的国际化信息资源

  在如图5-7所示的messagesource类图结构中,我们发现applicationcontext实现了messagesource的接口。也就是说applicationcontext的实现类本身也是一个messagesource对象。
  将applicationcontext和messagesource整合起来,乍一看挺让人费解的,spring这样设计的意图究竟是什么呢?原来spring认为:在一般情况下,国际化信息资源应该是容器级。我们一般不会将messagesource作为一个bean注入到其他的bean中,相反messagesource作为容器的基础设施向容器中所有的bean开放。只要我们考察一下国际化信息的实际消费场所就更能理解spring这一设计的用意了。国际化信息一般在系统输出信息时使用,如spring mvc的页面标签,控制器controller等,不同的模块都可能通过这些组件访问国际化信息,因此spring就将国际化消息作为容器的公共基础设施对所有组件开放。
  既然一般情况下我们不会直接通过引用messagesource bean使用国际信息,那如何声明容器级的国际化信息呢?我们其实在5.1.1节讲解spring容器的内部工作机制时已经埋下了伏笔:在介绍容器启动过程时,我们通过代码清单5-1对spring容器启动时的步骤进行剖析,④处的initmessagesource()方法所执行的工作就是初始化容器中的国际化信息资源:它根据反射机制从beandefinitionregistry中找出名称为“messagesource”且类型为org.springframework.context.messagesource的bean,将这个bean定义的信息资源加载为容器级的国际化信息资源。请看下面的配置:

代码清单5-22  容器级资源的配置 

?
1
2
3
4
5
6
7
8
9
<!--①注册资源bean,其bean名称只能为messagesource -->
<bean id="messagesource"
  class="org.springframework.context.support.resourcebundlemessagesource">
 <property name="basenames">
  <list>
  <value>com/baobaotao/i18n/fmt_resource</value>
  </list>
 </property>
</bean>

  下面,我们通过applicationcontext直接访问国际化信息,如下代码清单所示:

代码清单5-23  通过applicationcontext访问国际化信息 
java代码 

?
1
2
3
4
5
6
7
8
9
string[] configs = {"com/baobaotao/i18n/beans.xml"};
applicationcontext ctx = new classpathxmlapplicationcontext(configs);
//①直接通过容器访问国际化信息
object[] params = {"john", new gregoriancalendar().gettime()};
  
string str1 = ctx.getmessage("greeting.common",params,locale.us);
string str2 = ctx.getmessage("greeting.morning",params,locale.china); 
system.out.println(str1);
system.out.println(str2);

  运行以上代码,输出以下信息:

引用

how are you!john,today is 1/9/07 5:24 pm 
早上好!john,现在是下午5:24 

  假设messagesource bean名字没有命名为“messagesource”,以上代码将抛出nosuchmessageexception异常。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

标签:

相关文章

热门资讯

2020微信伤感网名听哭了 让对方看到心疼的伤感网名大全
2020微信伤感网名听哭了 让对方看到心疼的伤感网名大全 2019-12-26
Intellij idea2020永久破解,亲测可用!!!
Intellij idea2020永久破解,亲测可用!!! 2020-07-29
歪歪漫画vip账号共享2020_yy漫画免费账号密码共享
歪歪漫画vip账号共享2020_yy漫画免费账号密码共享 2020-04-07
电视剧《琉璃》全集在线观看 琉璃美人煞1-59集免费观看地址
电视剧《琉璃》全集在线观看 琉璃美人煞1-59集免费观看地址 2020-08-12
最新idea2020注册码永久激活(激活到2100年)
最新idea2020注册码永久激活(激活到2100年) 2020-07-29
返回顶部