Spring-国际化信息02-MessageSource接口
假设我们开发一个支持多国语言的Web应用系统,要求能够根据客户端系统的语言类型返回对应的界面。
这是典型的i18n国际化问题。 简单的来讲就是为每种语言提供一套相应的资源文件,并以规范化命名的方式保存在特定的目录中,由系统自动根据客户端语言选择合适的资源文件。
国际化信息也称为本地化信息。
一般需要两个条件才可以确定一个特定类型的本地化信息
比如中文本地化信息既有中国大陆地区的中文,又有中国台湾、中国香港地区的中文,还有新加坡地区的中文。
Java通过java.util.Locale类表示一个本地化对象,它允许通过语言参数和国家/地区参数创建一个确定的本地化对象。
语言参数使用ISO标准语言代码标识,由ISO-639标准定义,每种语言由两位小写字母表示。标准语言代码信息可参考: http://www.loc.gov/standards/iso639-2/php/English_list.php
国家/地区参数也由标准的ISO国家/地区代码标识,由ISO-3166标准定义,每个国家/地区由两个大写字母标识。详情请参考:https://www.iso.org/obp/ui/#search/code/
比如:
代码已托管到Github—> https://github.com/yangshangwei/SpringMaster
java.util.Locale是表示语言和国家/地区信息的本地化类,它是创建国际化应用的基础。
请看下面的几个创建本地化对象的实例:
package com.xgj.ioc.i18n.locale;
import java.util.Locale;
public class LocaleTest {
public static void main(String[] args) {
// (1)带有语言和国家/地区信息的本地化对象
Locale locale1 = new Locale("zh", "CN");
System.out.println("Locale(\"zh\", \"CN\"):" + locale1);
// (2)只带有语言信息的本地化对象
Locale locale2 = new Locale("zh");
System.out.println("Locale(\"zh\"):" + locale2);
// (3)等同于Locale("zh", "CN")
Locale locale3 = Locale.CHINA;
System.out.println("Locale.CHINA:" + locale3);
// (4)等同于Locale("zh")
Locale locale4 = Locale.CHINESE;
System.out.println("Locale.CHINESE:" + locale4);
// (5)获取本地系统默认的本地化对象
Locale locale5 = Locale.getDefault();
System.out.println("Locale.getDefault():" + locale5);
}
}
运行结果:
Locale("zh", "CN"):zh_CN
Locale("zh"):zh
Locale.CHINA:zh_CN
Locale.CHINESE:zh
Locale.getDefault():zh_CN
如果用户希望改变系统默认的本地化设置,可以在启动JVM时通过命令参数指定
java -Duser.language=en -Duser.regin=US LocaleTest
在IDE中,可以如下设置
运行一下:
Locale("zh", "CN"):zh_CN
Locale("zh"):zh
Locale.CHINA:zh_CN
Locale.CHINESE:zh
Locale.getDefault():en_US
可以看到Locale.getDefault() 已经是 en_US了。
JDK的java.util包中提供了几个支持本地化的格式化操作工具类,比如NumberFormat、DateFormat、MessageFormat。
下面我们来看下具体的用法
package com.xgj.ioc.i18n.tools;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.util.Date;
import java.util.Locale;
public class FormatToolTest {
public static void main(String[] args) {
// 中文本地化信息
Locale locale = Locale.CHINA;
NumberFormat numberFormat = NumberFormat.getCurrencyInstance(locale);
double amt = 123456.78;
System.out.println(numberFormat.format(amt));
// 英文本地化信息
Locale locale2 = Locale.US;
NumberFormat nFormat = NumberFormat.getCurrencyInstance(locale2);
double amt2 = 123456.78;
System.out.println(nFormat.format(amt2));
// 英文本地化信息
Locale locale3 = new Locale("en", "US");
Date date = new Date();
// 按照本地化的方式对日期进行格式化操作
DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM,
locale3);
System.out.println(dateFormat.format(date));
// 中文本地化信息
Locale locale4 = new Locale("zh", "CN");
Date date2 = new Date();
// 按照本地化的方式对日期进行格式化操作
DateFormat dateFormat2 = DateFormat.getDateInstance(DateFormat.MEDIUM,
locale4);
System.out.println(dateFormat2.format(date2));
}
}
运行结果:
¥123,456.78
$123,456.78
Aug 10, 2017
2017-8-10
MessageFormat在NumberFormat和DateFormat的基础上提供了强大的占位符字符串的格式化功能,支持时间、货币、数字以及对象属性的格式化操作。
package com.xgj.ioc.i18n.tools;
import java.text.MessageFormat;
import java.util.GregorianCalendar;
import java.util.Locale;
public class MessageFormatToolTest {
public static void main(String[] args) {
// (1)格式化信息串
String pattern1 = "{0},你好!你与{1}支付了货款{2}元";
String pattern2 = "At {1,time,short} on {1,date,long},{0} paid {2,number,currency}";
// (2)用于动态替换占位符的参数
Object[] params = { "XiaoGongJiang", new GregorianCalendar().getTime(),200 };
// (3)使用默认的本地化对象格式化信息
String msg1 = MessageFormat.format(pattern1, params);
// (4)使用指定的本地化对象格式化信息
MessageFormat messageFormat = new MessageFormat(pattern2, Locale.US);
String msg2 = messageFormat.format(params);
System.out.println(msg1);
System.out.println(msg2);
}
}
运行结果
XiaoGongJiang,你好!你与17-8-10 下午2:04支付了货款200元
At 2:04 PM on August 10, 2017,XiaoGongJiang paid $200.00
分析:
如果应用系统中某些信息需要支持国际化功能,则必须为希望支持的不同本地化类型分别提供对应的资源文件,并以规范的方式进行命名。
国际化资源文件的命名规范规定资源名称采用以下的方式进行命名:
<资源名>_<语言代码>_<国家/地区代码>.properties
其中,语言代码和国家/地区代码都是可选的.
<资源名>.properties命名的国际化资源文件是默认的资源文件,即某个本地化类型在系统中找不到对应的资源文件,就采用这个默认的资源文件.
<资源名>_<语言代码>.properties命名的国际化资源文件是某一语言默认的资源文件,即某个本地化类型在系统中找不到精确匹配的资源文件,将采用相应语言默认的资源文件。
我们命名资源名称为“resource”。 提供两个国际化信息配置文件
greeting.common=How are you?
greeting.morning=Good Morning!
greeting.afternoon=Good Afternoon!
greeting.common=\u4F60\u597D\u5417\uFF1F
greeting.morning=\u4E0A\u5348\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 编码] [输入文件 [输出文件]]
CMD,进入 native2ascii所在的目录,执行:
native2ascii -encoding utf-8 D:\workspace\workspace-sts\SpringMaster\src\main\resources\i18n\resource_zh_CN.properties
对于IDE来讲,无须安装任何插件就自然支持资源属性文件的这种编辑方式,比如我用的Spring Tool Suits,输入中文,自动转换为Unicode。
如果应用程序中拥有大量的本地化资源文件,直接通过传统的File操作资源文件显然太过笨拙。Java为我们提供了用于加载本地化资源文件的方便类java.util.ResourceBoundle。
来看下实例: 结合上面的项目结构,加载i18n目录下的名为resource资源文件。
package com.xgj.ioc.i18n.resourceBoundle;
import java.util.Locale;
import java.util.ResourceBundle;
public class ResourceBoundleTest {
public static void main(String[] args) {
ResourceBundle resBundle = ResourceBundle.getBundle("i18n/resource",
Locale.getDefault());
System.out.println(resBundle.getString("greeting.common"));
System.out.println(resBundle.getString("greeting.morning"));
System.out.println(resBundle.getString("greeting.afternoon"));
ResourceBundle resBundle2 = ResourceBundle.getBundle("i18n/resource",
Locale.US);
System.out.println(resBundle2.getString("greeting.common"));
System.out.println(resBundle2.getString("greeting.morning"));
System.out.println(resBundle2.getString("greeting.afternoon"));
}
}
resBundle加载了默认的本地化资源文件,因为是中文平台,所以是对应中国大陆中文的resource_zh_CN.properties资源文件。
resBundle2 加载了对应美国英语本地化的resource_en_US.properties资源文件。
运行结果:
你好吗?
上午好!
下午好!
How are you?
Good Morning!
Good Afternoon!
ResourceBundle在加载资源时,如果指定的本地化资源文件不存在,它按以下顺序尝试加载其他的资源:本地系统默认本地化对象对应的资源→默认的资源。
举例: 假设我们使用ResourceBundle.getBundle(“i18n/resource”,Locale.CANADA)加载加拿大的本地资源文件,由于不存在resource_en_CA.properties资源文件,它将尝试加载resource_zh_CN.properties的资源文件(中文平台,默认是中文本地化资源文件),假设resource_zh_CN.properties资源文件也不存在,它将继续尝试加载resource.properties的资源文件,如果这些资源都不存在,将抛出java.util.MissingResourceException异常。
在上面的资源文件中,属性值都是一般的字符串,它们不能结合运行时的动态参数构造出灵活的信息,而这种需求是很常见的。要解决这个问题,只须使用带占位符的格式化串作为资源文件的属性值并结合使用MessageFormat就可以满足要求了。
下面我们来改造下资源文件,通过格式化串让问候语更具个性化:
fmt_resource_en_US.properties:
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_zh_CN.properties:
greeting.common=\u4F60\u597D\u5417{0}\uFF1F\u3002\u4ECA\u5929\u662F{1}
greeting.morning=\u65E9\u4E0A\u597D{0}\uFF01\u73B0\u5728\u662F{1,time,short}
greeting.afternoon=\u4E0B\u5348\u597D{0}\uFF01\u73B0\u5728\u662F{1,date,long}
下面使用ResourceBoundle和MessageFormat来操作资源文件
package com.xgj.ioc.i18n.resourceBoundle;
import java.text.MessageFormat;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.ResourceBundle;
public class ResourceBoundleWithFormatTest {
public static void main(String[] args) {
// 加载本地资源
ResourceBundle reBundle1 = ResourceBundle.getBundle(
"i18n/fmt_resource", Locale.CHINA);
ResourceBundle reBundle2 = ResourceBundle.getBundle(
"i18n/fmt_resource", Locale.US);
Object[] params = { "XiaoGongJiang", new GregorianCalendar().getTime() };
// 用本地化对象进行格式化
String greetingCommonCH = new MessageFormat(
reBundle1.getString("greeting.common"), Locale.CHINA)
.format(params);
String greetingMorningCH = new MessageFormat(
reBundle1.getString("greeting.morning"), Locale.CHINA)
.format(params);
String greetingAfternoonCH = new MessageFormat(
reBundle1.getString("greeting.afternoon"), Locale.CHINA)
.format(params);
System.out.println(greetingCommonCH);
System.out.println(greetingMorningCH);
System.out.println(greetingAfternoonCH);
String greetingCommonEN = new MessageFormat(
reBundle2.getString("greeting.common"), Locale.US)
.format(params);
String greetingMorningEN = new MessageFormat(
reBundle2.getString("greeting.morning"), Locale.US)
.format(params);
String greetingAfternoonEN = new MessageFormat(
reBundle2.getString("greeting.afternoon"), Locale.US)
.format(params);
System.out.println(greetingCommonEN);
System.out.println(greetingMorningEN);
System.out.println(greetingAfternoonEN);
}
}
运行结果:
你好吗XiaoGongJiang?。今天是17-8-10 下午3:44
早上好XiaoGongJiang!现在是下午3:44
下午好XiaoGongJiang!现在是2017年8月10日
How are you XiaoGongJiang?,today is 8/10/17 3:44 PM
Good Morning XiaoGongJiang! now is 3:44 PM
Good Afternoon XiaoGongJiang! now is August 10, 2017