• 19. Spring Boot国际化


    国际化(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”指定我们自定义的国际资源文件的基本名即可,代码如下(当指定多个资源文件时,用逗号分隔)。

    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>

    验证

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

    图3:中文登陆页


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

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

    手动切换语言

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

    图5:切换语言按钮

    区域信息解析器自动配置

    我们知道,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 自动配置的区域信息解析器会失效,而我们定义的区域信息解析器则会生效。

    手动切换语言

    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. }

    3. 在 net.biancheng.www.config 的 MyMvcConfig 中添加以下方法,将自定义的区域信息解析器以组件的形式添加到容器中,代码如下。

    1. //将自定义的区域信息解析器以组件的形式添加到容器中
    2. @Bean
    3. public LocaleResolver localeResolver(){
    4. return new MyLocalResolver();
    5. }

    4. 启动 Spring Boot,访问登录页 login.html,结果如下图。
     

    图6:默认登陆页 


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

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


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

    图8:切换语言为中文

  • 相关阅读:
    菁染料CY3、CY5、CY5.5、CY7、CY7.5标记来苏糖Lyxose/艾杜糖Iditol/麦芽糖醇Maltitol
    首个保险“远程双录”管理办法出台!菊风持续推进金融业布局双录智能化升级
    JavaScript 数据结构相关知识点 如何判断对象是否为空
    2024.2.27 模拟实现 RabbitMQ —— 网络通信设计(客户端)
    JVM相关
    V5.1.1,新版发布|软件安全大于一切
    OpenOFDM接收端信号处理流程
    区块链:一文了解区块链,在生活中的应用,代码实现,图导,区块链智能合约
    logback的简单配置详解
    Games104 引擎工具链笔记
  • 原文地址:https://blog.csdn.net/tiger00598/article/details/125495561