Spring MVC -- 国际化

在全球化的今天,现在比过去更需要编写可以在不同语言国家和地区部署的应用程序。在这方面,需要了解两个术语:

  • 国际化:常常缩写为i18n,因为其单词internationalization以i开始,以n结尾,在它们中间有18个字母。国际化是开发支持多语言和数据格式的应用程序的技术,它使得页面能够根据访问者的语言或国家来呈现不同的翻译版本。
  • 本地化:常常缩写为l10n,这是将国际化应用程序改成支持特定语言区域(locale)的技术。语言区域是指一个特定的地理、政治或者文化区域,要考虑到语言区域的一个操作,就称作区分语言区域的操作。例如,显示日期就是一个区分语言区域的操作,因为日期必须以用户所在的国家或者地区使用的格式显示。2016年11月15,在美国显示为11/15/2016,但是在澳大利亚则显示为15/11/2016。

Java谨记国家互的需求,为字符和字符串提供了Unicode支持。因此,用Java编写国际化的应用程序是一件很容易的事情。国际化应用程序的具体方式取决于有多少静态数据需要以不同的语言显示出来。这里有两种方法:

  • 如果大量数据时静态的,就要针对每一个语言区域单独创建一个资源版本。这种方法一般适用于带有大量静态HTML页面的Web应用程序。这个比较简单,本篇博客不做讨论。
  • 如果需要国际化的静态数据有限,就可以将文本元素,如HTML标签内容和错误消息隔离为文本文件。每个文本文件中都保留着一个语言区域的所有文本元素译文。随后,应用程序会自动获取每一个元素。这将是本篇博客的重点。

本篇博客首先解释什么是语言区域,接着讲解国际化应用程序技术,最后介绍一个Spring MVC范例。

一 语言区域

1、java.util.Locale类

每一个Locale对象都代表了一个特定的地理、政治和文化地区。在操作 Date, Calendar等表示日期/时间的对象时,经常会用到;因为不同的区域,时间表示方式都不同。一个Locale对象包含3个主要参数:language、country和variant:

  • language是最重要的部分;但是语言本身有时并不足以区分一个语言区域。例如,说英语的国家很多,如美国和英国。但是在美国说的英语,与在英国用的英语并非一模一样。因此,需要指定语言国家。
  • variant是一个特定于供应商或者特定于浏览器的代号。例如,用WIN表示Windows,用MAC表示Macintosh,用POSIX表示POSIX。两个variant之间用一个下划线隔开,并将最重要的部分放在最前面。例如,传统西班牙语,用language、country和variant参数构造一个locale分别是es、ES、Traditional_WIN。

Locale的构造函数共有3个:

public Locale(String language)
public Locale(String language, String country)
public Locale(String language, String country, String variant)

参数language是一个有效的ISO语言码。下表显示了ISO 639语言码范例:

代码语言
de德语
el希腊语
en英语
es西班牙语
fr法语
hi印地语
it意大利语
ja日语
nl荷兰语
pt葡萄牙语
ru俄语
zh汉语

参数country是一个有效的ISO国家码,由两个字母组成,ISO 3166中指定为大写字母。下表展示了ISO 3166国家码范例:

国家代码
澳大利亚AU
巴西BR
加拿大CA
中国CN
埃及EG
法国FR
德语DE
印度IN
墨西哥MX
瑞士CH
英国GB
美国US

Locale类支持非常多的国家和地区。我们可以通过以下方法,查看Locale支持的全部区域:

 Locale[] ls = Locale.getAvailableLocales();
 
    System.out.print("All Locales: ");
    for(Locale locale:ls) {
      System.out.printf(locale+", ");
    }

输出结果如下:

All Locales: , ar_AE, ar_JO, ar_SY, hr_HR, fr_BE, es_PA, mt_MT, es_VE, bg, zh_TW, it, ko, uk, lv, da_DK, es_PR, vi_VN, en_US, sr_ME, sv_SE, es_BO, en_SG, ar_BH, pt, ar_SA, sk, ar_YE, hi_IN, ga, en_MT, fi_FI, et, sv, cs, sr_BA_#Latn, el, uk_UA, hu, fr_CH, in, es_AR, ar_EG, ja_JP_JP_#u-ca-japanese, es_SV, pt_BR, be, is_IS, cs_CZ, es, pl_PL, tr, ca_ES, sr_CS, ms_MY, hr, lt, es_ES, es_CO, bg_BG, sq, fr, ja, sr_BA, is, es_PY, de, es_EC, es_US, ar_SD, en, ro_RO, en_PH, ca, ar_TN, sr_ME_#Latn, es_GT, sl, ko_KR, el_CY, es_MX, ru_RU, es_HN, zh_HK, no_NO_NY, hu_HU, th_TH, ar_IQ, es_CL, fi, ar_MA, ga_IE, mk, tr_TR, et_EE, ar_QA, sr__#Latn, pt_PT, fr_LU, ar_OM, th, sq_AL, es_DO, es_CU, ar, ru, en_NZ, sr_RS, de_CH, es_UY, ms, el_GR, iw_IL, en_ZA, th_TH_TH_#u-nu-thai, hi, fr_FR, de_AT, nl, no_NO, en_AU, vi, nl_NL, fr_CA, lv_LV, de_LU, es_CR, ar_KW, sr, ar_LY, mt, it_CH, da, de_DE, ar_DZ, sk_SK, lt_LT, it_IT, en_IE, zh_SG, ro, en_CA, nl_BE, no, pl, zh_CN, ja_JP, de_GR, sr_RS_#Latn, iw, en_IN, ar_LB, es_NI, zh, mk_MK, be_BY, sl_SI, es_PE, in_ID, en_GB, 

下面选择其中的一个进行说明,如何利用它们来创建Locale对象:例如,输出“ja_JP”。其中,ja代表“语言”,这里指日语;“JP”代表国家,这里指日本。

我们可以通过如下方法,创建一个表示中国所用汉语的Locale对象,可以像下面这样编写:

Locale local = new Locale("zh", "CN");

此外,Locale类提供了static final域(编译期常量),用来返回特定国家或语言的语言区域:

public static final Locale CANADA
public static final Locale CANADA_FRENCH
public static final Locale CHINA
public static final Locale CHINESE
public static final Locale ENGLISH
public static final Locale FRANCE
public static final Locale FRENCH
public static final Locale GERMAN
public static final Locale GERMANY
public static final Locale ITALIAN
public static final Locale ITALY
public static final Locale JAPAN
public static final Locale JAPANESE
public static final Locale KOREA
public static final Locale KOREAN
public static final Locale PRC
public static final Locale ROOT
public static final Locale SIMPLIFIED_CHINESE
public static final Locale TAIWAN
public static final Locale TRADITIONAL_CHINESE
public static final Locale UK

因此,也可以通过调用其static于来构造Locale对象,下面的Locale对象是对应 “中国(简体中文)”的

Locale locale = Locale.SIMPLIFIED_CHINESE

此外,静态的getDefault()方法会返回用户计算机的语言区域:

Locale locale = Locale.getDefault()
2、例子

下面通过示例演示在Date中使用Locale的。参考代码如下(LocaleTest.java):

import java.util.Locale;
import java.util.Date;import java.text.DateFormat;
 
/**
 * Locale 的测试程序
 */
public class LocaleTest {
 
  public static void main(String[] args) {
    // 2种不同的Locale的创建方法
    testDiffDateLocales();
 
    // 显示所有的Locales
    testAllLocales();
  }
  
  /**
   * 2种不同的Locale的创建方法
   */
  private static void testDiffDateLocales() {
    // date为2013-09-19 14:22:30
    Date date = new Date(113, 8, 19, 14, 22, 30);
 
    System.out.println(date);
    // 创建“简体中文”的Locale
    Locale localeCN = Locale.SIMPLIFIED_CHINESE;
    // 创建“英文/美国”的Locale
    Locale localeUS = new Locale("en", "US");
 
    // 获取“简体中文”对应的date字符串
    String cn = DateFormat.getDateInstance(DateFormat.MEDIUM, localeCN).format(date);
    // 获取“英文/美国”对应的date字符串
    String us = DateFormat.getDateInstance(DateFormat.MEDIUM, localeUS).format(date);
     
    System.out.printf("cn=%s\nus=%s\n", cn, us);
  }
 
  /**
   * 显示所有的Locales
   */
  private static void testAllLocales() {
    Locale[] ls = Locale.getAvailableLocales();
 
    System.out.print("All Locales: ");
    for(Locale locale:ls) {
      System.out.printf(locale+", ");
    }
    System.out.println();
  }
}

输出如下:

Thu Sep 19 14:22:30 CST 2013
cn=2013-9-19
us=Sep 19, 2013
All Locales: , ar_AE, ar_JO, ar_SY, hr_HR, fr_BE, es_PA, mt_MT, es_VE, bg, zh_TW, it, ko, uk, lv, da_DK, es_PR, vi_VN, en_US, sr_ME, sv_SE, es_BO, en_SG, ar_BH, pt, ar_SA, sk, ar_YE, hi_IN, ga, en_MT, fi_FI, et, sv, cs, sr_BA_#Latn, el, uk_UA, hu, fr_CH, in, es_AR, ar_EG, ja_JP_JP_#u-ca-japanese, es_SV, pt_BR, be, is_IS, cs_CZ, es, pl_PL, tr, ca_ES, sr_CS, ms_MY, hr, lt, es_ES, es_CO, bg_BG, sq, fr, ja, sr_BA, is, es_PY, de, es_EC, es_US, ar_SD, en, ro_RO, en_PH, ca, ar_TN, sr_ME_#Latn, es_GT, sl, ko_KR, el_CY, es_MX, ru_RU, es_HN, zh_HK, no_NO_NY, hu_HU, th_TH, ar_IQ, es_CL, fi, ar_MA, ga_IE, mk, tr_TR, et_EE, ar_QA, sr__#Latn, pt_PT, fr_LU, ar_OM, th, sq_AL, es_DO, es_CU, ar, ru, en_NZ, sr_RS, de_CH, es_UY, ms, el_GR, iw_IL, en_ZA, th_TH_TH_#u-nu-thai, hi, fr_FR, de_AT, nl, no_NO, en_AU, vi, nl_NL, fr_CA, lv_LV, de_LU, es_CR, ar_KW, sr, ar_LY, mt, it_CH, da, de_DE, ar_DZ, sk_SK, lt_LT, it_IT, en_IE, zh_SG, ro, en_CA, nl_BE, no, pl, zh_CN, ja_JP, de_GR, sr_RS_#Latn, iw, en_IN, ar_LB, es_NI, zh, mk_MK, be_BY, sl_SI, es_PE, in_ID, en_GB, 

二 国际化Spring MVC应用程序

 国际化和本地化应用程序时,需要具备以下条件:

  • 将文本元素隔离成属性文件;
  • 选择和读取正确的属性文件;

下面详细介绍这两个步骤,并进行简单的示范。

1、将文本元素隔离成属性文件

国际化的应用程序是将每一个语言区域的文本元素都单独保存在一个独立的属性文件中。每个文件中都包含key/value对,并且每个key都唯一标示一个特定语言区域的对象。key始终是字符串,value则可以是字符串,也可以是其他任意类型的对象。例如,为了支持英语、德语、以及汉语,就要有3个属性文件,它们都有相同的key。

以下是英语版的属性文件。注意:它有greetings和farewell两个key:

greetings=Hello
farewell=Goodbye

德语版的属性文件如下:

greetings=Hallo
farewell=Auf Wiedersehen

汉语版的属性文件如下:

greetings=\u4f60\u597d
farewell=\u518d\u89c1

如果你是中文用户,你可以使用任何中文文本编辑器并写入汉字字符。完成后,将文件转换成Unicode。

现在,要学习java.util.ResourceBundle类。它能使你轻松的选择和读取特定于用户语言区域的属性,以及查找值。ResourceBundle是一个抽象类,但是它提供了静态的getBundle()方法,返回一个具体子类的实例。

ResourceBoundle有一个基准名,它可以是任意名称。但是,为了让ResourceBundle正确的选择属性文件,这个文件名中最好必须包含基准名ResourceBundle,后面再接下划线、语言码,还可以选择再加一条下划线和国家码。属性文件名的格式如下所示:

basename_lanuageCode_countryCode

例如,假设基准名为MyResources,并且定义了以下3个语言区域:

  • DE-de;
  • CN-zh;
  • US-en;

那么就会得到下面这3个属性文件:

  • MyResources_de_DE.properties;
  • MyResources_zh_CN.properties;
  • MyResources_en_US.properties;
2、选择和读取正确的属性文件

如前所述。ResourceBundle是一个抽象类。尽管如此,还是可以通过调用它的静态getBundle()方法来获取一个ResourceBundle实例。它的重载签名如下:

public static final ResourceBundle getBundle(String baseName)
public static final ResourceBundle getBundle(String baseName,Locale locale)

例如:

ResourceBundle rb = ResourceBundle .getBundle("MyResources",Locale.US);

这样就会加载ResourceBundle在相应属性文件中的值。

如果没有找到合适的属性文件,ResourceBundle对象就会返回到默认的属性文件。默认属性文件的名称为基准名加一个扩展名properties。在这个例子中,默认文件就是MyResource.properties。如果没有找到默认文件,将抛出java.util.MissingResourceException。

随后,读取值,利用ResourceBundle类的getString()方法传入一个key。

public  String getString(String key)

如果没有找到指定key的入口,就会抛出出java.util.MissingResourceException。

在Spring MVC中,不直接使用ResourceBundle,而是利用messageSource bean告诉Spring MVC要将属性文件保存在哪里。例如,下面的messageSource 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>
    </bean>

上面的bean定义中用了ReloadableResourceBundleMessageSource 类作为的实现类。另一个实现类是ResourceBundleMessageSource,但是它是不能重新加载的,这意味着,如果在任意属性文件中修改了某一个属性key或者value,并且正在使用ResourceBundleMessageSource,那么要使修改生效,就必须先重启JVM。

这两个实现之间的另一个区别是:使用ReloadableResourceBundleMessageSource时,是在应用程序目录下搜索这些属性文件,而使用ResourceBundleMessageSource时,属性文件则必须放在类路径下,即/WEB-INF/class目录下。

还要注意,如果只有一组属性文件,则可以用basename属性替代basenames,像下面这样:

    <bean id="messageSource"
        class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
        <property name="basename" value="/WEB-INF/resource/messages" />
    </bean>
3、告诉Spring MVC使用哪个语言区域

为用户选择语言区域时,最常用的方法或许是读取用户浏览器的accept-language标题值。accept-language标题值提供了关于偏好哪种语言的信息。

选择语言区域的其它方法还包括读取某个session属性或者cookie。

在Spring MVC中选择语言区域,可以使用语言区域解析器bean。它有几个实现:

  • AcceptHeaderLocaleResolver;
  • CookieLocaleResolver;
  • SessionLocaleResolver;
  • FixedLocaleResolver.;

所有这些实现都是org.springframework.web.server.i18n包的组成部分。AccpetHeaderLocaleResolver或许是其中最容易使用的一个。如果选择使用这个语言区域解析器,Spring MVC将会读取浏览器的accept-language标题,来确定浏览器要接受哪个语言区域。如果浏览器的某个语言区域与Spring MVC应用程序支持的某个语言区域匹配,就会使用这个语言区域。如果没有找到匹配的语言区域,则使用默认的语言区域。

下面是使用AccpetHeaderLocaleResolver的localeResovler bean定义:

    <bean id="localeResolver"
        class="org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver">
    </bean>
4、使用message标签

在Spring MVC中显示本地化消息最容易方法是使用Spring的message标签。为了使用这个标签,要在使用该标签的所有JSP页面最前面声明如下一个taglib指令:

<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>

message标签的属性见下表,所有这些属性都是可选的:

属性描述
arguments该标签的参数写成一个有界的字符串、一个对象数组或者单个对象
argumentSeparator用来分割该标签参数的字符
code获取消息的key
htmlEscape接受true或者false,表示被渲染文本是都应该进行HTML转义
javaScriptEscape接受true或者false,表示被渲染文本是都应该进行JavaScript转义
messageMessageSourceResolvable参数
scope保存var属性中定义的变量的范围
text如果code属性不存在,或者指定码无法获取消息,所显示的默认文本
var用于保存消息的有界变量

三 范例

下面我们将会创建一个i18n应用程序,该应用展示了用localeResovler bean将JSP页面中的消息本地化的方法。

1、目录结构

i18n应用程序目录结构如下图:

2、Product类
package domain;
import java.io.Serializable;
import java.math.BigDecimal;

public class Product implements Serializable {
    private static final long serialVersionUID = 78L;

    private String name;
    private String description;
    private BigDecimal 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 BigDecimal getPrice() {
        return price;
    }
    public void setPrice(BigDecimal price) {
        this.price = price;
    }
}
3、Validator

该应用包含一个名为ProductValidator的验证器:

package validator;

import java.math.BigDecimal;

import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;

import domain.Product;

public class ProductValidator implements Validator {

    @Override
    public boolean supports(Class<?> klass) {
        //支持Product类?
        return Product.class.isAssignableFrom(klass);
    }

    //将目标对象target的错误注册到errors对象中
    @Override
    public void validate(Object target, Errors errors) {
        Product product = (Product) target;
        
      //如果目标对象的name属性为null,或者为""字符串,则将错误注册到errors对象
        ValidationUtils.rejectIfEmpty(errors, "name", "productname.required");
        
        BigDecimal price = product.getPrice();
        //如果价格为负数 则将错误注册到errors对象中
        if (price != null && price.compareTo(BigDecimal.ZERO) < 0) {
            errors.rejectValue("price", "price.negative");
        }
    }
}

ProductValidator验证器是一个非常简单的验证器。它的validate()方法会检验Product是否有名称,并且价格是否不为负数。

4、Controller类

在Controller类中通过实例化validator类,可以使用Spring验证器:

package 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 validator.ProductValidator;
import domain.Product;

@Controller

public class ProductController {
    
    private static final Log logger = LogFactory.getLog(ProductController.class);
    
    @RequestMapping(value="/add-product")
    public String inputProduct(Model model) {
        model.addAttribute("product", new Product());
        return "ProductForm";
    }

    @RequestMapping(value="/save-product")
    public String saveProduct(@ModelAttribute Product product, BindingResult bindingResult,
            Model model) {
        //创建一个ProductValidator,并调用其validate()方法校验Product对象,并将验证错误填入bindingResult中。
        ProductValidator productValidator = new ProductValidator();
        productValidator.validate(product, bindingResult);

        if (bindingResult.hasErrors()) {
            FieldError fieldError = bindingResult.getFieldError();
            logger.debug("Code:" + fieldError.getCode() + ", field:"
                    + fieldError.getField());
            return "ProductForm";
        }
        
        // save product here        
        //model.addAttribute("product", product);
        return "ProductDetails";
    }
    
}

ProductController类的saveProduct()方法,有三个参数:

  • 第一个参数product,使用了注解@ModelAttribute,该对象的各个属性被用来接受表单的各个字段信息,并且将"product"属性添加到Model对象中;
  • 第二个参数bindingResult中设置了Spring所有的绑定错误(主要是类型转换问题,例如将表单String转换为BigDecimal 类型);
  • 第三个参数是Model。

注意:BindingResult接口是Errors接口的子类,在请求处理方法的签名中使用了BindingResult参数,就是告诉Spring关于表单对象数据校验的错误将由我们自己来处理,否则Spring会直接抛出异常。

该方法创建一个ProductValidator,并调用其validate()方法校验Product对象,并将验证错误填入bindingResult中。

ProductValidator productValidator = new ProductValidator();
productValidator.validate(product, bindingResult);

为了检验该验证器是否生成错误消息,需要在BindingResult中调用hasErrors()方法:

 if (bindingResult.hasErrors()) {
            FieldError fieldError = bindingResult.getFieldError();
            logger.debug("Code:" + fieldError.getCode() + ", field:"
                    + fieldError.getField());
            return "ProductForm";
        }

如果存在表单绑定错误或者是输入验证错误,将会打印出错误相关的字段,并重定位到ProductForm.jsp页面。

如果表单输入的数据均合法,则会重定位到ProductDetails.jsp页面。

5、视图

i18n应用包含2个视图文件:

ProductForm.jsp:

<%@ 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">
Current Locale : ${pageContext.response.locale}
<br/>
accept-language header: ${header["accept-language"]}

<form:form modelAttribute="product" action="save-product" 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"/>
            <form:errors path="price" cssClass="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>

为了实现本地化,JSP页面中的每一段文本都使用了message标签代替html中的label标签,如:

 <label for="name"><spring:message code="label.productName" text="default text" />:</label>

同时为了调试,当前的语言区域和accept-language标题显示在页面的最前面:

Current Locale : ${pageContext.response.locale}
<br/>
accept-language header: ${header["accept-language"]}

ProductDetails.jsp:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<title>Save Product</title>
<style type="text/css">@import url("<c:url value="/css/main.css"/>");</style>
</head>
<body>
<div id="global">
    <h4>The product has been saved.</h4>
    <p>
        <h5>Details:</h5>
        Product Name: ${product.name}<br/>
        Description: ${product.description}<br/>
        Price: $${product.price}
    </p>
</div>
</body>
</html>
View Code

 main.css:

#global {
    text-align: left;
    border: 1px solid #dedede;
    background: #efefef;
    width: 560px;
    padding: 20px;
    margin: 100px auto;
}

form {
  font:100% verdana;
  min-width: 500px;
  max-width: 600px;
  width: 560px;
}

form fieldset {
  border-color: #bdbebf;
  border-width: 3px;
  margin: 0;
}

legend {
    font-size: 1.3em;
}

form label { 
    width: 250px;
    display: block;
    float: left;
    text-align: right;
    padding: 2px;
}

#buttons {
    text-align: right;
}
#errors, li {
    color: red;
}
.error {
    color: red;
    font-size: 9pt;    
}
View Code
6、配置文件

部署描述符(web.xml文件):

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.1" 
        xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
            http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"> 
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/config/springmvc-config.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>    
    </servlet>

    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

下面给出springmvc-config.xml文件的所有内容:

<?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">

    <context:component-scan base-package="controller" />
    <mvc:annotation-driven/>
    <mvc:resources mapping="/css/**" location="/css/" />
    <mvc:resources mapping="/*.html" 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>
    </bean>

    <bean id="localeResolver"
        class="org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver">
    </bean>
</beans>

这里用到了messageSource  bean和localeResolver  bean:

  • messageSource  bean:声明用两个基准名设置了basenames属性:/WEB-INF/resource/messages和/WEB-INF/resource/labels。
  • localeResolver  bean:利用AcceptHeaderLocaleResolver类实现消息的本地化;

Spring MVC通过读取浏览器的accept-language标题,来确定浏览器接受哪个语言区域,然后再结合基准名查找该语言区域对应的属性文件,如果有,则使用语言区域。如果没有,则使用默认的语言区域。

该应用支持en、fr、zh三种语言区域,此外,还有一个默认的语言区域(和en语言区域一样),因此属性文件有四种版本。为了实现本地化,JSP页面中的每一段文本都使用了message标签代替。具体可以查看ProductForm.jsp文件。

7、属性文件

将属性文件分为两类:

  • messages_languageCode_countryCode.property:该类属性文件用来设置表单数据绑定的错误,主要包括类型转换错误、以及数据合法性验证错误;通过与Spring的表单errors标签结合,用来显示错误消息;
  • labels_languageCode_countryCode:该类属性文件用来设置Spring的message标签显示的内容;

messages_en.properties(英语):

productname.required=Please enter a name
price.negative=Invalid price

messages_fr.properties(法语):

productname.required=S'il vous pla�t entrer un nom de produit
price.negative=Valeur invalide

messages_zh.properties(汉语):

productname.required=\u4EA7\u54C1\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
price.negative=\u5546\u54C1\u4EF7\u683C\u4E0D\u80FD\u4E3A\u8D1F\u6570

messages.properties(默认):

productname.required.product.name=Please enter a name
price.negative=Invalid price

labels_en.properties(英语):

label.productName=Product Name
label.description=Description
label.price=Price
button.reset=Reset
button.submit=Add Product
form.name=Product Form
page.productform.title=New Product

labels_fr.properties(法语):

label.productName=Nom du Produit
label.description=Description
label.price=Prix
button.reset=Réinitialiser
button.submit=Ajouter Produit
form.name=Formulaire de Produit
page.productform.title=Formulaire

labels_zh.properties(汉语):

label.productName=\u4EA7\u54C1\u540D\u79F0
label.description=\u63CF\u8FF0
label.price=\u4EF7\u683C
button.reset=\u91CD\u7F6E
button.submit=\u589E\u52A0\u4EA7\u54C1
form.name=\u4EA7\u54C1\u8868\u5355
page.productform.title=\u65B0\u589E\u4EA7\u54C1

labels.properties(默认):

label.productName=Product Name
label.description=Description
label.price=Price
button.reset=Reset
button.submit=Add Product
form.name=Product Form
page.productform.title=New Product
8、测试

为了测试应用程序,要修改浏览器的accept-language标签。

对于Chrome浏览器,打开”设置“页面,点击“显示高级设置”,点击“语言和输入设置”,添加并移动语言到列表的顶部。

将应用程序部署到tomcat服务器,访问以下URL:

http://localhost:8008/i18n/add-product

你会看到Product的中文版:

修改accept-language标签,将英语移动到列表的顶部,再次刷新页面:

修改accept-language标签,将法语移动到列表的顶部,再次刷新页面:

 

注意:该应用的所有文件都采用UTF-8编码。

参考文章

[1]详解Java中用于国际化的locale类

[2]java中的编码问题

[3]Spring MVC学习指南

[4]SpringMVC国际化探索

转载于:https://www.cnblogs.com/zyly/p/10873703.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值