1、国际化语言代码
ISO-639 标准使用编码定义了国际上常见的语言,每一种语言由两个小写字母表示。
ISO-3166 标准使用编码定义了国家/地区,每个国家/地区由两个大写字母表示。
国家/地区 | 语言代码 |
简体中文(中国) | zh-CN |
繁体中文(台湾地区) | zh-TW |
繁体中文(香港) | zh-HK |
英语(英国) | en-GB |
英语(美国) | en-US |
英语(香港) | en-HK |
英语(全球) | en-WW |
2、HTTP中的常见首部
1)请求首部
请求首部中的Accept首部为客户端提供了一种将其喜好和能力告知服务器的方式,包括他们想要什么,可以使用什么,以及最重要的他们不想要什么。这样服务器就能根据这些额外信息,对要发送的内容做出更明智的决定。
请求首部 | 描述 |
Accept | 告知服务器发送何种媒体类型,例如:*/* |
Accept-Language | 表示浏览器所支持的语言,告知服务器发送何种语言 |
Accept-Charset | 告知服务器发送何种字符集 |
Accept-Encoding | 告知服务器采用何种编码 |
2)响应首部
响应首部 | 描述 |
Accept-Range | 对此资源来说,服务器可接受的范围类型 |
Set-Cookie | 在客户端写入cookie数据 |
3)实体首部
实体首部可以告知报文的接收者它在对什么进行处理
实体首部 | 描述 |
Content-Type | 主体的对象类型 |
Content-Language | 理解主体时最适宜使用的自然语言 |
Content-Encoding | 主体的编码方式 |
Content-Length | 主体的长度或尺寸 |
Content-MD5 | 主体的MD5校验和 |
Content-Range | 在整个资源中此实体标识的字节范围 |
3、Java中的Locale
在 Java 中,一个 java.util.Locale
对象表示了特定的地理、政治和文化地区。需要 Locale 来执行其任务的操作称为语言环境敏感的操作,它使用 Locale 为用户量身定制本地信息。
它有三个构造方法
Locale(String language)
:根据语言编码初始化Locale(String language, String country)
:根据语言编码、国家编码初始化Locale(String language, String country, String variant)
:根据语言编码、国家编码、变体初始化
此外,Locale 定义了一些常用的 Locale 常量:Locale.ENGLISH
、Locale.CHINESE
等。
4、Java中的国际化
1)国际化文件格式:<资源名>_<语言代码>_<国家/地区编码>.properties
2)国际化文件处理:native2ascii [-reverse] [-encoding 编码] [输入文件 [输出文件]]
注:
<资源名>.properties
命名的国际化资源文件是默认的资源文件,即某个国际化类型在系统中找不到对应的资源文件,就采用这个默认的资源文件
3)国际化文件加载:Java提供了用于加载资源文件的工具java.util.ResourceBoundle
ResourceBoundle
提供了多个名为 getBundle
的静态重载方法,这些方法的作用是用来根据资源名、Locale 选择指定语种的资源文件。
需要说明的是: getBundle
方法的第一个参数一般都是baseName
,这个参数表示相对classpath下的资源文件全路径名。例如在classpath路径下有以下几个文件
- i18n.properties
- i18n_en_US.properties
- i18n_zh_CN.properties
此时的baseName为:i18n。但如果是在classpath路径下的config文件夹中存放了以上几个文件,则此时的baseName为"config/i18n"
在查找文件的时候,会先根据baseName和Locale信息来拼接出完整的文件名,随后通过classLoader的getResource方法读取国际化文件,最后包装成PropertyResourceBundle实例对象
Java 中也提供了几个支持国际化的格式化工具类。例如:NumberFormat
、DateFormat
、MessageFormat
// 使用服务器的Locale进行数据格式化
NumberFormat numberFormat = NumberFormat.getInstance() ;
// 手动指定Locale来进行实例化
numberFormat = NumberFormat.getInstance(Locale.CHINA) ;
// 使用服务器的Locale进行数据格式化
DateFormat dateFormat = DateFormat.getInstance() ;
// 手动指定Locale来创建实例
dateFormat = DateFormat.getDateInstance(DateFormat.LONG, Locale.CHINA);
// DateFormat的子类SimpleDateFormat来实现国际化
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.CHINA);
// 文件格式化,其本身是执行具体的格式化类进行文本的格式化,如NumberFormat、DateFormat
MessageFormat mf = new MessageFormat("Hello {0}!", Locale.CHINA);
5、Spring提供的国际化支持
Spring中对国际化文件支持的基础接口是MessageSource
. Spring 为生产环境提供了两种开箱即用的实现:
- ResourceBundleMessageSource,建立在标准 ResourceBundle 之上
- ReloadableResourceBundleMessageSource,能够在不重新启动 VM 的情况下重新加载消息定义
注意,ApplicationContext接口也继承了
MessageSource
接口
如果使用了Springboot框架,则在以下配置类中已经进行了必要的装配
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration
我们可以通过spring.messages.basename
配置参数来配置国际化文件的基础名(多个用英文逗号分隔,每个名字都对应一个ResourceBundle),默认为:message
6、SpringMVC读取客户端语言列表
- jakarta.servlet.ServletRequest的getLocales方法从请求头中读取Accept-Language构建出Locale集合
- org.springframework.web.servlet.FrameworkServlet调用ServletRequest的getLocale方法获取第一个Locale
- 将Locale对象封装成LocaleContext,并设置到LocaleContextHolder中,其它地方便可直接从LocaleContextHolder中获取Locale信息
7、LocaleResolver
基于web的Locale设置解析策略的接口,既允许通过请求进行Locale设置解析,也允许通过请求和响应进行Locale设置修改。
此接口允许基于request, session, cookie等的实现。默认实现为org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver,只需使用相应HTTP标头提供的请求的区域设置。
使用org.springframework.web.servlet.support.RequestContext.getLocale()来检索控制器或视图中的当前区域设置,与实际的解析策略无关。
调用HttpServletRequest的getLocale方法便能获取到Locale信息,通过AcceptHeaderLocaleResolver可以设置所有支持的Locale,也可以设置默认的Locale。
当DispatcherServlet执行render方法时,会执行以下逻辑
1)如果localeResolver不为null,则通过localeResolver来解析Locale,否则调用Request的getLocale()方法获得Locale
2)将上一步得到的Locale设置到HttpServletResponse上
3)将上面的Locale作为参数执行ViewResolver的resolveViewName方法
请注意,AcceptHeaderLocaleResolver解析器无法改变用户的区域,因为它无法修改用户操作系统的区域设置。
可以借助RequestContextUtils.getLocale(HttpServletRequest)方法来获取Locale
8、LocaleChangeInterceptor
除了显式调用LocaleResolver.setLocale()来修改用户的区域之外,还可以将LocaleChangeInterceptor拦截器应用到处理程序映射中,它会发现当前HTTP请求中出现的特殊参数。其中的参数名称可以通过拦截器的paramName属性进行自定义。如果这种参数出现在当前请求中,拦截器就会根据参数值来改变用户的区域。
执行逻辑:
1)从请求的查询参数说获取Locale信息(参数名可自行设置),如果不为空则继续处理
2)获取使用的LocaleResolver
3)解析从参数获取到的区域信息,构建Locale实例对象
4)调用localeResolver的setLocale方法设置Locale
需要注意的是,如果使用的是AcceptHeaderLocaleResolver则会报错UnsupportedOperationException,所有在使用LocaleChangeInterceptor是记得配置LocaleResolver,例如使用CookieLocaleResolver
使用后的效果:
假设有以下请求
http://localhost:8080/court/welcome.htm?language=en_US
LocaleChangeInterceptor会读取参数language(先配置了该参数),然后将转换成的Locale设置到LocaleResolver(假设此时用的是CookieLocaleResolver),CookieLocaleResolver后续会将区域信息写入客户的cookie中。
在后续的请求中会从cookie中读取区域信息。这样,随时可以通过url地址来改变用户的区域信息。
9、读取国际化数据
SpringMVC提供了org.springframework.context.MessageSource类型来获取国际化配置数据,自然也可以通过ApplicationContext的getMessage方法来获取。
10、前后端分离的国际化
当前很多项目都是前后端分离的,对于前端来说,更多的可能是借助前端框架提供的国际化插件来实现,而后端更多关注的是响应时提示信息的国际化。
使用HandlerInterceptor
可以使用org.springframework.web.servlet.i18n.LocaleChangeInterceptor
- 添加拦截器LocaleChangeInterceptor
- 配置LocaleResolver
- 在业务代码中,通过LocaleResolver获取到Locale,接着调用MessageSource的接口来获取数据
这里也可以将LocaleChangeInterceptor替换成其它组件,例如自定义的Filter等,其目的就是调用LocaleResolver的方法从请求中解析出Locale,同时调用LocaleResolver的setLocale方法进行设置。
为了便于业务代码使用,也可以考虑给Handler创建切面,在切面中完成从LocaleResolver获取Locale的过程,并将Locale设置到一个自定义工具上,如:I18LocaleHolder