前言
在项目中,往往用户会存在多语言的述求,比如说一个系统既有中文的用户,又有英文的用户。怎么来实现多语言呢?
首先前后端分离的项目,前端会有自己的多语言实现方案,大致效果就是,用户切换语言,那些静态的按钮,菜单,标签等前端都可以自己切换。但是调用后端由后端返回的异常提示,消息体等,也需要后端实现多语言,大致的实现方案就是由前端传入一个参数,表示是期望后端提供什么类型的语言的消息体,后端就可以通过这个来实现国际化的消息了。本文介绍的是采用spring的国际化支持来实现国际化语言。
一、spring中国际化相关概念及理论知识
MessageSource接口
spring中国际化是通过MessageSource这个接口来支持的,该接口中定义了3个获取国际化消息的方法。
org.springframework.context.MessageSource
public interface MessageSource {
/**
* 获取国际化信息
* @param code 表示国际化资源中的属性名;
* @param args用于传递格式化串占位符所用的运行期参数;
* @param defaultMessage 当在资源找不到对应属性名时,返回defaultMessage参数所指定的默认信息;
* @param locale 表示本地化对象
*/
@Nullable
String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale);
/**
* 与上面的方法类似,只不过在找不到资源中对应的属性名时,直接抛出NoSuchMessageException异常
*/
String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException;
/**
* @param MessageSourceResolvable 将属性名、参数数组以及默认信息封装起来,它的功能和第一个方法相同
*/
String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;
}
国际化消息的三个实现类
ResourceBundleMessageSource
这个是基于Java的ResourceBundle基础类实现,允许仅通过资源名加载国际化资源
spring boot中默认使用该类实现。
读取配置文件中配置的国际化信息
在application.yml中配置一下配置就能实例
spring:
messages:
#resources/i18n/messages_zh_CN.properties ,取i18n/messages,规则为取文件目录开始,文件前缀结束
basename: i18n/messages
# 国际化编码
encoding: utf-8
# 在找不到当前系统对应的资源文件时,如果该属性为 true,则会默认查找当前系统对应的资源文件,否则就返回 null,返回 null 之后,最终又会调用到系统默认的 messages.properties 文件
fallback-to-system-locale: true
ReloadableResourceBundleMessageSource
这个功能和第一个类的功能类似,多了定时刷新功能,允许在不重启系统的情况下,更新资源的信息
该类也是读取配置文件中的国际化信息
实例化该类的方案如下
@Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasenames("classpath:i18n/messages");
messageSource.setDefaultEncoding("utf-8");
//设置缓存时间1个小时 1*60*60*1000毫秒
//可以设置成-1表示永久缓存,设置成0表示每次都从文件中读取
messageSource.setCacheMillis(1*60*60*1000);
messageSource.setFallbackToSystemLocale(true);
return messageSource;
}
StaticMessageSource
它允许通过编程的方式提供国际化信息。
比如说配置好信息存放在数据库中,自己可以自定义去db中获取
spring中使用国际化的步骤
通常我们使用spring的时候
1、国际化语言配置文件,
通常在src/main/resources目录下建i18n文件夹,再创建各类语言的properties资源文件
i18n/messages.properties
i18n/messages_en_US.properties
i18n/messages_zh_CN.properties
当然也可以将国际化语言资源包放置在其他载体中,如数据库。
2、向容器中注册一个MessageSource类型的bean,bean名称必须为:messageSource
实现类可以用上面说的三种中选择一种
3、从spring容器中获取MessageSource类型的bean,调用getMessage方法获取配置的语言内容
4、如果是web类型的程序,语言的选择应该还要根据请求来定。一般情况,可以让前端传一个语言参数,参数可以放置在cookie,header,parameter都可以。然后通过一个拦截器获取到参数,传递给线程变量,在获取语言的时候,从线程变量中取出该值,就可以动态的根据请求获取对应的语言信息了。
二、一个国际化的demo例子
需要自定义的几个类及文件
com.shenyun.flinkgui.i18n.constant.LanguageBaseConstant 类
package com.shenyun.flinkgui.i18n.constant;
public class LanguageBaseConstant {
public static final String LOCALE_LANGUAGE_KEY = "language";
}
com.shenyun.flinkgui.i18n.interceptor.LocaleChangeInterceptor 类
package com.shenyun.flinkgui.i18n.interceptor;
import com.shenyun.flinkgui.i18n.constant.LanguageBaseConstant;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.util.WebUtils;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;
public class LocaleChangeInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// cookie :language=en_US
// header :language=en_US
// parameter :language=en_US
//通过制定cookie,header,param
// 获取本地语言的优先级 cookie<header<parameter
String locale="";
//1、处理cookie中带有的语言信息
Cookie cookie = WebUtils.getCookie(request, LanguageBaseConstant.LOCALE_LANGUAGE_KEY);
String cookieLocale="";
if (cookie!=null) {
// Proceed in cookie
cookieLocale=cookie.getValue();
}
if(cookieLocale!=null&& !cookieLocale.trim().equals("")){
locale=cookieLocale;
}
// 2、每次请求的header中可以单独指定语言 language=zh_CN
String headerLocale = request.getHeader(LanguageBaseConstant.LOCALE_LANGUAGE_KEY);
if(headerLocale!=null && !headerLocale.trim().equals("")){
locale=headerLocale;
}
// 3、从get 参数数据获取 language=zh_CN
String parameterLocale=request.getParameter(LanguageBaseConstant.LOCALE_LANGUAGE_KEY);
if(parameterLocale!=null && !parameterLocale.trim().equals(""));{
locale=parameterLocale;
}
//4 语言信息写入线程变量
if (locale!=null) {
LocaleContextHolder.setLocale(parseLocaleValue(locale));
}
return true;
}
@Nullable
protected Locale parseLocaleValue(String localeValue) {
return StringUtils.parseLocale(localeValue);
}
}
因LocaleChangeInterceptor类是我自己自定义实现的,逻辑为,可以通过cookie,header,parameter传入使用的指定的语言类型。大致情况如下。
// cookie :language=en_US // header :language=en_US // parameter :language=en_US // 通过制定cookie,header,param // 获取本地语言的优先级 cookie<header<parameter
spring也有默认的实现,就看自己怎么选择了。
com.shenyun.flinkgui.i18n.utils.MessageResolverUtils 类
package com.shenyun.flinkgui.i18n.utils;
import cn.hutool.extra.spring.SpringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import java.util.Arrays;
/**
* desc: 获取i18n资源文件
*/
public class MessageResolverUtils {
@Autowired
private static MessageSource messageSource = SpringUtil.getBean(MessageSource.class);
public MessageResolverUtils() {
}
/**
* 根据 messageKey 获取国际化消息 委托给 spring messageSource
*
* @param code 消息key
* @return 解析后的国际化
*/
public static String getMessage(Object code) {
return messageSource.getMessage(code.toString(), null, code.toString(), LocaleContextHolder.getLocale());
}
/**
* 根据 messageKey 和参数 获取消息 委托给 spring messageSource
*
* @param code 消息key
* @param messageArgs 参数
* @return 解析后的国际化
*/
public static String getMessages(Object code, Object... messageArgs) {
Object[] objs = Arrays.stream(messageArgs).map(MessageResolverUtils::getMessage).toArray();
String message =
messageSource.getMessage(code.toString(), objs, code.toString(), LocaleContextHolder.getLocale());
return message;
}
}
com.shenyun.flinkgui.configure.AppConfiguration 类
package com.shenyun.flinkgui.configure;
import com.shenyun.flinkgui.i18n.constant.LanguageBaseConstant;
import com.shenyun.flinkgui.i18n.interceptor.LocaleChangeInterceptor;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.CookieLocaleResolver;
import java.util.Locale;
@Configuration
public class AppConfiguration implements WebMvcConfigurer {
@Bean(name = "localeResolver")
public LocaleResolver localeResolver() {
CookieLocaleResolver localeResolver = new CookieLocaleResolver();
localeResolver.setCookieName(LanguageBaseConstant.LOCALE_LANGUAGE_KEY);
// 设置默认语言,简体中文
localeResolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
// set language tag compliant
localeResolver.setLanguageTagCompliant(false);
return localeResolver;
}
@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
return new LocaleChangeInterceptor();
}
@Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasenames("classpath:i18n/messages");
messageSource.setDefaultEncoding("utf-8");
//设置缓存时间1个小时 1*60*60*1000毫秒
//可以设置成-1表示永久缓存,设置成0表示每次都从文件中读取
messageSource.setCacheMillis(1*60*60*1000);
messageSource.setFallbackToSystemLocale(true);
return messageSource;
}
// 注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeChangeInterceptor());
}
}
配置message资源文件
默认资源messages.properties
unknown.i18n=未知的国际化信息,请检查。。。
# response messages
response.get.success=获取成功
# validation messages
param.test.ok=参数 {0} 的值 {1}
英文配置messages_en_US.properties
unknown.i18n=Unknown i18n information, please check. . .
# response messages
response.get.success = Get success
# validation messages
param.test.ok=parmam {0} value is {1}
中文配置messages_zh_CN.properties,与默认配置保持一样即可
国际化语言使用
//根据配置的key获取国际化msg
String msg=MessageResolverUtils.getMessage("response.get.success");
以下是在controller层的一个使用例子,具体的情况根据自身的业务情况
@Controller
@RequestMapping("language")
public class LanguageController {
@ResponseBody
@RequestMapping("getLanDemo")
public String getLanDemo(){
return MessageResolverUtils.getMessage("response.get.success");
}
@ResponseBody
@RequestMapping("getLanDemo2")
public String getLanParamDemo(String name){
return MessageResolverUtils.getMessages("param.test.ok","name",name);
}
}
三、国际化消息配置在数据库中的思路
国际化消息实现类中,有一个StaticMessageSource,这个类中有2个方法比较重要
public void addMessage(String code, Locale locale, String msg);
public void addMessages(Map<String, String> messages, Locale locale);
自定义一个StaticMessageSource类
public class MessageSourceFromDb extends StaticMessageSource implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
//此处我们在当前bean初始化之后,模拟从db中获取国际化信息,然后调用addMessage来配置国际化信息
this.addMessage("desc", Locale.CHINA, "我是从db来的信息");
this.addMessage("desc", Locale.UK, "MessageSource From Db");
}
}
上面的类实现了spring的InitializingBean接口,重写afterPropertiesSet方法,这个方法会在当前bean初始化之后调用,在这个方法中模拟从db中获取国际化信息,然后调用addMessage来配置国际化信息
使用上面的类,实现MessageSource接口的实例化类即可从数据库中获取国际化消息,但是数据的自己设计表结构。
参考项:
1、文章:https://blog.csdn.net/ysj_2021/article/details/125650190
2、项目:Dinky: Dinky 是一个开箱即用的一站式实时计算平台,以 Apache Flink 为基础,连接 OLAP 和数据湖等众多框架,致力于流批一体和湖仓一体的建设与实践。