springboot国际化支持

概述

使用spring boot实现国际化,一般有两种情况,一种是返回网页的,一种是纯粹的rest服务返回json的,前者更多的和类似thymeleaf这样的模板引擎一起工作,后者可以不使用模板引擎直接解析,因为后者相对来说内容要简单得多,大部分情况返回json就够了,下面会包含这两部分内容。

过程

1 依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    <version>1.5.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>1.5.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>1.5.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.4</version>
</dependency>

如果只是单纯的rest服务并且不需要做服务端网页渲染的话,可以移除spring-boot-starter-thymeleaf依赖。

2 多语言选择

要实际国际化,无论怎样肯定首先要有一个地方对于选择哪门语言做出判断,比如自定义如下规则:

客户端请求头中添加请求头x-client-language: zh_CN时,根据该请求头做语言切换

建议在服务端项目中添加过滤器(不限定过滤器,有类似效果即可),读取请求头的语言信息生成Locale对象存到ThreadLocal,LocaleResolver对象的说明在第3步。

import com.github.gitveio.utils.ResourceUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.LocaleResolver;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Locale;


// like LocaleChangeInterceptor class
@WebFilter(filterName = "langFilter", urlPatterns = "/*")
public class LanguageFilter implements Filter {

    @Autowired
    private LocaleResolver localeResolver;

    @Override
    public void init(final FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
        try {
            String lang = ((HttpServletRequest) request).getHeader(REQUEST_LOCALE_HEADER);
            localeResolver.setLocale((HttpServletRequest) request, (HttpServletResponse) response, getLocale(lang));
            chain.doFilter(request, response);
        } finally {
            ResourceUtil.clear();
        }
    }

    @Override
    public void destroy() {
    }

    private Locale getLocale(String lang) {
        Locale locale = null;
        if (StringUtils.isEmpty(lang)) {
            locale = defaultLocale;
        } else {
            int splitIndex = lang.indexOf('-');
            if (splitIndex < 0) {
                splitIndex = lang.indexOf('_');
            }

            if (splitIndex < 0) {
                locale = new Locale(lang.toLowerCase());
            } else {
                locale = new Locale(lang.substring(0, splitIndex).toLowerCase(), lang.substring(splitIndex + 1).toUpperCase());
            }
        }
        return locale;
    }

    private static Locale defaultLocale = new Locale("zh", "CN");
    public static final String REQUEST_LOCALE_HEADER = "x-client-language";
}

将locale对象存放在threadlocal中主要是方便后面在业务代码中获取该locale对象,如果是纯粹的rest服务不用返回网页内容的情况,推荐直接放在request的属性列表即可。

import java.util.Locale;

public class ResourceUtil {
    private static final ThreadLocal<Locale> LOCALE_STORE = new InheritableThreadLocal();

    public static void setLocaleObject(Locale locale) {
        LOCALE_STORE.set(locale);
    }

    public static Locale getLocaleObject() {
        Locale l = (Locale) LOCALE_STORE.get();
        return l;
    }

    public static void clear() {
        LOCALE_STORE.remove();
    }
}

3 多语言选择和spring集成

有第2步的语言选择之后,还需要将选择的Locale存下来方便后面使用。
LocaleResolver接口的resolveLocale方法会被需要获取Locale对象的地方自动调用。
LocaleResolver接口的setLocale方法被第2步的过滤器调用,用来保存生成的Locale对象。

import org.springframework.web.servlet.LocaleResolver;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;

public class CustomLocaleResolver implements LocaleResolver {

    @Override
    public Locale resolveLocale(HttpServletRequest httpServletRequest) {
        return ResourceUtil.getLocaleObject();
    }

    @Override
    public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
        ResourceUtil.setLocaleObject(locale);
    }
}

将CustomLocaleResolver对象放入spring容器中,让spring感知到LocaleResolver,然后通过LocaleResolver获取Locale对象

@SpringBootApplication
@EnableAutoConfiguration
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }

    @Bean
    public LocaleResolver localeResolver() {
        LocaleResolver slr = new CustomLocaleResolver();
        return slr;
    }
}

4 添加国际化属性文件

messages目录下的文件就是多语言的属性文件,其他messages.properties是默认文件,后缀带en_US的是英文的,带zh_CN的是中文的。
下面的简单的示例:

比如定义一个属性名为view.title,中文是"项目管理",英文是"project management",默认是中文的"项目管理"
messages.properties文件内容:
view.title=\u9879\u76ee\u7ba1\u7406

messages_en_US.properties文件内容:
view.title=project management

messages_zh_CN.properties文件内容:
view.title=\u9879\u76ee\u7ba1\u7406

需要说明的是上面的\u开头的字符串代表的是unicode码,也就是"项目管理"的unicode码为\u9879\u76ee\u7ba1\u7406,编码转换工具

5 项目配置

application.yml文件添加配置:
spring:
  messages:
    basename: i18n/messages

6 编写测试模板文件

hello.html(默认目录在resources/templates)文件内容:

<html xmlns:th="http://www.thymeleaf.org">
<body>
hello, <span th:text=" #{view.title}">book info</span>
</body>
</html>

7 添加测试controller

hi方法是返回hello.html网页,在服务端网页渲染的场景下使用
test方法是直接返回字符串,在rest服务中这种情况更多一些

@Controller
public class TestController {
    @RequestMapping(value = "/hi")
    public String hi() {
        return "hello";
    }

    @RequestMapping(value = "/test")
    @ResponseBody
    public String test() {
        return getMessage("view.title");
    }

    private String getMessage(String key) {
        return messageSource.getMessage(key, null, ResourceUtil.getLocaleObject());
    }

    @Autowired
    private MessageSource messageSource;
}

8 测试

$ curl http://localhost:8080/hi -H "x-client-language: zh_CN"
<html>
<body>
hello, <span>项目管理</span>
</body>
</html>

$ curl http://localhost:8080/hi -H "x-client-language: en_US"
<html>
<body>
hello, <span>project management</span>
</body>
</html>

问题

1 controller返回String类型的模板名称无法返回网页,直接返回了字符串给客户端
controller使用@RestController或者controller方法使用@ResponseBody后,返回值会直接写回到客户端,导致客户端直接收到字符串,查看@RestController注解其实就是@Controller+@ResponseBody也就是说@ResponseBody注解会导致这个问题解决的话要么直接使用@Controller并且去掉@ResponseBody, 要么controller方法返回ModelAndView,比如:

@RequestMapping(value = "/hello")
public ModelAndView hello() {
    ModelAndView view = new ModelAndView();
    view.setViewName("hello");
    return view;
}

猜你喜欢

转载自www.cnblogs.com/gitveio/p/9236219.html