Locale是平常开发中比较容易忽视的技术点。特别是开发一些只作国内市场,只有中文的项目时,Locale可能就直接被忽视了。并且在项目提出多语言支持的时候,由于没有很好的理解,可能给本身埋了不少坑。javascript
其实java.util.Locale
的Java Doc有很详细的解释,我就不过多解释。html
A Locale object represents a specific geographical, political, or cultural region. An operation that requires a Locale to perform its task is called locale-sensitive and uses the Locale to tailor information for the user.
主要记住Locale实例包括如下信息就能够了。在多年的开发经验中,script和variant基本没有用到。就不过多介绍。前端
ISO 639 alpha-2 or alpha-3 language code
在实际使用中,基本咱们碰不到3位字母表示的语言。java
ISO 3166 alpha-2 country code or UN M.49 numeric-3 area code.
一样实际使用中,基本使用2位字母表示的国家react
IANA Language Subtag Registry 定义了完整列表。
感受script是地区的别称,variant是方言。了解一下就好。后端
在实际使用中,大部分同窗第一反应多是会写出如下Locale。api
若是你也是只想到这些,请打开你的浏览器->开发者工具->控制台中输入如下js浏览器
window.navigator.language
浏览器为中文,你又在国内。输出结果就是zh-CN
而后若是从新你设置浏览器语言,好比设置成英文。再执行一下,输出结果是en-CN
What?? en-CN?? 这是什么鬼? 首先经过这个Locale,咱们能够知道country,是和你实际在哪一个地区有关。
那该怎么处理? 后面详细说怎么应用。前端框架
Locale的使用场景基本就是根据不一样国家和语言,进行不一样的显示。实际经验如下2点为主。oracle
以金额显示为例,若是没有相似开发经验的话,你能够为会想,金额还有不一样的格式。通常不都是 1,000,000.00。那你就错了。举两个例子。
正确理解Locale,并正确使用能够写出既规范,又简练,质量又高的代码。而不是见招拆招,每一个语言写一个本身的实现。
使用正确的姿式建立很是重要,这在后面Spring里应用部分很是重要。
如下这段代码是我见过最多的建立方式。
Locale locale = new Locale("zh_CN");
其中zh_CN多是前端直接传入,为了方便直接做为Locale构造方法参数。其实这是一个错误的用法。这样的使用,建立出来的Locale.language就是zh_cn。
如下先列举一下两种正确姿式。而后比较一下结果
// 使用Locale构造方法 // 若是前端传入"zh_CN",此处须要自行解析并拆分 Locale locale = new Locale("zh", "CN"); // 使用Locale预置常量。请自行查看Locale源代码。 Locale locale = Locale.SIMPLIFIED_CHINESE
LocaleUtils.toLocale()
Locale locale = LocaleUtils.toLocale("zh_CN");
以上三种建立方式,能够建立出同样的Locale object。
本人推荐使用Commons Lang3 LocaleUtils.toLocale()
下面咱们的来对比一下错误和正解方式建立的Locale有什么区别。
public class LocaleShowCase { public static void main(String[] args) { logLocale(new Locale("zh_CN")); logLocale(Locale.SIMPLIFIED_CHINESE); } private static void logLocale(Locale locale) { System.out.println("================================="); System.out.println(String.format("Locale.toString: %s", locale.toString())); System.out.println(String.format("Language: %s", locale.getLanguage())); System.out.println(String.format("Country: %s", locale.getCountry())); System.out.println(String.format("LanguageTag: %s", locale.toLanguageTag())); System.out.println("================================="); } }
输出结果
================================= Locale.toString: zh_cn Language: zh_cn Country: LanguageTag: und ================================= ================================= Locale.toString: zh_CN Language: zh Country: CN LanguageTag: zh-CN =================================
让咱们来分析一下结果
何时使用"_",何时使用"-",确实比较搞。
好比request.getLocale()中解析Locale时,能够同时处理两种格式。而Commons Lang3 LocaleUtils.toLocale()的入参只支持下划线格式。
不过咱们能够定义这样的规范,在后端服务中只使用"_"格式,而前端只使用"-"格式。
前端框架太多,就只说一下最近在玩的umi+dva+react。
umi开发的项目中使用umi-plugin-react/locale
来处理Locale。
import { setLocale, getLocale } from 'umi-plugin-react/locale'; setLocale(language, true); getLocale();
资源文件使用"-"格式命名。
. |-- en-US | |-- common.ts | `-- form.ts |-- ja-JP | |-- common.ts | `-- form.ts |-- zh-CN | |-- common.ts | `-- form.ts |-- en-US.ts |-- ja-JP.ts `-- zh-CN.ts
import { formatMessage } from 'umi-plugin-react/locale'; formatMessage({id: 'xxx'})
import { formatDate } from 'umi-plugin-react/locale'; formatDate(new Date());
formatNumber(10000000.00);
这里只介绍基于Spring Boot开发的Stateless Rest API。SpringMVC已通过时,就不作介绍。
Spring Boot使用LocaleResolver来肯定当前API调用使用什么Locale。在LocaleResolver获取Locale以后,将Locale存入LocaleContextHolder中。
Spring Boot提供了几个标准实现,主要区别是针对Locale存放的地方不同提供对应获取方式
虽然Spring已经提供了多种获取LocaleResolver实现,可是在具体业务场景中会有更复杂的场景。好比须要根据当前登陆用户的语言设置。这个时间就须要咱们本身实现一套LocaleResovler。
在Spring中,咱们能够添加properties文件来作多语言支持。
. |-- java .... `-- resources `-- i18n |-- messages.properties |-- messages_ja.properties |-- messages_ja_JP.properties |-- messages_xx.properties |-- messages_zh.properties |-- messages_zh_CN.properties |-- another.properties |-- another_zh_CN.properties |-- another_zh_TW.properties |-- another_en.properties `-- another_ja.properties
能够看到在例子
language+country+variant > language+country > lanaguage
以message*.properties为例:
MessageSource
@Configuration public class MessageConfiguration { @Bean public MessageSource messageSource() { ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource(); messageSource.setDefaultEncoding("UTF-8"); messageSource.setBasenames("classpath:i18n/messages", "classpath:i18n/another"); return messageSource; } }
这里注意messageSource.setBasenames("classpath:i18n/messages", "classpath:i18n/another")
BaseName就是资源文件的组名。
@Service public class XXXService { private final MessageSource messageSource; public XXXService(MessageSource messageSource) { this.messageSource = messageSource; } public String getI18N(String key, Object[] params) { return messageSource.getMessage(key, params, LocaleContextHolder.getLocale()) } }
DateFormat fullDF = DateFormat.getDateInstance(DateFormat.FULL, locale); System.out.println(fullDF.format(new Date()));
System.out.println(NumberFormat.getInstance(locale).format(10000000));
通常产品基本须要用户登陆,在LocaleResovler中也提到。咱们能够根据当前用户的语言设置做为使用Locale。这样比较好控制服务接收到的Locale。并且咱们在开发时能够定义好系统支持的语言,好比支持zh_CN, en_US, ja_JP。这样在用户登陆后的API调用就不用担忧接收到不支持的Locale。而由于须要使用用户设置语言,咱们须要本身实现一个LocaleResovler。
@Data public class Principal { private String username; private String language; ... } public class CustomLocaleResolver implements LocaleResolver { private Locale defaultLocale; public CustomLocaleResolver(Locale defaultLocale) { this.defaultLocale = defaultLocale; } public Locale resolveLocale(HttpServletRequest request) { Principal principal = (Principal) SecurityContextHolder.getContext().getAuthentication(); if (principal != null && !StringUtils.isEmpty(principal.getLanguage())) { return LocaleUtils.toLocale(principal.getLanguage()); } else { return request.getHeader("Accept-Language") != null ? request.getLocale() : this.defaultLocale; } } public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) { throw new UnsupportedOperationException("Cannot change Principal data - use a different locale resolution strategy"); } } @Configuration public class LocaleConfiguration { @Bean public CustomLocaleResolver localeResolver(@Value("${default-language:zh_CN}") String defaultLanguage) { return new CustomLocaleResolver(LocaleUtils.toLocale(defaultLanguage)); } }
而用户没有登陆以前,而前端不作任何处理时,后端会接收到相似en_CN的Locale,而没法匹配资源文件。若是按如下资源文件设计,给每一个语言设置一个默认翻译,则能够解决接收到不规则Locale问题。
. |-- java .... `-- resources `-- i18n |-- messages.properties |-- messages_ja.properties |-- messages_ja_JP.properties |-- messages_en.properties |-- messages_en_US.properties |-- messages_en_GB.properties |-- messages_zh.properties |-- messages_zh_CN.properties `-- messages_zh_TW.properties
这样的编排方式,无论你在什么国家。对于中文,英文,日文都有一个默认的匹配。