在项目中,往往用户会存在多语言的述求,比如说一个系统既有中文的用户,又有英文的用户。怎么来实现多语言呢?
首先前后端分离的项目,前端会有自己的多语言实现方案,大致效果就是,用户切换语言,那些静态的按钮,菜单,标签等前端都可以自己切换。但是调用后端由后端返回的异常提示,消息体等,也需要后端实现多语言,大致的实现方案就是由前端传入一个参数,表示是期望后端提供什么类型的语言的消息体,后端就可以通过这个来实现国际化的消息了。本文介绍的是采用spring的国际化支持来实现国际化语言。
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的时候
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都可以。然后通过一个拦截器获取到参数,传递给线程变量,在获取语言的时候,从线程变量中取出该值,就可以动态的根据请求获取对应的语言信息了。
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
因LocaleChangeInterceptor类是我自己自定义实现的,逻辑为,可以通过cookie,header,parameter传入使用的指定的语言类型。大致情况如下。
// cookie :language=en_US // header :language=en_US // parameter :language=en_US // 通过制定cookie,header,param // 获取本地语言的优先级 cookiespring也有默认的实现,就看自己怎么选择了。
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
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 和数据湖等众多框架,致力于流批一体和湖仓一体的建设与实践。