使用总结
在resources目录下创建 messages.properties、messages_en_US.properties、messages_zh_CN.properties 国际化文件,在代码中注入MessageSource,通过MessageSource获取当前语言对应国际化文件的文本。
一.项目结构
二.快速使用
1.pom.xml中引入 spring-boot-starter 基础包即可。
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-web
spring-boot-starter 中包含了 spring-boot-autoconfigure,spring-boot-autoconfigure 包下有一个MessageSourceAutoConfiguration 配置类,SpringBoot会通过加载MessageSourceAutoConfiguration 自动注入一个 MessageSource 实例。
2.在resources目录下创建国际化配置文件 messages.properties、messages_en_US.properties、messages_zh_CN.properties。
我们的 messages 文件是直接创建在 resources 目录下的,IDEA 在展示的时候,会添加一层 Resource Bundle,这个大家不用管,千万别手动去创建这个目录。
messages.properties:
#默认翻译文件,内容可以留空,但文件必须存在。
messages_en_US.properties:
user.name=zhangsan
messages_zh_CN.properties:
user.name=张三
3.内容国际化
@Slf4j
@RestController
@RequestMapping("/hello")
public class HelloController {
@Resource
MessageSource messageSource;
@PostMapping("/getName")
public BaseResponse getName() {
String message = messageSource.getMessage("user.name", null, LocaleContextHolder.getLocale());
return BaseResponse.success(message);
}
}
@Data
public class BaseResponse implements Serializable {
private static final long serialVersionUID = -619135033544136919L;
private Integer code;
private String msg;
private T data;
public static BaseResponse success(T data) {
BaseResponse response = new BaseResponse();
response.setCode(200);
response.setMsg("success");
response.setData(data);
return response;
}
}
代码中注入 MessageSource ,即可通过 MessageSource 根据key获取当前语言对应resources目录下国际化文件中的文本内容。
4.接口请求
启动项目,Postman工具请求接口:
Locale:语言环境。
LocaleResolver:语言解析器,默认的实现类是 AcceptHeaderLocaleResolver。WebMvcAutoConfiguration springboot中对应的自动注入配置类。
MessageSource:信息源,默认实现类 ResourceBundleMessageSource。MessageSourceAutoConfiguration springboot中对应的自动注入配置类。
可以看一下AcceptHeaderLocaleResolver的解析代码:
public Locale resolveLocale(HttpServletRequest request) {
Locale defaultLocale = this.getDefaultLocale();
if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
//代码中设置了Locale 且 请求头中‘Accept-Language’为空,返回设置的Locale
return defaultLocale;
} else {
//否则,从HttpServletRequest中取当前Locale
Locale requestLocale = request.getLocale();
List supportedLocales = this.getSupportedLocales();
if (!supportedLocales.isEmpty() && !supportedLocales.contains(requestLocale)) {
Locale supportedLocale = this.findSupportedLocale(request, supportedLocales);
if (supportedLocale != null) {
return supportedLocale;
} else {
return defaultLocale != null ? defaultLocale : requestLocale;
}
} else {
return requestLocale;
}
}
}
AcceptHeaderLocaleResolver 主要是使用HttpServletRequest 对象根据请求头中的“Accept-Language”获取Locale语言环境。然后,如果“Accept-Language”的值为非法值,会去默认的messages.properties 文件中取文本。
除了默认的AcceptHeaderLocaleResolver,spring的 i18n 包中还提供了CookieLocaleResolver、SessionLocaleResolver、FixedLocaleResolver等实现类满足其它需求,当然,用户也可以自定义实现类自定义解析方式。
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeChangeInterceptor());
}
/**
* 语言环境切换拦截器
*
* @return
*/
@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
// 设置拦截请求url上参数的key,不设置,默认值"locale"
interceptor.setParamName("lang");
return interceptor;
}
/**
* 自定义语言解析器,覆盖默认的AcceptHeaderLocaleResolver
* tips:返回类型必须声明为LocaleResolver 接口类型,不然不会替换掉默认的AcceptHeaderLocaleResolver
*
* @return
*/
@Bean("localeResolver")
public LocaleResolver localeResolver() {
SessionLocaleResolver localeResolver = new SessionLocaleResolver();
// 设置默认语言环境"中文"
localeResolver.setDefaultLocale(Locale.CHINA);
return localeResolver;
}
}
提供了一个 SessionLocaleResolver 实例,SessionLocaleResolver 将客户端的 Locale 保存到 HttpSession 对象中,并且可以进行修改。只要 session 有效,浏览器就不必重复告诉服务端当前的语言环境。
另外还配置了一个拦截器监听语言环境的改变,这个拦截器会拦截请求url上 key 为 "lang" 的参数,这个参数指定当前的语言环境。语言环境的切换在请求url上指定“lang”参数即可,如下:
http://localhost:8081/hello/getName?lang=en-US
http://localhost:8081/hello/getName?lang=zh-CN
至于bean SessionLocaleResolver 的返回类型要声明为LocaleResolver 类型的原因(或者@Bean("localeResolver")显式声明bean名称),见下图:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeChangeInterceptor());
}
/**
* 语言环境切换拦截器
*
* @return
*/
@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
// 设置拦截请求url上参数的key,不设置,默认值"locale"
interceptor.setParamName("lang");
return interceptor;
}
// /**
// * 自定义语言解析器,覆盖默认的AcceptHeaderLocaleResolver
// * tips:返回类型必须声明为LocaleResolver 接口类型,不然不会替换掉默认的AcceptHeaderLocaleResolver
// *
// * @return
// */
// @Bean("localeResolver")
// public LocaleResolver localeResolver() {
// SessionLocaleResolver localeResolver = new SessionLocaleResolver();
// // 设置默认语言环境"中文"
// localeResolver.setDefaultLocale(Locale.CHINA);
// return localeResolver;
// }
/**
* @return
*/
@Bean("localeResolver")
public LocaleResolver localeResolver() {
// CookieLocaleResolver优先级低于LocaleChangeInterceptor 拦截器的语言设置
CookieLocaleResolver localeResolver = new CookieLocaleResolver();
localeResolver.setDefaultLocale(Locale.CHINA);
// cookie有效期1天,单位:秒,默认-1表示Cookie在浏览器关闭之后就失效
localeResolver.setCookieMaxAge(60 * 60 * 24);
// 设置cookie中语言环境的key,会根据这个key动态更新语言环境.
localeResolver.setCookieName("lang");
return localeResolver;
}
}
SessionLocaleResolver 是根据Session中特定的属性值确定Locale。SessionLocaleResolver会查找Session中属性名为 org.springframework.web.servlet.i18n.SessionLocaleResolver.LOCALE 的值,并将其转成Locale对象。
当session不存在,它还是根据accept-language HTTP请求头确定语言环境信息。
CookieLocaleResolver 是根据代码中指定的Cookie确定Locale。即上文的“localeResolver.setCookieName("lang");”。
当cookie不存在,它还是根据accept-language HTTP请求头确定环境信息。
SessionLocaleResolver和CookieLocaleResolver的区别是:前者一般要求用户登录后生成相应的用户会话才有效,而后者只要浏览器有Cookie存在即可。
/**
* 国际化工具类
*
* @author yangzihe
*/
@Component
public class MessageUtils {
public static MessageSource messageSource;
public MessageUtils(MessageSource messageSource) {
// spring的bean注入
MessageUtils.messageSource = messageSource;
}
/**
* 获取国际化翻译值
*/
public static String get(String msgKey) {
return messageSource.getMessage(msgKey, null, LocaleContextHolder.getLocale());
}
/**
* 获取中文翻译值
*/
public static String getWithChina(String msgKey) {
return messageSource.getMessage(msgKey, null, Locale.CHINA);
}
/**
* 获取国际化翻译值(包括占位符)
*/
public static String get(String msgKey, Object[] objects) {
return messageSource.getMessage(msgKey, objects, LocaleContextHolder.getLocale());
}
}
messages_zh_CN.properties:
user.name=张三
user.age=用户当前年龄为:{0}
代码中使用:
@Slf4j
@RestController
@RequestMapping("/hello")
public class HelloController {
@PostMapping("/getName")
public BaseResponse getName() {
String message = MessageUtils.get("user.name");
return BaseResponse.success(message);
}
@PostMapping("/getAge")
public BaseResponse getAge() {
Object[] objects = new Object[]{18};
String message = MessageUtils.get("user.age", objects);
return BaseResponse.success(message);
}
}
请求:
可看到文件中:"用户当前年龄为:{0}" 输出的文本为:"用户当前年龄为:18",占位符{0}被替换成了数字18。
当时的项目前后端有WebSocket通信的需求,socket通信使用的Locale无法从http的cookie或者session中取,需要独立传递。所以,前端在发起socket连接请求的时候传递lang参数,后端socket根据socket的lang参数设置当前语言环境。设置语言环境工具类如下:
/**
* 语言环境工具类
*
* @author yangzihe
* @date 2021/7/2
*/
public class LocaleUtils {
private static final String ZH_CN = "zh-CN";
private static final String EN_US = "en-US";
/**
* 自定义语言、国家:越南语
*/
private static final String VN_VN = "vn-VN";
/**
* 设置语言环境
*
* @param lang 语言环境
*/
public static void setLocale(String lang) {
if (StringUtils.isBlank(lang)) {
return;
}
Locale locale = null;
if (ZH_CN.equalsIgnoreCase(lang)) {
locale = Locale.CHINA;
} else if (EN_US.equalsIgnoreCase(lang)) {
locale = Locale.US;
} else if (VN_VN.equalsIgnoreCase(lang)) {
locale = new Locale("vn", "VN");
} else {
locale = Locale.CHINA;
}
LocaleContextHolder.setLocale(locale);
}
}
例如:我们想把国际化配置文件放在 resources/i18n 目录下:
那么,就需要在 application.properties 中进行额外配置:
spring.messages.basename=i18n/messages
基本格式:基名_语言_地区.properties
常见的资源文件命名规范
i18n_en.properties:所有英文语言的资源
i18n_en_US.properties:美国地区,英文语言的资源
i18n_zh.properties:所有中文语言的资源
i18n_zh_CN.properties:中国大陆的,中文语言的资源
基名.properties:默认资源文件,如果请求相应的资源文件不存在,将使用此资源文件
命名规范:
日本 : ja_JP
秘鲁 : es_PE
巴拿马 : es_PA
波斯尼亚和黑山共和国 : sr_BA
危地马拉 : es_GT
阿拉伯联合酋长国 : ar_AE
挪威 : no_NO
阿尔巴尼亚 : sq_AL
伊拉克 : ar_IQ
也门 : ar_YE
葡萄牙 : pt_PT
塞浦路斯 : el_CY
卡塔尔 : ar_QA
马其顿王国 : mk_MK
瑞士 : de_CH
美国 : en_US
芬兰 : fi_FI
马耳他 : en_MT
斯洛文尼亚 : sl_SI
斯洛伐克 : sk_SK
土耳其 : tr_TR
沙特阿拉伯 : ar_SA
英国 : en_GB
塞尔维亚及黑山 : sr_CS
新西兰 : en_NZ
挪威 : no_NO
立陶宛 : lt_LT
尼加拉瓜 : es_NI
爱尔兰 : ga_IE
比利时 : fr_BE
西班牙 : es_ES
黎巴嫩 : ar_LB
加拿大 : fr_CA
爱沙尼亚 : et_EE
科威特 : ar_KW
塞尔维亚 : sr_RS
美国 : es_US
墨西哥 : es_MX
苏丹 : ar_SD
印度尼西亚 : in_ID
乌拉圭 : es_UY
拉脱维亚 : lv_LV
巴西 : pt_BR
叙利亚 : ar_SY
多米尼加共和国 : es_DO
瑞士 : fr_CH
印度 : hi_IN
委内瑞拉 : es_VE
巴林 : ar_BH
菲律宾 : en_PH
突尼斯 : ar_TN
奥地利 : de_AT
荷兰 : nl_NL
厄瓜多尔 : es_EC
台湾地区 : zh_TW
约旦 : ar_JO
冰岛 : is_IS
哥伦比亚 : es_CO
哥斯达黎加 : es_CR
智利 : es_CL
埃及 : ar_EG
南非 : en_ZA
泰国 : th_TH
希腊 : el_GR
意大利 : it_IT
匈牙利 : hu_HU
爱尔兰 : en_IE
乌克兰 : uk_UA
波兰 : pl_PL
卢森堡 : fr_LU
比利时 : nl_BE
印度 : en_IN
西班牙 : ca_ES
摩洛哥 : ar_MA
玻利维亚 : es_BO
澳大利亚 : en_AU
新加坡 : zh_SG
萨尔瓦多 : es_SV
俄罗斯 : ru_RU
韩国 : ko_KR
阿尔及利亚 : ar_DZ
越南 : vi_VN
黑山 : sr_ME
利比亚 : ar_LY
中国 : zh_CN
台湾:zh_TW
香港 : zh_HK
白俄罗斯 : be_BY
以色列 : iw_IL
保加利亚 : bg_BG
马耳他 : mt_MT
巴拉圭 : es_PY
法国 : fr_FR
捷克共和国 : cs_CZ
瑞士 : it_CH
罗马尼亚 : ro_RO
波多黎哥 : es_PR
加拿大 : en_CA
德国 : de_DE
卢森堡 : de_LU
阿根廷 : es_AR
马来西亚 : ms_MY
克罗地亚 : hr_HR
新加坡 : en_SG
阿曼 : ar_OM
泰国 : th_TH
瑞典 : sv_SE
丹麦 : da_DK
洪都拉斯 : es_HN