一、初探

单体应用完成国际化还是比较简单的,可以看下面的示例代码。
引入必要的依赖

<!-- SpringBoot Web -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- Validator -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

<!-- i18n -->
<dependency>
   <groupId>org.webjars.bower</groupId>
   <artifactId>jquery-i18n-properties</artifactId>
   <version>1.2.7</version>
</dependency>

创建一个拦截器

import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.support.RequestContextUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class LocaleInterceptor extends LocaleChangeInterceptor {
    private static final String LOCALE = "Accept-Language";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String newLocale = request.getHeader(LOCALE);
        if (newLocale != null) {
            LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);
            if (localeResolver == null) {
                throw new IllegalStateException("No LocaleResolver found: not in a DispatcherServlet request?");
            }
            try {
                localeResolver.setLocale(request, response, parseLocaleValue(newLocale));
            } catch (IllegalArgumentException ignore) {
            }
        }
        return true;
    }
}

创建一个配置类

import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;

import java.nio.charset.StandardCharsets;
import java.util.Locale;

@Configuration
public class LocaleConfig implements WebMvcConfigurer{
    /**
     *	默认解析器 其中locale表示默认语言,当请求中未包含语种信息,则设置默认语种
     *	当前默认为简体中文,zh_CN
     */
    @Bean
    public SessionLocaleResolver localeResolver() {
        SessionLocaleResolver localeResolver = new SessionLocaleResolver();
        localeResolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
        return localeResolver;
    }

    /**
     *  默认拦截器
     *  拦截请求,获取请求头中包含的语种信息并重新注册语种信息
     */
    @Bean
    public WebMvcConfigurer localeInterceptor() {
        return new WebMvcConfigurer() {
            @Override
            public void addInterceptors(InterceptorRegistry registry) {
                registry.addInterceptor(new LocaleInterceptor());
            }
        };
    }

    @Bean
    public LocalValidatorFactoryBean localValidatorFactoryBean() {
        LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
        // 设置消息源
        bean.setValidationMessageSource(resourceBundleMessageSource());
        return bean;
    }

    @Bean
    public MessageSource resourceBundleMessageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.setDefaultEncoding(StandardCharsets.UTF_8.toString());
        // 多语言文件地址
        messageSource.addBasenames("i18n/message");
        return messageSource;
    }

    @Bean
    public MethodValidationPostProcessor validationPostProcessor() {
        MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
        processor.setValidator(localValidatorFactoryBean().getValidator());
        return processor;
    }

    @Override
    public Validator getValidator() {
        return localValidatorFactoryBean();
    }
}

然后在resource下创建i18n目录,选中右键 New =>Resource Bundle
在这里插入图片描述
填入base name,选择Project locales,再Add All,确定即可。
在这里插入图片描述
打开配置文件,填写对应的中英文数据
在这里插入图片描述
配置一下application.yml

spring:
  messages:
    basename: i18n.message
    cache-duration: 3600
    encoding: UTF-8

这样基本上就好了,使用也很简单,看下图
在这里插入图片描述

二、深入

对于微服务来讲,每个模块单独配置国际化还是很繁琐的事情。所以一般是将国际化存入数据库进行统一管理。而本地缓存使用Redis替换,从而更新国际化之后,相应的模块能同步。

先把原来的LocaleConfigLocaleInterceptor抽离到公共服务,同时增加一个自定义MessageSource

import com.xxx.common.redis.service.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.context.support.AbstractMessageSource;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.text.MessageFormat;
import java.util.Locale;
import java.util.Map;

@Slf4j
@Component("messageSource")
@RefreshScope
public class CustomMessageSource extends AbstractMessageSource {
    public static final String REDIS_LOCALE_MESSAGE_KEY = "i18n_message";

    @Resource
    private RedisService redisService;

    @Override
    protected MessageFormat resolveCode(String code, Locale locale) {
        String msg = this.getSourceFromCacheMap(code, locale);
        return new MessageFormat(msg, locale);
    }

    @Override
    protected String resolveCodeWithoutArguments(String code, Locale locale) {
        return this.getSourceFromCacheMap(code, locale);
    }

    /**
     * 缓存Map中加载国际化资源
     *
     * @param code
     * @param locale
     * @return
     */
    private String getSourceFromCacheMap(String code, Locale locale) {
        // 判断如果没有值则会去重新加载数据
        Map<String, Map<String, String>> localeMsgMap = redisService.getCacheMap(REDIS_LOCALE_MESSAGE_KEY);
        if (localeMsgMap == null || localeMsgMap.isEmpty()) {
            // 如果找不到国际化消息,就直接返回code
            return code;
        }
        String language = ObjectUtils.isEmpty(locale) ? LocaleContextHolder.getLocale().getLanguage() : locale.getLanguage();
        // 获取缓存中对应语言的所有数据项
        Map<String, String> propMap = localeMsgMap.get(language);
        if (!ObjectUtils.isEmpty(propMap) && propMap.containsKey(code)) {
            // 如果对应语言中能匹配到数据项,那么直接返回
            return propMap.get(code);
        }
        if(!Locale.ENGLISH.getLanguage().equalsIgnoreCase(language) && !Locale.CHINESE.getLanguage().equalsIgnoreCase(language)) {
            // 使用其他语言时,如果找不到对应的国际化消息,则默认使用英文
            language = Locale.ENGLISH.getLanguage();
            propMap = localeMsgMap.get(language);
            if (!ObjectUtils.isEmpty(propMap) && propMap.containsKey(code)) {
                return propMap.get(code);
            }
        }
        // 最后匹配不上,就直接返回code
        return code;
    }
}

并对LocaleConfig进行改造

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.validation.MessageInterpolatorFactory;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;

import javax.annotation.Resource;
import java.util.Locale;

@Slf4j
@Configuration
public class LocaleConfig implements WebMvcConfigurer {

     @Resource
     private CustomMessageSource customMessageSource;

    /**
     *	默认解析器 其中locale表示默认语言,当请求中未包含语种信息,则设置默认语种
     *	当前默认为CHINA,zh_CN
     */
    @Bean
    public SessionLocaleResolver localeResolver() {
        SessionLocaleResolver localeResolver = new SessionLocaleResolver();
        localeResolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
        return localeResolver;
    }

    /**
     *  默认拦截器
     *  拦截请求,获取请求头中包含的语种信息并重新注册语种信息
     */
    @Bean
    public WebMvcConfigurer localeInterceptor() {
        return new WebMvcConfigurer() {
            @Override
            public void addInterceptors(InterceptorRegistry registry) {
                registry.addInterceptor(new LocaleInterceptor());
            }
        };
    }

    @Bean
    public LocalValidatorFactoryBean localValidatorFactoryBean() {
        LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
        MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory();
        bean.setMessageInterpolator(interpolatorFactory.getObject());
        // 设置消息源
        bean.setValidationMessageSource(resourceBundleMessageSource());
        return bean;
    }

    @Bean
    public MessageSource resourceBundleMessageSource() {
        return customMessageSource;
    }

    @Bean
    public MethodValidationPostProcessor validationPostProcessor() {
        MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
        processor.setValidator(localValidatorFactoryBean().getValidator());
        return processor;
    }

    @Override
    public Validator getValidator() {
        return localValidatorFactoryBean();
    }
}

在需要的模块中引入即可。

将国际化的增删改查功能集成到系统管理,就可以通过数据库进行管理了。
在这里插入图片描述
对国际化进行增删改后,需要对Redis缓存进行更新。

/**
 * 清理Redis所有前缀匹配的缓存
 */
private void clearCache() {
    Collection<String> keys = redisService.keys(CustomMessageSource.REDIS_LOCALE_MESSAGE_KEY + "*");
    keys.stream().forEach(key -> {
        redisService.deleteObject(key);
    });
}

/**
 * 按key清理Redis缓存
 */
private void clearCache(String key) {
    redisService.deleteObject(CustomMessageSource.REDIS_LOCALE_MESSAGE_KEY + ":" + key);
}

之前创建的resouce/i18n目录则可以删除。使用也是和单体应用一样的。
在这里插入图片描述

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐