国际化
简单来说,国际化就是让应用(app、web)适应不同的语言和地区的需要,比如根据地区选择页面展示语言。
i18n=internationalization,首末字符i和n,18为中间的字符数
原理
基于传入语言or地区标识进行判断,输出不同内容。伪代码如下:
原理简单,但是如何优雅的实现?spring是否已经提供了现成的轮子?答案是肯定的。基于原理可以认为,实现国际化主要分为2部分
- 输入语言or地区标识
- 输出不同语言or地区的内容文案
输出
通过MessageSource实现不同语言输出。
在spring初始化refresh过程中,会初始化MessageSource。
而springboot会初始化ResourceBundleMessageSource实例作为MessageSource的默认实现
MessageSource内部则通过basename以及Locale定位到具体Resource Bundle文件,并基于code(properties key)读取对应的显示文本。
其中涉及到的三个概念
- basename标识
- Locale
- Resource Bundle
basename
用于指定Resource Bundle文件位置,可以通过配置文件配置, 默认为messages
spring.messages.basename=messagesLocale
Locale对象代表具体的地理,政治或文化地区,用来指定语言及地区。构造函数如下
variant 变体值,用于指示变化的任意值Locale。 同时, Locale类内置了众多常用国家地区的常量实例,如
Resource Bundle
ResourceBundle类和Properties类似,都可以读取程序内的文件,不过ResourceBundle更强大,提供了诸如缓存、Locale区分一类的操作。
所以MessageSource其实是对ResourceBundle的一种封装增加,优化了使用,并且托管与spring容器生命周期。这时就有一个很重要的选择:
如果通过静态类封装了restful的接口返回,可以自己扩展ResourceBundle类,而不是将MessageSource的spring实例放置在静态类中。
而idea中提供了Resource Bundle资源束的支持,方便用户添加管理国际化文案。
输入
看完输出的形式,可以知道,我们只需要确认Locale就可以实现国际化。所以我们再找一下Locale的轮子。
accept-language
servlet自带轮子,基于http协议,即通过header中的accept-language报文头,实现Locale的自动识别。
代码见 org.apache.catalina.connector.Request
spring提供的轮子
- LocaleResolver 实现次接口,用于自定义解析规则
- RequestContextUtils 基于request获取Locale,优先使用自定义LocaleResolver
- LocaleContextHolder通过ThreadLocal持有Locale对象,在FrameworkServlet支持servlet.service时执行初始化。
- LocaleChangeInterceptor通过Configuration配置Locale切换规则
综上,只要请求方在header中增加了accept-language报文头,即可在代码中通过LocaleContextHolder获取Locale对象。
lang
除了accept-language外,常见在url中增加了国家及地区参数,如: https://twitter.com/?lang=zh
通过lang配置国际化,需要通过LocaleChangeInterceptor进行配置,此配置的优先级高于accept-language
Test Case
LanguageTagCompliant
Locale的命名规则为 lang-country, 如: zh-CN,有时会看到zh_CH这种写法,这是另一种规范,可以在CookieLocaleResolver了解规范定义
需要在中LocaleChangeInterceptor开启兼容模式
但是为了符合规范,不推荐zh_CH这种写法。
倾向
按照《Http参数格式约定》文中所述,通用&非业务参数,一般会选择放到header中,所以比较倾向于accept-language这种定义方法。