利用maven生成了appfuse web 的ssh框架里面生成了一堆东西,调查了下
<context-param>
<param-name>javax.servlet.jsp.jstl.fmt.localizationContext</param-name>
<param-value>ApplicationResources</param-value>
</context-param>
<!-- Fallback locale if no bundles found for browser's preferred locale -->
<!-- Force a single locale using param-name 'javax.servlet.jsp.jstl.fmt.locale' -->
<context-param>
<param-name>javax.servlet.jsp.jstl.fmt.fallbackLocale</param-name>
<param-value>zh_CN</param-value>
</context-param>
<context-param>
<param-name>javax.servlet.jsp.jstl.fmt.locale</param-name>
<param-value>zh_CN</param-value>
</context-param>
因为希望把SpringSide搞成国际化项目,i18n就成了必做的事情。
照抄appfuse,折腾了很久后才发现appfuse式的sample总是只顾着演示自己的一亩三分地而忽略了很多其他东西。
1.从基础开始,没有Spring时,Java的i18n是这样的:
1.1 jsp环境
首先写一个messages.zh_CN.properties文件,放在class-path也就是/WEB-INF/classes里 welcome=欢迎 然后用native2ascii.exe把它转为 welcome=\u6b22\u8fce
在web.xml中定义messages文件 <context-param>
<param-name>javax.servlet.jsp.jstl.fmt.localizationContext</param-name>
<param-value>messages</param-value>
</context-param>
最后在jsp里使用
<%@ taglib uri="http://java.sun.com/jstl/fmt" prefix="fmt" %>
<fmt:message key="welcome"/>
如果有多个Resource Bundle文件, 就要在jsp里用<ftm:bundle>定义了.
1.2 pure Java环境
ResourceBundle rb = ResourceBundle.getBundle("messages");
String welcome = rb.getString("welcome");
2.Spring的增强及appfuse的做法
Spring增加了MessageSource的概念,一是ApplicationContext将充当一个单例的角色,不再需要每次使用i18时都初始化一次ResourceBundle,二是可以代表多个Resource Bundle.
在ApplicationContext的定义文件中,增加如下节点:
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="messages"/>
</bean>
则在pure java环境中。 context.getMessage("welcome", null, Locale.CHINA)
而在jsp环境中,Controller调用JSTL viewResolver再调用Jsp时,<fmt:message>将继续发挥它的功效。
因此,appfuse等sample都是在appfuse-servlet.xml 中定义一个<messageSource>。
3.Better Practice
3.1 要不要定义javax.servlet.jsp.jstl.fmt.localizationContext
Appfuse等sample,都是假定大家完全使用Controller作访问入口,jsp甚至藏在了/web-inf/中。而很不幸,大家的项目可能还是有很多直接访问jsp的地方,而直接访问jsp时,<messageSource>节点是没有作用的。
但如果定义了javax...localizationContext, 又会让MessageSource失效......
3.2 messageSource定义在ApplicationContext.xml还是appfuse-servlet.xml
ApplicationContext*.xml由ContextLoaderListener载入,而appfuse-servlet.xml靠dispatchServlet载入,并拥有一个指向ApplcationContex*.xml指针。所以,appfuse-servlet.xml能看到定义在ApplcationContext里的东西,而反之做不到。
明显, 把<messageSource>定义在ApplicationContext.xml 能获得更好的可见性。
但是appfuse没有在pure Java代码中使用i18n,也就没有考虑这个问题。
3.3 坚决不用鸡肋级<spring:message> tag
连appfuse也不用它,可见多么鸡肋。因为fmt在找不到资源时,最多显示???welcome???,而<spring:message>则会抛出异常,谁会喜欢这种定时炸弹阿。
3.4 有趣的theme 解决"做成图片的文字"的国际化
theme也就是把message的原理发挥了一下,让不同语言的美术字图片的路径也可以定义在theme_zh_CN.properties和theme_en_US.properties中。终于有一个不那么鸡肋的spring tag了。
4.简单归纳
1. jstl中仍然使用标准的<ftm:message>及其定义?
2.java中使用spring的<messageSource>实现单例
3.用<spring:theme>解决那些做成图片的文字的国际化问题
4.Spring 还有session,cookie locale resolver, 到时可以看一下.
在struts2中需要做国际化的有:
jsp页面的国际化,action错误信息的国际化,转换错误信息的国际化,校验错误信息的国际化
在之前的例子中已经做过和国际化相关的例子了,在struts.xml中配置过
<constant name="struts.custom.i18n.resources" value="message"></constant>
其中的message就是国际化资源文件的baseNmae。
我们先看看简单的在jsp中进行国际化
在src目录下新建message_en_US.properties,内容为
hello=add user
新建message_zh_CN.properties文件,内容为
hello=\u589e\u52a0\u7528\u6237
然后修改register2.jsp
要想使用国际化显示,可以将信息添加到<s:text></s:text> 标签中,也可以放在<s:i18n></s:i18n> 中,
在这里,先使用标签 <s:text></s:text>
增加以下内容:
<s:text name="hello"></s:text>
重启服务器后,浏览该页,默认会显示出“增加用户”,可以在IE中打开Internet选项,在常规中选择语言,增加英语(美国)[en-US],然后设置为第一项,刷新就可以看到输出“add user”。
action错误的国际化
在message_en_US.properties中增加以下内容
username.invalid=username invalid...
在message_zh_CN.properties中增加以下内容
username.invalid=\u7528\u6237\u540d\u4e0d\u5408\u6cd5...
修改RegisterAction中的validate方法,将错误加到ActionError中,在这里将使用到ActionSupport中的getText方法获得和国际化资源文件相关的信息。
以username验证为例:
if (null == username || username.length() < 5 || username.length() > 10) { this.addActionError(this.getText("username.invalid")); }这样就从资源文件中读取username.invalid的值,增加到ActionError中。
查看该页面不输入任何数据,提交后就可以看到显示效果了。
验证框架的国际化(field级别错误)
在message_en_US.properties文件中增加以下内容
username.xml.invalid=validate information
在message_zh_CN.properties文件中增加以下内容
username.xml.invalid=\u9a8c\u8bc1\u6846\u67b6\u4fe1\u606f
然后修改验证框架,需要将在properties文件中的内容增加到框架中。
以username为例
<field name="username"> <field-validator type="requiredstring"> <param name="trim">true</param> <message key="username.xml.invalid"></message> </field-validator> </field>
在message标签中增加属性key,值为properties文件中的key
标签中key大多是和国际化相关的
国际化资源文件的分类
当应用程序很大时,需要国际化的东西会很多,因此需要将国际化资源文件进行分类。
需要知道的是在src中的properties文件是全局资源文件,另外还可以分为包级别的和类级别的
首先看看包级别的
命名规则为package_language_country.properties
新建package_en_US.properties,内容为
username.xml.invalid=package validate information
新建package_zh_CN.properties,内容为
username.xml.invalid=\u5305\u9a8c\u8bc1\u4fe1\u606f
可以看到输出的信息为“包验证信息”,由此可见包级别的国际化资源文件的优先级高于全局国际化资源文件。
类级别
新建RegisterAction_en_US.properties,内容为
username.xml.invalid=class validate information
新建RegisterAction_zh_CN.properties,内容为
username.xml.invalid=\u7c7b\u9a8c\u8bc1\u4fe1\u606f
此时可以看到输出的信息为“类验证信息”。
由此可以得到国际化资源文件的优先级
全局<包级别<类级别
另外要进行表单的国际化时,要去掉theme="simple"
在RegisterAction_en_US.properties中增加
username.name=username
在RegisterAction_zh_CN.properties中增加
username.name=\u7528\u6237\u540d
修改表单标签
<s:textfield name="username" key="username.name"></s:textfield>
注意到key一般是和国际化相关的。
另外除了用<s:text>这个标签外,还可以使用<s:i18n>这个标签
<s:i18n name="temp"></s:i18n>
标签中包含name,代表着可以定义资源文件的baseName,如可以定义成temp,那么对应着
temp_en_US.properties和temp_zh_CN.properties这两个资源文件。
如定义
<s:i18n name="hello"> <s:text name="world"> <s:param>struts2</s:param> </s:text> </s:i18n>
注意到可以在<s:text>标签中增加<s:i18n> 标签。
在hello_en_US.properties文件中增加
world=hello {0}
hello_zh_CN.properties中增加
world=\u4f60\u597d,struts2
在struts2的默认拦截器栈中已经定义了i18n拦截器,所以struts2已经是一个国际化的框架了。
struts2会查找从客户端提交的request_locale属性,并存到session中的WW_TRANS_I18N_LOCALE字段
中。
这个<s:text> 标签外,还可以使用<s:i18n> 这个标签 <s:i18n name="temp"></s:i18n>
总结一下显示方法:
<s:textname="hello"></s:text>
getText("username.invalid")
<message key="username.xml.invalid"></message>
<s:textfield name="username" key="username.name"></s:textfield>
<s:i18n name="temp"></s:i18n>