Spring MVC-08循序渐进之国际化(AcceptHeaderLocaleResolver)

2021-08-17 10:36:37 浏览数 (1)

  • 概述
  • 概述
  • 国际化SpringMVC应用程序
    • 将文本元件隔离成属性文件
    • 选择和读取正确的属性文件
  • 告诉Spring MVC使用哪个语言区域
  • 使用message标签
  • Demo
  • 测试
  • 源码

概述

我们之前梳理过Spring相关的国际化的知识点,如下

Spring-国际化信息01-基础知识

Spring-国际化信息02-MessageSource接口

Spring-国际化信息03-容器级的国际化信息资源

在这里,我们将国际化与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.

比如英语版本

代码语言:javascript复制
greetings=hello
farewell=goodbye

汉语版本

代码语言:javascript复制
greetings=u4F60u597D
farewell=u518Du89C1

汉语中的属性文件value,汉字需要转换为Unicode码, 一般IDE都会自带这种转换功能。我们直接输入汉字,就可以直接得到对应的Unicode码了。

接下来我们要学习java.util.ResourceBundle , 详见 http://blog.csdn.net/yangshangwei/article/details/76946002#t8

ResourceBundle能够轻松的选择和读取特定用户语言区域的属性,以及查找值。 ResourceBundle是一个抽象类,但它提供了静态的getBundle方法,以返回一个具体子类的实例。

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

代码语言:javascript复制
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要将属性文件保存在哪里

代码语言:javascript复制
<bean id="messageSource"
        class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
        <property name="basenames" >
            <list>
                <value>/WEB-INF/resource/messagesvalue>
                <value>/WEB-INF/resource/labelsvalue>
            list>
        property>
    bean>

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

代码语言:javascript复制
    <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_resourcevalue>
    util:list>

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

如果只有一组属性文件,则可以使用basename属性代替basenames

代码语言:javascript复制
<bean id="messageSource"
        class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
        <property name="basename" >
            <list>
                <value>/WEB-INF/resource/messagesvalue>
            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定义

代码语言:javascript复制
<bean id="localeResolver"
        class="org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver">
    bean>

使用message标签

在Spring MVC中显示本地化消息的最容易方法就是使用Spring的message标签。

为了使用message标签,需要在使用该标签的所有JSP页面最前面声明这个taglib指令

代码语言:javascript复制
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>

message标签属性如下,均是可选项

属性

描述

arguments

该标签的参数写成一个有界的字符、一个对象数组或者单个对象

argumentSeparator

用来分隔该标签参数的字符

code

获取消息的key

htmlEscape

接受True或者False,表示被渲染文本是否应该进行HTML转义

JavaScriptEscape

接受True或者False,表示被渲染文本是否应该进行JavaScript转义

message

MessageSourceResolvable参数

scope

保存var属性中定义的变量的范围

text

如果code属性不存在,或者指定码无法获取消息时,所显示的默认文本

var

用来保存消息的有界变量


Demo

Domain类

代码语言:javascript复制
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;
    }
}

控制层

代码语言:javascript复制
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配置文件

代码语言:javascript复制
<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="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/messagesvalue>
                <value>/WEB-INF/resource/labelsvalue>
            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标题显示在页面的最上方

代码语言:javascript复制
<%@ 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" %>

<html>
<head>
<title><spring:message code="page.productform.title"/>title>
<style type="text/css">@import url(""/>");style>
head>
<body>

<div id="global">

    
    
    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="button.reset"/>">
                <input id="submit" type="submit" tabindex="5" 
                    value="button.submit"/>">
            p>
        fieldset>
    form:form>
div>
body>
html>

测试

Accept-Language说明 :https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Accept-Language

指令

代码语言:javascript复制
<language>
用含有两到三个字符的字符串表示的语言码。
代码语言:javascript复制
完整的语言标签。除了语言本身之外,还会包含其他方面的信息,显示在中划线("-")后面。最常见的额外信息是国家或地区变种(如"en-US")或者表示所用的字母系统(如"sr-Lat")。其他变种诸如拼字法("de-DE-1996")等通常不被应用在这种场合。
代码语言:javascript复制
*
任意语言;"*"表示通配符。
代码语言:javascript复制
;q= (q-factor weighting)
值代表优先顺序,用相对质量价值 表示,又称为权重。

源码

代码已提交到github

https://github.com/yangshangwei/SpringMvcTutorialArtisan

0 人点赞