万维网(World Wide Web)的迅猛发展推动了跨国业务的发展,它成为一种在全世界范围内发布产品信息、吸引客户的有效手段。为了使企业Web应用能支持全球客户,软件开发者应该开发出支持多国语言、国际化的Web应用。
1 本地化与国际化的概念
国际化(简称为I18N)指的是软件设计阶段,就应该使软件具有支持多种语言和地区的功能。这样,当需要在应用中添加对一种新的语言和国家的支持时,不需要对已有的软件返工,无需修改应用的程序代码。
本地化意味着针对不同语言的客户,开发出不同的软件版本;国际化意味着同一个软件可以面向使用各种不同语言的客户。
如果一个应用支持国际化,它应该具备以下特征:
• 当应用需要支持一种新的语言时,无需修改应用程序代码。
• 文本、消息和图片从源程序代码中抽取出来,存储在外部。
• 应该根据用户的语言和地理位置,对与特定文化相关的数据,如日期、时间和货币,进行正确的格式化。
• 支持非标准的字符集。
• 可以方便快捷地对应用作出调整,使它适应新的语言和地区。
在对一个Web应用进行国际化时,除了应该对网站上的文本、图片和按钮进行国际化外,还应该对数字和货币等根据不同国家的标准进行相关的格式化,这样才能保证各个国家的用户都能顺利地读懂这些数据。
Locale(本地)指的是一个具有相同风俗、文化和语言的区域。如果一个应用没有事先把I18N作为那前的功能,那么当这个应用需要支持新的Locale时,开发人员必需对嵌入在源代码中的文本、图片和消息进行修改,然后重新编译源代码。每当这个应用需要支持新的Locale时,就必需重复这些繁琐的步骤,这种做法显然大大降低了软件开发效率。
2 Web应用的中文本地化
无论时对Web应用的本地化还是国际化,都会涉及字符编码转换问题。当数据流的源与目的地使用不同的字符编码时,就需要对字符编码进行正确的转换。
2.1 处理HTTP请求数据编码
默认情况下,IE浏览器发送请求时采用“ISO-8859-1”字符编码,如果Web应用程序要正确地读取用户发送的中文数据,则需要进行编码转换。
一种方法是在处理请求前,先设置HttpServletRequest对象的字符编码:
request.setCharacterEncoding(“gb2312”);
还有一种办法是对用户输入的请求数据进行编码转换:
String clientData = request.getParameter(“clientData”);
if(clientData != null)
clientData = new String(clientData.getBytes(“ISO-8859-1”), “GB2312”);
2.2 处理数据库数据编码
如果数据库系统的字符编码为“GB2312”,那么可以直接读取数据库中的中文数据,而无需进行编码转换。如果数据库字符编码为“ISO-8859-1”,那么必需先对来自数据库的数据进行编码转换,然后才能使用。
2.3 处理XML配置文件编码
如果在XML文件中包含中文,可以将XML文件的字符编码为“GB2312”。这样,当Java程序加载和解析XML文件时无需再进行编码转换。
<?xml version=’1.0’ encoding=”GB2312”?>
2.4 处理响应结果的编码
可以通过以下方式来设置响应结果的编码:
• 在Servlet中
response.setContentType(“text/html;charset=GB2312”);
• 在JSP中
<%@ page contentType=”text/html;charset=GB2312” %>
• 在HTML中
<head>
<META HTTP-EQUIV=”Content-Type” CONTENT=”text/html; charset=GB2312”>
</head>
3 Java对I18N的支持
Java在其核心库中提供了支持I18N的类和接口。Struts框架依赖于这些Java I18N组件来实现对I18N的支持,因此,掌握Java I18N组件的使用方法有助于理解Struts应用的国际化机制。
3.1 Locale类
java.util.Locale类时最重要的Java I18N类,在Java语言中,几乎所有对国际化和本地化的支持都依赖于这个类。
Locale类的实例代表一种特定的语言和地区。如果Java类库中的某个类在运行时需要根据Locale对象来调整其功能,那么就称这个类是本地敏感的(Locale-Sensitive)。例如,java.text.DateFormat类就是本地敏感的,因为它需要依照特定的Locale对象来对日期进行相关的格式化。
Locale对象本身并不执行和I18N相关的格式化或解析工作。Locale对象仅仅负责向本地敏感的类提供本地化信息。例如,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实例。例如,如果要获得Japanese Locale实例,可以使用如下两种方法之一:
Locale locale1 = Locale.JAPAN;
Locale locale2 = new Locale(“ja”, “JP”);
3.1.1 Web容器中Locale对象的来源
Java虚拟机在启动时会查询操作系统,为运行环境设置默认的Locale。Java程序可以调用java.util.Locale类的静态方法getLocale()来获得默认的Locale:
Locale defaultLocale = Locale.getDefault();
Web容器在其本地环境中通常会使用以上默认的Locale;而对于特定的中断用户,Web容器会从HTTP请求中获取Locale信息。
3.1.2 在Web应用中访问Locale对象
在创建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。
3.1.3 在Struts应用中访问Locale对象
有序Web服务器并不和客户浏览器保持长期的连接,因此每个发送到Web容器的HTTP请求中都包含了Locale信息。Struts配置文件的<controller>元素的locale属性指定是否把Locale对象保存在session范围中,默认值为true,表示会把Locale对象保存在session范围中。在处理每一个用户请求时,RequestProcessor类都会调用它的processLocale()方法。
尽管每次发送的HTTP请求都包含Locale信息,processLocale()方法把Locale对象存储在session范围中必需满足以下条件:
• Struts配置文件的<controller>元素的locale属性为true。
• 在session范围内Locale对象还不存在。
processLocale()方法把Locale对象存储在session范围中时,属性key为Globals.LOCALE_KEY,这个常量的字符串值为“org.apache.struts.action.LOCALE”。
如果应用程序允许用户在同一个会话中改变Locale,那么应该对每一个新的HttpServletRequest调用其getLocale()方法,来判断用户是否改变了Locale,如果Locale发生改变,就把新的Locale对象保存在session范围内。
在Struts应用程序中可以很方便地获取Locale信息。例如,如果在Action类中访问Locale信息,可以调用在Struts Action基类中定义的getLocale()方法。
Action类的getLocale()方法调用RequestUtils.getUserLocale()方法。
getUserLocale()方法先通过HttpServletRequest参数获得HttpSession对象,然后再通过HttpSession对象来读取Locale对象。如果存在HttpSession对象并且HttpSession中存储了Locale对象,就返回该Locale对象,否则就直接调用HttpServletRequest的getLocale()方法取得Locale对象,并降它返回。
在Web应用程序的其他地方也可以直接调用RequestUtils类的getUserLocale()方法来获取Locale对象。
3.2 ResourceBundle类
java.util.ResourceBundle类提供存放和管理与Locale相关的资源的功能。这些资源包括文本域或按钮的Label、状态信息、图片名、错误信息和网页标题等。
Struts框架并没有直接使用Java语言提供的ResourceBundle类。在Struts框架中提供了两个类:
• org.apache.struts.util.MessageResources
• org.apache.struts.util.PropertyMesasgeResources
这两个类具有和ResourceBundle相似的功能,其中PropertyMessageResources是MessageResources类的子类。
3.3 MessageFormat类和符合消息。
Java的ResourceBundle和Struts的MessageResources类都允许使用静态和动态的文本。静态文本指定的是实现就已经具有明确内容的文本。动态文本指的是只有在运行时才能确定内容的文本。
通常把包含可变数据的消息成为符合消息。符合消息允许在程序运行时把动态数据加入到消息文本中。这能够减少Resource Bundle中的静态消息数量,从而减少把静态消息文本翻译成其他Locale版本所花费的时间。
当然,在Resource Bundle中使用符合信息会使文本的翻译变得更加困难。因为文本包含了直到运行时才知道的替代值,而把包含替代值的消息文本翻译成不同的语言时,往往要对语言做适当调整。
Struts框架封装了MessageFormat类的功能,支持复合消息文本,该功能的实现对于Struts的其他组件是透明的。
4 Struts框架对国际化的支持
Struts框架对国际化的支持体现在能够输出何用户Locale相符合的文本何图片上。当Struts配置文件的<controller>元素的locale属性为true时,Struts框架把用户的Locale实例保存在session范围内,这样,Struts框架能自动根据这一Lcoale实例来从Resource Bundle中选择合适的资源文件。当用户的Locale为英文时,Struts框架就会向用户返回来自于application_en.properties文件的文本内容:当用户的Locale为中文时,Struts框架就会向用户返回来自于appcation_ch.properties文件的文本内容。
4.1 创建Struts的Resource Bundle
对于多应用模块的Struts应用,可以为每个子应用配置一个或多个Resource Bundle,应用模块中的Action、ActionForm Bean、JSP页和客户化标签都可以访问这些Bundle。Struts配置文件中的每个<message-resources>元素定义了一个Resource Bundle。当应用中包含多个Resource Bundle时,它们通过<message-resources>元素的key属性来区别。
Resource Bundle的持久化消息文本存储在资源文件中,其扩展名为“.properties”,这一文件中消息的格式为:key=value。
在创建Resource Bundle的资源文件时,可以先提供一个默认的资源文件,默认资源文件应该取名为application.properties。如果应用程序需要支持中文用户,可以再创建一个包含中文消息的资源文件,文件名为:application_ch_CH.properties或application_ch.properties。
当Struts框架处理Locale为中文的用户请求时,Struts框架首先在WEB-INF/classes/目录下寻找application_ch_CH.properties文件,如果存在该文件,就从该文件中获取文本消息,否则再一次寻找application_ch.properties和application.properties文件。
应该总是为Resource Bundle提供默认的资源文件,这样,当不存在和某个Locale对应的资源文件时,就可以使用默认的资源文件。
应该把Resource Bundle的资源文件放在能被定位并加载的位置。对于Web应用程序,资源文件的存放目录为WEB-INF/classes目录。如果在配置Resource Bundle时还给定了包名,那么包名应该和资源文件所在的子目录对应。
4.2 访问 Resource Bundle
Struts应用的每个Resource Bundle和org.apache.struts.util.MessageResources类(实际上是其子类PropertyMessageResources)的一个实例对应。MessageResources对象中存放了来自资源文件的文本。当应用程序初始化时,这些MessageResources实例被存储在ServletContext中(即application范围内),因此任何一个Web组件都可以访问它们。一个MessageResources对象可以包含多种本地化版本的资源文件的数据。
Struts应用、子应用模块、Resource Bundle和资源文件之间存在以下关系:
• 一个Struts应用可以有多个子应用模块,必须有且只有一个默认子应用模块。
• 一个子应用模块可以有多个Resource Bundle,必须有且只有一个默认的Resource Bundle。
• 一个Resource Bundle可以有多个资源文件,必须有且只有一个默认资源文件。
下面介绍在Struts应用中访问Resource Bundle的途径。
<!--[if !supportLists]-->1. <!--[endif]-->通过编程来访问Resource Bundle
在Action积累中定义了getResources(request)方法,它可以返回默认的Message Resources对象,代表当前应用模块使用的默认Resource Bundle。如果要活得特定的MessageResources对象,可以调用Action基类的getResources(request, key)方法,其中参数key和Struts配置文件中的<message-resources>元素的key属性对应。得到了MessageResources对象后,就可以通过它的方法来访问消息文本。
Org.apache.struts.util.MessageResources的getMessage()方法有好几种重载形式,下面列出常用的几种:
• 根据参数置顶的Locale检索对应的资源文件,然后返回和参数key对应的消息文本:
getMessage(java.util.Locale locale, java.lang.String key)
• 根据参数指定的Locale检索对应的资源文件,然后返回和参数key对应的消息文本,args参数用于替换复合消息文本中的参数:
getMessage(java.util.Locale locale, java.lang.String key, java.lang.Object[] args)
• 根据默认的Locale检索对应的资源文件,然后返回和参数key对应的消息文本:
getMessaeg(java.lang.String key)
<!--[if !supportLists]-->2. <!--[endif]-->使用和Resource Bundle绑定的Struts组件
Struts框架中的许多内在组件和Resource Bundle是绑定在一起的,如:
• ActionMessage类和<html:errors>标签。
每个ActionMessage实例代表Resource Bundle中的一条消息。在调用ActionMessage的构造方法时,需要传递消息key。
对于复合消息,在创建ActionMessage对象时,可以调用带两个参数的构造方法:ActionMessage(java.lang.String key, java.lang.Object[] values),values参数用户替换复合消息中的参数。
在JSP页面中,使用<html:errors>标签,就能读取并显示ActionErrors集合中所有ActionMessage对象包含的消息文本。
• Struts Bean标签库的<bean:message>标签。
Struts框架包含了一些可以访问应用的消息资源的客户化标签,其中使用最频繁的一个标签为<bean:message>标签。<bean:message>标签从应用的Resource Bundle中获取消息字符串。
<bean:message>有一个bundle属性,用于指定被访问的Resource Bundle,它和<message-resources>元素的key属性匹配。如果没有设置bundle属性,将访问默认的Resource Bundle。
• 在Validator验证框架中访问Resource Bundle。
• 在声明型异常处理中访问Resource Bundle。
5 异常处理的国际化
在处理异常时,也应该考虑对I18N的支持。除非已经对应用抛出的异常消息做本地化,否则不应该直接向终端用户展示原始的异常消息。不懂Java语言的终端用户很难理解从Java虚拟机堆栈中抛出的Java异常消息,如果这些异常消息使用的不是用户的本地语言,那就更加会让用户困惑不解。
应该先把异常捕获,对异常消息进行本地化后,再把它展示给用户。Struts的Resource Bundle和ActionMessage类可以完成这一功能。直接向终端用户显示Java异常绝对是不可取的。即使发生了无法恢复的系统错误,也应该向用户显示本地化的系统错误页。
6 小结
本文档介绍了软件的本地化与国际化的概念,然后消息介绍了对Struts应用实现国际化的原理和方法。与国际化密切相关的两个组件是Locale和Resource Bundle:
• Locale:包含了用户的本地化信息,如语言和国家。
• Resource Bundle:包含了多个消息资源文件,每个消息资源文件存放和一种Locale相对应的本地化消息文本。
Struts框架的初始化时,把Resource Bundle(即MessageResources对象)存储在application范围内;在响应用户请求时,把包装用户Locale信息的Locale实例存储在session范围内。Struts框架能自动根据这一Locale实例,从Resource Bundle中检索相应的资源文件,再从资源文件中读取本地化的消息文本。
对Struts应用实现国际化应该遵循以下原则:
• 尽量不在Servlet中使用含非英文字符的常量字符串。
• 对于JSP文件,应该对page指令中的charset属性进行相应的设置。
• 不要在JSP文件中直接包含本地化的消息资源,儿应该把消息资源存放在Resource Bundle的资源文件中。
• 不要在每个JSP或Servlet中设置HTTP请求的字符编码,可以在Servlet过滤器中设置编码:
HttpServletRequest.setCharaterEncoding(String encoding);
• 尽量使用“UTF-8”作为HTTP请求和响应的字符编码,而不是“GBK”或“GB2312”。
• 充分考虑底层数据库所使用的编码,它可能会给应用程序的移植带来麻烦。