• Spring Boot 2.x系列【16】功能篇之国际化实现方案


    有道无术,术尚可求,有术无道,止于术。

    本系列Spring Boot版本2.7.0

    什么是国际化?

    国际化(internationalization)是设计和制造能够适应不同区域要求产品的一种方式。它要求从产品中抽离所有地域语言,国家/地区和文化相关的元素。换言之,应用程序的功能和代码设计考虑在不同地区运行的需要,其代码简化了不同本地版本的生产。开发这样的程序的过程,就称为国际化。

    国际化也称作i18n,其来源是英文单词 internationalization,取首尾字母,18 是首尾之间有18个单词,这是常用的一种简称方式。

    简单来说,国际化就是不同语言地区的访客,看到的内容是当地的语言内容,比如同一个网站,在国内看到的是中文,在美丽国则显示英文。

    Spring Boot 支持国际化消息,以便应用程序满足不同语言用户。接下来我们看下如何使用。

    基础知识

    浏览器语言

    浏览器在发送请求时,会携带一个Accept-Language请求头,用于告诉服务器端浏览器当前支持的语言,不同国家地区环境,浏览器会自动根据操作系统或者IP 获取当地的语言标识,比如在中国时,会发送zh-cn,当然也可以直接设置语言。
    在这里插入图片描述
    下方示例表示,该浏览器可接收中文和英文的资源响应,权重分别为0.7 和0.3 ,那么客户端在服务器有中文版资源的情况下,会请求其返回中文版对象的响应,没有中文版时,则请求返回英文版响应。

    Accept-Language: zh-cn, zh; q=0.7, en-us,en; q=0.3
    
    • 1

    既然浏览器有这个机制,那么实现国际化就很简单了,后台只需要根据这个标识,返回不同语言的资源就可以了。

    Locale

    Localejava.util包提供的类,每一个Locale 对象都代表了一个特定的地理、政治和文化地区。主要用来获取本机操作系统信息,包括语言、国家等信息,国际化、日期处理时都需要使用到此类。

    Locale对象在逻辑上由下面描述的字段组成(这里只介绍两个):

    • language:ISO 639 alpha-2 或者 alpha-3 标准的语言编码,比如:“en”(英语)、“ja”(鬼子语)、“kok”(康卡尼语)、“zh”(中文)
    • country (region):ISO 3166 alphaUN M.49 numeric-3 2 标准国家代码区域代码。比如:“US”(美国)、“FR”(法国)、“029”(加勒比海)

    Locale类提供了三个构造函数:

       Locale(String language)
       Locale(String language, String country)
       Locale(String language, String country, String variant)
    
    • 1
    • 2
    • 3

    比如创建地区为中国,语言为中文的Locale对象:

     Locale local = new Locale("zh", "CN");
    
    • 1

    使用getDefault 方法可以根据操作系统获取当前程序默认的Locale对象:

            // 获取操作系统的本地信息
            Locale locale=Locale.getDefault();
            // 国家信息
            System.out.println("country:"+locale.getCountry()); // CN
            System.out.println("displayCountry:"+locale.getDisplayCountry()); // 中国
            // 语言信息
            System.out.println("language:"+locale.getLanguage()); // zh
            System.out.println("displayLanguage:"+locale.getDisplayLanguage()); // 中文
            SpringApplication. run(BasicApplication.class);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    使用getAvailableLocales 方法可以获取到Local支持的全部区域:

    Locale[] locales = Locale.getAvailableLocales();
    
    • 1

    MessageSource

    在Spring 中定义了一个MessageSource接口,从名字上理解是一个消息资源的意思,该接口声明了几个获取消息的接口。

    这个类的作用就是读取资源属性文件(xx.properties),然后根据xx.properties文件的名称信息(本地化信息),获取匹配当前系统的国别语言信息(也可以程序指定)。

    public interface MessageSource {
    	// 解析code对应的信息进行返回,如果对应的code不能被解析则返回默认信息defaultMessage。
        @Nullable
        String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale);
    	// 解析code对应的信息进行返回,如果对应的code不能被解析则抛出异常NoSuchMessageException
        String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException;
    	// 通过自定义MessageSourceResolvable解析器去获取信息
        String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    MessageSourceSpring Boot 支持国际化的重要接口,应用启动时,会加载该类型的Bean 实例,读取国际化资源,使用时只需要调用Bean 进行获取即可。

    Spring Boot 提供了自动配置类MessageSourceAutoConfiguration,可以看到注入了一个属性配置类和一个MessageSource
    在这里插入图片描述

    MessageSourceProperties

    MessageSourceProperties用于配置消息资源,说明如下:

    spring: 
      messages:
        # 配置资源位置,比如 resources/i18n/messages.properties 文件,则需要配置为 i18n/messages
        basename: i18n/messages
        # 编码格式 ,默认 UTF-8
        encoding: UTF-8
        # 缓存时长,未设置时永久缓存,单位秒
        cache-duration: 1000
        # 是否使用消息代码作为默认消息而不是抛出一个 NoSuchMessageException
        use-code-as-default-message: false
        # 是否总是应用 MessageFormat 规则
        always-use-message-format: false
        # 如果没有特定语言环境的文件,是否回退到系统语言环境
        fallback-to-system-locale: true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    LocaleResolver

    LocaleResolverSpring MVC提供的一个接口:

    public interface LocaleResolver {
    	 // 根据request对象根据指定的方式获取一个Locale,如果没有获取到,则使用用户指定的默认的Locale
        Locale resolveLocale(HttpServletRequest request);
    	// 设置Locale
        void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    从声明的方法可以看出这个接口的主要作用是根据请求对象,解析出相应的Locale,这也是实现国际化的重要接口。

    Spring MVC在处理请求时,会去容器中获取LocaleResolver的Bean 对象,调用其解析方法获取到当前Locale ,然后将其放到LocaleContextHolder中,以便线程后续的执行获取到。

    Spring Boot Spring MVC的自动配置中,配置了一个AcceptHeaderLocaleResolver解析器:
    在这里插入图片描述
    可以看到该解析器处理逻辑,是解析浏览器中请求头中Accept-Language字段值,来判断其地区的:

    在这里插入图片描述

    案例演示

    案例一:后台消息国际化

    需求场景:对于后台返回的响应信息、错误信息等,需要根据不同地区返回不同语言信息,比如中文地区返回操作成功,英文地区返回success,这些也是后端最主要的国际化消息用途。

    实现方案流程图

    在这里插入图片描述
    其中1至4 步,Spring Boot已经帮我们处理好了,我们只需要编写国际化资源文件,然后通过MessageSource 获取国际化消息即可。

    1. 添加国际化资源文件

    首先要设置配置文件的编码方式,不然会出现中文����乱码问题。
    在这里插入图片描述
    resources目录下添加i18n 文件夹,然后右键new =>Resource Bundle:
    在这里插入图片描述

    输入文件前缀messages,并点击加号添加多个语言地区:
    在这里插入图片描述

    添加zh_CNen_US,分别表示中文和英文:
    在这里插入图片描述
    会在目录下生成三个国际化文件,分别表示默认、中文、英文,接着点击Resource Bundle添加属性。
    在这里插入图片描述

    点击添加属性,输入名称
    在这里插入图片描述

    按照国家语言,输入不同的属性值,这样在三个配置文件中,同一个msg属性,都根据不同的地区有不同语言的属性值:
    在这里插入图片描述
    继续添加一个:
    在这里插入图片描述
    最后添加完成:
    在这里插入图片描述

    2. 添加配置

    添加配置,指定国际化资源的位置,

    spring:
      messages:
        # classpath*:[beaname].properties
        basename: i18n/messages
    
    • 1
    • 2
    • 3
    • 4

    3. 获取国际化消息

    编写一个工具类,根据属性名称和Locale 对象获取国际化消息:

    @Component
    public class MessageSourceUtils {
    
        private static MessageSource messageSource;
    
        @Autowired
        public MessageSourceUtils(MessageSource messageSource) {
            MessageSourceUtils.messageSource = messageSource;
        }
    
        public static String getMsg(String code) {
            // LocaleContextHolder 中封装了Locale 对象;
            // 该Locale 对象是AcceptHeaderLocaleResolver解析器, 根据Request请求中Accept-Language消息头解析得到的
            Locale locale = LocaleContextHolder.getLocale();
            // MessageSource中获取国际化信息
            return messageSource.getMessage(code, null, locale);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在响应结果对象中,使用工具类获取国际化消息:

    @Data
    public class Result<T> {
    
        private String code;
    
        private String msg;
    
        private T data;
    
        public static Result success() {
            Result result = new Result<>();
            result.setCode("0");
            // 响应消息国际化处理返回
            result.setMsg(MessageSourceUtils.getMsg("msg"));
            return result;
        }
    
        public static Result error(String code, String msg) {
            Result result = new Result();
            result.setCode(code);
            result.setMsg(MessageSourceUtils.getMsg("msg"));
            return result;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    在统一异常捕获时,也使用工具类获取国际化消息:

    @ControllerAdvice
    public class GlobalExceptionHandler {
    
        @ExceptionHandler(RuntimeException.class)
        @ResponseBody
        public Result handleException(RuntimeException e) {
            return Result.error("500", MessageSourceUtils.getMsg(e.getMessage()));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    4. 测试

    编写访问接口,返回成功Result

        @RequestMapping(value = "/test")
        public Result test() {
            return Result.success();
        }
    
    • 1
    • 2
    • 3
    • 4

    中文时,可以看到返回了操作成功
    在这里插入图片描述

    设置谷歌浏览器为英语(美国):
    在这里插入图片描述
    可以看到返回了success
    在这里插入图片描述
    再编写一个异常接口:

    public interface ExceptionConstants {
        String SYSTEM_ERROR = "errorMsg";
    }
    
    @RestController
    @RequestMapping("/test")
    public class TestController{
    
        @RequestMapping(value = "/ex")
        public Result ex() {
            if(true){
                throw new RuntimeException(ExceptionConstants.SYSTEM_ERROR);
            }
            return Result.success();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    测试发现,满足需求,案例成功!

    案例二:Thymeleaf 国际化

    使用Thymeleaf模板创建页面时,很容易实现国际化,因为它提供了#{…}消息表达式,直接从国际化资源中获取。

    可以参考Thymeleaf系列【2】标准表达式(1)

    案例三:前后端分离国际化

    前后端分离项目,比如前端使用VUE ,这个时候就需要前端自己使用前端的国际化插件,编写资源,实现国际化了。

    比如在 vue 的项目中,可以直接使用 vue-i18n 进行实现,流程和后端差不多。

    案例四:页面实现中英文切换

    在很多官方文档中,可以看到中英文切换的按钮,默认根据当地区域选择一种语言,然后还可以点击切换,这种方式更具有灵活性。
    在这里插入图片描述

    如果要实现这种需求,依靠Spring Boot默认的解析浏览器请求消息头的方式,就不适用了。

    需要自定义解析器,比如下方实例中,根据自定义消息头设置Locale,如果用户刚进入页面,使用默认的地区,当点击了切换按钮后,全局请求接口都添加用户选择的区域,比如切换到英文,则前端全局添加lang: en_US请求头,那么后端就会获取到英文Locale对象,也就能实现语言切换了。

    自定义解析器如下:

    public class RequestHeaderLocaleResolver implements LocaleResolver {
    
        // 请求头参数
        private static final String LANG_HEADER = "lang";
    
        // 解析请求,设置Locale地区化
        @Override
        public Locale resolveLocale(HttpServletRequest request) {
            // 1. 获取请求中的lang参数
            String lang = request.getHeader(LANG_HEADER);
            // 2. 没有lang参数,使用默认Locale
            if (StringUtils.isEmpty(lang)) {
                return Locale.getDefault();
            } else {
                // 4. 根据lang参数,设置国际化,分割出国家和地址
                String[] s = lang.split("_");
                return new Locale(s[0], s[1]);
            }
        }
    
        @Override
        public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    在配置类中,注入自定义LocaleResolver到Spring 容器中:

    @Configuration
    public class MyMvcConfig  {
    
        @Bean
        public LocaleResolver localeResolver(){
            return new MyLocal();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
  • 相关阅读:
    Java Maven 项目读取项目版本号
    经过一个多月的等待我有幸成为Spring相关项目的Contributor
    24节气- ||大雪|| 文案、海报分享,冬寒雪落,归家愈暖。
    Python魔术方法
    探索 SK 示例 -- GitHub 存储库中的机器人
    大数据常见面试题Hadoop篇(3)
    【C语言课程设计】医院管理系统
    【SpringBoot篇】分页查询 | 扩展SpringMvc的消息转换器
    nginx upstream健康检测
    【2024_CUMCM】熵权TOPSIS方法
  • 原文地址:https://blog.csdn.net/qq_43437874/article/details/125782060