Struts应用的国际化
1.1 本地化与国际化的概念
国际化(简称I18N)指的是在软件设计阶段,就应该使软件具有支持多种语言和地区的功能。
本地化意味着针对不同语言的客户,开发出不同的软件版本;国际化意味着同一个软件可以面向使用各种不同语言的客户。
如果一个应用支持国际化,它应该具备以下特征:
l 当应用需要支持一种新的语言时,无需修改应用程序代码。
l 文本、消息和图片从源程序代码中抽取出来,存储在外部。
l 应该根据用户的语言和地理位置,对与特定文化相关数据,如日期、时间和货币,进行正确的格式化。
l 支持非标准的字符集。
l 可以方便快捷地对应用作出调整,使它适应新的语言和地区。
I18N是Internationalization的简称,因为该单词的首字母I与尾字母N中间隔着18个字符,由此得名。 |
1.2 Web应用的中文本地化
无论是对Web应用的本地化还是国际化,都会涉及字符编码转换问题。Web应用的各种可能的输入和输出流如图,当数据流的源与目的地使用不同的字符编码时,就需要对字符编码进行正确的转换。
1.2.1 处理HTTP请求数据编码
默认情况下,IE浏览器发送请求时采用”ISO-8859 -1” 字符编码,如果Web应用程序要正确地读取用户发送的中文数据,则需要进行编码转换。
两种方法:
l 在处理请求前,先设置HttpServletRequest对象的字符编码:
request.setCharacterEncoding(“gb 2312” ); |
l 对用户输入的请求数据进行编码转换:
String clientData = request.getParameter(“clientData”); if ( clientData != null ) clientData = new String( clientData.getBytes(“ISO-8859 -1” ), ”GB 2312” ); |
1.2.2 处理数据库数据编码
Connection con = DbUtil.connectToDb(); PreparedStatement pstmt = null; ResultSet rs = null; pStmt = con.prepareStatement(“select FILED1 from MYTABLE”); rs = pStmt.executeQuery(); while ( rs.next() ) { String field1 = rs.getString(“FILED 1” ); String field1_ch = new String(field1.getBytes(“ISO-8859 -1” ), “GB 2312” ); //process data } |
如果数据库系统字符编码使用的”ISO-8859 -1” 的话,那么需要如上的操作转换为GB2312。
1.2.3 处理XML配置文件编码
如果在XML文件中包含中文,可以将XML文件的字符编码设为”GB 2312” ,这样,当Java程序加载和解析XML文件时无需再进行编码转换:
<?xml version=’ 1.0’ encoding=”GB 2312” ?> |
1.2.4 处理响应结果的编码
可以通过以下方式来设置响应结果的编码:
l 在Servlet中
response.setContentType(“text/html;charset=GB 2312” );
l 在JSP中
<%@ page contentType=”text/html;charset=GB 2312” %>
l 在HTML中
<head>
<META HTTP-EQUIV=”Content-Type” CONTENT=”text/html;charset=GB 2312” >
</head>
1.3 Java对I18N的支持
Java在其核心库中提供了支持I18N的类和接口。
1.3.1 Locale类
java.util.Locale类是最重要的Java I18N类。
Locale类的实例代表一种特定的语言和地区。如果Java类库中的某个类在运行时需要根据Locale对象来调整其功能,那么称这个类是本地敏感的(Locale-Sensitive)。例如,java.text.DateFormat类就是本地敏感的,因为它需要依照特定的Locale对象来对日期进行相应的格式化。
创建Locale对象时,需要明确地指定其语言和国家代码:
Locale usLocale = new Locale(“en”, “US”); //美国的 Locale chLocale = new Locale(“ch”, “CH”); //中国的 |
第一个参数是语言代码,由两个小写字母组成,遵从ISO-639规范,可以从http://www.unicode.org/unicode/onlinedat/languages.html中获得完整的语言代码列表。
第二个参数是国家代码,由两个大写字母组成,遵从ISO-3166规范,可以从http://www.unicode.org/unicode/onlinedat/countries.html中获得完整的国家代码列表。
Locale类提供了几个静态常量,代表一些常用的Locale实例,如日本,可以使用:
Locale locale1 = Locale.JAPAN
Locale locale2 = new Locale(“ja”, “JP”);
1. Web容器中Locale对象的来源
Java虚拟机在启动时会查询操作系统,为运行环境设置默认的Locale,Java程序可以调用java.util.Locale类的静态方法getLocale()来获得默认的Locale。
Locale defaultLocale = Locale.getDefautl();
2. 在Web应用中访问Locale对象
对于Web应用程序,通常不必创建自己的Locale实例,因为Web容器会负责创建所需的Locale实例。在应用程序中,可以调用HttpServletRequest对象的以下两个方法,来取得包含Web客户的Locale信息的Locale实例:
public java.util.Locale getLocale(); public java.util.Enumeration getLocales(); |
这两个方法都会访问HTTP请求中的Accept-Language头信息,getLocale()方法返回客户优先使用的Locale,而getLocales()方法返回一个Enumeration集合对象,它包含了按优先级降序排列的所有Locale对象。如果客户没有配置任何Locale,getLocale()方法会返回默认的Locale。
大多数浏览器允许用户配置Locale,如IE,在Internet选项的语言中,可以进行配置:
3. 在Struts应用中访问Locale对象
由于Web服务器并不和客户浏览器保持长期的连接,因此每个发送到Web容器的HTTP请求中都包含了Locale信息,Struts配置文件的<controller>元素的Locale属性指定是否把Locale对象保存在session范围内,默认是true。在处理每一个用户请求时,RequestProcessor类都会调用它的processLocale()方法:
protected void processLocale(HttpServletRequest request, HttpServletResponse response) { //Are we configured to select the Locale automatically? if ( !moduleConfig.getControllerConfig().getLocale() ) { return; }
//Has a Locale already been selected? HttpSession session = request.getSession(); if ( session.getAttribute(Globals.LOCALE_KEY) != null ) { return; }
//Use the Locale returned by the servlet container(if any) Locale locale = request.getLocale(); if ( locale != null ) { session.setAttribute(Globals.LOCALE_KEY, locale); } } |
尽管每次发送的HTTP请求都包含Locale信息,processLocale()方法把Locale对象存储在session范围内,需要满足两个条件:
l Struts配置文件的<controller>元素的locale属性为true
l 在session范围内Locale对象不存在
在Struts应用中可以很方便地读取Locale信息,在Action类中,可以调用Struts Action基类中定义的getLocale()方法。return RequestUtils.getUserLocale(request, null);
public static Locale getUserLocale(HttpServletRequest request, String Locale) { Locale userLocale = null; HttpSession session = request.getSession(false);
if ( locale == null ) { locale = Globals.LOCALE_KEY; }
//Only check session if sessions are enabled if ( session != null ) { userLocale = (Locale)session.getAttribute(locale); }
if ( userLocale == null ) { //Returns Locale based on Accept-Language header or the server default userLocale = request.getLocale(); }
return userLocale; } |
getUserLocale()方法先通过HttpServletRequest参数获得HttpSession对象,然后再通过HttpSession对象来读取Locale对象,如果存在HttpSession对象并且其中存储了Locale对象,就返回该Locale对象,否则就直接调用HttpServletRequest的getLocale()方法获取Locale对象,并返回。
1.3.2 ResourceBundle类
java.util.ResourceBundle类提供存放和管理与Locale相关的资源的功能。这些资源包括文本域或按钮的Label、状态信息、图片名、错误信息和网页标题等。
Struts框架没有直接使用Java语言提供的ResourceBundle类,而是提供了:
l org.apache.struts.util.MessageResource
l org.apache.struts.util.PropertyMessageResource
1.3.3 MessageFormat类和复合消息
Java的ResourceBundle类和Struts的MessageResource类都允许使用静态和动态的文本。静态的就是在ResourceBundle中添加固定的信息:
error.requriedfield.name=The Name field is requried to save.
error.requriedfield.phone=The Phone field is requried to save.
但是如果表单有100个域,就需要写100个,就使得我们的ResourceBundle很庞大并且难于维护,这就可以使用动态方式:
error.requriedfield=The {0} field is requried to save.
lable.phone=Phone
lable.name=Name
以此类推,还可以使用{1}、{2}来表示,在运行时,MessageFormat类的format()方法可以把参数{0}替换成真正的动态文本内容。
import java.util.ResourceBundle; import java.util.Locale; import java.text.MessageFormat;
public class FormatExample { public static void main(String[] args) { //Load the resource bundle ResourceBundle bundle = ResourceBundle.getBundle(“ApplicationResources”); //Get the message template String requriedFieldMessage = bundle.getString(“error.requriedfield”); //Create a String array of size one to hold the arguments String[] messageArgs = new String[1]; //Get the “Name” field from the bundle and load it in as an argument messageArgs[0] = bundle.getString(“lable.name”); //Format the message using the message and the arguments String formattedNameMessage = MessageFormat.format(requriedFieldMessage, messageArgs); System.out.println(formattedNameMessage); //Get the “Phone” field from the bundle and load it in as an argument messageArgs[0] = bundel.getString(“lable.phone”); //Format String formattedPhoneMessage = MessageFormat.format(requriedFieldMessage, messageArgs); System.out.println(formattedPhoneMessage); } } |
通常把包含可变数据的消息成为复合消息,复合消息允许在程序运行时把动态数据加入到消息文本中,这能够减少Resource Bundle中的静态消息数量,从而减少把静态消息文本翻译成其他Locale版本所花费的时间。
1.4 Struts框架对国际化的支持
Struts框架对国际化的支持体现在能够输出和用户Locale相符合的文本和图片上。当Struts配置文件的<controller>元素的locale属性为true时,Struts框架把用户的Locale实例保存在session范围内,这样,Struts框架能自动根据这一Locale实例来从Resource Bundle中选择合适的资源文件。如图,当用户的Locale为英文时,Struts框架就会向用户返回来自于application_en.properties文件的文本内容;当用户的Locale为中文时,Struts框架就会向用户返回来自于application_ch.properties文件的内容。
1.4.1 创建Struts的Resource Bundle
对于多应用模块的Struts应用,可以为每个子应用配置一个或多个Resource Bundle,饮用模块中的Action、ActionForm Bean、JSP页和客户化标签都可以访问这些Bundle。Struts配置文件中的每个<message-resources>元素定义一个Resource Bundle。当应用中包含多个Resource Bundle时,它们通过<message-resources>元素的key属性来区别。
<message-resources parameter=”application” /> <message-resources key=”IMAGE_RESOURCE_KEY” parameter=”imageresources” /> |
Resource Bundle的持久化消息文本存储在资源文件中,其扩展名为”.properties”,默认资源文件应该取名为application.properties,如果应用程序需要支持中文用户,可以再创建一个包含中文消息的资源文件,文件名为:application_ch_CN.properties或application_ch.properties。
当Struts框架处理Locale为中文的用户请求时,它会依次搜索如下资源文件:
l application_ch_CN.properties
l application_ch.properties
l application.properties
Struts框架在/WEB-INF/classes/目录下依次寻找这些资源文件。如果在配置Resource Bundle时还给定了包名,那么properties文件应该在classes/对应的包名目录下。
1.4.2 访问Resource Bundle
Struts应用的每个Resource Bundle和org.apache.struts.util.MessageResources类(实际上是其子类PropertyMessageResources)的一个实例对应。MessageResources对象中存放了来自资源文件的文本。当应用初始化时,这些MessageResources实例被存储在ServletContext中(即application范围内),因此任何一个Web组件都可以访问它们。
Struts应用、子应用模块、ResourceBundle和资源文件之间存在以下关系:
l 一个Struts应用可以有多个子应用模块,必须有且只有一个默认子应用模块
l 一个子应用模块可以有多个Resource Bundle,必须有且只有一个默认Resource Bundle
l 一个Resource Bundle可以有多个资源文件,必须有且只有一个默认资源文件
在Struts应用中访问Resource Bundle的途径:
1. 通过编程来访问Resource Bundle
在Action基类中定义了getResources(request)方法,它可以返回默认的MessageResources对象,代表当前应用模块使用的默认Resource Bundle,如果要获得特定的MessageResources对象,可以调用Action基类的getResources(request, key)方法,key对应<message-resources>元素的key属性。
org.apache.struts.util.MessageResources的getMessage()方法有几种重载形式:
l 根据参数指定的Locale检索对应的资源文件,然后返回和参数key对应的消息文本:
getMessage(java.util.Locale locale, java.lang.String key);
l 根据参数指定的Locale检索对应的资源文件,然后返回和参数key对应的消息文本,args参数用于替换复合消息文本中的参数:
getMessage(java.util.Locale locale, java.lang.String key, java.lang.Object[] args);
l 根据默认的Locale检索对应的资源文件,然后返回和参数key对应的消息文本:
getMessage(java.lang.String key);
2. 使用和Resource Bundle绑定的Struts组件
Struts框架中的许多内在组件和Resource Bundle是绑定在一起的,如:
l ActionMessage类和<html:errors>标签
每个ActionMessage实例代表Resource Bundle中的一条消息。调用ActionMessage的构造方法时,需要传递消息key。一般在ActionForm Bean的validate()方法中:
errors.add(“username”, new ActionMessage(“hello.no.username.error”);
对于复合消息,在创建ActionMessage对象时,调用带两个参数的构造方法:ActionMessage(java.lang.String key, java.lang.Object[] values),values用于替换复合消息中的参数。
在JSP页面中,使用<html:errors>标签,就能读取显示ActionErrors集合众所有的ActionMessage对象包含的消息文本。
l Struts Bean标签库的<bean:message>标签
<bean:message>标签从应用的Resource Bundle中获取消息字符串。如:
<head><title><bean:message key=”hello.jsp.title” /></title></head>
<bean:message>标签根据存储在session范围内的Locale实例,从默认的Resource Bundle中检索和Locale对应的资源文件,再从资源文件中读取和”hello.jsp.title”对应的消息字符串。<bean:message>标签还有一个属性bundle,它和<message-resources>元素的key属性对应,如果没有,将访问默认的Resource Bundle。
l 在Validator验证框架中访问Resource Bundle
l 在声明型异常处理中访问Resource Bundle
1.5 对helloapp应用实现国际化
1.5.1 对JSP文件的文件、 图片和按钮进行国际化
(1) 设置字符串编码。
可以将所有的JSP页面的字符编码统一设为UTF-8:
<%@ page contentType=”text/html;charset=UTF -8” language=”java” %>
(2) 对文本国际化。
在JSP文件中不应该直接包含本地化的消息文本,而应该通过<bean:message>标签从Resource Bundle中获得。
(3) 对按钮国际化
<html:submit property=”submit”>
<bean:message key=”hello.jsp.page.submit”/>
</html:submit>
(4) 对图片国际化
<html:img pageKey=”hello.jsp.page.strutsimage” altKey=”hello.jsp.page.struts” />
1.5.2 创建临时中文资源文件
因为中文资源文件还需要进行转换,所以创建一个临时的,下一步进行转换,命名为application_temp.properties。
1.5.3 对临时资源文件进行编码转换
JDK提供了native2ascii命令,它能够实现字符编码转换。转换上一步的临时中文资源文件:native2ascii –encoding gb2312 application_temp.properties application_zh_CN.properties
将会把temp中的中文转换成”/u7b2c/u4e00/u4e 2a ….”形式。需要验证,可能转换会错误。
1.5.4 创建英文资源文件
和application.properties相同,只是文件名叫application_en.properties。
1.5.5 采用Servlet过滤器设置请求数据的字符编码
调用HttpServletRequest的setCharacterEncoding(“UTF -8” )方法,能够把用户的请求数据的字符编码也设为UTF-8,这样,Wen应用的输入和输出都采用同一种字符编码,就无需在程序中进行编码转换了。
编写SetCharacterEncodingFilter.java类,并在web.xml中加入
<filter> <filter-name>Set Character Encoding</filter-name> <filter-class>SetCharacterEncodingFilter</filter-class> </filter> <filter-mapping> <filter-name>Set Character Encoding</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> |
1.5.6 运行国际化的helloapp应用
通过修改IE选项的语言信息,可以改变使用的Locale信息。
在同一个会话中,Struts框架仅读取HTTP请求的Locale信息一次,然后就把Locale对象保存在session范围内,因此在同一个会话中,Locale对象保持不变,选用的资源文件也不会变化。 |
1.6 异常处理的国际化
1.7 小结
与国际化密切相关的两个组件是Locale和Resource Bundle:
l Locale:包含了用户的本地化信息,如语言和国家
l Resource Bundle:包含了多个消息资源文件,每个消息资源文件存放和一种Locale相对应的本地化消息文本。
Struts框架在初始化时,把Resource Bundle(即MessageResources对象)存储在application范围内,在响应用户请求时,把包含用户Locale信息的Locale实例存储在session范围内。Struts框架能自动根据这一Locale实例,从Resource Bundle中检索相应的资源文件,再从资源文件中读取本地化的消息文本。
对Struts应用实现国际化应该遵循以下原则:
l 尽量不在Servlet中使用含非英文字符的常量字符串。
l 对于JSP文件,应该对page指令中的charset属性进行相应的设置。
l 不要在JSP文件中直接包含本地化的消息资源,而应该把消息资源存放在Resource Bundle的资源文件中。
l 不必在每个JSP或Servlet中设置HTTP请求的字符编码,可以在Servlet过滤器中设置:HttpServletRequest.setCharacterEncoding(String encoding);
l 尽量使用”UTF -8” 作为HTTP请求和响应的字符编码,而不是”GBK”或”GB 2312”
l 充分考虑底层数据库所使用的编码,它可能会给应用程序的移植带来麻烦。