文章目录
- 资源文件的命名
- 资源文件基名的指定
- struts.xml 文件指定资源文件的基名
- struts.properties 文件指定资源文件的基名
- 通过标签 i18n 指定资源文件的基名
- 资源文件的位置
- 包级资源文件
- 类级资源文件
- 全局级资源文件
- 默认资源文件
- 资源文件的加载顺序
- Action 中加载资源文件
- 在 JSP 中访问资源文件
- 怎么获取资源文件中指定key的值
- s:text
- s:submit
- s:textfield
- s:password
- s:radio
- 在自定义的 Action 中获取资源文件中指定 key 的值
- ActionSupport
- 资源文件中消息值中的占位符
- 消息值中使用表达式
- 设置和获取默认的语言环境
- 拦截器 i18n
- 案例演示
- 案例一
- 案例二
- 参考文章
资源文件的国际化 internationalization,这个单词是以 i 开头 n 结尾,中间共 18 个字母,所以缩写为 i18n,国际化的核心:页面显示的文字语言是可配置的。
Struts2 国际化是建立在 Java 国际化的基础上的,一样是通过提供不同语言环境的消息资源(资源属性文件),然后通过ResourceBundle 加载指定 Locale 对应的资源文件,再取得该资源文件中指定 key 对应的消息(值),其整个过程与 JAVA 程序的国家化完全相同,只是 Struts2 框架对 JAVA 程序国际化进行了进一步封装,从而简化了应用程序的国际化。
资源文件的命名
资源文件必须是 properties 文件,命名规则为:基名 _语言代号_地区代号(即国家代号).properties
。
资源文件基名的指定
struts.xml 文件指定资源文件的基名
基名是需要指明出来的,例如,资源文件名称为 globalMessages_en_US.properties,那么基名 globalMessages 可以在配置文件 struts.xml 中指明出来,如下所示:
<struts><!-- 设置常量 struts.custom.i18n.resources 的值,其实就是指明资源文件的基名 --><constant name="struts.custom.i18n.resources" value="globalMessages"/>
</struts>
上述方式指定的是全局性的资源文件。
struts.properties 文件指定资源文件的基名
struts.custom.i18n.resources=baseName
struts.i18n.encoding=GBK
上述方式指定的是全局性的资源文件。
通过标签 i18n 指定资源文件的基名
如果不在配置文件 struts.xml 中指明,也可以通过 i18n 标签来指明,如下所示:
<s:i18n name="globalMessages">
<s:text name="firstname"/>
</s:i18n>
标签 i18n 的属性 name 就是用来指明资源文件的基名。标签体中的标签 text 的属性 name 指定 key 的名称。
指明了基名之后呢,<s:text name="firstname"/>
这个标签的底层代码,会调用 Action 的某个方法,这个方法会根据当前系统的语言环境去读取名称以 globalMessages 开头的资源文件,获取 key 为 firstname 的值,如果获取不到,则会到配置文件 struts.xml 或者 struts.properties 所指定的资源文件中获取 key 为 firstname 的值,如果还是找不到则会直接输出属性 name 的值到页面中(即直接输出 key 的名称)。
资源文件的位置
如果项目非常大,所有资源文件放入同一个目录下是不好的。资源文件需要分层和分类。
Maven 项目中,资源文件只能放在目录 resources 下。其实除了包级别资源文件和类级别资源文件的位置有强制要求外,全局级别的资源文件可以存放在目录 classes 中的任意位置上。只是在 struts.xml 需要指定资源文件的具体路径罢了。
如下图展示在 Maven 项目中将资源文件存放在 LoginAction 所在包目录下:
在 struts.xml 要指明资源文件的具体路径:
如下图展示了 eclipse 中如何存放资源文件:
上图所示的包级资源文件 package_zh_CN.properties 能被 com.tarena.com.outman.day05 下所有 Action 使用;包级资源文件 package_zh_CN.properties 能被 com.tarena.com.outman 下所有 Action 使用;类级资源文件 OneAction_zh_CN.properties 只能被 com.tarena.com.outman.day05 下的 OneAction 访问。
包级资源文件
存放在某个包下面的资源文件。命名规则:package_language_country.properties,其中 package 就是固定的基名。
表示某资源文件是被一个包中所有 Action 所使用的。
包级资源文件是其所在包及子包中的所有 Action 共享的资源文件。
Maven 项目中, 必须将资源文件存放在目录 resources 下面,如果要创建包级资源文件,那么要先在目录 resources 下面创建对应的包目录,然后在该包目录下创建资源文件,在构建项目时会将资源文件复制到 classes 目录中相对应的包目录下。
struts.xml 文件中不需要指定资源文件的基名。如下图所示指定包级资源文件的基名也可以,是因为查找包级资源文件时根本就不会去解析常量 struts.custom.i18n.resources 的值。这个常量是用来指定全局级别的资源文件的基名的。
类级资源文件
以某个类名命名的资源文件。命名规则:ActionName_language_country.properties,其中 ActionName 是 Action 类的类名,也就是以 Action 类的类名作为资源文件的基名。
表示某资源文件只能被指定类使用。
类级资源文件是指定类专享的资源文件。
下图展示 Maven 项目如何存放类级资源文件:
类级别的资源文件,不需要在 struts.xml 中指定资源文件的基名。
全局级资源文件
全局级资源文件直接存放在目录 resources 下即可。
全局级的资源文件,必须通过文件 struts.xml 或者 struts.properties 指定资源文件的基名,否则找不到资源文件:
默认资源文件
当在中文操作系统下,如果 myres_zh_CN.properties、myres.properties 两个文件都存在,则优先会使用 myres_zh_CN.properties,当myres_zh_CN.properties 不存在时候,会使用默认的 myres.properties。没有提供语言和地区的资源文件则会去读取系统默认的资源文件。
资源文件都必须是 ISO-8859-1 编码,因此,对于所有非西方语系的处理,都必须先转换为 Java Unicode Escape 格式。转换方法是通过 JDK 自带的工具 native2ascii。
资源文件的加载顺序
Action 中加载资源文件
假设我们在 ChildAction 中调用了 getText("title")
,Struts 2.0 将会按下面的顺序来加载资源文件:
(1)优先加载系统中 ChildAction 类文件所在目录下的,且 baseName 为 ChildAction 的系列资源文件。
(2)如果在(1)中找不到指定 key 对应的消息值,且 ChildAction 有父类 ParentAction,则加载 ParentAction 类文件所在目录下的,且 baseName 为 ParentAction 的系列资源文件。
(3)如果在(2)中找不到指定 key 对应的消息值,且 ChildAction 有实现接口 IChildAction,则加载 IChildAction 类文件所在目录下的,且 baseName 为 IChildAction 的系列资源文件。
(4)如果在(3)中找不到指定 key 对应的消息值,且 ChildAction 有实现接口 ModelDriven(即使用模型驱动模式),则对于 getModel()方法返回的 model 对象,重新执行第(1)步操作。
(5)如果在(4)中找不到指定 key 对应的消息值,则加载 ChildAction 类文件所在目录下的,且 baseName 为 package 的系列资源文件。
(6)如果在(5)中找不到指定 key 对应的消息值,则沿着当前包上溯,直到最顶层包去查找 baseName 为 package 的系列资源文件。
(7)如果在(6)中找不到指定 key 对应的消息值,则查找以常量 struts.custom.i18n.resources 的值作为 baseName 的系列资源文件。
(8)如果经过上面的步骤一直找不到 key 对应的消息值,将直接将 key 的名称输出。
在 JSP 中访问资源文件
对于在 JSP 中访问国际化消息,则简单的多,可以分为两种形式:
1.使用 <s:i18n/>
标签作为父标签
(1)从 <s:i18n/>
标签指定的国际化资源文件中读取指定 key 对应的消息值。
(2)如果在(1)中找不到指定 key 对应的消息值,则查找以常量 struts.custom.i18n.resources 的值作为基名的系列资源文件。
(3)如果经过上面步骤还是找不到指定 key 对应的消息值,则直接将 key 的名称输出。
2.没使用 <s:i18n/>
标签作为父标签
直接查找以常量 struts.custom.i18n.resources 的值作为基名的系列资源文件,如果找不到指定 key 对应的值,则直接将 key 的名称输出。
怎么获取资源文件中指定key的值
s:text
这个标签的属性 name 是用来指定显示在页面上的文本。
<s:text name="firstname"/>
这个标签的底层代码,会调用 Action 的某个方法,这个方法会根据当前系统的语言环境去读取资源文件,然后查找名为 firstname 的 key,找到则将该 key 的值返回,找不到就直接输出属性 name 的值到页面中。
s:submit
<s:submit value="%{getText('login')}"/>
这个标签是通过EL 表达式去调用 Action 的方法 getText() 来获取名为 login 的 key 对应 的值。
getText() 这个方法来自于类 ActionSupport,这个方法的处理逻辑是这样的:
1.先获取 request 中的参数 request_locale,如果获取不到,则会到 session 对象中获取名为 WW_TRANS_I18N_LOCALE 的 key 对应的值,判断用户所使用的语言是哪种
2.根据获取到的语言和指明的资源文件基名到目录 classes 下面读取资源文件,获取并返回指定 key 的值,如果找不到则返回 null。
调用的是谁的 getText()?请求的是 LoginAction,转发到 login.jsp,那么这个 jsp 中获取资源文件中的值的有关标签调用的就是 LoginAction 的 getText()。
这个标签还可以通过属性 key 来指定资源文件中相对应的 key 的名称,从而获得相对应的国际化信息,如下所示:
<s:submit key="loginSubmit" />
s:textfield
这个是文本输入框,属性 name 是用来指定参数名称。属性 label 是用来指定显示在页面中的文本。
通过调用 Action 的方法 getText() 方法来获取资源文件中的值,如下所示:
<s:textfield name="firstname" label="%{getText('firstname')}"/>
属性 label 指定 EL 表达式,调用 Action 的方法 getText() 去国际化资源文件中获取名为 firstname 的 key 对应的值。
还可以通过属性 key 来指定资源文件中的 key,如下所示:
<s:textfield name="username" key="username"/>
s:password
这是密码文本输入框。
通过调用 Action 的方法 getText() 方法来获取资源文件中的值,如下所示:
<s:password name="password" label="%{getText('password')}"/>
通过属性 key 来指定资源文件中的 key,如下所示:
<s:password name="password" key="password"/>
s:radio
单选框
属性 name 指定参数名;属性 list 指定遍历的集合,集合中的元素有两个属性,左边属性指定元素 radio 的属性 value 的值,右边属性则指定元素 label 的文本内容。
通过 Action 的方法 getText 获取资源文件中指定 key 的值,如下所示:
<s:radio name="loginUser.sex" list="#{'1':getText('male'), '0':getText('female')}"/>
在自定义的 Action 中获取资源文件中指定 key 的值
示例代码1:
public String execute() {// 方法getText()继承自父类ActionSupport,用来读取资源文件中的数据,获取指定key所对应的值String str = getText("firstname");System.out.println(str);String[] names = {"liaowenxiong", "liudehua"};// title=欢迎您,{0}String title = getText("title", names);// 返回"欢迎您,liaowenxiong"System.out.println(title);return "success";}
示例代码2:
// 获取默认的语言环境
Locale aDefault = Locale.getDefault();
// 根据语言环境aDefault加载基名为globalMessages的资源文件
ResourceBundle rb = ResourceBundle.getBundle("globalMessages", aDefault);
// title1=欢迎您,{0}
String title = MessageFormat.format(rb.getString("title1"), "liaowenxiong");
System.out.println(title);
ActionSupport
自定义的 Action 必须继承自 ActionSupport。ActionSupport 类中已经封装了对资源文件的访问。
ActionSupport 中主要的方法:
getText(),这个方法中有去获取 ActionContext 对象中的key 为 com.opensymphony.xwork2.ActionContext.locale 的值。
getLocale(),这个方法就是去获取 ActionContext 对象中的key 为 com.opensymphony.xwork2.ActionContext.locale 的值。
扩展:struts.xml 中,标签 <action/>
如果没有指定属性 class 的值,那么调用的就是 ActionSupport 类。
资源文件中消息值中的占位符
title=欢迎您,{0}
如上所示,{0}
就是占位符。
Struts2 中提供了如下两种方式来填充消息字符串中的占位符:
1.JSP 页面,在 <s:text./>
标签中使用多个 <s:param/>
标签来填充消息中的占位符。
2.Action 中,在调用 getText 方法时使用 getText(String aTextName,List args) 或 getText(String key, String[] args) 方法来填充占位符。
String[] names = {"liaowenxiong", "liudehua"};
// title=欢迎您,{0}
String title = getText("title", names);// 返回"欢迎您,liaowenxiong"
消息值中使用表达式
Struts2 还提供了对占位符的一种替代方式,这种方式允许在国际化消息资源文件中使用表达式,对于这种方式,则可避免在使用国际化消息时还需要为占位符传入参数值。
如下在消息值中使用表达式:
succTip=${username}, 欢迎, 您已经登录!
在上面的消息值中,通过使用 EL 表达式,可以从 ValueStack 中取出属性 username 的值,自动填充到消息值中。
设置和获取默认的语言环境
ActionContext context = ActionContext.getContext();
// 获取应用默认的语言环境
Locale locale = context.getLocale();
// 获得应用默认的语言环境
locale = Locale.getDefault();
locale = new Locale("zh", "CN");
// 设置应用默认的语言环境
context.setLocale(locale);
拦截器 i18n
这个拦截器注册在默认的拦截器栈中。
i18n 拦截器在调用 Action 方法前,会到 request 中获取参数 request_locale
的值,这个值就是语言环境,如果获取到这个值会将其封装成 Locale 对象,并将这个对象与用户 Session 中的名为 WW_TRANS_I18N_LOCALE
的 key 进行映射;并且还会将这个对象与 ActionContex 对象中的名为 com.opensymphony.xwork2.ActionContext.locale
的 key 进行映射。
案例演示
案例一
实现 Maven Java Web 项目国际化的步骤:
1.写属性文件(也叫资源文件)
写两个属性文件,一个是中文版本的,一个是英文版本的,名称分别为:globalMessages_en_US.properties 和 globalMessages_zh_CN.properties,把资源文件存放在 resources 目录下。
globalMessages_en_US.properties 内容如下:
firstname=firstname
lastname=lastname
age=age
#title=Welcome,{0}
title=Welcome to xxx
username=username
password=password
login=login
select=Please select your language
chinese=Chinese
english=English
globalMessages_zh_CN.properties 的内容如下:
firstname=姓
lastname=名字
age=年龄
#title=欢迎您,{0}
title=欢迎来到xxx
username=用户名
password=密码
login=登录
select=请选择语言
chinese=中文
english=英文
2.自定义 Action
示例代码:
public class LoginAction extends ActionSupport {public String execute() {return "success";}
}
自定义的 Action 必须继承自 ActionSupport,因为需要调用 ActionSupport 中的方法 getText()。
3.配置 struts.xml
示例代码:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN""http://struts.apache.org/dtds/struts-2.3.dtd">
<struts><!-- 是否启用开发模式 --><constant name="struts.devMode" value="true"/><!-- 指明资源文件的前缀,也就是资源文件的基名 --><constant name="struts.custom.i18n.resources" value="globalMessages"/><!-- 配置字符编码 --><constant name="struts.i18n.encoding" value="UTF-8"/><package name="struts2-tag" extends="struts-default" namespace=""><!-- http://localhost:8080/si/login1.action --><action name="login1" class="priv.lwx.struts2.i18n.web.LoginAction"><result name="success">/login1.jsp</result></action></package>
</struts>
4.写 jsp 文件
示例代码:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@taglib uri="/struts-tags" prefix="s" %>
<!DOCTYPE html>
<html>
<head><title></title>
</head>
<body>
<%-- 通过调用Action的getText方法,获取资源文件中名为 firstname 的key 所对应的值 --%>
<h1><s:text name="firstname"/>
</h1><s:form action="login" method="post"><%--EL表达式的底层是去调用Action的方法getText(),这个方法会根据当前系统的语言环境去读取对应的资源文件,例如,当前系统是中文环境,就会去读取文件globalMessages_zh_CN.properties,获取文件中指定key的值。--%><s:textfield name="firstname" label="%{getText('firstname')}"/><s:textfield name="lastname" label="%{getText('lastname')}"/><s:textfield name="age" label="%{getText('age')}"/><s:submit/>
</s:form>
</body>
</html>
5.测试
测试的时候可以先设置浏览器的语言,再访问这个地址:http://localhost:8080/si/login1.action
chrome 设置语言:
IE 浏览器:
打开IE -> 打开internet选项 -> 点击语言 -> 点击添加,可以看到如下“添加语言”对话框:
案例二
开发一个选择语言环境的页面,这个页面可以嵌入任何Web应用的首页中,这样就可以让用户自行选择语言环境了。
select-language.jsp:
<%@ page import="java.util.Date" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib uri="/struts-tags" prefix="s" %>
<html>
<%--我们就可以在JSP页面中通过<s:include .../>标签来包含该页面,包含该页面后,就可以让用户选择语言了。--%>
<head><title>Title</title><script>function langSelecter_onChanged() {// alert("hello");document.getElementById("langForm").submit();}</script>
</head>
<body>
<%--获取Session中的key为“WW_TRANS_I18N_LOCALE”的值,再将这个值存储到ValueStack的context中,将对应的key命名为SESSION_LOCALE--%>
<s:set var="SESSION_LOCALE" value="#session['WW_TRANS_I18N_LOCALE']"/>
<%--创建Locales的实例,这个对象会存储在ValueStack的context中--%>
<s:bean var="locales" name="priv.lwx.struts2.i18n.bean.Locales"><%--SESSION_LOCALE == null ? locale : SESSION_LOCALE这个三目表达式的含义:判断SESSION_LOCALE的值是否为NULL,若为空则返回locale(从ValueStack的root区获取,root的栈顶存放的是ActionSupport对象,该对象有名为locale的属性,类型是Locale);若不为空则返回SESSION_LOCALE的值--%><%--为什么ValueStack中的root区可以获取到属性locale的值,因为Action对象就是在root区的栈顶,Action对象有属性locale,那么为什么Action有这个属性?从ActionSupport继承下来的。所以转发到此JSP的Action必须继承自ActionSupport--%><s:param name="current" value="#SESSION_LOCALE == NULL ? locale : #SESSION_LOCALE"/><%--Locales实例化后,会调用getCurrent方法,并将属性value的值作为参数传入方法中--%>
</s:bean>
<%--让用户选择语言的表单--%>
<form id="langForm" action="<s:url/>" style="background-color: #bbbbbb;padding-top: 4px;padding-bottom: 4px"><s:text name="selectTips"/><%--使用s:select标签遍历Locales对象的属性locales的值,这个值是一个Map对象,里面存储着应用所支持的语言信息--%><s:select id="langSelecter" label="language" name="request_locale" list="#locales.locales"listKey="value" listValue="key" value="#SESSION_LOCALE == NULL ? locale : #SESSION_LOCALE"onchange="langSelecter_onChanged();" theme="simple"/><%--用户发送请求,携带参数request_locale,那么系统会将该参数的值存储在Session的属性WW_TRANS_I18N_LOCALE中,该属性值直接决定Struts2系统的语言环境。--%>
</form>
<s:debug/>
</body>
</html>
bean 类 Locales 的代码:
package priv.lwx.struts2.i18n.bean;import java.util.Hashtable;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;/*** 这个类的作用就是用来获取当前应用所支持的全部语言** @author liaowenxiong* @date 2022/5/26 09:53*/public class Locales {// 因为本示例也需要实现国际化,所以也需要获取到用户的语言环境private Locale current;// Locales实例化的时候会自动调用该方法,将获取到的Locale对象存储到Locales实例的成员变量current中public void setCurrent(Locale cur) {System.out.println("方法setCurrent被调用了...");System.out.println("cur:" + cur);this.current = cur;}/*** 获取当前应用所支持的全部语言,以Map对象返回** @param* @return Map<Locale>* @throws* @author liaowenxiong* @date 2022/5/26 11:49*/public Map<String, Locale> getLocales() {System.out.println("方法getLocales被调用了...");// 构造一个Map对象,将当前应用所支持的语言存储在该对象中Map<String, Locale> locales = new Hashtable<String, Locale>();System.out.println("current:" + current);// current是用户的语言环境,globalMessages是资源属性文件的基名。下面这行代码会根据current所表示的// 语言环境及所指定的基名去读取对应的资源文件,将资源文件中的数据全部载入到ResourceBundle对象中。其实// 这个ResourceBundle对象类似Properties对象,它们都是Map对象ResourceBundle bundle = ResourceBundle.getBundle("globalMessages", current);// 添加当前应用所支持的语言,key是系统支持语言的显示名字,value是系统支持语言的Locale实例// getString方法就是从读取的资源文件中获取指定key对应的值locales.put(bundle.getString("usen"), Locale.US);locales.put(bundle.getString("zhcn"), Locale.CHINA);// 在JSP中会遍历这个Map对象,将里面的数据以下拉列表的形式显示出来return locales;}
}
创建属性文件 globalMessages_en_US.properties:
selectTips=Please select your language
usen=American English
zhcn=Simplified Chinese
创建属性文件 globalMessages_zh_CN.properties:
selectTips=请选择语言
usen=美式英语
zhcn=简体中文
参考文章
1.https://www.likecs.com/show-203884910.html
2.https://www.cnblogs.com/likailan/p/3307409.html