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