Struts2的资源文件和国际化(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

猜你喜欢

转载自blog.csdn.net/liaowenxiong/article/details/124922738