Blogger Information
Blog 41
fans 0
comment 0
visits 25319
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
19、【转载】Spring Boot国际化
自由之上
Original
462 people have browsed it

国际化(Internationalization 简称 I18n,其中“I”和“n”分别为首末字符,18 则为中间的字符数)是指软件开发时应该具备支持多种语言和地区的功能。
换句话说就是,开发的软件需要能同时应对不同国家和地区的用户访问,并根据用户地区和语言习惯,提供相应的、符合用具阅读习惯的页面和数据,例如,为中国用户提供汉语界面显示,为国外用户提供提供英语界面显示。

在 Spring 项目中实现国际化,通常需要以下 3 步:

  1. 编写国际化资源(配置)文件;
  2. 使用 ResourceBundleMessageSource 管理国际化资源文件;
  3. 在页面获取国际化内容。

1. 编写国际化资源文件

在 Spring Boot 的类路径下创建国际化资源文件,文件名格式为:基本名语言代码国家或地区代码,例如 login_en_US.properties、login_zh_CN.properties。

以 spring-boot-springmvc-demo1为例,在 src/main/resources 下创建一个 i18n 的目录,并在该目录中按照国际化资源文件命名格式分别创建以下三个文件,

  • login.properties:无语言设置时生效
  • login_en_US.properties :英语时生效
  • login_zh_CN.properties:中文时生效

以上国际化资源文件创建完成后,IDEA 会自动识别它们,并转换成如下的模式:

图1:国际化资源文件

打开任意一个国际化资源文件,并切换为 Resource Bundle 模式,然后点击“+”号,创建所需的国际化属性,如下图。

图2:编辑国际化资源文件

2. 使用 ResourceBundleMessageSource 管理国际化资源文件

Spring Boot 已经对 ResourceBundleMessageSource 提供了默认的自动配置。

Spring Boot 通过 MessageSourceAutoConfiguration 对 ResourceBundleMessageSource 提供了默认配置,其部分源码如下。

  1. @Configuration(proxyBeanMethods = false)
  2. @ConditionalOnMissingBean(name = AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME, search = SearchStrategy.CURRENT)
  3. @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
  4. @Conditional(org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration.ResourceBundleCondition.class)
  5. @EnableConfigurationProperties
  6. public class MessageSourceAutoConfiguration {
  7. private static final Resource[] NO_RESOURCES = {};
  8. // 将 MessageSourceProperties 以组件的形式添加到容器中
  9. // MessageSourceProperties 下的每个属性都与以 spring.messages 开头的属性对应
  10. @Bean
  11. @ConfigurationProperties(prefix = "spring.messages")
  12. public MessageSourceProperties messageSourceProperties() {
  13. return new MessageSourceProperties();
  14. }
  15. //Spring Boot 会从容器中获取 MessageSourceProperties
  16. // 读取国际化资源文件的 basename(基本名)、encoding(编码)等信息
  17. // 并封装到 ResourceBundleMessageSource 中
  18. @Bean
  19. public MessageSource messageSource(MessageSourceProperties properties) {
  20. ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
  21. //读取国际化资源文件的 basename (基本名),并封装到 ResourceBundleMessageSource 中
  22. if (StringUtils.hasText(properties.getBasename())) {
  23. messageSource.setBasenames(StringUtils
  24. .commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename())));
  25. }
  26. //读取国际化资源文件的 encoding (编码),并封装到 ResourceBundleMessageSource 中
  27. if (properties.getEncoding() != null) {
  28. messageSource.setDefaultEncoding(properties.getEncoding().name());
  29. }
  30. messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
  31. Duration cacheDuration = properties.getCacheDuration();
  32. if (cacheDuration != null) {
  33. messageSource.setCacheMillis(cacheDuration.toMillis());
  34. }
  35. messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
  36. messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
  37. return messageSource;
  38. }
  39. ...
  40. }

从以上源码可知:

  • Spring Boot 将 MessageSourceProperties 以组件的形式添加到容器中;
  • MessageSourceProperties 的属性与配置文件中以“spring.messages”开头的配置进行了绑定;
  • Spring Boot 从容器中获取 MessageSourceProperties 组件,并从中读取国际化资源文件的 basename(文件基本名)、encoding(编码)等信息,将它们封装到 ResourceBundleMessageSource 中;
  • Spring Boot 将 ResourceBundleMessageSource 以组件的形式添加到容器中,进而实现对国际化资源文件的管理。

查看 MessageSourceProperties 类,其代码如下。

  1. public class MessageSourceProperties {
  2. private String basename = "messages";
  3. private Charset encoding;
  4. @DurationUnit(ChronoUnit.SECONDS)
  5. private Duration cacheDuration;
  6. private boolean fallbackToSystemLocale;
  7. private boolean alwaysUseMessageFormat;
  8. private boolean useCodeAsDefaultMessage;
  9. public MessageSourceProperties() {
  10. this.encoding = StandardCharsets.UTF_8;
  11. this.fallbackToSystemLocale = true;
  12. this.alwaysUseMessageFormat = false;
  13. this.useCodeAsDefaultMessage = false;
  14. }
  15. ...
  16. }

通过以上代码,我们可以得到以下 3 点信息:

  • MessageSourceProperties 为 basename、encoding 等属性提供了默认值;
  • basename 表示国际化资源文件的基本名,其默认取值为“message”,即 Spring Boot 默认会获取类路径下的 message.properties 以及 message_XXX.properties 作为国际化资源文件;
  • 在 application.porperties/yml 等配置文件中,使用配置参数“spring.messages.basename”即可重新指定国际化资源文件的基本名。

通过以上源码分析可知,Spring Boot 已经对国际化资源文件的管理提供了默认自动配置,我们这里只需要在 Spring Boot 全局配置文件中,使用配置参数“spring.messages.basename”指定我们自定义的国际资源文件的基本名即可,代码如下(当指定多个资源文件时,用逗号分隔)。

  1. spring.messages.basename=i18n.login

3. 获取国际化内容

由于页面使用的是 Tymeleaf 模板引擎,因此我们可以通过表达式 #{…} 获取国际化内容。

以 spring-boot-adminex 为例,在 login.html 中获取国际化内容,代码如下。

  1. <!DOCTYPE html>
  2. <html lang="en" xmlns:th="http://www.thymeleaf.org">
  3. <head>
  4. <meta charset="utf-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
  6. <meta name="description" content="">
  7. <meta name="author" content="ThemeBucket">
  8. <link rel="shortcut icon" href="#" type="image/png">
  9. <title>Login</title>
  10. <!--将js css 等静态资源的引用修改为 绝对路径-->
  11. <link href="css/style.css" th:href="@{/css/style.css}" rel="stylesheet">
  12. <link href="css/style-responsive.css" th:href="@{/css/style-responsive.css}" rel="stylesheet">
  13. <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
  14. <!--[if lt IE 9]>
  15. <script src="js/html5shiv.js" th:src="@{/js/html5shiv.js}"></script>
  16. <script src="js/respond.min.js" th:src="@{/js/respond.min.js}"></script>
  17. <![endif]-->
  18. </head>
  19. <body class="login-body">
  20. <div class="container">
  21. <form class="form-signin" th:action="@{/user/login}" method="post">
  22. <div class="form-signin-heading text-center">
  23. <h1 class="sign-title" th:text="#{login.btn}">Sign In</h1>
  24. <img src="/images/login-logo.png" th:src="@{/images/login-logo.png}" alt=""/>
  25. </div>
  26. <div class="login-wrap">
  27. <p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
  28. <input type="text" class="form-control" name="username" placeholder="User ID" autofocus
  29. th:placeholder="#{login.username}"/>
  30. <input type="password" class="form-control" name="password" placeholder="Password"
  31. th:placeholder="#{login.password}"/>
  32. <label class="checkbox">
  33. <input type="checkbox" value="remember-me" th:text="#{login.remember}">
  34. <span class="pull-right">
  35. <a data-toggle="modal" href="#myModal" th:text="#{login.forgot}"> </a>
  36. </span>
  37. </label>
  38. <button class="btn btn-lg btn-login btn-block" type="submit">
  39. <i class="fa fa-check"></i>
  40. </button>
  41. <div class="registration">
  42. <!--Thymeleaf 行内写法-->
  43. [[#{login.not-a-member}]]
  44. <a class="" href="/registration.html" th:href="@{/registration.html}">
  45. [[#{login.signup}]]
  46. </a>
  47. <!--thymeleaf 模板引擎的参数用()代替 ?-->
  48. <br/>
  49. <a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文</a>|
  50. <a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">English</a>
  51. </div>
  52. </div>
  53. <!-- Modal -->
  54. <div aria-hidden="true" aria-labelledby="myModalLabel" role="dialog" tabindex="-1" id="myModal"
  55. class="modal fade">
  56. <div class="modal-dialog">
  57. <div class="modal-content">
  58. <div class="modal-header">
  59. <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
  60. <h4 class="modal-title">Forgot Password ?</h4>
  61. </div>
  62. <div class="modal-body">
  63. <p>Enter your e-mail address below to reset your password.</p>
  64. <input type="text" name="email" placeholder="Email" autocomplete="off"
  65. class="form-control placeholder-no-fix">
  66. </div>
  67. <div class="modal-footer">
  68. <button data-dismiss="modal" class="btn btn-default" type="button">Cancel</button>
  69. <button class="btn btn-primary" type="button">Submit</button>
  70. </div>
  71. </div>
  72. </div>
  73. </div>
  74. <!-- modal -->
  75. </form>
  76. </div>
  77. <!-- Placed js at the end of the document so the pages load faster -->
  78. <!-- Placed js at the end of the document so the pages load faster -->
  79. <script src="js/jquery-1.10.2.min.js" th:src="@{/js/jquery-1.10.2.min.js}"></script>
  80. <script src="js/bootstrap.min.js" th:src="@{/js/bootstrap.min.js}"></script>
  81. <script src="js/modernizr.min.js" th:src="@{/js/modernizr.min.js}"></script>
  82. </body>
  83. </html>

4、验证

启动 Spring Boot,使用浏览器访问登陆页,此时浏览器默认使用中文,结果如下图。

图3:中文登陆页

将浏览器语言切换为英文,再次访问登陆页,结果如下图。

图4:英文登录页(猛击图片,查看原图)

5、手动切换语言

如下图所示,在登陆页(login.html)最下方有两个切换语言的链接,想要通过点击它们来切换进行国际化的语言,该怎么做呢?

图5:切换语言按钮

1、区域信息解析器自动配置

我们知道,Spring MVC 进行国际化时有 2 个十分重要的对象:

  • Locale:区域信息对象
  • LocaleResolver:区域信息解析器,容器中的组件,负责获取区域信息对象

我们可以通过以上两个对象对区域信息的切换,以达到切换语言的目的。

Spring Boot 在 WebMvcAutoConfiguration 中为区域信息解析器(LocaleResolver)进行了自动配置,源码如下。

  1. @Bean
  2. @ConditionalOnMissingBean(name = DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME)
  3. @SuppressWarnings("deprecation")
  4. public LocaleResolver localeResolver() {
  5. if (this.webProperties.getLocaleResolver() == WebProperties.LocaleResolver.FIXED) {
  6. return new FixedLocaleResolver(this.webProperties.getLocale());
  7. }
  8. if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
  9. return new FixedLocaleResolver(this.mvcProperties.getLocale());
  10. }
  11. AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
  12. Locale locale = (this.webProperties.getLocale() != null) ? this.webProperties.getLocale()
  13. : this.mvcProperties.getLocale();
  14. localeResolver.setDefaultLocale(locale);
  15. return localeResolver;
  16. }

从以上源码可知:

  • 该方法默认向容器中添加了一个区域信息解析器(LocaleResolver)组件,它会根据请求头中携带的“Accept-Language”参数,获取相应区域信息(Locale)对象。
  • 该方法上使用了 @ConditionalOnMissingBean 注解,其参数 name 的取值为 localeResolver(与该方法注入到容器中的组件名称一致),该注解的含义为:当容器中不存在名称为 localResolver 组件时,该方法才会生效。换句话说,当我们手动向容器中添加一个名为“localeResolver”的组件时,Spring Boot 自动配置的区域信息解析器会失效,而我们定义的区域信息解析器则会生效。

2、手动切换语言

  1. 修改 login.html 切换语言链接,在请求中携带国际化区域信息,代码如下。

    1. <!--thymeleaf 模板引擎的参数用()代替 ?-->
    2. <a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文</a>|
    3. <a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">English</a>
  2. 在 net.biancheng.www 下创建一个 component 包,并在该包中创建一个区域信息解析器 MyLocalResolver,代码如下。

  1. package net.biancheng.www.componet;
  2. import org.springframework.util.StringUtils;
  3. import org.springframework.web.servlet.LocaleResolver;
  4. import javax.servlet.http.HttpServletRequest;
  5. import javax.servlet.http.HttpServletResponse;
  6. import java.util.Locale;
  7. //自定义区域信息解析器
  8. public class MyLocalResolver implements LocaleResolver {
  9. @Override
  10. public Locale resolveLocale(HttpServletRequest request) {
  11. //获取请求中参数
  12. String l = request.getParameter("l");
  13. //获取默认的区域信息解析器
  14. Locale locale = Locale.getDefault();
  15. //根据请求中的参数重新构造区域信息对象
  16. if (StringUtils.hasText(l)) {
  17. String[] s = l.split("_");
  18. locale = new Locale(s[0], s[1]);
  19. }
  20. return locale;
  21. }
  22. @Override
  23. public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
  24. }
  25. }
  1. 在 net.biancheng.www.config 的 MyMvcConfig 中添加以下方法,将自定义的区域信息解析器以组件的形式添加到容器中,代码如下。
  1. //将自定义的区域信息解析器以组件的形式添加到容器中
  2. @Bean
  3. public LocaleResolver localeResolver(){
  4. return new MyLocalResolver();
  5. }
  1. 启动 Spring Boot,访问登录页 login.html,结果如下图。

    图6:默认登陆页

  2. 点击页面最下方的“English”链接,将语言切换到英语,结果如下图。

    图7:切换国家化语言为英语

  3. 点击页面最下方的“中文”链接,将语言切换到中文,结果如下图。

    图8:切换语言为中文

Statement of this Website
The copyright of this blog article belongs to the blogger. Please specify the address when reprinting! If there is any infringement or violation of the law, please contact admin@php.cn Report processing!
All comments Speak rationally on civilized internet, please comply with News Comment Service Agreement
0 comments
Author's latest blog post