Spring Boot国际化(i18n)- 第58篇

国际化(internationalization)是设计和制造容易适应不同区域要求的产品的一种方式。它要求从产品中抽离所有地域语言,国家/地区和文化相关的元素。换言之,应用程序的功能和代码设计考虑在不同地区运行的需要,其代码简化了不同本地版本的生产。开发这样的程序的过程,就称为国际化。

       那么当我们使用Spring Boot如何进行国际化呢?那么当你读完这篇文章你会学到如下知识:

(1) spring boot 加入thymeleaf

(2) 页面元素国际化;

(3) spring boot默认国际化原理说明;

(4) firefox浏览器修改区域语言;

(5)chrome浏览器修改区域语言;

(6)修改默认messages配置前缀;

(7) 代码中如何获取国际化信息;

(8) 优化代码获取国际化信息;

(9) 区域解析器之AcceptHeaderLocaleResolver

(10) 会话区域解析器之SessionLocaleResolver

(11) Cookie区域解析器之CookieLocaleResolver

(12)固定的区域解析器之FixedLocaleResolver 

(13)使用参数修改用户的区域;

       接下里我们看看这些具体应该怎么操作。

(1)spring boot 加入thymeleaf

       Spring boot集成thymeleaf

(18)使用模板(thymeleaf-freemarker)【从零开始学Spring Boot 

这篇文章有介绍过,所以这里就不过多进行介绍了。在这里我们为之后的讲解做点基本准备。

模板文件resources/templates/hello.html:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4.    <meta charset="UTF-8"/>
  5.    <title>hello spring boot</title>
  6. </head>
  7. <body>
  8.       
  9.        <p>欢迎你登录到 阿里巴巴 网站</p>
  10.           
  11. </body>
  12. </html>

这里没有特殊的代码,访问就是显示一些文字,这里还没加入国际化的相关东西,之后添加。

编写访问地址:com.kfit.controller.HelloController:

  1. package com.kfit.controller;
  2. import org.springframework.stereotype.Controller;
  3. import org.springframework.web.bind.annotation.RequestMapping;
  4. @Controller
  5. public class HelloController {
  6.       
  7.        @RequestMapping("/hello")
  8.        public String hello(){
  9.               return "/hello";
  10.        }
  11.       
  12. }

这里就是访问http://127.0.0.1:8080/hello就跳转到hell.html进行访问。

到这里准备工作就好了。

(2) 页面元素国际化;

       我们观察hello.html里面的信息直接就是中文显示,所以我们现在的需求是当访问语言是zh的时候显示为中文,当语言为en的时候显示为英文,那么怎么操作呢?

首先我们先定义国际化资源文件,springboot默认就支持国际化的,而且不需要你过多的做什么配置,只需要在resources/下定义国际化配置文件即可,注意名称必须以messages开发。

我们定义如下几个文件:

messages.properties (默认,当找不到语言的配置的时候,使用该文件进行展示)。

messages_zh_CN.properties(中文)

messages_en_US.properties(英文)

具体的代码如下:

  1. messages.properties:
  2. welcome = 欢迎你登录到 阿里巴巴网站(default
  3. messages_zh_CN.properties:
  4. welcome = \u6b22\u8fce\u4f60\u767b\u5f55\u5230\u963f\u91cc\u5df4\u5df4 \u7f51\u7ad9\uff08\u4e2d\u6587\uff09
  5. 对应的信息是:
  6. welcome = 欢迎你登录到 阿里巴巴网站(中文)
  7. messages_en_US.properties:
  8. welcome= welcome to login to alibabawebsite(English)

配置信息就这么简单,那么在前端展示怎么修改呢,修改hello.html文件,使用#{key}的方式进行使用messages中的字段信息:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4.    <meta charset="UTF-8"/>
  5.    <title>hello spring boot</title>
  6. </head>
  7. <body>
  8.        <p><labelth:text="#{welcome}"></label></p>
  9. </body>
  10. </html>

重新访问:http://127.0.0.1:8080/hello应该显示:

欢迎你登录到 阿里巴巴 网站(中文)

(3)spring boot默认国际化原理说明

       在这里我们先打住下,简单说下原理:

第一个问题,为什么命名必须是messages开头,需要看一个源码文件:

org.springframework.boot.autoconfigure.MessageSourceAutoConfiguration:

 

这里提取部分代码:

  1. /**
  2.  *{@link EnableAutoConfiguration Auto-configuration} for{@linkMessageSource}.
  3.  *
  4.  *@author Dave Syer
  5.  *@author Phillip Webb
  6.  *@author Eddú Meléndez
  7.  */
  8. @Configuration
  9. @ConditionalOnMissingBean(value =MessageSource.class, search = SearchStrategy.CURRENT)
  10. @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
  11. @Conditional(ResourceBundleCondition.class)
  12. @EnableConfigurationProperties
  13. @ConfigurationProperties(prefix ="spring.messages")
  14. public class MessageSourceAutoConfiguration {
  15.        private static final Resource[]NO_RESOURCES = {};
  16.        /**
  17.         * Comma-separated list of basenames, eachfollowing the ResourceBundle convention.
  18.         * Essentially a fully-qualified classpath location. If itdoesn't contain a package
  19.         * qualifier (such as"org.mypackage"), it will be resolved from the classpath root.
  20.         */
  21.        private String basename ="messages";
  22.        /**
  23.         * Message bundles encoding.
  24.         */
  25.        private Charsetencoding = Charset.forName("UTF-8");
  26.        /**
  27.         * Loaded resource bundle files cacheexpiration, in seconds. When set to-1, bundles
  28.         * are cached forever.
  29.         */
  30.        private int cacheSeconds = -1;
  31.        /**
  32.         * Set whether to fall back to the systemLocale if no files for a specific Locale
  33.         * have been found. if this is turned off, theonly fallback will be the default file
  34.         * (e.g. "messages.properties" forbasename "messages").
  35.         */
  36.        private boolean fallbackToSystemLocale =true;

看到没有,如果我们没有在application.properties中配置spring.messages属性,那么使用默认的messages,好了这个问题就这么简单解决了。

第二问题:为什么我看到的是中文(或者英文)呢?




       为了让web应用程序支持国际化,必须识别每个用户的首选区域,并根据这个区域显示内容。在Spring MVC应用程序中,用户的区域是通过区域解析器来识别的,它必须是实现LocaleResolver接口。Spring MVC提供了几个LocaleResolver实现,让你可以按照不同的条件来解析区域。除此之外,你还可以实现这个接口创建自己的区域解析器。如果没有做特殊的处理的话,Spring 采用的默认区域解析器是AcceptHeaderLocaleResolver。它通过检验HTTP请求的头部信息accept-language来解析区域。这个头部是由用户的wb浏览器底层根据底层操作系统的区域设置进行设定的。请注意,这个区域解析器无法改变用户的区域,因为它无法修改用户操作系统的区域设置。

       既然无法修改,那么我们的代码怎么测试呢?请看如下内容?

(4) firefox浏览器修改区域语言;

       打开firefox浏览器访问http://127.0.0.1:8080/hello (计算机系统语言是中文的),应该是看到如下信息:

欢迎你登录到阿里巴巴 网站(中文)

那么我们修改我们的语言呢,在浏览器地址栏输入如下信息:

about:config

回车进入一个警告页面,然后点击按钮【我保证会小心】(注:由于版本不一样,可能会有些不一样,但是操作是一样的)。

在搜索框输入accept,然后找到intl.accept_languages修改对应的值,我这里原本是:

zh-cn, zh, en-us, en

为了看到效果,修改为:

en-us, en

修改完之后,刷新http://127.0.0.1:8080/hello,可以看到信息:

welcome to login to alibaba website(English)

好了,没有什么特殊的需求的,记得把intl.accept_languages修改为原来的值。

(5)chrome浏览器修改区域语言;

       我觉得firefox修改起来真是简单,chrome浏览器就稍微麻烦点了。

第一种方案就是下载插件:QuickLanguage Switcher,下载完插件之后,默认选项的比较少,你可以在扩展程序中,打开别的语言选项或者添加自定义的语言。这个只要插件下来来操作就很简单了,切换语言就会自动刷新页面,就能看到效果了,特别方便。注意的是:默认有一个English,这个使用的配置文件是:messages_en.properties,所以需要添加一个配置文件才能看到效果。

第二种方案是修改本地的一个配置文件,参考如下地址

http://stackoverflow.com/questions/7769061/how-to-add-custom-accept-languages-to-chrome-for-pseudolocalization-testing

但是我这里不管怎么修改,重启浏览器之后,就被重置回来了,也就是这种方案我这里没有配置成功。第一种方案肯定是可以的。

       别的浏览器就自行尝试了,因为这不是我们这不是我们实际使用的重点,那么接下来才是重点哦。

(6)修改默认messages配置前缀;

       我们在上面说了,默认的文件名称前缀是messages_xx.properties,那么如何修改这个名称和路径呢?

       这个也很简单,只需要修改application.properties文件即可加入如下配置:

  1. ########################################################
  2. ### i18n setting.
  3. ########################################################
  4. #指定message的basename,多个以逗号分隔,如果不加包名的话,默认从classpath路径开始,默认: messages
  5. spring.messages.basename=i18n/messages
  6. #设定加载的资源文件缓存失效时间,-1的话为永不过期,默认为-1
  7. spring.messages.cache-seconds= 3600
  8. #设定Message bundles的编码,默认: UTF-8
  9. #spring.messages.encoding=UTF-8

上面各个参数都注释很清楚了,这里不多说了,那么我们这里是把文件放到了i18n下,那么我们在resources下新建目录i18n,然后复制我们创建的messages_xxx.properties文件到此目录下。为了区分这是读取了新的文件,我们可以在每个文件中—i18n以进行区分。

重新访问http://127.0.0.1:8080/hello可以看到最新的效果了。

英文:

welcome to login to alibabawebsite(English-en)--i18n

中文:

欢迎你登录到 阿里巴巴 网站(中文)--i18n

 

(7) 代码中如何获取国际化信息;

       以上讲的是在模板文件进行国际化,那么在代码中如何获取到welcome呢,这个比较简单,只要在需要的地方注入类:

  1. @Autowired
  2. private MessageSource messageSource;

需要注意的是messageSource

org.springframework.context.MessageSource

下的类。

那么怎么使用了,在使用前我们需要先知道一个知识点,如何得到当前请求的Locale

那么怎么获取呢,有两种获取方式:

第一种方式是:

  1. Locale locale = LocaleContextHolder.getLocale();


第二种方式是:

Locale locale1= RequestContextUtils.getLocale(request);

个人喜好第一种方式,因为不需要什么参数就可以获取到,第二种方式依赖于当前的request请求对象。

有了当前请求的Locale剩下的就简单了:

  1. String msg = messageSource.getMessage("welcome",null,locale);
  2. String msg2= messageSource.getMessage("welcome", null,locale1);

通过以上代码的其中一种方式就可以获取到messages_xxx.properties文件配置的welcome属性值了。切换区域获取的信息也是不一样的,打印信息如下:

msg=欢迎你登录到 阿里巴巴 网站(中文)--i18n

msg2欢迎你登录到 阿里巴巴 网站(中文)--i18n

msg=welcome to login to alibabawebsite(English-en)--i18n

msg2welcometo login to alibaba website(English-en)--i18n

(8) 优化代码获取国际化信息;

       学习是永无止境的,活到老学到老。查看上面的代码你会发现这个是实际中使用起来的时候还是不是很方面,ok,没有关系,这个小节我们就对上面的代码优化下,

自定义我们自己的MessageSource,具体代码如下:

  1. package com.kfit.base.service;
  2. import java.util.Locale;
  3. import javax.annotation.Resource;
  4. import org.springframework.context.MessageSource;
  5. import org.springframework.context.i18n.LocaleContextHolder;
  6. import org.springframework.stereotype.Component;
  7. /**
  8.  *国际化工具类
  9.  *@author Angel--守护天使
  10.  *@version v.0.1
  11.  *@date 2016年8月5日下午2:44:03
  12.  */
  13. @Component
  14. public class LocaleMessageSourceService {
  15.    
  16.     @Resource
  17.     private MessageSource messageSource;
  18.    
  19.     /**
  20.      *@param code:对应messages配置的key.
  21.      *@return
  22.      */
  23.     public String getMessage(String code){
  24.        return this.getMessage(code,new Object[]{});
  25.     }
  26.    
  27.     public String getMessage(String code,String defaultMessage){
  28.        return this.getMessage(code,null,defaultMessage);
  29.     }
  30.    
  31.     public String getMessage(String code,String defaultMessage,Locale locale){
  32.        return this.getMessage(code,null,defaultMessage,locale);
  33.     }
  34.    
  35.     public String getMessage(String code,Locale locale){
  36.        return this.getMessage(code,null,"",locale);
  37.     }
  38.    
  39.     /**
  40.      *
  41.      *@param code:对应messages配置的key.
  42.      *@param args :数组参数.
  43.      *@return
  44.      */
  45.     public String getMessage(String code,Object[]args){
  46.        return this.getMessage(code,args,"");
  47.     }
  48.    
  49.     public String getMessage(String code,Object[]args,Locale locale){
  50.        return this.getMessage(code,args,"",locale);
  51.     }
  52.    
  53.    
  54.     /**
  55.      *
  56.      *@param code:对应messages配置的key.
  57.      *@param args :数组参数.
  58.      *@param defaultMessage :没有设置key的时候的默认值.
  59.      *@return
  60.      */
  61.     public String getMessage(String code,Object[]args,String defaultMessage){
  62.        //这里使用比较方便的方法,不依赖request.
  63.        Locale locale =LocaleContextHolder.getLocale();
  64.        return this.getMessage(code,args,defaultMessage,locale);
  65.     }
  66.    
  67.     /**
  68.      *指定语言.
  69.      *@param code
  70.      *@param args
  71.      *@param defaultMessage
  72.      *@param locale
  73.      *@return
  74.      */
  75.     public String getMessage(String code,Object[]args,String defaultMessage,Locale locale){
  76.        return messageSource.getMessage(code,args,defaultMessage,locale);
  77.     }
  78. }





在这个代码中我封装了3个常用的方法,那么应该怎么调用呢?也很简单,在需要的地方使用如下代码进行注入:

  1. @Resource
  2. private LocaleMessageSourceService localeMessageSourceService;

在需要的地方使用如下代码进行调用:

String msg3 = localeMessageSourceService.getMessage("welcome");

这个代码比之前的代码使用起来爽多了,还有很多神妙之处,大家自己发现吧。

(9) 区域解析器之AcceptHeaderLocaleResolver

       看到上面,大家会认为国际化也就到此了,但是我告诉大家这之后才是spring的强大之处呢,考虑的多么周到呢。

       我们在之前说过,我们只所以可以看到国际化的效果是因为有一个区域解析器在进行处理。默认的区域解析器就是AcceptHeaderLocaleResolver。我们简单说明这个解析器:

       Spring采用的默认区域解析器是AcceptHeaderLocaleResolver。它通过检验HTTP请求的accept-language头部来解析区域。这个头部是由用户的web浏览器根据底层操作系统的区域设置进行设定。请注意,这个区域解析器无法改变用户的区域,因为它无法修改用户操作系统的区域设置。

       好了,这个默认的介绍到这里,因为这个我们无法改变,所以这种默认的设置在实际中使用的比较少。所以你如果看到当前还无法满足的需求的话,那么接着往下看,博主已经帮你都想到了。

(10) 会话区域解析器之SessionLocaleResolver

       会话区域解析器也就是说,你设置完只针对当前的会话有效,session失效,还原为默认状态。那么这个具体怎么操作呢?具体操作起来也是很简单的,我们需要在我们的启动类App.java(按你的实际情况进行修改)配置区域解析器为SessionLocaleResolver,具体代码如下:

  1.   @Bean
  2.        public LocaleResolver localeResolver() {
  3.            SessionLocaleResolver slr = new SessionLocaleResolver();
  4.            //设置默认区域,
  5.            slr.setDefaultLocale(Locale.CHINA);
  6.            return slr;
  7.        }

其实到这里我们就完事了,你可以通过setDefaultLocale()设置默认的语言,启动就访问http://127.0.0.1:8080/hello 进行查看,是不是已经实现国际化了。

       到这里当然还不是很完美,还需要在进一步的优化了,那么怎么在页面中进行切换呢?那么假设页面上有两个按钮【切换为中文】、【切换为英文】就可以进行切换了。接下来一起来实现下:在hello.html添加如下代码:

  1. <form action="/changeSessionLanauage" method="get">
  2.               <input  name="lang" type="hidden" value="zh" />
  3.               <button>切换为中文</button>
  4.        </form>
  5.       
  6.        <form action="/changeSessionLanauage" method="get">
  7.               <input name="lang"type="hidden"value="en"/>
  8.               <button>切换为英文</button>
  9.        </form>

这里就是两个表单,切换语言,那么/changeSessionLanauage怎么编写呢,看如下代码:

  1. @RequestMapping("/changeSessionLanauage")
  2.        public String changeSessionLanauage(HttpServletRequest request,String lang){
  3.               System.out.println(lang);
  4.               if("zh".equals(lang)){
  5.                      //代码中即可通过以下方法进行语言设置
  6.                      request.getSession().setAttribute(SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME,new Locale("zh","CN")); 
  7.               }elseif("en".equals(lang)){
  8.                      //代码中即可通过以下方法进行语言设置
  9.                      request.getSession().setAttribute(SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME,new Locale("en","US")); 
  10.               }
  11.               return "redirect:/hello";
  12.        }

这部分代码最核心的部分就是如何设置会话的区域,也就是如下代码:

request.getSession().setAttribute(SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME,new Locale("en","US")); 

这个代码就可以把当前会话的区域进行切换了,是不是很简单。以上代码你会发现只针对会话的设置,我们在这里在优化下,针对下面讲的Cookie也会作用到,这样这个代码就很智能了,代码修改为如下:

  1. @RequestMapping("/changeSessionLanauage")
  2.        public String changeSessionLanauage(HttpServletRequest request,HttpServletResponse response,String lang){
  3.                             System.out.println(lang);
  4.               LocaleResolver localeResolver =RequestContextUtils.getLocaleResolver(request);
  5.               if("zh".equals(lang)){
  6.                      localeResolver.setLocale(request, response, new Locale("zh","CN"));
  7.               }elseif("en".equals(lang)){
  8.                      localeResolver.setLocale(request, response, new Locale("en","US"));
  9.               }
  10.               return "redirect:/hello";
  11.        }

在这里使用:

LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);

获取当前使用的区域解析器LocaleResolver 调用里面的方法

setLocale设置即可,这样的代码就是不管是会话还是cookie区域解析器都是一样的代码了。

(11)Cookie区域解析器之CookieLocaleResolver

       Cookie区域解析器也就是说,你设置完针对cookie生效,session失效。那么这个具体怎么操作呢?我们需要在我们的启动类App.java(按你的实际情况进行修改)配置区域解析器为CookieLocaleResolver(SessionLocaleResolver部分请注释掉),具体代码如下:

  1. @Bean
  2.        public LocaleResolver localeResolver() {
  3.               CookieLocaleResolver slr =newCookieLocaleResolver();
  4.            //设置默认区域,
  5.            slr.setDefaultLocale(Locale.CHINA);
  6.            slr.setCookieMaxAge(3600);//设置cookie有效期.
  7.            return slr;
  8.        }

其实到这里我们就完事了,你可以通过setDefaultLocale()设置默认的语言,启动就访问http://127.0.0.1:8080/hello 进行查看,是不是已经实现国际化了。

       到这里当然还不是很完美,还需要在进一步的优化了,那么怎么在页面中进行切换呢?

这里主要是用到了,这个部分我们在(10)中已经进行配置了,所以到这里已近是可以进行使用了,点击页面中的按钮进行体验。

(12)固定的区域解析器之FixedLocaleResolver 

       一直使用固定的Local,改变Local 是不支持的。既然无法改变,那么不…好了,还是简单介绍下如何使用吧,还是在App.java进行编码:

  1. /**
  2.         * cookie区域解析器;
  3.         *@return
  4.         */
  5.        @Bean
  6.        public LocaleResolver localeResolver() {
  7.               FixedLocaleResolver slr = newFixedLocaleResolver ();
  8.            //设置默认区域,
  9.            slr.setDefaultLocale(Locale.US);
  10.            return slr;
  11.        }

好了,这个就这么简单,没有别的知识了。

(13)使用参数修改用户的区域;

       除了显式调用LocaleResolver.setLocale()来修改用户的区域之外,还可以将LocaleChangeInterceptor拦截器应用到处理程序映射中,它会发现当前HTTP请求中出现的特殊参数。其中的参数名称可以通过拦截器的paramName属性进行自定义。如果这种参数出现在当前请求中,拦截器就会根据参数值来改变用户的区域。

       只需要在App.java中加入:    

  1.    @Bean
  2.         public LocaleChangeInterceptor localeChangeInterceptor() {
  3.                LocaleChangeInterceptorlci =newLocaleChangeInterceptor();
  4.                // 设置请求地址的参数,默认为:locale
  5. //            lci.setParamName(LocaleChangeInterceptor.DEFAULT_PARAM_NAME);
  6.                return lci;
  7.        }
  8.       
  9.    @Override
  10.   public void addInterceptors(InterceptorRegistry registry) {
  11.       registry.addInterceptor(localeChangeInterceptor());
  12.    }

注意这个是可以和会话区域解析器以及Cookie区域解析器一起使用的,但是不能和FixedLocaleResolver一起使用,否则会抛出异常信息。

好了,国际化就到此为主了,博主也是累了,这部分知识在国内还真没有,博主也是各种收集资料才总结出了这么一篇文章,绝对原创,大家都都点赞评论。

购买完整视频,请前往:http://www.mark-to-win.com/TeacherV2.html?id=287