在程序设计领域, 把在无需改写源代码即可让开发出来的应用程序能够支持多种语言和数据格式的技术称为国际化.与国际化对应的是本地化, 指让一个具备国际化支持的应用程序支持某个特定的地区。
Struts2实现国际化的步骤
1、建立国际化资源文件
建立国际化资源文件,就是建立多个properties文件,每个文件通过文件名不同对应一个国家的语音环境。不同的properties文件内定义了很多key-value键值对,key是程序中访问的名称,而value值不同的properties文件分别对应该国家的消息内容。
properties文件命名规则一般为:
默认的:basename.properties
美国的:basename_en_US.properties
中国的:basename_zh_CN.properties
其中basename可以是任意名字。有的程序员习惯命名为i18n。
2、配置资源文件
配置资源文件,分为在action范围、包范围和全局范围三种方式:
I. Action 范围资源文件
在Action类文件所在的路径建立名为 ActionName_language_country.properties 的文件
例:InputAction_zh_CN.properties。
II. 包范围资源文件
在包的根路径下建立文件名为 package_language_country.properties 的属性文件,一旦建立,处于该包下的所有 Action 都可以访问该资源文件。注意:包范围资源文件的 baseName 就是package,不是Action所在的包名。
例:package_zh_CN.properties。
III. 全局资源文件
在classes路径下,及src下,建立文件名basename_language_country.properties,并在struts.xml中配置常量<constant name="struts.custom.i18n.resources" value="baseName"/>
例:建立文件—- i18n_zh_CN.properties。
在struts.xml配置<constant name="struts.custom.i18n.resources" value="i18n"/>
IV国际化资源文件加载的顺序如何呢
原则:离当前 Action 较近的将被优先加载. 加载顺序(了解即可):
假设我们在某个 ChildAction 中调用了getText(“username”):
(1) 加载和 ChildAction 的类文件在同一个包下的系列资源文件 ChildAction.properties
(2) 加载 ChildAction 实现的接口 IChild,且和 IChildn 在同一个包下 IChild.properties 系列资源文件。
(3) 加载 ChildAction 父类 Parent,且和 Parent 在同一个包下的 baseName 为Parent.properties 系列资源文件。
(4) 若 ChildAction 实现 ModelDriven接口,则对于getModel()方法返回的model 对象,重新执行第(1)步操作。
(5) 查找当前包下package.properties 系列资源文件。
(6) 沿着当前包上溯,直到最顶层包来查找 package.properties的系列资源文件。
(7) 查找 struts.custom.i18n.resources 常量指定 baseName 的系列资源文件。
(8) 直接输出该key的字符串值。
3、在action 或JSP页面中使用本地化后的消息
I 在action中使用本地化的消息
第一步:action要实现TextProvider接口(可通过继承(扩展)ActionSupport方式)
第二步:通过getText()方法访问value;
- (1)如果在properties文件中定义的key-value键值对的value为普通字符串,可以直接通过this.getText(“key”)方法直接访问value。
- (2)如果在properties文件中定义的key-value键值对的value为占位符,如{0},需要通过this.getText(“key”,args)方法去访问,其中args参数根据程序需要传入相应的值。如果args传入的是一个list则{0}对应list[0]的值,{1}对应list[1]的值。
II 在jsp中使用本地化的消息
(1)simple主题:
- a、通过
<s:text name="username">
来访问,其中“username”就是properties中定义的key。 - b、通过强制解析ognl访问:
<s:submit name="submit" value="%{getText('submit')}"></s:submit>
%getText(‘submit’)}中的submit就是key。
(2)非simple主题:直接通过key属性访问
<s:textfield name="username" key="username"></s:textfield>
4、Struts2 框架是如何确定 Local 对象的?
要想知道Struts2 框架是如何确定 Local 对象的,就需要阅读I18N 拦截器的源码。 通过struts-default.xml我们知道默认的I18N拦截器是com.opensymphony.xwork2.interceptor.I18nInterceptor
代码0:阅读源码,找到intercept方法:
public String intercept(ActionInvocation invocation) throws Exception {
if (LOG.isDebugEnabled()) {
LOG.debug("intercept '#0/#1' {",
invocation.getProxy().getNamespace(), invocation.getProxy().getActionName());
}
//这localeFinder是一个内部类,这个类主要是用来返回从request的参数中获取locale信息以及是否要将locale信息存储在session中。见代码1。
LocaleFinder localeFinder = new LocaleFinder(invocation);
//返回从request中获取到的locale(可能为null),见代码3和2
Locale locale = getLocaleFromParam(localeFinder.getRequestedLocale());
//调用storeLocale方法,如果从request中获取到了locale,则存储到session中,如果没有尝试分别从session和CurrentInvocation获取local信息。session中有的话,就是session中,没有的化读取系统默认的值。
locale = storeLocale(invocation, locale, localeFinder.getStorage());
//将locale信息保存到invocation
saveLocale(invocation, locale);
if (LOG.isDebugEnabled()) {
LOG.debug("before Locale=#0", invocation.getStack().findValue("locale"));
}
final String result = invocation.invoke();
if (LOG.isDebugEnabled()) {
LOG.debug("after Locale=#0", invocation.getStack().findValue("locale"));
LOG.debug("intercept } ");
}
return result;
}
代码1:内部类localeFinder的代码:
protected class LocaleFinder {
//用来记录是否要在ssession中保存locale信息
protected String storage = Storage.SESSION.toString();
protected Object requestedLocale = null;
protected ActionInvocation actionInvocation = null;
//构造函数初始化
protected LocaleFinder(ActionInvocation invocation) {
actionInvocation = invocation;
//查找request是否带有locale的参数
find();
}
protected void find() {
//get requested locale
//获取请求参数集
Map<String, Object> params = actionInvocation.getInvocationContext().getParameters();
//默认为保存到session中。
storage = Storage.SESSION.toString();
//调用外部类的findLocaleParameter方法,获取请求参数中是否有参数名为request_locale的参数。源码见代码2。
//parameterName的值初始化为request_locale
requestedLocale = findLocaleParameter(params, parameterName);
//如果获取到了则直接返回
if (requestedLocale != null) {
return;
}
//requestOnlyParameterNam的值初始化为request_only_locale
//调用外部类的findLocaleParameter方法,获取请求参数中是否有参数名为request_only_locale的参数
requestedLocale = findLocaleParameter(params, requestOnlyParameterName);
//如果获取到,将storage设置为不在session中保存。
if (requestedLocale != null) {
storage = Storage.NONE.toString();
}
}
//返回是否在session中存储的标记
public String getStorage() {
return storage;
}
//返回从request中获取到的locale,可能为null,也可能获取到了
public Object getRequestedLocale() {
return requestedLocale;
}
}
代码2:findLocaleParameter方法源码。
protected Object findLocaleParameter(Map<String, Object> params, String parameterName) {
//尝试从params,中获取“*request_locale*”的参数,
Object requestedLocale = params.remove(parameterName);
//如果获得的requestedLocale是个数组的话,返回第一个。
if (requestedLocale != null && requestedLocale.getClass().isArray()
&& ((Object[]) requestedLocale).length > 0) {
requestedLocale = ((Object[]) requestedLocale)[0];
if (LOG.isDebugEnabled()) {
LOG.debug("requested_locale=#0", requestedLocale);
}
}
return requestedLocale;
}
代码3:getLocaleFromParam方法,从param中获取locale
protected Locale getLocaleFromParam(Object requestedLocale) {
Locale locale = null;
//如果从request中获取到了的locale信息,则解析该信息,并赋值给locale。
if (requestedLocale != null) {
locale = (requestedLocale instanceof Locale) ?
(Locale) requestedLocale :
LocalizedTextUtil.localeFromString(requestedLocale.toString(), null);
if (locale != null && LOG.isDebugEnabled()) {
LOG.debug("applied request locale=#0", locale);
}
}
//如果locale信息不被java支持则获得默认locale
if (locale != null && !Arrays.asList(Locale.getAvailableLocales()).contains(locale)) {
locale = Locale.getDefault();
}
//返回从request中获取到的locale(可能为null)
return locale;
}
代码4:storeLocale方法源码,存储locale到session中
protected Locale storeLocale(ActionInvocation invocation, Locale locale, String storage) {
//save it in session
//获取session
Map<String, Object> session = invocation.getInvocationContext().getSession();
//如果session不为null,执行同步锁定session操作
if (session != null) {
synchronized (session) {
//如果前面没有通过request获得到locale,则把storage设置为不保存,并尝试读取保存的locale(分别从session和CurrentInvocation)。
if (locale == null) {
storage = Storage.NONE.toString();
locale = readStoredLocale(invocation, session);
}
//如果storage为保存,说明通过request_locale获得到了locale,则在session中保存
if (Storage.SESSION.toString().equals(storage)) {
session.put(attributeName, locale);
}
}
}
return locale;
}
具体确定 Locale 对象的过程:
- Struts2 使用 i18n 拦截器 处理国际化,并且将其注册在默认的拦截器栈中,i18n拦截器在执行Action方法前,自动查找请求中一个名为 request_locale 的参数。
- 如果该参数存在,拦截器就将其作为参数,转换成Locale对象,并将其设为用户默认的Locale(代表国家/语言环境)。并把其设置为 session 的 WW_TRANS_I18N_LOCALE 属性
若 request 没有名为request_locale 的参数,则 i18n 拦截器会从 Session 中获取 WW_TRANS_I18N_LOCALE
的属性值,若该值不为空,则将该属性值设置为浏览者的默认Locale。若 session 中的 WW_TRANS_I18N_LOCALE 的属性值为空,则从 ActionContext 中获取 Locale 对象。