【SpringBoot根底系列】手把手实现国际化反对实例开发
国际化的反对,对于app开发的小伙伴来说应该比价常见了;作为java后端的小伙伴,一般来讲接触国际化的机会不太多,毕竟业务发展到海内的企业并没有太多
SpringBoot提供了国际化的反对,网上也有相干的教程,然而理论体验的时候,发现并没有预期的那么顺利;本文将介绍一下SpringBoot如何反对国家化,以及在反对的过程中,一些注意事项
<!-- more -->
I. 我的项目环境
1. 我的项目依赖
本我的项目借助SpringBoot 2.2.1.RELEASE
+ maven 3.5.3
+ IDEA
进行开发
开一个web服务用于测试
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency></dependencies>
2. 配置文件
配置文件中,指定国际化的参数,thmeleaf的配置信息
application.yml
spring: messages: basename: i18n/messages/messages encoding: UTF-8 fallbackToSystemLocale: false thymeleaf: mode: HTML encoding: UTF-8 servlet: content-type: text/html cache: false
3. 国际化信息文件
下面的配置 spring.messages.basename
指定国际化配置文件的目录与前缀,取值为i18n/messages/messages
所以在资源目录下,新建文件 i18n/messages
,国际化文件名为 messages-xxx.properties
,我的项目后果如
对应的信息如简体中文 messages_zh_CN.properties
200=胜利500=內部异样name=用户名pwd=明码
英文 messages_en_US.properties
200=success500=unexpected exceptionname=user namepwd=password
繁体 messages_zh_TW.properties
200=胜利500=內部異常name=用戶名pwd=密碼
阐明
留神spring.messages.basename
这个配置的取值为国际化文件的目录 + 文件名前缀
,比方下面若少了最初一层的messages
,会提醒取不到配置
其次在IDEA中,选中国家化文件之后,点击下方的Resource Bundle
,能够进入如上图中更敌对的编辑框,反对一次批改多个语言的信息
II. 国际化反对
后面是国际化的根本配置,那么如何依据后面配置中的key,获取不同语言的value呢?
1. MessageSource
在SpringBoot中次要借助MessageSource
来获取不同语言的value信息
如一个最根本的封装
public class MsgUtil { private static MessageSource messageSource; public static void inti(MessageSource messageSource) { MsgUtil.messageSource = messageSource; } /** * 获取单个国际化翻译值 */ public static String get(String msgKey) { try { return messageSource.getMessage(msgKey, null, LocaleContextHolder.getLocale()); } catch (Exception e) { return msgKey; } }}
2. 测试demo
接下来写一个根底的测试demo,依据传参来批改LocalContextHolder
中的值,从而实现不同语言的切换
@Controller@SpringBootApplicationpublic class Application { public Application(MessageSource messageSource) { MsgUtil.inti(messageSource); } public static void main(String[] args) { SpringApplication.run(Application.class); } @Data @Accessors(chain = true) public static class RspWrapper<T> { private int code; private String msg; private T data; } @GetMapping(path = "change") @ResponseBody public String changeLocal(String language) { String[] s = language.split("_"); LocaleContextHolder.setLocale(new Locale(s[0], s[1])); RspWrapper res = new RspWrapper<>().setCode(200).setMsg(MsgUtil.get("200")).setData(true); return JSON.toJSONString(res); }}
演示如下
3. 子线程反对
下面尽管能够依据申请参数来切换语言,然而有个问题,如果在子线程中进行国际化反对,则会不失效
@GetMapping(path = "change2")@ResponseBodypublic String changeLocal(String language) { String[] s = language.split("_"); LocaleContextHolder.setLocale(new Locale(s[0], s[1])); RspWrapper res = new RspWrapper<>().setCode(200).setMsg(MsgUtil.get("200")).setData(true); return JSON.toJSONString(res);}
如下图,即使批改了language,返回都是默认的中文
针对这种解决办法是在设置Locale时,指定第二个可继承参数为true
@GetMapping(path = "change3")@ResponseBodypublic String changeLocal(String language) { String[] s = language.split("_"); LocaleContextHolder.setLocale(new Locale(s[0], s[1])); RspWrapper res = new RspWrapper<>().setCode(200).setMsg(MsgUtil.get("200")).setData(true); return JSON.toJSONString(res);}
4. Cookies形式缓存国际化信息
下面虽说反对了依据传参来设置国际化,然而须要每次传参都带上这个参数language=zh_CN
,还须要咱们本人来解析这个申请参数,咱们能够思考借助拦截器来实现对立的Local设置
这个拦截器能够本人依照下面的形式写,当然更举荐的是间接应用已封装好的
@Configurationpublic class AutoConfig implements WebMvcConfigurer { /** * 这个如果不存在,则会抛异样: nested exception is java.lang.UnsupportedOperationException: Cannot change HTTP accept header - use a different locale resolution strategy * * @return */ @Bean public LocaleResolver localeResolver() { // 也能够换成 SessionLocalResolver, 区别在于国际化的利用范畴 CookieLocaleResolver localeResolver = new CookieLocaleResolver(); localeResolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE); return localeResolver; } /** * 依据申请参数,来设置本地化 * * @return */ @Bean public LocaleChangeInterceptor localeChangeInterceptor() { LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor(); // Defaults to "locale" if not set localeChangeInterceptor.setParamName("language"); return localeChangeInterceptor; } @Override public void addInterceptors(InterceptorRegistry interceptorRegistry) { interceptorRegistry.addInterceptor(localeChangeInterceptor()); }}
请留神下面的 localResolver
, 当咱们不注册这个bean的时候,运行则会抛出异样nested exception is java.lang.UnsupportedOperationException: Cannot change HTTP accept header - use a different locale resolution
下面的实例中,采纳的是CookieLocaleResolver
,因而会在cookie中缓存语言信息,一次批改,后续都会失效
测试如下
@GetMapping(path = "say")@ResponseBodypublic String say(String name) { RspWrapper res = new RspWrapper<>().setCode(200).setMsg(MsgUtil.get("200")).setData(MsgUtil.get("name") + ":" + name); return JSON.toJSONString(res);}@GetMapping(path = "say2")@ResponseBodypublic String say2(String name) { RspWrapper res = new RspWrapper<>().setCode(200).setMsg(MsgUtil.get("200")).setData(MsgUtil.get("name") + ":" + name); return JSON.toJSONString(res);}
次要一个中央设置了语言,后续的拜访不带语言参数时,都会复用之前设置的语言,这样应用来说就更简洁了
5. 页面元素国际化
下面介绍的是返回的json串反对国际化,另外一个场景就是咱们返回的页面,心愿渲染的数据也能够实现国际化反对
在上文的根底上实现这个也没什么难度了
在资源目录下,新建目录templates
,新建模板文件 index.html
<!DOCTYPE html><html xmlns:th="http://www.thymeleaf.org"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="author" content="YiHui"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> <title>一灰灰blog 国际化测试页面</title></head><body><div> <div class="title">hello world!</div> <br/> <div class="content" th:text="'name: ' + ${name}">默认用户名</div> <br/> <div class="sign" th:text="'pwd: ' + ${pwd}">默认明码</div> <br/></div></body></html>
对应的controller
@GetMapping(path = {"", "/", "/index"})public String index(Model model) { model.addAttribute("name", MsgUtil.get("name")); model.addAttribute("pwd", MsgUtil.get("pwd")); return "index";}
虽说下面这样实现了国家化的反对,然而看起来不太优雅,难道还须要后端接口进行本义一下么,没有更简略的形式么?
Themeleaf提供了更简略的反对形式,将下面的$改成#即可
<!DOCTYPE html><html xmlns:th="http://www.thymeleaf.org"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="author" content="YiHui"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> <title>一灰灰blog 国际化测试页面</title></head><body><div> <div class="title">hello world!</div> <br/> <div class="content" th:text="'name: ' + #{name}">默认用户名</div> <br/> <div class="sign" th:text="'pwd: ' + #{pwd}">默认明码</div> <br/> <div class="content" th:text="'200: ' + #{200}">200</div> <br/> <div class="content" th:text="'500: ' + #{500}">500</div></div></body></html>
对应的rest
@GetMapping(path = "show")public String show() { return "show";}
6. 注意事项
在实现国际化的过程中,遇到了上面几个问题,特此记录一下
6.1 配置信息无奈获取
在应用messageSource.getMessage(msgKey, null, LocaleContextHolder.getLocale())
查问配置信息,后果提醒org.springframework.context.NoSuchMessageException: No message found under code '200' for locale 'en_US'.
呈现下面这个问题,当然优先判断是否真的配置了这个参数,其次确认spring.messages.basename
是否精确,对应的value为目录 + 语言的前缀
- 如我的配置文件为
i18n/messages/messages_en_US.properties
, 那么这个value就应该是i18n/messages/messages
6.2 中文乱码问题
- 设置编码
spring.messages.encoding=utf-8
如果发现下面这个设置了仍然没有失效,那么考虑一下配置文件是否为utf-8编码
6.3 依据申请反对国际化
须要增加本地化的拦截器LocaleChangeInterceptor
,来实现依据申请参数,解析语言环境
其次须要注册LocaleResolver
,比方demo中应用CookieLocaleResolver
,来保留国际化信息 (如果不设置它会抛异样)
II. 其余
0. 我的项目
- 工程:https://github.com/liuyueyi/spring-boot-demo
- 我的项目源码: https://github.com/liuyueyi/spring-boot-demo/blob/master/spring-boot/150-i18n
1. 一灰灰Blog
尽信书则不如,以上内容,纯属一家之言,因集体能力无限,不免有疏漏和谬误之处,如发现bug或者有更好的倡议,欢送批评指正,不吝感谢
上面一灰灰的集体博客,记录所有学习和工作中的博文,欢送大家前去逛逛
- 一灰灰Blog集体博客 https://blog.hhui.top
- 一灰灰Blog-Spring专题博客 http://spring.hhui.top