【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. Day17_99_IO_FileReader文件字符输入流

    FileReader文件字符输入流 * 继承结构 Java.lang.Object - java.io.Reader; 抽象类 java.io.InputStreamReader; <转换流: ...

  2. 基本dos命令

    Dos命令 打开cmd方法 开始---windows系统---命令提示符 win键 + R键 输入cmd --- 回车 按住Shift键---右击任意文件夹-----单击在此处打开PowerShell ...

  3. Pytorch系列:(三)模型构建

    nn.Module 函数详解 nn.Module是所有网络模型结构的基类,无论是pytorch自带的模型,还是要自定义模型,都需要继承这个类.这个模块包含了很多子模块,如下所示,_parameters ...

  4. 判断请求是否属于Ajax请求

    我们有时候需要根据请求类型来判断返回视图名称还是JSON数据,这里记录一个判断Ajax的工具类方便日后好找 通过传入Request对象获取头信息,根据头信息判断是否属于Ajax请求 public cl ...

  5. 1087 All Roads Lead to Rome

    Indeed there are many different tourist routes from our city to Rome. You are supposed to find your ...

  6. SpringAOP_设置注入实现

    SpringAOP_设置注入实现 AOP_面向切面编程初步了解 让我们先想象一个场景,你正在编写一个项目,在开发过程中的多个模块都有某段重复的代码,于是你选择将其抽象成一个方法,然后在需要的地方调用这 ...

  7. 从苏宁电器到卡巴斯基第11篇:我在苏宁电器当营业员 III

    积分换礼的是是非非 在苏宁购物是需要会员卡的(免费办理),我们需要利用这个会员卡来开单,顾客的消费可以换算成积分,贮存在会员卡里面.这个积分可以用于积分换礼,比如电磁炉.乐扣保鲜盒或者其它一些家用器具 ...

  8. Windows PE第九章 线程局部存储

    线程局部存储(TLS) 这个东西并不陌生了,之前写过了一个关于这个的应用,利用静态TLS姿势实现代码段静态加密免杀或者所谓的加壳思路.地址在这:http://blog.csdn.net/u013761 ...

  9. 预防NSA勒索病毒攻击脚本

    预防445端口勒索病毒修复脚本 直接复制下去,创建一个文件,名字随意后缀是.bat,然后双击就可以了(如果提示拒绝访问,就直接右键管理员,尤其是Win8 Win10). :+添加关键注册表以及停掉并且 ...

  10. visual studio 将他人的 vtk 程序在本机生成

    在网上下载了一些关于vtk的资源,在本机使用visual studio 打开后,生成时出现类似与以下的错误 无法打开包括文件:"vtkStructuredPointsToPolyDataFi ...