本文提供相关源码,请放心食用,详见网页侧边栏或底部,有疑问请评论或 Issue
一、LocaleResolver
国际化的支持中一个重要的类是 LocaleResolver,它提供了四种默认实现:
AcceptHeaderLocaleResolver
没有任何具体实现,通过浏览器头部的语言信息来进行多语言选择。
FixedLocaleResolver
设置固定的语言信息,这样整个系统的语言是一成不变的,用处不大。
CookieLocaleResolver
将语言信息设置到 Cookie 中,这样整个系统就可以获得语言信息
SessionLocaleResolver
将语言信息放到 Session 中,这样整个系统就可以从Session中获得语言信息。
一般来说,我们都使用基于 Cookie 或者是基于 Session 的 LocaleResolver,也可以自定义 LocaleResolver。
当每次请求时,都会调用我们设置的 LocaleResolver,根据它的 resolveLocale()
方法来解析 Locale
,从而实现国际化。
二、MessageSource
Spring 中定义了一个 MessageSource 接口,以用于支持信息的国际化和包含参数的信息的替换。MessageSource 接口的定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public interface MessageSource {
String getMessage(String code, Object[] args, String defaultMessage, Locale locale);
String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException;
String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException; }
|
我们主要使用 ResourceBundleMessageSource
和 ReloadableResourceBundleMessageSource
这两个实现类。
ResourceBundleMessageSource
是基于 JDK ResourceBundle 的 MessageSource 接口实现类。它会将访问过的 ResourceBundle 缓存起来,以便于下次直接从缓存中获取进行使用。
如果我们使用了 Springboot 的配置项 spring.messages.basename
,它就是默认的实现类。
ReloadableResourceBundleMessageSource
是以 ResourceBundleMessageSource 结尾的,但实际上它跟ResourceBundleMessageSource没有什么直接的关系。
ReloadableResourceBundleMessageSource 也是对 MessageSource 的一种实现,其用法配置等和 ResourceBundleMessageSource 基本一致。所不同的是 ReloadableResourceBundleMessageSource 内部是使用 PropertiesPersister 来加载对应的文件,这包括 properties
文件和 xml
文件,然后使用 java.util.Properties 来保存对应的数据。
这两者的详细比较可以参考文章:Spring(21)——国际化MessageSource
三、入门程序
3.1 国际化文件
在 resource 目录下建立文件夹 i18
,在其中建立以下6个文件:
(1) login.properties、login_zh_CN.properties:
login.properties & login_zh_CN.properties1 2 3 4
| login.username=用户名 login.password=密码 login.title=登录 login.verify.code=验证码
|
(2) login_en_US.properties:
login_en_US.properties1 2 3 4
| login.username=username login.password=password login.title=login login.verify.code=verifyCode
|
(3) messages.properties、messages_zh_CN.properties
messages.properties & messages_zh_CN.properties1 2 3
| language.en_US=英语 language.zh_CN=中文 welcome.msg={0}:欢迎登陆系统
|
(4) messages_en_US.properties
messages_en_US.properties1 2 3
| language.en_US=English language.zh_CN=Chinese welcome.msg={0}:Welcome to login
|
zh_CN
和 en_US
代表相应语言的文件,而不带后缀的代表默认的语言文件,当出现不能匹配的语言时,从不带后缀的文件中读取。
3.2 application.yaml
1 2 3 4 5 6 7
| spring: messages: encoding: UTF-8 cache-duration: 3600 basename: i18n/messages,i18n/login fallback-to-system-locale: false always-use-message-format: false
|
spring.messages.basename
指定了国际化文件的路径,默认在 resource
目录下查找。i18n 代表了目录名,messages 代表了国际化文件的前缀,同一个前缀的文件为一组,多组之前使用逗号隔开。
3.3 CustomSessionLocaleResolver
这里我们使用自定义的 LocaleResolver,实现基于 Session 的 LocaleResolver。
在 resolveLocale
方法中,首先检查请求参数是否携带 18N_LANGUAGE
,如果存在,则将其保存到 session中;如果不存在,从 session 中取出并返回。如果 session 中也不存在,使用预设的,即 Locale.getDefault()
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| public class CustomSessionLocaleResolver implements LocaleResolver { private static final String I18N_LANGUAGE = "language"; private static final String I18N_LANGUAGE_SESSION = "i18n_language_session";
@Override public Locale resolveLocale(HttpServletRequest req) { String i18nLanguage = req.getParameter(I18N_LANGUAGE); Locale locale = Locale.getDefault(); if (!StringUtils.isEmpty(i18nLanguage)) { String[] language = i18nLanguage.split("_"); locale = new Locale(language[0], language[1]);
HttpSession session = req.getSession(); session.setAttribute(I18N_LANGUAGE_SESSION, locale); } else { HttpSession session = req.getSession(); Locale localeInSession = (Locale) session.getAttribute(I18N_LANGUAGE_SESSION); if (localeInSession != null) { locale = localeInSession; } } return locale; }
@Override public void setLocale(HttpServletRequest req, HttpServletResponse res, Locale locale) {
} }
|
3.4 I18nConfig
该类为配置类,目前只是简单的将 CustomSessionLocaleResolver 注入进去:
1 2 3 4 5 6 7
| @Configuration public class I18nConfig { @Bean public LocaleResolver localeResolver() { return new CustomSessionLocaleResolver(); } }
|
3.5 login.html
编写一个登录页来测试下,这里使用到了模板引擎,因此首先在 pom 中导入依赖:
pom.xml1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
|
然后编写 login 页面:
login.html1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| <!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title th:text="#{login.title}">登陆</title> </head> <body> <div class="container"> <div class="form row"> <div class="form-horizontal col-md-offset-3" id="login_form"> <h3 class="form-title"></h3> <div> <a th:text="#{language.zh_CN}" th:href="@{/login(language='zh_CN')}">中文</a> <a th:text="#{language.en_US}" th:href="@{/login(language='en_US')}">英文</a> </div> <form class="form-horizontal" method="post" action="/login"> <div class="col-md-9"> <div class="form-group"> <input class="form-control" type="text" th:placeholder="#{login.username}" name="username" autofocus="autofocus"/> </div> <div class="form-group"> <input class="form-control" type="password" th:placeholder="#{login.password}" name="password"/> </div> <div class="form-group"> <input class="form-control" type="text" th:placeholder="#{login.verify.code}" name="verifyCode" maxlength="4"/> </div> <div class="form-group"> <button type="submit" class="btn btn-success pull-right" th:text="#{login.title}">登录</button> </div> </div> </form> </div> </div> </div> <script> </script> </body> </html>
|
3.6 测试
这里就不再自定义 MessageSource 了,使用默认的 MessageSource,编写 Controller 进行测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Controller public class TestController { @Autowired private MessageSource messageSource;
@GetMapping("/test") @ResponseBody public Object test(Locale locale) { String[] params = {"Jack Zhang"}; return messageSource.getMessage("welcome.msg", params, locale); }
@GetMapping({"/login","/"}) public String showLoginPage(){ return "login.html"; } }
|
首先我们来测试下 “/test” 接口。首次请求时,session 中没有数据,因此使用默认的 Locale.getDefault()
,也就是 zh_CN:
在 CustomSessionLocaleResolver 中,我们知道,只要增加 language
参数就能实现修改 语言,当修改语言为 en_US 时:
当我们去掉参数后继续访问,语言已经被切换到 en_US 了,因为是从 session 中读取:
下面来测试下 login 页面,因为刚刚切换了语言,所以默认是 en_US:
切换为中文后:
去掉请求参数,也一样保持中文:
四、更多自定义功能
可以参考文章:自己动手在Spring-Boot上加强国际化功能