【SpringBoot基础系列】手把手实现国际化支持实例开发

国际化的支持,对于app开发的小伙伴来说应该比价常见了;作为java后端的小伙伴,一般来讲接触国际化的机会不太多,毕竟业务开展到海外的企业并没有太多

SpringBoot提供了国际化的支持,网上也有相关的教程,然而实际体验的时候,发现并没有预期的那么顺利;本文将介绍一下SpringBoot如何支持国家化,以及在支持的过程中,一些注意事项

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=success
500=unexpected exception
name=user name
pwd=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
@SpringBootApplication
public 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")
@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);
}

如下图,即便修改了language,返回都是默认的中文

针对这种解决办法是在设置Locale时,指定第二个可继承参数为true

@GetMapping(path = "change3")
@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);
}

4. Cookies方式缓存国际化信息

上面虽说支持了根据传参来设置国际化,但是需要每次传参都带上这个参数language=zh_CN,还需要我们自己来解析这个请求参数,我们可以考虑借助拦截器来实现统一的Local设置

这个拦截器可以自己按照上面的方式写,当然更推荐的是直接使用已封装好的

@Configuration
public 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")
@ResponseBody
public 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")
@ResponseBody
public 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. 项目

1. 一灰灰Blog

尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

【SpringBoot基础系列】手把手实现国际化支持实例开发的更多相关文章

  1. SpringBoot基础系列之自定义配置源使用姿势实例演示

    [SpringBoot基础系列]自定义配置源的使用姿势介绍 前面一篇博文介绍了一个@Value的一些知识点,其中提了一个点,@Value对应的配置,除了是配置文件中之外,可以从其他的数据源中获取么,如 ...

  2. 【SpringBoot 基础系列】实现一个自定义配置加载器(应用篇)

    [SpringBoot 基础系列]实现一个自定义配置加载器(应用篇) Spring 中提供了@Value注解,用来绑定配置,可以实现从配置文件中,读取对应的配置并赋值给成员变量:某些时候,我们的配置可 ...

  3. SpringBoot基础系列-SpringCache使用

    原创文章,转载请标注出处:<SpringBoot基础系列-SpringCache使用> 一.概述 SpringCache本身是一个缓存体系的抽象实现,并没有具体的缓存能力,要使用Sprin ...

  4. SpringBoot基础系列-使用日志

    原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9996897.html SpringBoot基础系列-使用日志 概述 SpringBoot ...

  5. SpringBoot基础系列-使用Profiles

    原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9996884.html SpringBoot基础系列-使用Profile 概述 Profi ...

  6. SpringBoot基础系列-SpringBoot配置

    原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9990680.html SpringBoot基础系列-SpringBoot配置 概述 属性 ...

  7. 【SpringBoot基础系列-实战】如何指定 bean 最先加载(应用篇)

    [基础系列-实战]如何指定 bean 最先加载(应用篇) 在日常的业务开发中,绝大多数我们都是不关注 bean 的加载顺序,然而如果在某些场景下,当我们希望某个 bean 优于其他的 bean 被实例 ...

  8. SpringBoot基础系列一

    SpringBoot基础知识概览 特性 核心理念:约定优于配置 特点: 1. 开箱即用,根据项目依赖自动配置 2. 功能强大的服务体系,如嵌入式服务.安全 3. 绝无代码生成,不用写.xml配置,用注 ...

  9. springBoot基础系列--properties配置

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/7183408.html SpringBoot中免除了大部分手动配置,但是对于一些特定的情况, ...

随机推荐

  1. prometheus入门介绍及相关组件、原理讲解

    1:介绍 Prometheus 是由 SoundCloud 开源监控告警解决方案. prometheus是由谷歌研发的一款开源的监控软件,目前已经被云计算本地基金会托管,是继k8s托管的第二个项目. ...

  2. 14.Quick QML-TextInput详解

    1.TextInput属性用来编辑一行文本,对应QLineEdit,除了正常输入外,我们还可以设置echoMode属性改为密码显示状态,也可以通过validator属性和inputMask属性来设置验 ...

  3. IDAPython类库---idc.py的源码

    #!/usr/bin/env python #--------------------------------------------------------------------- # IDAPy ...

  4. hdu4642博弈(矩阵)

    题意:       给一个01矩阵,每次可以选择1的格子,选择之后以他为左上角的矩阵全都取反,两个人轮班取,不能取的人输. 思路:        博弈的题目,结论是右下角是0就输,1就赢,原因可以这么 ...

  5. Bettercap2.X版本的使用

    目录 Bettercap 安装 ARP欺骗 DNS 欺骗 注入脚本 结合Beef-XSS 替换下载文件 Bettercap 很多人应该都听过或者用过Ettercap,这是Kali下一款优秀的ARP欺骗 ...

  6. 深入浅出带你玩转sqlilabs(一)

    一.MySQL数据库结构分层 1.1库名,表名,列名,数据库用户等 Mysql数据库结构示例: 数据库A zblog = www.zblog.com 表名 列名(字段) 数据 数据库B dede = ...

  7. Day003 JavaDoc

    JavaDoc javadoc命令是用来生成自己的Api文档的 参数信息 @author 作者名 @version 版本号 @since 指明需要最早使用的jdk版本 @param 参数名 @retu ...

  8. phpstorm中加上符号($,括号等)后搜索不到

    Ctrl+F右边选中这个Regex后带上符号就搜索不到,不要勾选这个就可以带符搜索了

  9. webpack 快速入门 系列 —— 初步认识 webpack

    初步认识 webpack webpack 是一种构建工具 webpack 是构建工具中的一种. 所谓构建,就是将资源转成浏览器可以识别的.比如我们用 less.es6 写代码,浏览器不能识别 less ...

  10. 算法转 Java 后端,2021秋招斩获腾讯、京东、百度等大厂 offer 面经分享!|JavaGuide|Guide哥

    这是一篇来自算法转Java后端的一位读者的投稿!文章主要分享了作者从迷茫崩溃到最终斩获腾讯.京东.百度等大厂 offer 的经历.文章内容很全面.真实,相信可以对正在准备面试的小伙伴有帮助! 看到公众 ...