现在的公司都讲究国际化、全球化。但是国际化并不代表全球化,国际化只是把我们的系统有中文的地方翻译成支持多国语言,让不懂中文的人能用,国际化主要解决这个问题。而全球化,那就得重新写一个系统,因为你只把语言翻译一下,别人能看懂,但是使用习惯和体验上,以及界面视觉感观都是不同的。所以,我在这里强调一下什么是国际化,什么是全球化,搞懂这个你们公司才能做大做强!
国际化,相对于 SpringBoot 来说非常的简单。因为 SpringBoot 默认就是支持国际化的,不需要我们做过多的配置就能实现国际化。
在 SpringBoot 中做到国际化,需要做一下几个改动。
第一,在项目中的 resources/ 下定义国际化配置文件,文件名默认以 messages 开头。举例,如下:
- messages.properties (默认,当找不到语言的配置的时候,使用该文件进行展示)。
- messages_zh_CN.properties(中文)
- messages_en_US.properties(英文)
为了演示,我们先做两个配置文件。messages_en_US.properties 内容如下:
welcome=welcome to www.xttblog.com
messages_zh_CN.properties 文件配置内容如下:
welcome=欢迎访问网站
然后在项目中,如果使用的是 thymeleaf 模版引擎的话,就可以使用 #{welcome} 进行国际化了。
<!DOCTYPEhtml>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>hello spring boot</title>
</head>
<body>
<p><label th:text="#{welcome}"></label></p>
</body>
</html>
注意,操作国际化,需要在 HTTP 请求的 accept-language 中设置需要的语言。因为 SpringBoot 默认采用的是 AcceptHeaderLocaleResolver 进行 Locale 解析。而 AcceptHeaderLocaleResolver 依靠的就是 accept-language。我们前面的这篇文章《Cannot change HTTP accept header – use a different locale resolution strategy 问题解决方法》中的异常,就是因为使用了 AcceptHeaderLocaleResolver 引起的。
除了 AcceptHeaderLocaleResolver 解析器,SpringBoot 还提供了 SessionLocaleResolver、CookieLocaleResolver 和 FixedLocaleResolver 解析器。
顾名思义,通过名字我们就可以看出它们各自的作用域。SessionLocaleResolver 称为会话区域解析器,你设置完只针对当前的会话有效,session 失效,还原为默认状态。CookieLocaleResolver 称为 Cookie 区域解析器,也就是说,你设置完针对 cookie 生效。FixedLocaleResolver 就是指一直使用固定的 Local,改变 Local 是不支持的。一旦启动时设定,则是固定的,无法改变 Local。
如果想要改编默认的解析器该怎么办呢?操作很简单,几行代码就可以搞定。举例如下:
@Configuration
public class I18nConfig {
/*@Bean
public LocaleResolver localeResolver() {
CookieLocaleResolver localeResolver = new CookieLocaleResolver();
localeResolver.setCookieName("localeCookie");
//设置默认区域
localeResolver.setDefaultLocale(Locale.ENGLISH);
//设置cookie有效期.
localeResolver.setCookieMaxAge(3600);
return localeResolver;
}*/
@Bean
public LocaleResolver localeResolver() {
SessionLocaleResolver localeResolver = new SessionLocaleResolver();
//设置默认区域
localeResolver.setDefaultLocale(Locale.CHINA);
return localeResolver;
}
/*@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
// 设置请求地址的参数,默认为:locale
lci.setParamName(LocaleChangeInterceptor.DEFAULT_PARAM_NAME);
return lci;
}*/
}
你要使用哪一个,只需要把对应的注释解开即可。
那么如何切换语言选择呢?有两种方式,一种是自定义自己的实现,还有一种是配置 LocaleChangeInterceptor 拦截器。用法举例如下:
@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
// 设置请求地址的参数,默认为:locale
lci.setParamName(LocaleChangeInterceptor.DEFAULT_PARAM_NAME);
return lci;
}
LocaleChangeInterceptor 不可以和 FixedLocaleResolver 一起使用,否则会抛出异常信息。
LocaleChangeInterceptor 默认的会检查请求参数中是否有 locale 参数,如果有则设置新的语言。locale 参数是可配置的,默认拦截所有 url,你可以在使用时限定拦截哪些 URL。
如果在后台中想要获取国际化的信息该怎么办呢?答案是可以使用 Locale locale = LocaleContextHolder.getLocale(); 或者 Locale locale= RequestContextUtils.getLocale(request); 使用案例如下:
@Autowired
private MessageSource messageSource;
@RequestMapping("/test")
public String test(HttpServletRequest request){
Locale locale= RequestContextUtils.getLocale(request);
//Locale locale = LocaleContextHolder.getLocale();
String msg = messageSource.getMessage("welcome",null,locale);
System.out.println(msg);
return "test";
}
在 SpringBoot 中进行国际化,你会发现非常的简单,但是你认为光这样做就国际化完成了,那就错了。

看上面这张图,这还不够。

看起来好像有点复杂,其实这都是细节。
SpringBoot 国际化除了简单,还能高度定制化。如果我们要靠 Redis 来实现,也轻而易举。因为多数系统,现在都不使用 Session 了,使用 Redis 了,所以我们要定制。
那也很简单,我们有两种方法来实现。一种是继承 AbstractLocaleContextResolver 抽象类,一种是实现 LocaleResolver 接口。其实原理都一样。
/**
* 自定义国际化语言解析器
*
*/
public class MyLocaleResolver implements LocaleResolver{
private static final String I18N_LANGUAGE = "i18n_language";
private static final String I18N_LANGUAGE_SESSION = "i18n_language_session";
@Override
public Locale resolveLocale(HttpServletRequest req) {
String i18n_language = req.getParameter(I18N_LANGUAGE);
Locale locale = Locale.getDefault();
if(!StringUtils.isEmpty(i18n_language)) {
String[] language = i18n_language.split("_");
locale = new Locale(language[0], language[1]);
//将国际化语言保存到session
HttpSession session = req.getSession();
session.setAttribute(I18N_LANGUAGE_SESSION, locale);
}else {
//如果没有带国际化参数,则判断session有没有保存,有保存,则使用保存的,
//也就是之前设置的,避免之后的请求不带国际化参数造成语言显示不对
HttpSession session = req.getSession();
Locale localeInSession = (Locale) session.getAttribute(I18N_LANGUAGE_SESSION);
if(localeInSession != null) {
locale = localeInSession;
}
}
return locale;
}
@Override
public void setLocale(HttpServletRequest req, HttpServletResponse res, Locale locale) {}
}
然后把 MyLocaleResolver 加入 Bean 工厂。
//使用WebMvcConfigurerAdapter可以扩展SpringMvc的功能,包括拦截器,转换器等
//@EnableWebMvc //设置@EnableWebMvc为完全接管SpringMvc,但一般不要设置完全接管SpringMvc
@Configuration
public class CustomMvcConfig extends WebMvcConfigurerAdapter {
/**
* 配置自己的国际化语言解析器
* @return
*/
@Bean
public LocaleResolver localeResolver() {
return new MyLocaleResolver();
}
/**
* 配置自己的拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
//super.addInterceptors(registry);
}
}
下面看我的一个 Redis 案例的 demo。继承了 AbstractLocaleContextResolver。
/**
* RedisLocaleResolver
* @author xtt
* @date 2019/1/21 下午3:10
*/
public class RedisLocaleResolver extends AbstractLocaleContextResolver {
@Autowired
private JedisPool pool;
@Value("${token.expires.after}")
private int tokenExpiresAfter = 30 * 24 * 3600;
public static final String LOCALE_SESSION_ATTRIBUTE_NAME = "xttblog:i18n:local:";
public static final String TIME_ZONE_SESSION_ATTRIBUTE_NAME = "xttblog:i18n:time_zone:";
private String localeAttributeName = LOCALE_SESSION_ATTRIBUTE_NAME;
private String timeZoneAttributeName = TIME_ZONE_SESSION_ATTRIBUTE_NAME;
public void setLocaleAttributeName(String localeAttributeName) {
this.localeAttributeName = localeAttributeName;
}
public void setTimeZoneAttributeName(String timeZoneAttributeName) {
this.timeZoneAttributeName = timeZoneAttributeName;
}
private Locale getRedisLocale(HttpServletRequest request){
// 从 request 中获取 token
String token = "";
Locale locale = null;
try (Jedis jedis = pool.getResource()) {
String language = jedis.get(this.localeAttributeName + token);
if (!StringUtils.isEmpty(language)) {
String [] array = language.split("_");
locale = new Locale(array[0],array[1]);
}
}
return locale;
}
private TimeZone getRedisTimeZone(HttpServletRequest request){
// 从 request 中获取 token
String token = "";
TimeZone timeZone = null;
try (Jedis jedis = pool.getResource()) {
String time_zone = jedis.get(this.timeZoneAttributeName + token);
if (!StringUtils.isEmpty(time_zone)) {
timeZone = TimeZone.getTimeZone(time_zone);
}
}
return timeZone;
}
@Override
public Locale resolveLocale(HttpServletRequest request) {
Locale locale = getRedisLocale(request);
if (locale == null) {
locale = determineDefaultLocale(request);
}
return locale;
}
@Override
public LocaleContext resolveLocaleContext(final HttpServletRequest request) {
return new TimeZoneAwareLocaleContext() {
@Override
public Locale getLocale() {
return resolveLocale(request);
}
@Override
@Nullable
public TimeZone getTimeZone() {
TimeZone timeZone = getRedisTimeZone(request);
if (timeZone == null) {
timeZone = determineDefaultTimeZone(request);
}
return timeZone;
}
};
}
/**
* 设置 Locale
* @return {@link }
* @author xtt
* @date 2019/1/21 下午3:38
*/
@Override
public void setLocaleContext(HttpServletRequest request,
@Nullable HttpServletResponse response,
@Nullable LocaleContext localeContext) {
Locale locale = null;
TimeZone timeZone = null;
if (localeContext != null) {
locale = localeContext.getLocale();
if (localeContext instanceof TimeZoneAwareLocaleContext) {
timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone();
}
}
String token = "";
try (Jedis jedis = pool.getResource()) {
jedis.set(this.localeAttributeName + token, locale.toString(), "NX", "EX", tokenExpiresAfter);
jedis.set(this.timeZoneAttributeName + token, timeZone.toString(), "NX", "EX", tokenExpiresAfter);
}
}
/**
* 如果设置了 Locale,则用设置的,否则使用请求头中的 Locale
* @param request the request to resolve the locale for
* @return the default locale (never {@code null})
* @see #setDefaultLocale
* @see javax.servlet.http.HttpServletRequest#getLocale()
*/
protected Locale determineDefaultLocale(HttpServletRequest request) {
Locale defaultLocale = getDefaultLocale();
if (defaultLocale == null) {
defaultLocale = request.getLocale();
}
return defaultLocale;
}
/**
* 请求的默认时区,如果未找到时区会话属性,则使用默认时区。
* @param request
* @return
* @see 设置时区参考 setDefaultTimeZone 方法
*/
@Nullable
protected TimeZone determineDefaultTimeZone(HttpServletRequest request) {
return getDefaultTimeZone();
}
}
使用很简单,如下:
@Bean
public LocaleResolver localeResolver() {
RedisLocaleResolver localeResolver = new RedisLocaleResolver();
//设置默认区域
localeResolver.setDefaultLocale(Locale.CHINA);
return localeResolver;
}
以上,如果喜欢,或者想要源码,请关注我的微信公众号联系我!

: » SpringBoot + Redis 实现国际化
原创文章,作者:bd101bd101,如若转载,请注明出处:https://blog.ytso.com/tech/pnotes/252019.html