SpringBoot系统之i18n国际化语言集成教程

@

1、环境搭建

本博客介绍一下SpringBoot集成i18n,实现系统语言国际化处理,ok,先创建一个SpringBoot项目,具体的参考我的博客专栏:SpringBoot系列博客专栏链接

环境准备:

  • IntelliJ IDEA
  • Maven

项目集成:

  • Thymeleaf(模板引擎,也可以选jsp或者freemark)
  • SpringBoot2.2.1.RELEASE

2、resource bundle资源配置

ok,要实现国际化语言,先要创建resource bundle文件:

在resources文件夹下面创建一个i18n的文件夹,其中:

  • messages.properties是默认的配置
  • messages_zh_CN.properties是(中文/中国)
  • messages_en_US.properties是(英文/美国)
  • etc.



    IDEA工具就提供了很简便的自动配置功能,如图,只要点击新增按钮,手动输入,各配置文件都会自动生成属性



    messages.properties:
  1. messages.loginBtnName=登录~
  2. messages.password=密码~
  3. messages.rememberMe=记住我~
  4. messages.tip=请登录~
  5. messages.username=用户名~

messages_zh_CN.properties:

  1. messages.loginBtnName=登录
  2. messages.password=密码
  3. messages.rememberMe=记住我
  4. messages.tip=请登录
  5. messages.username=用户名

messages_en_US.properties:

  1. messages.loginBtnName=login
  2. messages.password=password
  3. messages.rememberMe=Remember me
  4. messages.tip=Please login in
  5. messages.username=userName

在项目的application.properties修改默认配置,让SpringBoot的自动配置能读取到resource bundle资源文件

  1. ## 配置i18n
  2. # 默认是i18n(中文/中国)
  3. spring.mvc.locale=zh_CN
  4. # 配置resource bundle资源文件的前缀名eg:i18n是文件夹名,messages是资源文件名,支持的符号有.号或者/
  5. spring.messages.basename=i18n.messages
  6. # 设置缓存时间,2.2.1是s为单位,之前版本才是毫秒
  7. spring.messages.cache-duration=1
  8. # 设置资源文件编码格式为utf8
  9. spring.messages.encoding=utf-8

注意要点:

  • spring.messages.basename必须配置,否则SpringBoot的自动配置将失效

    MessageSourceAutoConfiguration.ResourceBundleCondition 源码:
  1. protected static class ResourceBundleCondition extends SpringBootCondition {
  2. //定义一个map缓存池
  3. private static ConcurrentReferenceHashMap<String, ConditionOutcome> cache = new ConcurrentReferenceHashMap<>();
  4. @Override
  5. public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
  6. String basename = context.getEnvironment().getProperty("spring.messages.basename", "messages");
  7. ConditionOutcome outcome = cache.get(basename);//缓存拿得到,直接从缓存池读取
  8. if (outcome == null) {//缓存拿不到,重新读取
  9. outcome = getMatchOutcomeForBasename(context, basename);
  10. cache.put(basename, outcome);
  11. }
  12. return outcome;
  13. }
  14. private ConditionOutcome getMatchOutcomeForBasename(ConditionContext context, String basename) {
  15. ConditionMessage.Builder message = ConditionMessage.forCondition("ResourceBundle");
  16. for (String name : StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(basename))) {
  17. for (Resource resource : getResources(context.getClassLoader(), name)) {
  18. if (resource.exists()) {
  19. //匹配resource bundle资源
  20. return ConditionOutcome.match(message.found("bundle").items(resource));
  21. }
  22. }
  23. }
  24. return ConditionOutcome.noMatch(message.didNotFind("bundle with basename " + basename).atAll());
  25. }
  26. //解析资源文件
  27. private Resource[] getResources(ClassLoader classLoader, String name) {
  28. String target = name.replace('.', '/');//spring.messages.basename参数值的点号换成斜杆
  29. try {
  30. return new PathMatchingResourcePatternResolver(classLoader)
  31. .getResources("classpath*:" + target + ".properties");
  32. }
  33. catch (Exception ex) {
  34. return NO_RESOURCES;
  35. }
  36. }
  37. }
  • cache-duration在2.2.1版本,指定的是s为单位,找到SpringBoot的MessageSourceAutoConfiguration自动配置类

3、LocaleResolver类

SpringBoot默认采用AcceptHeaderLocaleResolver类作为默认LocaleResolver,LocaleResolver类的作用就是作为i18n的分析器,获取对应的i18n配置,当然也可以自定义LocaleResolver类


  1. import org.slf4j.Logger;
  2. import org.slf4j.LoggerFactory;
  3. import org.springframework.lang.Nullable;
  4. import org.springframework.util.StringUtils;
  5. import org.springframework.web.servlet.LocaleResolver;
  6. import javax.servlet.http.HttpServletRequest;
  7. import javax.servlet.http.HttpServletResponse;
  8. import java.util.Locale;
  9. /**
  10. * <pre>
  11. * 自定义LocaleResolver类
  12. * </pre>
  13. * @author nicky
  14. * <pre>
  15. * 修改记录
  16. * 修改后版本: 修改人: 修改日期: 2019年11月23日 修改内容:
  17. * </pre>
  18. */
  19. public class CustomLocalResolver implements LocaleResolver {
  20. Logger LOG = LoggerFactory.getLogger(this.getClass());
  21. @Nullable
  22. private Locale defaultLocale;
  23. public void setDefaultLocale(@Nullable Locale defaultLocale) {
  24. this.defaultLocale = defaultLocale;
  25. }
  26. @Nullable
  27. public Locale getDefaultLocale() {
  28. return this.defaultLocale;
  29. }
  30. @Override
  31. public Locale resolveLocale(HttpServletRequest request) {
  32. Locale defaultLocale = this.getDefaultLocale();//获取application.properties默认的配置
  33. if(defaultLocale != null && request.getHeader("Accept-Language") == null) {
  34. return defaultLocale;//http请求头没获取到Accept-Language才采用默认配置
  35. } else {//request.getHeader("Accept-Language")获取得到的情况
  36. Locale requestLocale = request.getLocale();//获取request.getHeader("Accept-Language")的值
  37. String localeFlag = request.getParameter("locale");//从URL获取的locale值
  38. //LOG.info("localeFlag:{}",localeFlag);
  39. //url链接有传locale参数的情况,eg:zh_CN
  40. if (!StringUtils.isEmpty(localeFlag)) {
  41. String[] split = localeFlag.split("_");
  42. requestLocale = new Locale(split[0], split[1]);
  43. }
  44. //没传的情况,默认返回request.getHeader("Accept-Language")的值
  45. return requestLocale;
  46. }
  47. }
  48. @Override
  49. public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
  50. }
  51. }

4、I18n配置类

I18n还是要继承WebMvcConfigurer,注意,2.2.1版本才是实现接口就可以,之前1.+版本是要实现WebMvcConfigurerAdapter适配器类的

  1. import com.example.springboot.i18n.component.CustomLocalResolver;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties;
  4. import org.springframework.boot.context.properties.EnableConfigurationProperties;
  5. import org.springframework.context.annotation.Bean;
  6. import org.springframework.context.annotation.Configuration;
  7. import org.springframework.web.servlet.LocaleResolver;
  8. import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
  9. import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
  10. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  11. import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
  12. /**
  13. * <pre>
  14. * I18nConfig配置类
  15. * </pre>
  16. * <p>
  17. * <pre>
  18. * @author nicky.ma
  19. * 修改记录
  20. * 修改后版本: 修改人: 修改日期: 2019/11/24 11:15 修改内容:
  21. * </pre>
  22. */
  23. //Configuration必须加上,不然不能加载到Spring容器
  24. @Configuration
  25. //使WebMvcProperties配置类可用,这个可以不加上,本博客例子才用
  26. @EnableConfigurationProperties({ WebMvcProperties.class})
  27. public class I18nConfig implements WebMvcConfigurer{
  28. //装载WebMvcProperties 属性
  29. @Autowired
  30. WebMvcProperties webMvcProperties;
  31. /**
  32. * 定义SessionLocaleResolver
  33. * @Author nicky.ma
  34. * @Date 2019/11/24 13:52
  35. * @return org.springframework.web.servlet.LocaleResolver
  36. */
  37. // @Bean
  38. // public LocaleResolver localeResolver() {
  39. // SessionLocaleResolver sessionLocaleResolver = new SessionLocaleResolver();
  40. // // set default locale
  41. // sessionLocaleResolver.setDefaultLocale(Locale.US);
  42. // return sessionLocaleResolver;
  43. // }
  44. /**
  45. * 定义CookieLocaleResolver
  46. * @Author nicky.ma
  47. * @Date 2019/11/24 13:51
  48. * @return org.springframework.web.servlet.LocaleResolver
  49. */
  50. // @Bean
  51. // public LocaleResolver localeResolver() {
  52. // CookieLocaleResolver cookieLocaleResolver = new CookieLocaleResolver();
  53. // cookieLocaleResolver.setCookieName("Language");
  54. // cookieLocaleResolver.setCookieMaxAge(1000);
  55. // return cookieLocaleResolver;
  56. // }
  57. /**
  58. * 自定义LocalResolver
  59. * @Author nicky.ma
  60. * @Date 2019/11/24 13:45
  61. * @return org.springframework.web.servlet.LocaleResolver
  62. */
  63. @Bean
  64. public LocaleResolver localeResolver(){
  65. CustomLocalResolver localResolver = new CustomLocalResolver();
  66. localResolver.setDefaultLocale(webMvcProperties.getLocale());
  67. return localResolver;
  68. }
  69. /**
  70. * 定义localeChangeInterceptor
  71. * @Author nicky.ma
  72. * @Date 2019/11/24 13:45
  73. * @return org.springframework.web.servlet.i18n.LocaleChangeInterceptor
  74. */
  75. @Bean
  76. public LocaleChangeInterceptor localeChangeInterceptor(){
  77. LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
  78. //默认的请求参数为locale,eg: login?locale=zh_CN
  79. localeChangeInterceptor.setParamName(LocaleChangeInterceptor.DEFAULT_PARAM_NAME);
  80. return localeChangeInterceptor;
  81. }
  82. /**
  83. * 注册拦截器
  84. * @Author nicky.ma
  85. * @Date 2019/11/24 13:47
  86. * @Param [registry]
  87. * @return void
  88. */
  89. @Override
  90. public void addInterceptors(InterceptorRegistry registry) {
  91. registry.addInterceptor(localeChangeInterceptor()).addPathPatterns("/**");
  92. }
  93. }

注意要点:

  • 旧版代码可以不加LocaleChangeInterceptor 拦截器,2.2.1版本必须通过拦截器
  • 如下代码,bean的方法名必须为localeResolver,否则会报错
  1. @Bean
  2. public LocaleResolver localeResolver(){
  3. CustomLocalResolver localResolver = new CustomLocalResolver();
  4. localResolver.setDefaultLocale(webMvcProperties.getLocale());
  5. return localResolver;
  6. }

原理:

跟一下源码,点进LocaleChangeInterceptor类



DispatcherServlet是Spring一个很重要的分发器类,在DispatcherServlet的一个init方法里找到这个LocaleResolver的init方法



这个IOC获取的bean类名固定为localeResolver,写例子的时候,我就因为改了bean类名,导致一直报错,跟了源码才知道Bean类名要固定为localeResolver



抛异常的时候,也是会获取默认的LocaleResolver的



找到资源文件,确认,还是默认为AcceptHeaderLocaleResolver



配置了locale属性的时候,还是选用AcceptHeaderLocaleResolver作为默认的LocaleResolver

  1. spring.mvc.locale=zh_CN

WebMvcAutoConfiguration.localeResolver方法源码,ConditionalOnMissingBean主键的意思是LocaleResolver没有自定义的时候,才作用,ConditionalOnProperty的意思,有配了属性才走这里的逻辑

  • 拦截器拦截的请求参数默认为locale,要使用其它参数,必须通过拦截器设置 ,eg:localeChangeInterceptor.setParamName("lang");

  • LocalResolver种类有:CookieLocaleResolver(Cookie)、SessionLocaleResolver(会话)、FixedLocaleResolver、AcceptHeaderLocaleResolver(默认)、.etc

5、Thymeleaf集成

本博客的模板引擎采用Thymeleaf的,所以新增项目时候就要加上maven相关依赖,没有的话,自己加上:

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-thymeleaf</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-web</artifactId>
  8. </dependency>

ok,然后去找个bootstrap的登录页面,本博客已尚硅谷老师的例子为例,进行拓展,引入静态资源文件:

Thymeleaf的i18n支持是采用#符号的

  1. <!DOCTYPE html>
  2. <html lang="en" xmlns:th="http://www.thymeleaf.org">
  3. <head>
  4. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  6. <meta name="description" content="">
  7. <meta name="author" content="">
  8. <title>SpringBoot i18n example</title>
  9. <!-- Bootstrap core CSS -->
  10. <link href="asserts/css/bootstrap.min.css" th:href="@{asserts/css/bootstrap.min.css}" rel="stylesheet">
  11. <!-- Custom styles for this template -->
  12. <link href="asserts/css/signin.css" th:href="@{asserts/css/signin.css}" rel="stylesheet">
  13. </head>
  14. <body class="text-center">
  15. <form class="form-signin" action="dashboard.html">
  16. <img class="mb-4" th:src="@{asserts/img/bootstrap-solid.svg}" alt="" width="72" height="72">
  17. <h1 class="h3 mb-3 font-weight-normal" th:text="#{messages.tip}">Please sign in</h1>
  18. <label class="sr-only" th:text="#{messages.username}">Username</label>
  19. <input type="text" class="form-control" th:placeholder="#{messages.username}" required="" autofocus="">
  20. <label class="sr-only" th:text="#{messages.password} ">Password</label>
  21. <input type="password" class="form-control" th:placeholder="#{messages.password}" required="">
  22. <div class="checkbox mb-3">
  23. <label>
  24. <input type="checkbox" value="remember-me" > [[#{messages.rememberMe}]]
  25. </label>
  26. </div>
  27. <button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{messages.loginBtnName}">Sign in</button>
  28. <p class="mt-5 mb-3 text-muted">© 2019</p>
  29. <a class="btn btn-sm" th:href="@{/login(locale='zh_CN')} ">中文</a>
  30. <a class="btn btn-sm" th:href="@{/login(locale='en_US')} ">English</a>
  31. </form>
  32. </body>
  33. </html>

切换中文网页:



切换英文网页:

当然不点链接传locale的方式也是可以自动切换的,浏览器设置语言:

原理localeResolver类会获取Accept language参数

附录:

logging manual:SpringBoot官方手册

example source:例子代码下载链接

SpringBoot系列之i18n集成教程的更多相关文章

  1. SpringBoot系列之缓存使用教程

    介绍SpringBoot项目中使用缓存,之前先介绍一下Spring的缓存抽象和JSR107,本博客是我在学习尚硅谷视频和参考其它博客之后做的笔记,仅供学习参考 @ 目录 一.Spring的缓存抽象 1 ...

  2. SpringBoot系列之集成Thymeleaf用法手册

    目录 1.模板引擎 2.Thymeleaf简介 2.1).Thymeleaf定义 2.2).适用模板 3.重要知识点 3.1).th:text和th:utext 3.2).标准表达式 3.3).Thy ...

  3. SpringBoot系列之学习教程汇总

    对应SpringBoot系列博客专栏,例子代码,本博客不定时更新 一.配置篇 SpringBoot系列之@PropertySource读取yaml文件     >> source down ...

  4. SpringBoot系列之从入门到精通系列教程

    对应SpringBoot系列博客专栏,例子代码,本博客不定时更新 Spring框架:作为JavaEE框架领域的一款重要的开源框架,在企业应用开发中有着很重要的作用,同时Spring框架及其子框架很多, ...

  5. SpringBoot系列之日志框架介绍及其原理简介

    SpringBoot系列之日志框架介绍及其原理简介 1.常用日志框架简介 市面上常用日志框架:JUL.JCL.jboss-logging.logback.log4j.log4j2.slf4j.etc. ...

  6. SpringBoot系列之集成Mybatis教程

    SpringBoot系列之集成Mybatis教程 环境准备:IDEA + maven 本博客通过例子的方式,介绍Springboot集成Mybatis的两种方法,一种是通过注解实现,一种是通过xml的 ...

  7. SpringBoot系列之Spring Data Jpa集成教程

    SpringBoot系列之Spring Data Jpa集成教程 Spring Data Jpa是属于Spring Data的一个子项目,Spring data项目是一款集成了很多数据操作的项目,其下 ...

  8. SpringBoot系列之集成logback实现日志打印(篇二)

    SpringBoot系列之集成logback实现日志打印(篇二) 基于上篇博客SpringBoot系列之集成logback实现日志打印(篇一)之后,再写一篇博客进行补充 logback是一款开源的日志 ...

  9. SpringBoot系列之集成Dubbo的方式

    SpringBoot系列之集成Dubbo的方式 本博客介绍Springboot框架集成Dubbo实现微服务的3种常用方式,对于Dubbo知识不是很熟悉的,请先学习我上一篇博客:SpringBoot系列 ...

随机推荐

  1. 你必须知道的容器监控 (2) cAdvisor

    本篇已加入<.NET Core on K8S学习实践系列文章索引>,可以点击查看更多容器化技术相关系列文章.上一篇我们了解了docker自带的监控子命令以及开源监控工具Weave Scop ...

  2. openflow流表项中有关ip掩码的匹配的问题(控制器为ryu)

    一.写在前面 唉,被分配到sdn安全方向,顶不住,顶不住,感觉搞不出来什么有搞头的东西.可若是让我水水的应付,我想我也是做不到的,世上无难事只怕有心人.好了,进入正题,本次要讨论的时一个比较细节的东西 ...

  3. jhipster入门

    环境: 阿里云linux /////////////////////////////////////////////////////////////////////yum install java-1 ...

  4. 设置H5页面文字不可复制

    * { moz-user-select: -moz-none; -moz-user-select: none; -o-user-select: none; -khtml-user-select: no ...

  5. C语言文件输入/输出 ACM改进版(用freopen函数方便检验)

    这次用到的文件打开函数不再是fopen,而是stdio.h中包含的另一个函数freopen FILE * freopen ( const char * filename,const char * mo ...

  6. POJ2431 优先队列+贪心 - biaobiao88

    以下代码可对结构体数组中的元素进行排序,也差不多算是一个小小的模板了吧 #include<iostream> #include<algorithm> using namespa ...

  7. Flink 从 0 到 1 学习 —— Flink Data transformation(转换)

    toc: true title: Flink 从 0 到 1 学习 -- Flink Data transformation(转换) date: 2018-11-04 tags: Flink 大数据 ...

  8. 路由器配置深入浅出—路由器接口PPP协议封装及PAP和CHAP验证配置

    知识域: 是针对点对点专线连接的接口的二层封装协议配置 PPP的PAP和CHAP验证,cpt支持,不一定要在gns3上做实验. 路由器出厂默认是hdlc封装,修改为ppp封装后,可以采用pap验证或者 ...

  9. JVM垃圾收集策略与算法

    垃圾收集策略与算法 程序计数器.虚拟机栈.本地方法栈随线程而生,也随线程而灭:栈帧随着方法的开始而入栈,随着方法的结束而出栈.这几个区域的内存分配和回收都具有确定性,在这几个区域内不需要过多考虑回收的 ...

  10. Windows 10 中CPU虚拟化已开启,但是docker无法运行

    在管理员模式下的PowerShell中执行: bcdedit /set hypervisorlaunchtype Auto 然后重启电脑即可