SpringBoot + Redis 实现国际化

JAVA herman 87浏览
公告:“业余草”微信公众号提供免费CSDN下载服务,关注业余草微信公众号,添加作者微信:xmtxtt,发送下载链接帮助你免费下载!
本博客日IP超过1300,PV 1800 左右,急需赞助商。
打开支付宝首页搜“567452957”领红包,间接赞助博主,谢谢!

现在的公司都讲究国际化、全球化。但是国际化并不代表全球化,国际化只是把我们的系统有中文的地方翻译成支持多国语言,让不懂中文的人能用,国际化主要解决这个问题。而全球化,那就得重新写一个系统,因为你只把语言翻译一下,别人能看懂,但是使用习惯和体验上,以及界面视觉感观都是不同的。所以,我在这里强调一下什么是国际化,什么是全球化,搞懂这个你们公司才能做大做强!

国际化,相对于 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 中进行国际化,你会发现非常的简单,但是你认为光这样做就国际化完成了,那就错了。

web 系统国际化

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

系统国际化

看起来好像有点复杂,其实这都是细节。

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)!可加QQ1群:135430763(2000人群已满),QQ2群:454796847(已满),QQ3群:187424846(已满)。QQ群进群密码:xttblog,想加微信群的朋友,可以微信搜索:xmtxtt,备注:“xttblog”,添加助理微信拉你进群。备注错误不会同意好友申请。再次感谢您的关注!后续有精彩内容会第一时间发给您!原创文章投稿请发送至532009913@qq.com邮箱。商务合作可添加助理微信进行沟通!

本文原文出处:业余草: » SpringBoot + Redis 实现国际化