不少開發者會有這樣的經歷,在若干年以前,一些企業、機構、學校的官方網站會要求:「翻譯一下,也作個英文版的」,結果每每就是又單獨維護一套英文版的頁面。javascript
而在當今的軟件開發領域,隨着愈來愈多的產品須要真正面向海外市場售賣的狀況,之前那種簡單粗暴的作法就不適用了;這就須要靈活的修改軟件,使之能適應目標市場的語言、地區差別以及技術須要。php
本文嘗試對相關概念,特別是在 web 前端領域的應用,作出簡單介紹。html
提起國際化時,也經常牽扯出幾個類似的術語:前端
internationalization
,因首尾字母間有 18 個字母,簡稱爲 i18n
;指的是將軟件與特定語言及地區脫鉤的過程。當軟件被移植到不一樣的語言及地區時,軟件自己不用作內部工程上的改變或修正localization
,因爲一樣的緣由被簡稱爲 l10n
;是指爲特定區域翻譯文件,併爲了使軟件能在該特定語言環境或地區使用,而應用特殊佈局、加入本地特殊化部件等的過程globalization
,有時會用來表示以上二者的合稱;也會簡稱爲 g11n
簡單來講:國際化搭臺、本地化唱戲。國際化意味着產品有適用於任何地方的「潛力」;本地化則是爲了更適合於「特定」地方的使用,而另外增添的特點。用一項產品來講,國際化只需作一次,但本地化則要針對不一樣的區域各作一次。二者之間是互補的,而且合起來才能讓一個系統適用於各地。vue
一個通過國際化的軟件具有以下特徵:java
除了以上提到的貨幣和日期,還有不少元素與文化、地域、語言相關,好比:jquery
除了 i18n
、l10n
、g11n
等這些奇怪的叫法,咱們平常也常常見到 locale
這個詞,其取值通常爲 zh-CN
、en-US
等。android
一個
locale
對象就是個結合了語言和地區的特殊標識符git
locale
由"互聯網工程任務組"(IETF)的「BCP 47」 文檔系列(tools.ietf.org/html/bcp47) 定義。github
其 常見的典型形式 由分別表示語言和地區的兩部分組成,用 -
鏈接;也能夠省略地區。
舉例來講:
locale code | 一般的含義 |
---|---|
en | 英語 |
en-US | 美國講的英語 |
en-GB | 英國講的英語 |
zh-CN | 簡體中文 |
zh-HK | 香港地區繁體中文 |
zh-TW | 臺灣地區繁體中文 |
zh-SG | 新加坡簡體中文 |
zh-MO | 澳門地區繁體中文 |
es-AR | 阿根廷講的西班牙語 |
ar-001 | 通用阿拉伯語 |
ar-AE | 阿聯酋講的阿拉伯語 |
通常來講,根據 locale 的設置,把一個 hello
分別翻譯成 「こんにちは」 或 「你好」 就能夠了;但涉及數字、日期、貨幣等,須要特殊的格式,或計算年號等,能夠用一些專用的類來處理。
若是代碼的行爲取決於 locale,則說它是「區域敏感的(locale-sensitive)」
好比,Java 中的 NumberFormat 類就是區域敏感的;其數字返回值的格式取決於 locale
:可能爲 902 300
(法國),或 902.300
(德國),又或者 902,300
(美國)。
固然,相似的類如今也出如今了 JS 中,相關內容會在後面說起。
須要注意的是,locale
就只是個標識符,而識別單詞邊界、格式化等具體的工做,仍是都須要由區域敏感的代碼模塊來完成的。
locale 的前半部分表示語言,一般由 2 或 3 位小寫字母組成,符合 ISO 639
(www.loc.gov/standards/i… 標準。
例如:
Language Code | Description |
---|---|
de | German |
en | English |
fr | French |
ru | Russian |
ja | Japanese |
jv | Javanese |
ko | Korean |
zh | Chinese |
locale 的後半部分表示地區,由符合 ISO 3166
(www.chemie.fu-berlin.de/diverse/doc… 標準的 2 或 3 位大寫字母,或符合 UN M.49
標準的 3 位數字組成。這部分是可選的。
例如:
A-2 Code | A-3 Code | Numeric Code | Description |
---|---|---|---|
AU | AUS | 036 | Australia |
BR | BRA | 076 | Brazil |
CA | CAN | 124 | Canada |
CN | CHN | 156 | China |
DE | DEU | 276 | Germany |
FR | FRA | 250 | France |
IN | IND | 356 | India |
RU | RUS | 643 | Russian Federation |
US | USA | 840 | United States |
這部份內容不多用到,權做了解便可,能夠參閱文末的 wiki 連接。
語言標籤由一個或多個子標籤(subtags)組成,用連字號
-
分隔。子標籤只能由基本拉丁字母或數字組成
在 BCP 47 語言標記標準中,完整的子標籤依次爲:
子標籤 | 是否必須 | 是否常見 | 解釋 |
---|---|---|---|
language | 是 | 是 | 見 2.1 |
extended language | 否 | 否 | 最多3個,每一個由3字母組成。實際上尚未使用 |
script | 否 | 通常 | 4個字母,首字母大寫 |
region | 否 | 是 | 見 2.2 |
variant | 否 | 否 | 近一步區分 language 沒法覆蓋的方言;爲5至8個字母,或4字母后跟1個數字 |
extension | 否 | 通常 | 好比以 u 開頭,後跟連字符與2至8個字符組成的文本(),用來表示 unicode,會影響 NumberFormat 等地區敏感的國際化模塊,具體見下方例子 |
private-use | 否 | 否 | 以 x- 前綴開頭的私用語言標籤 |
最基礎的狀況舉例以下:
hi
:印地語de-AT
:在奧地利使用的德語zh-Hans-CN
:在中國使用的中文簡體zh-Hant-HK
:在中國香港使用的中文繁體zh-Hans
:同時包含了 zh-CN
、zh-SG
(新加坡)等使用簡體字的地區而關於 extension 的部分,以這個例子直觀的看一下:
var date = new Date(1945,7,15);
function printDate(locale) {
var dtf = new Intl.DateTimeFormat(
locale,
{era:"short"} //一個 options 對象,還有不少參數可設置
);
console.log( dtf.format(date) );
}
printDate("en"); //8 15, 1945 AD
printDate('ja-JP-u-ca-japanese'); //昭和20年8月15日
printDate('zh-Hant-u-ca-buddhist'); //佛曆2488年8月15日
printDate('zh-Hans-u-nu-thai-ca-japanese'); //昭和๒๐年๘月๑๕日
printDate("zh-Hans-u-nu-fullwide-ca-persian"); //波斯歷1324年5月24日
printDate('zh-T' + 'W-u-ca-r' + 'oc'); //這個能夠本身試一下~
複製代碼
簡單說其形式就是 u-
後面自由組合 nu-
和 ca-
兩個子部分,分別表示 number 和 calendar 聽從何種語言。具體可用值能夠查看 MDN 上 Intl.DateTimeFormat 的頁面。
如今作 web 前端開發,隨着工具、框架的完善和標準的普及,已經較少出現「中文亂碼」的狀況,但這曾經是先後端都常常遇到的狀況;這時就要了解字符編碼的問題,在一些須要國際化的狀況下也要作相應的轉換或聲明。
計算機在設計之初,並無考慮多個國家、多種不一樣語言的應用場景。當時定義一種 ASCII
碼,將字母、數字和其餘符號編號用 7 比特的二進制數來表示。後來,計算機在世界範圍內開始普及,爲了適應多種文字,出現了多種編碼格式,例如中文漢字通常使用的編碼格式爲 GB2312
、GBK
。
由此,又產生了一個問題,不一樣字符編碼之間互相沒法識別。因而出現了 Unicode
編碼。它爲每種語言的每一個字符設定了統一而且惟一的二進制編碼。
但 Unicode
也有一個缺點:爲了支持全部語言的字符,因此它須要用更多位數去表示,好比 ASCII
表示一個英文字符只須要一個字節,而 Unicode
則須要兩個字節。很明顯,字符數多,效率會很低。
爲了解決這個問題,由出現了一些中間格式的字符編碼:如常見的 UTF-8
,以及 UTF-16
、UTF-32
等。
發黃的相片
古老的信
以及褪色的聖誕卡
年輕時爲你寫的...Java 代碼裏
已經有了成熟的 i18n 方案
複製代碼
之因此要在這裏提一下「古舊的後端語言」 -- Java 中的國際化,是由於其解決方案對後來的 jQuery、Vue.js 等工具/框架的國際化方案,以及新的 ECMAScript Internationalization API 都產生了深入的影響。
Java 將不一樣語言的文本存儲在後綴爲
.properties
的文件中,其格式爲
<資源名>_<語言代碼>_<國家/地區編碼>.properties
其中,語言編碼和國家/地區編碼都是可選的;以 <資源名>.properties
命名的國際化資源文件是默認的資源文件。
注意這裏是用 _
作的鏈接,和標準中規定的 -
稍有不一樣。
# MessagesBundle.properties
greetings = Hello.
farewell = Goodbye.
inquiry = How are you?
複製代碼
# MessagesBundle_zh_CN.properties
greetings = 嗨!
farewell = 再見!
inquiry = 吃了沒?
複製代碼
注:若是是用 ASCII 編碼的中文資源文件,還須要用 native2ascii 轉成 Unicode 編碼
在 Java 中,用一個 java.util.Locale
對象選擇區域設置;而 java.util.ResourceBoundle
則用於加載本地化資源文件。
import java.util.Locale;
import java.util.ResourceBundle;
public class I18NSample {
static public void main(String[] args) {
String language;
String country;
if (args.length != 2) {
language = new String("en");
country = new String("US");
} else {
language = new String(args[0]);
country = new String(args[1]);
}
Locale currentLocale;
ResourceBundle messages;
currentLocale = new Locale(language, country);
messages = ResourceBundle.getBundle("MessagesBundle",currentLocale);
System.out.println(messages.getString("greetings"));
System.out.println(messages.getString("inquiry"));
System.out.println(messages.getString("farewell"));
System.out.println("-----");
}
}
複製代碼
這段程序簡單易懂,根據運行參數選擇不一樣的語言包,並從中取出相應字段。
javac I18NSample.java
java I18NSample
//Hello.
//How are you?
//Goodbye.
java I18NSample zh CN
//嗨!
//吃了沒?
//再見!
複製代碼
Java 中也提供了幾個區域敏感的國際化格式工具類。例如:NumberFormat
、DateFormat
、MessageFormat
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.Date;
import java.util.GregorianCalendar;
import java.text.NumberFormat;
import java.text.DateFormat;
import java.text.MessageFormat;
public class I18NSample2 {
static public void main(String[] args) {
double num = 123456.78;
NumberFormat format = NumberFormat.getCurrencyInstance(Locale.SIMPLIFIED_CHINESE);
System.out.format("%f 的本地化(%s)結果: %s\n", num, Locale.SIMPLIFIED_CHINESE, format.format(num));
// 123456.780000 的本地化(zh_CN)結果: ¥123,456.78
Date date = new Date();
DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.JAPANESE);
System.out.format("%s 的本地化(%s)結果: %s\n", date, Locale.JAPANESE, df.format(date));
// Wed Oct 10 23:25:33 CST 2018 的本地化(ja)結果: 2018/10/10
DateFormat df2 = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.SIMPLIFIED_CHINESE);
System.out.format("%s 的本地化(%s)結果: %s\n", date, Locale.SIMPLIFIED_CHINESE, df2.format(date));
// Wed Oct 10 23:25:33 CST 2018 的本地化(zh_CN)結果: 2018年10月10日
Object[] params = {"Jack", new GregorianCalendar().getTime(), 8888};
String pattern1 = "{0},你好!你於 {1} 消費 {2} 元。";
String msg1 = MessageFormat.format(pattern1, params);
System.out.println(msg1);
// Jack,你好!你於 2018/10/10 下午11:25 消費 8,888 元。
String pattern2 = "At {1,time,short} On {1,date,long},{0} paid {2,number, currency}.";
MessageFormat mf = new MessageFormat(pattern2, Locale.US);
String msg2 = mf.format(params);
System.out.println(msg2);
// At 11:25 PM On October 10, 2018,Jack paid $8,888.00.
}
}
複製代碼
格里高利曆(Gregorian calendar)是公曆的標準名稱,是一種源自於西方社會的歷法。由於由教皇格里高利十三世於 1582 年頒佈而得名。而公元即「公曆紀元」,又稱「西元」。 -- 百度百科
首先 GregorianCalendar 這個類的命名解釋如上,固然寫國際化代碼時聽聽英國那個同名的 Gregorian 教皇合唱團也是極好的~
其次,在 MessageFormat 中,咱們在消息模版字符串見到了一些佔位符。其規則大體爲:
{ ArgumentIndex , FormatType , FormatStyle }
,後二者可選FormatType
能夠是 number、date、time 等FormatStyle
能夠是 short、medium、long、full、integer、currency、percent 等做爲早期推進 web 前端開發的最主要工具,jQuery 是一個時代當仁不讓的開發標配。
在處理國際化需求時,jQuery 自己並不包含相關模塊,應用比較普遍的一個第三方輕量化插件是 jQuery.i18n.properties
。
jQuery.i18n.properties
的特色jQuery.i18n.properties
採用 .properties
文件對 JavaScript 代碼進行國際化。要在 Java 程序和前端 JavaScript 程序中共享資源文件時,這種方式也提供了便利。<資源名>_<語言代碼>-<國家/地區編碼>.properties
,其中語言和區域之間採用 -
鏈接,目的是爲了兼容瀏覽器由 navigator.language
或 jQuery.i18n.browserLang()
得到的 locale 值jQuery.i18n.properties({
name:'strings',// 資源文件名稱
path:'bundle/',// 資源文件所在目錄路徑
mode:'both',// 模式:變量或 Map
language:'pt_PT',// 對應的語言
cache:false, //瀏覽器是否對資源文件進行緩存
encoding: 'UTF-8', //編碼。默認爲 UTF-8
callback: function() {// 回調方法
}
});
複製代碼
在 callback 回調中,能夠調用·jQuery.i18n.prop(key)
方法。該方法以 map 的方式使用資源文件中的值,其中 key 指的是資源文件中的 key。當 key 指定的值含有佔位符時,可使用 jQuery.i18n.prop(key,var1,var2 … )
的形式,其中 var1,var2 … 對各佔位符依次進行替換。
隨着 Angular、React、Vue.js 具備指標性的開發工具相繼問世,這三駕馬車把先後端分離也帶入了新的時代,更多的開發邏輯從後端讓渡到前端來,相應的國際化需求也更爲常見,對其靈活性易用性的要求也更高。
此處以 Vue.js 爲例分析其常見的 i18n 解決方案。
vue-i18n
(kazupon.github.io/vue-i18n/) 是較經常使用的一款 Vue.js 國際化插件,其主要特性包括:
<div id="app">
<p>{{ $t("message.hello") }}</p>
</div>
複製代碼
import Vue from 'vue'
import VueI18n from 'vue-i18n'
// 註冊插件
Vue.use(VueI18n)
// 多語言信息
// 通常能夠單獨模塊化,放到 i18n.js 等文件中
const messages = {
en: {
message: {
hello: 'hello world'
}
},
ja: {
message: {
hello: 'こんにちは、世界'
}
}
}
// 傳入一個 options,建立一個 VueI18n 實例
const i18n = new VueI18n({
locale: 'ja', // 區域設置
messages,
})
// 在 Vue 的實例化選項中傳入 i18n
new Vue({ i18n }).$mount('#app')
複製代碼
輸出:
<p>こんにちは、世界</p>
複製代碼
可見,Vue.js 中再也不依賴於 .properties
資源文件,而是用 JS 自身的對象或模塊化解決。
除了簡單的鍵值對,Vue.js 還支持多種靈活的語法:
{
"en": { // 英語
"key1": "this is message1", // 基礎的鍵值對
"nested": { // 嵌套的
"message1": "this is nested message1"
},
"errors": [ // 數組,裏面能夠放各類值
"this is 0 error code message",
{"internal1": "this is internal 1 error message"},
["this is nested array error 1"]
],
//經常使用詞語能夠用來拼接
"the_world": 'the world',
"dio": 'DIO:',
"linked": '@:message.dio @:message.the_world !!!!'
},
"ja": { // 日語
// ...
}
}
複製代碼
<p>{{ $t('key1') }}</p>
<p>{{ $t('nested.message1') }}</p>
<p>{{ $t('errors[0]') }}</p>
<p>{{ $t('errors[1].internal1') }}</p>
<p>{{ $t('errors[2][0]') }}</p>
<p>{{ $t('message.linked') }}</p>
複製代碼
傳統的佔位符形式,此處被稱爲「列表格式化」(List formatting):
<p>{{ $t('message.hello', ['你好','小明']) }}</p>
複製代碼
const messages = {
en: {
message: {
hello: '{0} world, I am {1}'
}
},
zh: {
message: {
hello: '我是{1}, 世界,{0}!我是{1}!'
}
}
}
const i18n = new VueI18n({
locale: 'zh',
messages
})
複製代碼
輸出:
<p>我是小明, 世界,你好!我是小明!</p>
複製代碼
也支持傳入鍵值的形式,稱爲「命名格式化」(Named formatting):
<p>{{ $t('message.goodbye', {day: "明天"}) }}</p>
複製代碼
const messages = {
zh: {
message: {
hello: '我是{1}, 世界,{0}!我是{1}!',
goodbye: '再見!{day}見!'
}
}
}
複製代碼
輸出:
<p>再見!明天見!</p>
複製代碼
<p>{{ $d(new Date(1945,7,15)) }}</p>
<p>{{ $d(new Date(1945,7,15), 'config2', 'ja-JP') }}</p>
複製代碼
const dateTimeFormats = {
'en-US': {
},
'ja-JP': {
config1: {
year: 'numeric', month: 'short', day: 'numeric'
},
config2: {
year: 'numeric', month: 'short', day: 'numeric',
weekday: 'short', hour: 'numeric', minute: 'numeric', hour12: true,
era: 'short'
}
}
}
const messages = {
en: {
},
ja: {
}
}
const i18n = new VueI18n({
locale: 'ja', // 區域設置
messages,
dateTimeFormats // 多傳入一個日期時間格式
})
複製代碼
輸出:
<p>1945/8/15</p>
<p>西暦1945年8月15日(水) 午前0:00</p>
複製代碼
語法如代碼所示,具體配置項可查看 www.ecma-international.org/ecma-402/2.…
<p>{{ $n(100, 'currency') }}</p>
<p>{{ $n(100, 'currency', 'ja-JP') }}</p>
複製代碼
const numberFormats = {
'en-US': {
currency: {
style: 'currency', currency: 'USD'
}
},
'ja-JP': {
currency: {
style: 'currency', currency: 'JPY', currencyDisplay: 'symbol'
}
}
}
const messages = {
en: {
},
ja: {
}
}
const i18n = new VueI18n({
locale: 'ja', // 區域設置
messages,
numberFormats // 多傳入一個數字格式
})
複製代碼
輸出:
<p>$100.00</p>
<p>¥100</p>
複製代碼
語法一樣不難理解,具體配置項可查看 developer.mozilla.org/en-US/docs/…
除了在 Vue 的根實例中做爲構造器參數傳入國際化設置外,也能夠對單獨的 Vue 組件中設置 i18n,其做用域限於組件自己,和全局國際化重複的字段會優先顯示。
<div id="app">
<p>{{ $t("message.hello") }}</p>
<component1></component1>
</div>
複製代碼
// Vue 根實例和全局的`i18n`
const i18n = new VueI18n({
locale: 'ja',
messages: {
en: {
message: {
hello: 'hello world',
greeting: 'good morning'
}
},
ja: {
message: {
hello: 'こんにちは、世界',
greeting: 'おはようございます'
}
}
}
})
// 定義組件
const Component1 = {
template: ` <div class="container"> <p>Component1 locale messages: {{ $t("message.hello") }}</p> <p>Fallback global locale messages: {{ $t("message.greeting") }}</p> </div>`,
i18n: { // 局部的 `i18n`
messages: {
en: { message: { hello: 'hello component1' } },
ja: { message: { hello: 'こんにちは、component1' } }
}
}
}
new Vue({
i18n,
components: {
Component1
}
}).$mount('#app')
複製代碼
輸出:
<div id="app">
<p>こんにちは、世界</p>
<div class="container">
<p>Component1 locale messages: こんにちは、component1</p>
<p>Fallback global locale messages: おはようございます</p>
</div>
</div>
複製代碼
有時會有 操做失敗,請參考<a href="{1}">{0}</a>頁面
或 總金額:<em>{0}</em>元
這類本地化信息。
直觀的方法多是將其做爲幾段,避開 html 部分進行拼接;或是利用 v-html="$t('xxx')"
將其做爲總體注入。
但以上兩種措施,要麼繁瑣而不優雅,要麼會帶來 XSS 風險。
在 vue-i18n 中,組件插值(Component interpolation)能夠較好的解決此類問題:
<div id="app">
<i18n path="info" tag="p">
<span place="limit"> <!--注意 place 屬性的應用-->
{{ changeLimit }}</span>
<a place="action" :href="changeUrl">
{{ $t('change') }}</a>
</i18n>
</div>
複製代碼
const messages = {
en: {
info: 'You can {action} until {limit} minutes from departure.',
change: 'change your flight'
}
}
const i18n = new VueI18n({
locale: 'en',
messages,
})
new Vue({
i18n,
data: {
changeUrl: '/change',
changeLimit: 15
}
}).$mount('#app')
複製代碼
輸出:
<div id="app">
<p>
You can
<a place="action" href="/change">change your flight</a>
until
<span place="limit">15</span>
minutes from departure.
</p>
</div>
複製代碼
i18next
另外一個優秀的 Vue.js 國際化插件是 i18next
(github.com/i18next/i18…) ,本文再也不展開介紹
在 2012 年底,ECMA International 推出了 Standard ECMA-402 標準的首個版本,也就是廣爲人知的 ECMAScript Internationalization API。這個標準彌補了 ECMAScript 中早該支持的本地化方法。基本上全部現代瀏覽器都已經支持該 API。
Intl
全局對象Intl
對象是 ECMAScript 國際化 API 的一個命名空間,它提供了精確的字符串對比,數字格式化,日期和時間格式化。Collator
,NumberFormat
和 DateTimeFormat
對象的構造函數是 Intl
對象的屬性。
Intl.Collator
collators的構造函數,用於啓用對語言敏感的字符串比較的對象。Intl.DateTimeFormat
用於啓用語言敏感的日期和時間格式的對象的構造函數。Intl.NumberFormat
用於啓用語言敏感數字格式的對象的構造函數。Intl.PluralRules
用於啓用多種敏感格式和多種語言語言規則的對象的構造函數。正如咱們在 2.3 中已經見過的 DateTimeFormat
例子,這幾種構造函數都使用一樣的模式來識別語言區域和肯定使用哪種語言格式:它們都接收 locales 和 options 參數。
options
參數必須是一個對象,其屬性值在不一樣的構造函數和方法中會有所變化。若是 options
參數未提供或者爲 undefined
,全部的屬性值則使用默認的。
再看幾個簡單的例子:
var number = 123456.789;
// 德語使用逗號做爲小數點,使用.做爲千位分隔符
console.log(new Intl.NumberFormat('de-DE').format(number));
// → 123.456,789
// 經過編號系統中的nu擴展鍵請求, 例如中文十進制數字
console.log(new Intl.NumberFormat('zh-Hans-CN-u-nu-hanidec').format(number));
// → 一二三,四五六.七八九
// 德語中, ä 使用 a 的排序
console.log(new Intl.Collator('de').compare('ä', 'z'));
// → -1
// 瑞典語中, ä 在 z 的後面
console.log(new Intl.Collator('sv').compare('ä', 'z'));
// → 1
var date = new Date(Date.UTC(2012, 11, 20, 3, 0, 0));
// 使用24小時制
options = {
year: 'numeric', month: 'numeric', day: 'numeric',
hour: 'numeric', minute: 'numeric', second: 'numeric',
hour12: false
};
console.log(date.toLocaleString('en-US', options));
// → "12/19/2012, 19:00:00"
複製代碼
intl.js
polyfill對於一些沒有原生 Intl API 的老舊瀏覽器,可使用 github.com/andyearnsha… 達到大部分功能的支持。
在新的 API 出現以前,計算農曆是一件困難的事情,且有最大年份限制;採用新的 API 能夠很好的解決這個問題。
如下方法改良自 jsfiddle.net/DerekL/mGXK…
function getLunarDate(date) {
const TIAN_GAN = ["甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"];
const DI_ZHI = ["子", "醜", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"];
const SHI = ["初", "十", "廿", "三"];
const YUE = ["", "十"];
const GE = ["一", "二", "三", "四", "五", "六", "七", "八", "九", "十"];
const locale = "zh-TW-u-ca-chinese";
const fmt = (key, d=null)=>{
return Intl.DateTimeFormat(locale,{[key]:"numeric"}).format(d||date).match(/\d+/)[0];
};
const isLeapMonth = (d)=>{
let _date = new Date(date);
_date.setDate(-d);
return fmt("month", _date) === m;
};
let y = fmt("year");
let m = fmt("month");
let d = fmt("day");
isL = isLeapMonth(d);
y = TIAN_GAN[(y - 1) % 10]
+ DI_ZHI[(y - 1) % 12];
m = (YUE[(m - 1) / 10 | 0]
+ GE[(m - 1) % 10]).replace(/^一$/, "正");
d = (SHI[(d) / 10 | 0]
+ GE[(d - 1) % 10]).replace(/^十十$/, "初十").replace(/^廿十$/, "二十");
return y + "年" + (isL ? "閏" : "") + m + "月" + d;
}
var date = new Date(1945,7,15);
var lunar = getLunarDate(date);
console.log(lunar); //乙酉年七月初八
複製代碼