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.ENGLISHLocale.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 中也提供了几个支持国际化的格式化工具类。例如:NumberFormatDateFormatMessageFormat

// 使用服务器的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读取客户端语言列表

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()来检索控制器或视图中的当前区域设置,与实际的解析策略无关。

SpringMVC中国际化处理_Java_02

调用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中国际化处理_资源文件_03

SpringMVC提供了org.springframework.context.MessageSource类型来获取国际化配置数据,自然也可以通过ApplicationContext的getMessage方法来获取。


10、前后端分离的国际化

当前很多项目都是前后端分离的,对于前端来说,更多的可能是借助前端框架提供的国际化插件来实现,而后端更多关注的是响应时提示信息的国际化。

使用HandlerInterceptor

可以使用org.springframework.web.servlet.i18n.LocaleChangeInterceptor

SpringMVC中国际化处理_Java_04

  1. 添加拦截器LocaleChangeInterceptor
  2. 配置LocaleResolver
  3. 在业务代码中,通过LocaleResolver获取到Locale,接着调用MessageSource的接口来获取数据

这里也可以将LocaleChangeInterceptor替换成其它组件,例如自定义的Filter等,其目的就是调用LocaleResolver的方法从请求中解析出Locale,同时调用LocaleResolver的setLocale方法进行设置。

为了便于业务代码使用,也可以考虑给Handler创建切面,在切面中完成从LocaleResolver获取Locale的过程,并将Locale设置到一个自定义工具上,如:I18LocaleHolder