本博客日IP超过2000,PV 3000 左右,急需赞助商。
极客时间所有课程通过我的二维码购买后返现24元微信红包,请加博主新的微信号:xttblog2,之前的微信号好友位已满,备注:返现
受密码保护的文章请关注“业余草”公众号,回复关键字“0”获得密码
所有面试题(java、前端、数据库、springboot等)一网打尽,请关注文末小程序
腾讯云】1核2G5M轻量应用服务器50元首年,高性价比,助您轻松上云
现在的公司都讲究国际化、全球化。但是国际化并不代表全球化,国际化只是把我们的系统有中文的地方翻译成支持多国语言,让不懂中文的人能用,国际化主要解决这个问题。而全球化,那就得重新写一个系统,因为你只把语言翻译一下,别人能看懂,但是使用习惯和体验上,以及界面视觉感观都是不同的。所以,我在这里强调一下什么是国际化,什么是全球化,搞懂这个你们公司才能做大做强!
国际化,相对于 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; }
以上,如果喜欢,或者想要源码,请关注我的微信公众号联系我!
最后,欢迎关注我的个人微信公众号:业余草(yyucao)!可加作者微信号:xttblog2。备注:“1”,添加博主微信拉你进微信群。备注错误不会同意好友申请。再次感谢您的关注!后续有精彩内容会第一时间发给您!原创文章投稿请发送至532009913@qq.com邮箱。商务合作也可添加作者微信进行联系!
本文原文出处:业余草: » SpringBoot + Redis 实现国际化