概述
我们之前梳理过Spring相关的国际化的知识点,如下
Spring-国际化信息02-MessageSource接口
在这里,我们将国际化与Spring MVC结合起来,看SpringMVC如何整合国际化(其实03中已经阐述了)。
这里我们来重新看下
概述
概括的来讲,我们需要了解两个术语
- 国际化,即我们常讲的i18n (internationalization 以i开头n结尾,中间有18个字母)
- 本地化,即我们常讲的L10N(localization,中间的 10 代表在首字母“L”和尾字母“N”之间省略了 10 个字母) 。这是将国际化应用程序改成支持特定语言区域(locale)的技术。 举个例子:同样是日期,2018年02月27日 , 美国显示为02/27/2018, 澳大利亚则为27/02/2018 , 中国就是2018/02/27。
Java为字符和字符串提供了unicode支持,因此使用Java编写国际化的应用程序是一件很容易的事情。
国际化应用程序的具体方式取决于有多少静态数据需要以不同的语言显示出来,一般来讲
- 如果大量数据都是静态的,就要针对每一个语言区域单独创建一个资源版本,这种一般适用于带有大量静态HTML页面的Web应用程序。这个很简单,我们不讨论这个.
- 如果需要国际化的静态数据量有限,就可以将文本元素,比如元件标签和错误消息隔离成文本文件。每个文本文件中都保存着一个语言区域的所有文本元素译文。 随后,应用程序会自动获取每一个元素,这样做的优势是显而易见的。我们这里讨论是这种场景。
国际化SpringMVC应用程序
国际化和本地化应用程序时,需要具备以下条件:
1. 将文本元文件隔离成属性文件
2. 选择和读取正确的属性文件
将文本元件隔离成属性文件
被国际化的应用程序是将每一个语言区域的文本元素都单独保存在一个独立的属性文件中。 每个文件中都包含key/value对,并且每个key都是唯一标示一个特定语言区域的对象 。
key始终是字符串,value则可以是字符串,也可以是其他任意类型的对象。
为了支持美国英语、汉语,就要有2个属性文件,他们都有着相同的key.
比如英语版本
greetings=hello farewell=goodbye
汉语版本
greetings=\u4F60\u597D farewell=\u518D\u89C1
汉语中的属性文件value,汉字需要转换为Unicode码, 一般IDE都会自带这种转换功能。我们直接输入汉字,就可以直接得到对应的Unicode码了。
接下来我们要学习java.util.ResourceBundle ,
详见 http://blog.csdn.net/yangshangwei/article/details/76946002#t8
ResourceBundle能够轻松的选择和读取特定用户语言区域的属性,以及查找值。 ResourceBundle是一个抽象类,但它提供了静态的getBundle方法,以返回一个具体子类的实例。
ResourceBundle有一个基准名,它可以是任意名称。 但为了让ResourceBundle正确的选择属性文件,这个文件名中最好必须包含基准名ResourceBundle,后面再接下划线、语言码,还可以选择再加一条下划线和国家码。
basename_languageCode_countryCode
假设基准名为MyResource, 并且定义了2个语言区域
- US-en
- CN-zh
那么,就会得到如下2个属性文件
- MyResource_en_US.properties
- MyResource_zh_CN.properties
选择和读取正确的属性文件
如前所述,虽然ResourceBundle是一个抽象类,但是它提供了静态的getBundle方法来获取一个ResourceBundle实例
比如
如果没有找到合适的属性文件,ResourceBundle对象就会返回到默认的属性文件, 默认的属性文件为基准名加上一个扩展名properties. 如果默认文件也没有找到,则将抛出java.util.MissingResourceException.
随后读取值,利用getString方法即可,如果未找到指定的key,则将抛出java.util.MissingResourceException.
但在SpringMVC中,我们不直接使用ResourceBundle,而是利用messageSource bean来告诉Spring MVC要将属性文件保存在哪里
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> <property name="basenames" > <list> <value>/WEB-INF/resource/messages</value> <value>/WEB-INF/resource/labels</value> </list> </property> </bean>
上面的bean定义中用ReloadableResourceBundleMessageSource类作为实现, 另外一个是ResourceBundleMessageSource,但是ResourceBundleMessageSource不能重新加载,这意味着如果有任何属性文件中修改了某一个属性key或者value,并且正在使用ResourceBundleMessageSource,那么要使生效的话,就必须要重启JVM。
<bean id="resource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> <property name="basenames" ref="resourceList"/> <!-- 刷新资源文件的周期,以秒为单位 --> <property name="cacheSeconds" value="5"/> </bean> <util:list id="resourceList"> <value>i18n/fmt_resource</value> </util:list>
这两个实现之间的另外一区别是: ReloadableResourceBundleMessageSource是在应用程序目录下搜索这些属性文件,而使用ResourceBundleMessageSource,属性文件则必须放在类路径下,即WEB-INF/class目录下。
如果只有一组属性文件,则可以使用basename属性代替basenames
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> <property name="basename" > <list> <value>/WEB-INF/resource/messages</value> </list> </property> </bean>
告诉Spring MVC使用哪个语言区域
为用户选择语言区域时,最常用的方法或许是通过读取用户浏览器的accept-language标题值。 accept-language标题提供了用户偏好哪种语言的信息.
选择语言区域的其他方法还包括读取某个session属性或者cookie。
在Spring MVC中选择语言区域,可以使用语言解析器Bean,它包括几个实现,如下
AcceptHeaderLocaleResolver
SessionLocaleResolver
CookieLocaleResolver
这些实现都是org.springframework.web.servlet.i18n包的组成部分。 AcceptHeaderLocaleResolver或许是最容易使用的一个。
如果使用AcceptHeaderLocaleResolver这个语言区域解析器,Spring MVC将会读取浏览器的accept-language标题,来确定浏览器接受哪个语言区域. 如果与应用程序支持的语言匹配,这就会使用这个语言区域,否则就会使用默认的语言区域。
下面是使用AcceptHeaderLocaleResolver的localeResolver bean定义
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver"> </bean>
使用message标签
在Spring MVC中显示本地化消息的最容易方法就是使用Spring的message标签。
为了使用message标签,需要在使用该标签的所有JSP页面最前面声明这个taglib指令
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
message标签属性如下,均是可选项
Demo
Domain类
package com.artisan.domain; import java.io.Serializable; import javax.validation.constraints.Size; import org.hibernate.validator.constraints.NotBlank; public class Product implements Serializable { private static final long serialVersionUID = 78L; @NotBlank @Size(min=1, max=10) private String name; private String description; private Float price; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public Float getPrice() { return price; } public void setPrice(Float price) { this.price = price; } }
控制层
package com.artisan.controller; import javax.validation.Valid; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import com.artisan.domain.Product; @Controller @RequestMapping("/product") public class ProductController { private static final Log logger = LogFactory.getLog(ProductController.class); @RequestMapping(value="/product_input") public String inputProduct(Model model) { model.addAttribute("product", new Product()); return "ProductForm"; } @RequestMapping(value="/product_save") public String saveProduct(@Valid @ModelAttribute Product product, BindingResult bindingResult, Model model) { // 校验 if (bindingResult.hasErrors()) { FieldError fieldError = bindingResult.getFieldError(); logger.info("Code:" + fieldError.getCode() + " ,field:" + fieldError.getField()); return "ProductForm"; } // save product here model.addAttribute("product", product); return "ProductDetails"; } }
Spring MVC配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 扫描控制层的注解,使其成为Spring管理的Bean --> <context:component-scan base-package="com.artisan.controller" /> <!-- 静态资源文件 --> <mvc:annotation-driven /> <mvc:resources mapping="/css/**" location="/css/" /> <mvc:resources mapping="/*.jsp" location="/" /> <!-- 视图解析器 --> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/" /> <property name="suffix" value=".jsp" /> </bean> <!-- 国际化资源文件 --> <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> <property name="basenames"> <list> <value>/WEB-INF/resource/messages</value> <value>/WEB-INF/resource/labels</value> </list> </property> <!-- 如果在国际化资源文件中找不到对应代码的信息,就用这个代码作为名称 --> <property name="useCodeAsDefaultMessage" value="true" /> </bean> <bean id="localeResolver" class="org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver"> </bean> </beans>
这里用到了messageSource 和 localeResolver 这两个bean。 messageSource 声明用两个基准名设置了basenames属性 /WEB-INF/resource/messages 和 /WEB-INF/resource/labels 。 localeResolver 利用 AcceptHeaderLocaleResolver类实现消息的本地化。
我们支持en和zh两种语言区域,因此属性文件都有两个版本,除此之外我们还添加了当两种都找不到时的默认语言区域的版本。
为了实现本地化,JSP页面中的每一段文本都要用message标签代替。
为了方便查看,我们将当前语言区域和accept-language标题显示在页面的最上方
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <!DOCTYPE HTML> <html> <head> <title><spring:message code="page.productform.title"/></title> <style type="text/css">@import url("<c:url value="/css/main.css"/>");</style> </head> <body> <div id="global"> <!-- 为方便查看,这里打印出来当前的语言和accept-language --> <!-- 为防止编译报错,pom中需要加入jsp-api依赖--> Current Locale : ${pageContext.response.locale} <br/> accept-language header: ${header["accept-language"]} <br/> <form:form commandName="product" action="product_save" method="post"> <fieldset> <legend><spring:message code="form.name"/></legend> <p> <label for="name"><spring:message code="label.productName" text="default text" />:</label> <form:input id="name" path="name" cssErrorClass="error"/> <form:errors path="name" cssClass="error"/> </p> <p> <label for="description"><spring:message code="label.description"/>: </label> <form:input id="description" path="description"/> </p> <p> <label for="price"><spring:message code="label.price" text="default text" />: </label> <form:input id="price" path="price" cssErrorClass="error"/> </p> <p id="buttons"> <input id="reset" type="reset" tabindex="4" value="<spring:message code="button.reset"/>"> <input id="submit" type="submit" tabindex="5" value="<spring:message code="button.submit"/>"> </p> </fieldset> </form:form> </div> </body> </html>
测试
Accept-Language说明 :https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Accept-Language
指令
<language> 用含有两到三个字符的字符串表示的语言码。
<locale> 完整的语言标签。除了语言本身之外,还会包含其他方面的信息,显示在中划线("-")后面。最常见的额外信息是国家或地区变种(如"en-US")或者表示所用的字母系统(如"sr-Lat")。其他变种诸如拼字法("de-DE-1996")等通常不被应用在这种场合。
* 任意语言;"*"表示通配符。
;q= (q-factor weighting) 值代表优先顺序,用相对质量价值 表示,又称为权重。
源码
代码已提交到github
https://github.com/yangshangwei/SpringMvcTutorialArtisan