摘要:特別是開(kāi)發(fā)一些只做國(guó)內(nèi)市場(chǎng),只有中文的項(xiàng)目時(shí),可能就直接被忽視了。應(yīng)用場(chǎng)景的使用場(chǎng)景基本就是根據(jù)不同國(guó)家和語(yǔ)言,進(jìn)行不同的顯示。比如中解析時(shí),可以同時(shí)處理兩種格式。一組為,一組為。對(duì)于中文,英文,日文都有一個(gè)默認(rèn)的匹配。
摘要
Locale是日常開(kāi)發(fā)中比較容易忽視的技術(shù)點(diǎn)。特別是開(kāi)發(fā)一些只做國(guó)內(nèi)市場(chǎng),只有中文的項(xiàng)目時(shí),Locale可能就直接被忽視了。而且在項(xiàng)目提出多語(yǔ)言支持的時(shí)候,因?yàn)闆](méi)有很好的理解,可能給自己埋了很多坑。
什么是Locale其實(shí)java.util.Locale的Java Doc有很詳細(xì)的解釋?zhuān)揖筒贿^(guò)多解釋。
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實(shí)例包括以下信息就可以了。在多年的開(kāi)發(fā)經(jīng)驗(yàn)中,script和variant基本沒(méi)有用到。就不過(guò)多介紹。
language
script
country (region)
variant
lanugageISO 639 alpha-2 or alpha-3 language code
在實(shí)際使用中,基本我們碰不到3位字母表示的語(yǔ)言。
countryISO 3166 alpha-2 country code or UN M.49 numeric-3 area code.
同樣實(shí)際使用中,基本使用2位字母表示的國(guó)家
scirpt 和 variantIANA Language Subtag Registry 定義了完整列表。
感覺(jué)script是地區(qū)的別稱(chēng),variant是方言。了解一下就好。
在實(shí)際使用中,大部分同學(xué)第一反應(yīng)可能是會(huì)寫(xiě)出以下Locale。
zh_CN
en_US
ja_JP
如果你也是只想到這些,請(qǐng)打開(kāi)你的瀏覽器->開(kāi)發(fā)者工具->控制臺(tái)中輸入以下js
window.navigator.language
瀏覽器為中文,你又在國(guó)內(nèi)。輸出結(jié)果就是zh-CN
然后如果重新你設(shè)置瀏覽器語(yǔ)言,比如設(shè)置成英文。再執(zhí)行一下,輸出結(jié)果是en-CN
What?? en-CN?? 這是什么鬼? 首先通過(guò)這個(gè)Locale,我們可以知道country,是和你實(shí)際在哪個(gè)地區(qū)有關(guān)。
那該怎么處理? 后面詳細(xì)說(shuō)怎么應(yīng)用。
Locale的使用場(chǎng)景基本就是根據(jù)不同國(guó)家和語(yǔ)言,進(jìn)行不同的顯示。實(shí)際經(jīng)驗(yàn)以下2點(diǎn)為主。
多語(yǔ)言 (下文會(huì)詳細(xì)說(shuō)明)
金額顯示。
日期格式顯示。
以金額顯示為例,如果沒(méi)有類(lèi)似開(kāi)發(fā)經(jīng)驗(yàn)的話(huà),你可以為會(huì)想,金額還有不同的格式。一般不都是 1,000,000.00。那你就錯(cuò)了。舉兩個(gè)例子。
日語(yǔ)。日本人金額是不帶小數(shù)點(diǎn)的。
法語(yǔ)。法語(yǔ)中千位分隔符為空格,小數(shù)點(diǎn)為逗號(hào)。比如 1 000 000,00
正確理解Locale,并正確使用可以寫(xiě)出既規(guī)范,又簡(jiǎn)練,質(zhì)量又高的代碼。而不是見(jiàn)招拆招,每個(gè)語(yǔ)言寫(xiě)一個(gè)自己的實(shí)現(xiàn)。
創(chuàng)建Locale 實(shí)例的正確姿勢(shì)使用正確的姿勢(shì)創(chuàng)建非常重要,這在后面Spring里應(yīng)用部分非常重要。
以下這段代碼是我見(jiàn)過(guò)最多的創(chuàng)建方式。
Locale locale = new Locale("zh_CN");
其中zh_CN可能是前端直接傳入,為了方便直接作為L(zhǎng)ocale構(gòu)造方法參數(shù)。其實(shí)這是一個(gè)錯(cuò)誤的用法。這樣的使用,創(chuàng)建出來(lái)的Locale.language就是zh_cn。
以下先列舉一下兩種正確姿勢(shì)。然后比較一下結(jié)果
Locale API
// 使用Locale構(gòu)造方法 // 如果前端傳入"zh_CN",此處需要自行解析并拆分 Locale locale = new Locale("zh", "CN"); // 使用Locale預(yù)置常量。請(qǐng)自行查看Locale源代碼。 Locale locale = Locale.SIMPLIFIED_CHINESE
Commons-Lang LocaleUtils.toLocale()
Locale locale = LocaleUtils.toLocale("zh_CN");
以上三種創(chuàng)建方式,可以創(chuàng)建出一樣的Locale object。
本人推薦使用Commons Lang3 LocaleUtils.toLocale()
下面我們的來(lái)對(duì)比一下錯(cuò)誤和正解方式創(chuàng)建的Locale有什么區(qū)別。
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("================================="); } }
輸出結(jié)果
================================= Locale.toString: zh_cn Language: zh_cn Country: LanguageTag: und ================================= ================================= Locale.toString: zh_CN Language: zh Country: CN LanguageTag: zh-CN =================================
讓我們來(lái)分析一下結(jié)果
首先看一下數(shù)據(jù)。錯(cuò)誤的創(chuàng)建方式,其實(shí)是把zh_CN作為language。Country和LanguageTag為空
Language輸出時(shí)均為小寫(xiě)
Country輸出時(shí)均為大寫(xiě)
LanugageTag為L(zhǎng)anguage和Country以"-"連接
Locale.toString則是Language和Country以"_"連接
zh_CN vs zh-CN什么時(shí)候使用"_",什么時(shí)候使用"-",確實(shí)比較搞。
比如request.getLocale()中解析Locale時(shí),可以同時(shí)處理兩種格式。而Commons Lang3 LocaleUtils.toLocale()的入?yún)⒅恢С窒聞澗€(xiàn)格式。
不過(guò)我們可以定義這樣的規(guī)范,在后端服務(wù)中只使用"_"格式,而前端只使用"-"格式。
前端前端框架太多,就只說(shuō)一下最近在玩的umi+dva+react。
UMI Locale處理umi開(kāi)發(fā)的項(xiàng)目中使用umi-plugin-react/locale來(lái)處理Locale。
import { setLocale, getLocale } from "umi-plugin-react/locale"; setLocale(language, true); getLocale();多語(yǔ)言
資源文件使用"-"格式命名。
. |-- 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顯示多語(yǔ)言
import { formatMessage } from "umi-plugin-react/locale"; formatMessage({id: "xxx"})日期顯示
import { formatDate } from "umi-plugin-react/locale"; formatDate(new Date());數(shù)字顯示
formatNumber(10000000.00);后端服務(wù)
這里只介紹基于Spring Boot開(kāi)發(fā)的Stateless Rest API。SpringMVC已經(jīng)過(guò)時(shí),就不做介紹。
Locale處理 如何確認(rèn)當(dāng)前API調(diào)用使用的Locale?Spring Boot使用LocaleResolver來(lái)確定當(dāng)前API調(diào)用使用什么Locale。在LocaleResolver獲取Locale之后,將Locale存入LocaleContextHolder中。
Spring Boot提供了幾個(gè)標(biāo)準(zhǔn)實(shí)現(xiàn),主要區(qū)別是針對(duì)Locale存放的地方不一樣提供對(duì)應(yīng)獲取方式
AcceptHeaderLocaleResovler 從Request Header的Accept-Language中獲取
CookieLocaleResolver 從Cookie中獲取
FixedLocaleResolver 固定Locale,只使用系統(tǒng)配置的Locale
SessionLocaleResolver 從Session中獲取
雖然Spring已經(jīng)提供了多種獲取LocaleResolver實(shí)現(xiàn),但是在具體業(yè)務(wù)場(chǎng)景中會(huì)有更復(fù)雜的場(chǎng)景。比如需要根據(jù)當(dāng)前登錄用戶(hù)的語(yǔ)言設(shè)置。這個(gè)時(shí)間就需要我們自己實(shí)現(xiàn)一套LocaleResovler。
多語(yǔ)言 資源文件在Spring中,我們可以添加properties文件來(lái)做多語(yǔ)言支持。
. |-- 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
可以看到在例子
有兩組資源文件。一組為message,一組為anthor。在項(xiàng)目中可以這種方式進(jìn)行模塊化管理。
每組資源文件可以有自己支持的locale列表
每個(gè)文件定義對(duì)應(yīng)locale的翻譯
沒(méi)有l(wèi)ocale資源文件為默認(rèn)語(yǔ)言。如messages.properties, another.properties。當(dāng)沒(méi)有l(wèi)ocale匹配時(shí),使用默認(rèn)資源文件內(nèi)容。
messages_xx.properties? 這是合法的。但建議使用,也基本不會(huì)碰到。這里是說(shuō)明一下,框架是支持的。用new Locale("xx")可以創(chuàng)建language為xx的Locale。
Locale匹配優(yōu)先級(jí)language+country+variant > language+country > lanaguage
以message*.properties為例:
zh_CN -> messages_zh_CN.properties
zh或zh_JP -> messages_zh.properties
en_US -> messages.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()));數(shù)字顯示
System.out.println(NumberFormat.getInstance(locale).format(10000000));實(shí)際場(chǎng)景中的應(yīng)用
一般產(chǎn)品基本需要用戶(hù)登錄,在LocaleResovler中也提到。我們可以根據(jù)當(dāng)前用戶(hù)的語(yǔ)言設(shè)置作為使用Locale。這樣比較好控制服務(wù)接收到的Locale。而且我們?cè)陂_(kāi)發(fā)時(shí)可以定義好系統(tǒng)支持的語(yǔ)言,比如支持zh_CN, en_US, ja_JP。這樣在用戶(hù)登錄后的API調(diào)用就不用擔(dān)心接收到不支持的Locale。而因?yàn)樾枰褂糜脩?hù)設(shè)置語(yǔ)言,我們需要自己實(shí)現(xiàn)一個(gè)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)); } }
而用戶(hù)沒(méi)有登錄之前,而前端不做任何處理時(shí),后端會(huì)接收到類(lèi)似en_CN的Locale,而無(wú)法匹配資源文件。如果按以下資源文件設(shè)計(jì),給每個(gè)語(yǔ)言設(shè)置一個(gè)默認(rèn)翻譯,則可以解決接收到不規(guī)則Locale問(wèn)題。
. |-- 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
這樣的編排方式,不管你在什么國(guó)家。對(duì)于中文,英文,日文都有一個(gè)默認(rèn)的匹配。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/75580.html
摘要:特別是開(kāi)發(fā)一些只做國(guó)內(nèi)市場(chǎng),只有中文的項(xiàng)目時(shí),可能就直接被忽視了。應(yīng)用場(chǎng)景的使用場(chǎng)景基本就是根據(jù)不同國(guó)家和語(yǔ)言,進(jìn)行不同的顯示。比如中解析時(shí),可以同時(shí)處理兩種格式。一組為,一組為。對(duì)于中文,英文,日文都有一個(gè)默認(rèn)的匹配。 摘要 Locale是日常開(kāi)發(fā)中比較容易忽視的技術(shù)點(diǎn)。特別是開(kāi)發(fā)一些只做國(guó)內(nèi)市場(chǎng),只有中文的項(xiàng)目時(shí),Locale可能就直接被忽視了。而且在項(xiàng)目提出多語(yǔ)言支持的時(shí)候,因?yàn)?..
摘要:概述為我們提供國(guó)際化支持,通過(guò)設(shè)置系統(tǒng)的環(huán)境,根據(jù)運(yùn)行環(huán)境使用不同的語(yǔ)言顯示。提供接口的作用是解析客戶(hù)端使用的地區(qū),目的是為了根據(jù)這些信息實(shí)現(xiàn)視圖多語(yǔ)言即國(guó)際化。接口繼承接口,增加時(shí)區(qū)支持。 概述 Spring MVC為我們提供國(guó)際化支持,通過(guò)設(shè)置系統(tǒng)的環(huán)境,根據(jù)運(yùn)行環(huán)境使用不同的語(yǔ)言顯示。Spring提供LocaleResolver接口的作用是解析客戶(hù)端使用的地區(qū)(Locale),目...
摘要:與一樣,該類(lèi)繼承抽象類(lèi),并且通過(guò)外部的屬性文件定義邏輯視圖名稱(chēng)與真正的視圖對(duì)象的關(guān)系,屬性文件默認(rèn)是下的,可以通過(guò)或?qū)傩詠?lái)指定,該屬性指的是文件的基名稱(chēng),也就是說(shuō)以屬性值開(kāi)頭的屬性文件。 概述 本章再學(xué)習(xí)另外兩個(gè)ViewResolver,分別是XmlViewResolver和ResourceBundleViewResolver,從功能上說(shuō),這兩個(gè)視圖解析器都是從外部資源文件中查找視圖V...
摘要:概述上一篇就默認(rèn)的進(jìn)行了分析,詳細(xì)請(qǐng)參考,本節(jié)我們繼續(xù)分析學(xué)習(xí),主要分析解析器類(lèi)繼承關(guān)系如下圖由上面類(lèi)圖可知,繼承并實(shí)現(xiàn)接口,主要是操作的工具類(lèi),繼承接口,增加了信息操作。即通過(guò)實(shí)現(xiàn)的選擇。 概述 上一篇就Spring MVC默認(rèn)的LocaleResovler(AcceptHeaderLocaleResolver)進(jìn)行了分析,詳細(xì)請(qǐng)參考https://segmentfault.com/...
摘要:此解析器與差不多,更改下配置文件中的類(lèi)全路徑即可??偨Y(jié)本章介紹了以及三個(gè)視圖解析器。這部分內(nèi)容有點(diǎn)兒多,我會(huì)盡快結(jié)束。 概述 通過(guò)上幾篇的學(xué)習(xí),我們分析了并試驗(yàn)了ViewResolverComposite、BeanNameViewResolver和ContentNegotiatingViewResolver,這三個(gè)類(lèi)都直接實(shí)現(xiàn)ViewResolver接口。Spring MVC提供了很多...
閱讀 2121·2021-11-19 11:37
閱讀 807·2021-11-11 16:54
閱讀 1238·2021-11-02 14:44
閱讀 3161·2021-09-02 15:40
閱讀 2429·2019-08-30 15:44
閱讀 1039·2019-08-29 11:17
閱讀 1121·2019-08-26 14:06
閱讀 1625·2019-08-26 13:47