有道无术,术尚可求,有术无道,止于术。
本系列Spring Boot版本2.7.0
国际化(internationalization)
是设计和制造能够适应不同区域要求产品的一种方式。它要求从产品中抽离所有地域语言,国家/地区和文化相关的元素。换言之,应用程序的功能和代码设计考虑在不同地区运行的需要,其代码简化了不同本地版本的生产。开发这样的程序的过程,就称为国际化。
国际化也称作i18n
,其来源是英文单词 internationalization
,取首尾字母,18 是首尾之间有18个单词,这是常用的一种简称方式。
简单来说,国际化就是不同语言地区的访客,看到的内容是当地的语言内容,比如同一个网站,在国内看到的是中文,在美丽国则显示英文。
Spring Boot
支持国际化消息,以便应用程序满足不同语言用户。接下来我们看下如何使用。
浏览器在发送请求时,会携带一个Accept-Language
请求头,用于告诉服务器端浏览器当前支持的语言,不同国家地区环境,浏览器会自动根据操作系统或者IP 获取当地的语言标识,比如在中国时,会发送zh-cn
,当然也可以直接设置语言。
下方示例表示,该浏览器可接收中文和英文的资源响应,权重分别为0.7 和0.3 ,那么客户端在服务器有中文版资源的情况下,会请求其返回中文版对象的响应,没有中文版时,则请求返回英文版响应。
Accept-Language: zh-cn, zh; q=0.7, en-us,en; q=0.3
既然浏览器有这个机制,那么实现国际化就很简单了,后台只需要根据这个标识,返回不同语言的资源就可以了。
Locale
是java.util
包提供的类,每一个Locale 对象都代表了一个特定的地理、政治和文化地区。主要用来获取本机操作系统信息,包括语言、国家等信息,国际化、日期处理时都需要使用到此类。
Locale
对象在逻辑上由下面描述的字段组成(这里只介绍两个):
ISO 639 alpha-2
或者 alpha-3
标准的语言编码,比如:“en”(英语)、“ja”(鬼子语)、“kok”(康卡尼语)、“zh”(中文)ISO 3166 alpha
或 UN M.49 numeric-3 2
标准国家代码区域代码。比如:“US”(美国)、“FR”(法国)、“029”(加勒比海)Locale
类提供了三个构造函数:
Locale(String language)
Locale(String language, String country)
Locale(String language, String country, String variant)
比如创建地区为中国,语言为中文的Locale
对象:
Locale local = new Locale("zh", "CN");
使用getDefault
方法可以根据操作系统获取当前程序默认的Locale
对象:
// 获取操作系统的本地信息
Locale locale=Locale.getDefault();
// 国家信息
System.out.println("country:"+locale.getCountry()); // CN
System.out.println("displayCountry:"+locale.getDisplayCountry()); // 中国
// 语言信息
System.out.println("language:"+locale.getLanguage()); // zh
System.out.println("displayLanguage:"+locale.getDisplayLanguage()); // 中文
SpringApplication. run(BasicApplication.class);
使用getAvailableLocales
方法可以获取到Local
支持的全部区域:
Locale[] locales = Locale.getAvailableLocales();
在Spring 中定义了一个MessageSource
接口,从名字上理解是一个消息资源的意思,该接口声明了几个获取消息的接口。
这个类的作用就是读取资源属性文件(xx.properties)
,然后根据xx.properties
文件的名称信息(本地化信息),获取匹配当前系统的国别语言信息(也可以程序指定)。
public interface MessageSource {
// 解析code对应的信息进行返回,如果对应的code不能被解析则返回默认信息defaultMessage。
@Nullable
String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale);
// 解析code对应的信息进行返回,如果对应的code不能被解析则抛出异常NoSuchMessageException
String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException;
// 通过自定义MessageSourceResolvable解析器去获取信息
String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;
}
MessageSource
是Spring Boot
支持国际化的重要接口,应用启动时,会加载该类型的Bean 实例,读取国际化资源,使用时只需要调用Bean 进行获取即可。
Spring Boot
提供了自动配置类MessageSourceAutoConfiguration
,可以看到注入了一个属性配置类和一个MessageSource
。
MessageSourceProperties
用于配置消息资源,说明如下:
spring:
messages:
# 配置资源位置,比如 resources/i18n/messages.properties 文件,则需要配置为 i18n/messages
basename: i18n/messages
# 编码格式 ,默认 UTF-8
encoding: UTF-8
# 缓存时长,未设置时永久缓存,单位秒
cache-duration: 1000
# 是否使用消息代码作为默认消息而不是抛出一个 NoSuchMessageException
use-code-as-default-message: false
# 是否总是应用 MessageFormat 规则
always-use-message-format: false
# 如果没有特定语言环境的文件,是否回退到系统语言环境
fallback-to-system-locale: true
LocaleResolver
是Spring MVC
提供的一个接口:
public interface LocaleResolver {
// 根据request对象根据指定的方式获取一个Locale,如果没有获取到,则使用用户指定的默认的Locale
Locale resolveLocale(HttpServletRequest request);
// 设置Locale
void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale);
}
从声明的方法可以看出这个接口的主要作用是根据请求对象,解析出相应的Locale
,这也是实现国际化的重要接口。
Spring MVC
在处理请求时,会去容器中获取LocaleResolver
的Bean 对象,调用其解析方法获取到当前Locale
,然后将其放到LocaleContextHolder
中,以便线程后续的执行获取到。
在Spring Boot
对Spring MVC
的自动配置中,配置了一个AcceptHeaderLocaleResolver
解析器:
可以看到该解析器处理逻辑,是解析浏览器中请求头中Accept-Language
字段值,来判断其地区的:
需求场景:对于后台返回的响应信息、错误信息等,需要根据不同地区返回不同语言信息,比如中文地区返回操作成功
,英文地区返回success
,这些也是后端最主要的国际化消息用途。
实现方案流程图:
其中1至4 步,Spring Boot
已经帮我们处理好了,我们只需要编写国际化资源文件,然后通过MessageSource
获取国际化消息即可。
首先要设置配置文件的编码方式,不然会出现中文����
乱码问题。
resources
目录下添加i18n
文件夹,然后右键new =>Resource Bundle
:
输入文件前缀messages
,并点击加号添加多个语言地区:
添加zh_CN
、en_US
,分别表示中文和英文:
会在目录下生成三个国际化文件,分别表示默认、中文、英文,接着点击Resource Bundle
添加属性。
点击添加属性,输入名称
按照国家语言,输入不同的属性值,这样在三个配置文件中,同一个msg
属性,都根据不同的地区有不同语言的属性值:
继续添加一个:
最后添加完成:
添加配置,指定国际化资源的位置,
spring:
messages:
# classpath*:[beaname].properties
basename: i18n/messages
编写一个工具类,根据属性名称和Locale 对象获取国际化消息:
@Component
public class MessageSourceUtils {
private static MessageSource messageSource;
@Autowired
public MessageSourceUtils(MessageSource messageSource) {
MessageSourceUtils.messageSource = messageSource;
}
public static String getMsg(String code) {
// LocaleContextHolder 中封装了Locale 对象;
// 该Locale 对象是AcceptHeaderLocaleResolver解析器, 根据Request请求中Accept-Language消息头解析得到的
Locale locale = LocaleContextHolder.getLocale();
// MessageSource中获取国际化信息
return messageSource.getMessage(code, null, locale);
}
}
在响应结果对象中,使用工具类获取国际化消息:
@Data
public class Result<T> {
private String code;
private String msg;
private T data;
public static Result success() {
Result result = new Result<>();
result.setCode("0");
// 响应消息国际化处理返回
result.setMsg(MessageSourceUtils.getMsg("msg"));
return result;
}
public static Result error(String code, String msg) {
Result result = new Result();
result.setCode(code);
result.setMsg(MessageSourceUtils.getMsg("msg"));
return result;
}
}
在统一异常捕获时,也使用工具类获取国际化消息:
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
@ResponseBody
public Result handleException(RuntimeException e) {
return Result.error("500", MessageSourceUtils.getMsg(e.getMessage()));
}
}
编写访问接口,返回成功Result
:
@RequestMapping(value = "/test")
public Result test() {
return Result.success();
}
中文时,可以看到返回了操作成功
:
设置谷歌浏览器为英语(美国):
可以看到返回了success
:
再编写一个异常接口:
public interface ExceptionConstants {
String SYSTEM_ERROR = "errorMsg";
}
@RestController
@RequestMapping("/test")
public class TestController{
@RequestMapping(value = "/ex")
public Result ex() {
if(true){
throw new RuntimeException(ExceptionConstants.SYSTEM_ERROR);
}
return Result.success();
}
}
测试发现,满足需求,案例成功!
使用Thymeleaf
模板创建页面时,很容易实现国际化,因为它提供了#{…}
消息表达式,直接从国际化资源中获取。
前后端分离项目,比如前端使用VUE ,这个时候就需要前端自己使用前端的国际化插件,编写资源,实现国际化了。
比如在 vue 的项目中,可以直接使用 vue-i18n
进行实现,流程和后端差不多。
在很多官方文档中,可以看到中英文切换的按钮,默认根据当地区域选择一种语言,然后还可以点击切换,这种方式更具有灵活性。
如果要实现这种需求,依靠Spring Boot
默认的解析浏览器请求消息头的方式,就不适用了。
需要自定义解析器,比如下方实例中,根据自定义消息头设置Locale
,如果用户刚进入页面,使用默认的地区,当点击了切换按钮后,全局请求接口都添加用户选择的区域,比如切换到英文,则前端全局添加lang: en_US
请求头,那么后端就会获取到英文Locale
对象,也就能实现语言切换了。
自定义解析器如下:
public class RequestHeaderLocaleResolver implements LocaleResolver {
// 请求头参数
private static final String LANG_HEADER = "lang";
// 解析请求,设置Locale地区化
@Override
public Locale resolveLocale(HttpServletRequest request) {
// 1. 获取请求中的lang参数
String lang = request.getHeader(LANG_HEADER);
// 2. 没有lang参数,使用默认Locale
if (StringUtils.isEmpty(lang)) {
return Locale.getDefault();
} else {
// 4. 根据lang参数,设置国际化,分割出国家和地址
String[] s = lang.split("_");
return new Locale(s[0], s[1]);
}
}
@Override
public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
}
}
在配置类中,注入自定义LocaleResolver
到Spring 容器中:
@Configuration
public class MyMvcConfig {
@Bean
public LocaleResolver localeResolver(){
return new MyLocal();
}
}