国际化
简单来说,国际化就是让应用(app、web)适应不同的语言和地区的需要,比如根据地区选择页面展示语言。
i18n=internationalization,首末字符i和n,18为中间的字符数
原理
基于传入语言or地区标识进行判断,输出不同内容。伪代码如下:
![af2558e4d589afe3b8c5613c276eb9a9.jpeg](https://img-blog.csdnimg.cn/img_convert/af2558e4d589afe3b8c5613c276eb9a9.jpeg)
原理简单,但是如何优雅的实现?spring是否已经提供了现成的轮子?答案是肯定的。基于原理可以认为,实现国际化主要分为2部分
- 输入语言or地区标识
- 输出不同语言or地区的内容文案
输出
通过MessageSource实现不同语言输出。
在spring初始化refresh过程中,会初始化MessageSource。
![bbf6fed3ac0f60df19dc1392b6a50b60.jpeg](https://img-blog.csdnimg.cn/img_convert/bbf6fed3ac0f60df19dc1392b6a50b60.jpeg)
而springboot会初始化ResourceBundleMessageSource实例作为MessageSource的默认实现
![165813c824057dab64fe4fcee0a1fd4b.jpeg](https://img-blog.csdnimg.cn/img_convert/165813c824057dab64fe4fcee0a1fd4b.jpeg)
MessageSource内部则通过basename以及Locale定位到具体Resource Bundle文件,并基于code(properties key)读取对应的显示文本。
其中涉及到的三个概念
- basename标识
- Locale
- Resource Bundle
basename
用于指定Resource Bundle文件位置,可以通过配置文件配置, 默认为messages
spring.messages.basename=messagesLocale
Locale对象代表具体的地理,政治或文化地区,用来指定语言及地区。构造函数如下
![fd721f28570ad9f6ac1afd9fb4cf0ed9.jpeg](https://img-blog.csdnimg.cn/img_convert/fd721f28570ad9f6ac1afd9fb4cf0ed9.jpeg)
variant 变体值,用于指示变化的任意值Locale。 同时, Locale类内置了众多常用国家地区的常量实例,如
![341425e10adfc497d6b79ad651d16e81.jpeg](https://img-blog.csdnimg.cn/img_convert/341425e10adfc497d6b79ad651d16e81.jpeg)
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
![701340af42f3bc6eb276188ff9ebb692.jpeg](https://img-blog.csdnimg.cn/img_convert/701340af42f3bc6eb276188ff9ebb692.jpeg)
spring提供的轮子
- LocaleResolver 实现次接口,用于自定义解析规则
- RequestContextUtils 基于request获取Locale,优先使用自定义LocaleResolver
- LocaleContextHolder通过ThreadLocal持有Locale对象,在FrameworkServlet支持servlet.service时执行初始化。
- LocaleChangeInterceptor通过Configuration配置Locale切换规则
综上,只要请求方在header中增加了accept-language报文头,即可在代码中通过LocaleContextHolder获取Locale对象。
![285ab7554cac72f2f71fec1771d4b251.jpeg](https://img-blog.csdnimg.cn/img_convert/285ab7554cac72f2f71fec1771d4b251.jpeg)
lang
除了accept-language外,常见在url中增加了国家及地区参数,如: https://twitter.com/?lang=zh
通过lang配置国际化,需要通过LocaleChangeInterceptor进行配置,此配置的优先级高于accept-language
![bb4adb0912fa1f68be4c50954e620cbc.jpeg](https://img-blog.csdnimg.cn/img_convert/bb4adb0912fa1f68be4c50954e620cbc.jpeg)
Test Case
![75a68f79f4e8f4ef1792c546d9814e98.jpeg](https://img-blog.csdnimg.cn/img_convert/75a68f79f4e8f4ef1792c546d9814e98.jpeg)
LanguageTagCompliant
Locale的命名规则为 lang-country, 如: zh-CN,有时会看到zh_CH这种写法,这是另一种规范,可以在CookieLocaleResolver了解规范定义
![3e6b9bd47596ddbe92c5f25a63e56f69.jpeg](https://img-blog.csdnimg.cn/img_convert/3e6b9bd47596ddbe92c5f25a63e56f69.jpeg)
需要在中LocaleChangeInterceptor开启兼容模式
![1e3fdf2ef7837bd22a8a085118ecebc1.jpeg](https://img-blog.csdnimg.cn/img_convert/1e3fdf2ef7837bd22a8a085118ecebc1.jpeg)
但是为了符合规范,不推荐zh_CH这种写法。
倾向
按照《Http参数格式约定》文中所述,通用&非业务参数,一般会选择放到header中,所以比较倾向于accept-language这种定义方法。