cejs
Version:
A JavaScript module framework that is simple to use.
1,614 lines (1,420 loc) • 84 kB
JavaScript
/**
* @name CeL function for locale / i18n (Internationalization, ja:地域化) 系列
* @fileoverview 本檔案包含了地區語系/文化設定的 functions。
* @since
* @see http://blog.miniasp.com/post/2010/12/24/Search-and-Download-International-Terminology-Microsoft-Language-Portal.aspx
* http://www.microsoft.com/language/zh-tw/default.aspx Microsoft | 語言入口網站
*/
'use strict';
// 'use asm';
// --------------------------------------------------------------------------------------------
// 不採用 if 陳述式,可以避免 Eclipse JSDoc 與 format 多縮排一層。
typeof CeL === 'function' && CeL.run({
// module name
name : 'application.locale',
// data.numeral.to_Chinese_numeral|data.numeral.to_positional_Chinese_numeral|data.numeral.to_English_numeral
require : 'data.numeral.to_Chinese_numeral'
//
+ '|data.numeral.to_positional_Chinese_numeral',
// 設定不匯出的子函式。
// no_extend : '*',
// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
code : module_code
});
function module_code(library_namespace) {
var module_name = this.id,
// requiring
to_Chinese_numeral = this.r('to_Chinese_numeral'), to_positional_Chinese_numeral = this
.r('to_positional_Chinese_numeral');
/**
* null module constructor
*
* @class locale 的 functions
*/
var _// JSDT:_module_
= function() {
// null module constructor
};
/**
* for JSDT: 有 prototype 才會將之當作 Class
*/
_// JSDT:_module_
.prototype = {};
/**
* <code>
<a href="http://www.ietf.org/rfc/bcp/bcp47.txt" accessdate="2012/8/22 15:23" title="BCP 47: Tags for Identifying Languages">BCP 47</a> language tag
http://www.whatwg.org/specs/web-apps/current-work/#the-lang-and-xml:lang-attributes
The lang attribute (in no namespace) specifies the primary language for the element's contents and for any of the element's attributes that contain text. Its value must be a valid BCP 47 language tag, or the empty string.
<a href="http://www.w3.org/International/articles/language-tags/" accessdate="2012/9/23 13:29">Language tags in HTML and XML</a>
language-extlang-script-region-variant-extension-privateuse
http://www.cnblogs.com/sink_cup/archive/2011/04/15/written_language_and_spoken_language.html
http://zh.wikipedia.org/wiki/%E6%B1%89%E8%AF%AD
<a href="http://en.wikipedia.org/wiki/IETF_language_tag" accessdate="2012/8/22 15:25">IETF language tag</a>
TODO:
en-X-US
</code>
*/
function language_tag(tag) {
return language_tag.parse.call(this, tag);
}
// 3_language[-3_extlang][-3_extlang][-4_script][-2w|3d_region]
language_tag.language_RegExp = /^(?:(?:([a-z]{2,3})(?:-([a-z]{4,8}|[a-z]{3}(?:-[a-z]{3}){0,1}))?))(?:-([a-z]{4}))?(?:-([a-z]{2}|\d{3}))?((?:-(?:[a-z\d]{2,8}))*)$/;
// x-fragment[-fragment]..
language_tag.privateuse_RegExp = /^x((?:-(?:[a-z\d]{1,8}))+)$/;
// 片段
language_tag.privateuse_fragment_RegExp = /-([a-z\d]{1,8})/g;
language_tag.parse = function(tag) {
this.tag = tag;
// language tags and their subtags, including private use and
// extensions, are to be treated as case insensitive
tag = String(tag).toLowerCase();
var i = 1, match = language_tag.language_RegExp.exec(tag);
if (match) {
library_namespace.debug(match.join('<br />'), 3,
'language_tag.parse');
// 3_language[-3_extlang][-3_extlang][-4_script][-2w|3d_region]
// <a href="http://en.wikipedia.org/wiki/ISO_639-3"
// accessdate="2012/9/22 17:5">ISO 639-3 codes</a>
// list: <a href="http://en.wikipedia.org/wiki/ISO_639:a"
// accessdate="2012/9/22 16:56">ISO 639:a</a>
// 國際語種代號標準。
this.language = match[i++];
// TODO: 查表對照轉換, fill this.language
this.extlang = match[i++];
/**
* @see <a
* href="http://en.wikipedia.org/wiki/ISO_15924#List_of_codes"
* accessdate="2012/9/22 16:57">ISO 15924 code</a>
*/
// 書寫文字。match[] 可能是 undefined。
this.script = (match[i++] || '').replace(/^[a-z]/, function($0) {
return $0.toUpperCase();
});
/**
* @see <a
* href="http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements"
* accessdate="2012/9/22 16:58">ISO 3166-1 alpha-2 code</a>
*/
// 國家/地區/區域/領域代碼。match[] 可能是 undefined。
this.region = (match[i++] || '').toUpperCase();
// TODO: variant, extension, privateuse
this.external = match[i++];
if (library_namespace.is_debug(2)) {
for (i in this) {
library_namespace.debug(i + ' : ' + this[i], 2,
'language_tag.parse');
}
}
} else if (match = language_tag.privateuse_RegExp.exec(tag)) {
// x-fragment[-fragment]..
library_namespace.debug('parse privateuse subtag [' + tag + ']', 2,
'language_tag.parse');
tag = match[1];
this.privateuse = i = [];
// reset 'g' flag
language_tag.privateuse_fragment_RegExp.exec('');
while (match = language_tag.privateuse_fragment_RegExp.exec(tag)) {
i.push(match[1]);
}
library_namespace.debug('privateuse subtag: ' + i, 2,
'language_tag.parse');
} else if (library_namespace.is_debug()) {
library_namespace.warn('unrecognized language tag: [' + tag + ']');
}
return this;
};
// 查表對照轉換。
language_tag.convert = function() {
// TODO
throw new Error('language_tag.convert: '
// gettext_config:{"id":"not-yet-implemented"}
+ gettext('Not Yet Implemented!'));
};
/**
* <code>
new language_tag('cmn-Hant-TW');
new language_tag('zh-cmn-Hant-TW');
new language_tag('zh-Hant-TW');
new language_tag('zh-TW');
new language_tag('cmn-Hant');
new language_tag('zh-Hant');
new language_tag('x-CJK').language;
new language_tag('zh-Hant').language;
</code>
*/
// 語系代碼,應使用 language_tag.language_code(region) 的方法。
// 主要的應該放後面。
// mapping: region code (ISO 3166) → default language code (ISO 639)
// https://en.wikipedia.org/wiki/Template:ISO_639_name
language_tag.LANGUAGE_CODE = {
// 中文
ZH : 'zh',
// http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry
// Preferred-Value: cmn
CN : 'cmn-Hans',
// cmn-Hant-HK, yue-Hant-HK
HK : 'cmn-Hant',
TW : 'cmn-Hant',
// ja-JP
JP : 'ja',
// ko-KR
KR : 'ko',
// en-GB
GB : 'en',
// There is no "en-UK" language code although it is often used on web
// pages. http://microformats.org/wiki/en-uk
// https://moz.com/community/q/uk-and-gb-when-selecting-targeted-engines-in-campaign-management
UK : 'en',
// en-US
US : 'en',
FR : 'fr',
DE : 'de',
// ru-RU
RU : 'ru',
// arb-Arab
Arab : 'arb'
};
/**
* Get the default language code of region.
*
* @param {String}region
* region code (ISO 3166)
* @returns {String} language code (ISO 639)
*/
language_tag.language_code = function(region, regular_only) {
var code = language_tag.LANGUAGE_CODE[language_tag.region_code(region)];
if (!code
// identity alias
&& !language_tag.LANGUAGE_CODE[code = region.toLowerCase()]) {
if (library_namespace.is_debug())
library_namespace
.warn('language_tag.language_code: 無法辨識之國家/區域:['
+ region + ']');
if (regular_only)
return;
}
return code;
}
// mapping: region name → region code (ISO 3166)
// https://en.wikipedia.org/wiki/ISO_3166-1
// language_tag.region_code() 會自動測試添加"國"字,因此不用省略這個字。
language_tag.REGION_CODE = {
臺 : 'TW',
臺灣 : 'TW',
台 : 'TW',
台灣 : 'TW',
// for language_tag.LANGUAGE_CODE
中文 : 'ZH',
陸 : 'CN',
大陸 : 'CN',
中國 : 'CN',
中國大陸 : 'CN',
jpn : 'JP',
日 : 'JP',
日本 : 'JP',
港 : 'HK',
香港 : 'HK',
韓國 : 'KR',
英國 : 'GB',
美國 : 'US',
法國 : 'FR',
德國 : 'DE',
俄國 : 'RU',
俄羅斯 : 'RU',
阿拉伯 : 'Arab'
};
// reverse
(function() {
for ( var region_code in language_tag.REGION_CODE) {
if ((region_code = language_tag.REGION_CODE[region_code])
// identity alias: REGION_CODE[TW] = 'TW'
&& !language_tag.REGION_CODE[region_code])
language_tag.REGION_CODE[region_code] = region_code;
}
for ( var language_code in language_tag.LANGUAGE_CODE) {
// reversed alias
// e.g., ja → JP
// e.g., cmn-hans → CN
language_tag.REGION_CODE[language_tag.LANGUAGE_CODE[language_code]
.toLowerCase()] = language_code;
}
// 因為下面的操作會改變 language_tag.LANGUAGE_CODE,因此不能與上面的同時操作。
for ( var language_code in language_tag.LANGUAGE_CODE) {
if ((language_code = language_tag.LANGUAGE_CODE[language_code])
// identity alias
&& !language_tag.LANGUAGE_CODE[language_code])
language_tag.LANGUAGE_CODE[language_code] = language_code;
}
})();
/**
* Get the default region code of region.
*
* @param {String}region
* region name
* @returns {String} region code (ISO 3166)
*/
language_tag.region_code = function(region, regular_only) {
var code = language_tag.REGION_CODE[region];
if (!code) {
library_namespace.debug('嘗試解析 [' + region + ']。', 3,
'language_tag.region_code');
if (/^[a-z\-]+$/i.test(region)) {
library_namespace.debug('嘗試 reversed alias 的部分。', 3,
'language_tag.region_code');
// language_code → region_code
// e.g., cmn-Hant → search cmn-hant
code = language_tag.REGION_CODE[region.toLowerCase()];
} else if (code = region.match(/^(.+)[語文]$/)) {
code = language_tag.REGION_CODE[code[1]]
// e.g., 英語 → search 英國
|| language_tag.REGION_CODE[code[1] + '國'];
} else {
// e.g., 英 → search 英國
code = language_tag.REGION_CODE[region + '國'];
}
if (!code && (code = region.match(/-([a-z]{2,3})$/)))
// e.g., zh-tw → search TW
code = language_tag.REGION_CODE[code[1].toUpperCase()];
if (!code
// identity alias
&& !language_tag.REGION_CODE[code = region.toUpperCase()]) {
// 依舊無法成功。
if (library_namespace.is_debug())
library_namespace
.warn('language_tag.region_code: 無法辨識之國家/區域:['
+ region + ']');
if (regular_only)
return;
}
}
return code;
}
_// JSDT:_module_
.language_tag = language_tag;
// -----------------------------------------------------------------------------------------------------------------
// 各個 domain 結尾標點符號的轉換。
var halfwidth_to_fullwidth_mapping = {
'.' : '。'
}, fullwidth_to_halfwidth_mapping = {
'、' : ',',
'。' : '.'
};
var PATTERN_language_code_is_CJK = /^(?:cmn|yue|ja)-/;
function convert_punctuation_mark(punctuation_mark, domain_name) {
if (!punctuation_mark)
return punctuation_mark;
// test domains_using_fullwidth_form
if (PATTERN_language_code_is_CJK.test(domain_name)) {
// 東亞標點符號。
if (punctuation_mark in halfwidth_to_fullwidth_mapping) {
return halfwidth_to_fullwidth_mapping[punctuation_mark];
}
if (/^ *\.{3,} *$/.test(punctuation_mark)) {
// 中文預設標點符號前後無空白。
punctuation_mark = punctuation_mark.trim();
return '…'.repeat(punctuation_mark.length > 6 ? Math
.ceil(punctuation_mark.length / 3) : 2);
}
if (/^ja-/.test(domain_name) && punctuation_mark === ',') {
return '、';
}
if (punctuation_mark.length === 1) {
// https://en.wikipedia.org/wiki/Halfwidth_and_Fullwidth_Forms_(Unicode_block)
var char_code = punctuation_mark.charCodeAt(0);
if (char_code < 0xff) {
return String.fromCharCode(char_code + 0xfee0);
}
}
} else if (/^[^\x20-\xfe]/.test(punctuation_mark)) {
if (punctuation_mark in fullwidth_to_halfwidth_mapping) {
return fullwidth_to_halfwidth_mapping[punctuation_mark];
}
if (/^…+$/.test(punctuation_mark)) {
return punctuation_mark.length > 2 ? '...'
.repeat(punctuation_mark.length) : '...';
}
if (punctuation_mark.length === 1) {
var char_code = punctuation_mark.charCodeAt(0);
if (char_code > 0xfee0) {
return String.fromCharCode(char_code - 0xfee0);
}
}
}
if (punctuation_mark.length > 1) {
// PATTERN_punctuation_marks
return punctuation_mark.replace(/%(\d)|(:)\s*|./g, function(p_m,
NO, p_m_with_spaces) {
if (NO)
return p_m;
if (p_m_with_spaces) {
if (!PATTERN_language_code_is_CJK.test(domain_name))
return p_m;
p_m = p_m_with_spaces;
}
return convert_punctuation_mark(p_m, domain_name);
});
}
return punctuation_mark;
}
// -----------------------------------------------------------------------------------------------------------------
var plural_rules__domain_name = 'gettext_plural_rules';
// plural_rules[language_code]
// = [ #plural forms, function(){ return #plural form; } ]
var plural_rules_of_language_code = Object.create(null);
gettext.set_plural_rules = function set_plural_rules(plural_rules_Object) {
for ( var language_code in plural_rules_Object) {
var plural_rule = plural_rules_Object[language_code];
language_code = gettext.to_standard(language_code);
if (language_code) {
plural_rules_of_language_code[language_code] = plural_rule;
}// else: 尚未支援的語言。
}
};
// ------------------------------------
// matched: [ all behavior switch, is NO, NO ]
var PATTERN_plural_switch_header = /\{\{PLURAL: *(%)?(\d+) *\|/,
// matched: [ all behavior switch, previous, is NO, NO, parameters ]
PATTERN_plural_switches_global = new RegExp('('
+ PATTERN_plural_switch_header.source + ')'
+ /([\s\S]+?)\}\}/.source, 'ig');
// 處理 {{PLURAL:%1|summary|summaries}}
// 處理 {{PLURAL:$1|1=you|$1 users including you}}
// 處理 {{PLURAL:42|42=The answer is 42|Wrong answer|Wrong answers}}
// https://raw.githubusercontent.com/wikimedia/mediawiki-extensions-Translate/master/data/plural-gettext.txt
// https://translatewiki.net/wiki/Plural
// https://docs.transifex.com/formats/gettext#plural-forms-in-a-po-file
function adapt_plural(converted_text, value_list, domain_name) {
var plural_count, plural_rule = plural_rules_of_language_code[domain_name];
if (Array.isArray(plural_rule)) {
plural_count = plural_rule[0];
plural_rule = plural_rule[1];
}
// console.trace([ domain_name, plural_count, plural_rule ]);
converted_text = converted_text.replace_till_stable(
//
PATTERN_plural_switches_global, function(all, _previous, is_NO, NO,
parameters) {
// https://translatewiki.net/wiki/Plural
// And you can nest it freely
// 自 end_mark 向前回溯。
// TODO: using lookbehind search?
var previous = '', nest_matched;
while (nest_matched = parameters
.match(PATTERN_plural_switch_header)) {
previous += _previous
//
+ parameters.slice(0, nest_matched.index);
_previous = nest_matched[0];
is_NO = nest_matched[1];
NO = nest_matched[2];
parameters = parameters.slice(nest_matched.index
+ _previous.length);
}
var value = is_NO ? value_list[NO] : NO;
if (value < 0)
value = -value;
var plural_NO = (typeof plural_rule === 'function'
//
? plural_rule(+value) : plural_rule) + 1;
var converted, default_converted, delta = 1;
parameters = parameters.split('|');
parameters.some(function(parameter, index) {
var matched = parameter.match(/^(\d+)=([\s\S]*)$/);
if (matched) {
delta--;
index = +matched[1];
parameter = matched[2];
if (index == value) {
converted = parameter;
return true;
}
if (!default_converted)
default_converted = parameter;
return;
}
index += delta;
if (plural_NO >= 1) {
if (index === plural_NO) {
converted = parameter;
// Do not return. Incase {{PLURAL:5|one|other|5=5}}
} else if (index === 2 && plural_count !== 2
// Special case. e.g., {{PLURAL:2||s}}
// @ zh(plural_count=1), ru(3), NOT fr(2)
&& value != 1 && parameters.length === 2) {
converted = parameter;
// assert: Should be the last element of parameters.
} else {
default_converted = parameter;
}
return;
}
/**
* https://translatewiki.net/wiki/Plural
*
* If the number of forms written is less than the number of
* forms required by the plural rules of the language, the last
* available form will be used for all missing forms.
*/
default_converted = parameter;
if (index == value) {
converted = parameter;
return true;
}
});
return previous
//
+ (converted === undefined ? default_converted : converted);
});
return converted_text;
}
gettext.adapt_plural = adapt_plural;
// -----------------------------------------------------------------------------------------------------------------
// JavaScript 國際化 i18n (Internationalization) / 在地化 本土化 l10n (Localization)
// / 全球化 g11n (Globalization).
/**
* 為各種不同 domain 轉換文字(句子)、轉成符合當地語言的訊息內容。包括但不僅限於各種語系。<br />
* 需要確認系統相應 domain resources 已載入時,請利用 gettext.use_domain(domain, callback)。
*
* TODO: using localStorage.<br />
* https://translatewiki.net/wiki/Plural
*
* @example <code>
// More examples: see /_test suite/test.js
* </code>
*
* @param {String|Function|Object}text_id
* 欲呼叫之 text id。<br /> ** 若未能取得,將直接使用此值。因此即使使用簡單的代號,也建議使用
* msg#12, msg[12] 之類的表示法,而非直接以整數序號代替。<br />
* 嵌入式的一次性使用,不建議如此作法: { domain : text id }
* @param {String|Function}conversion_list
* other conversion to include
*
* @returns {String}轉換過的文字。
*
* @since 2012/9/9 00:53:52
*
* @see <a
* href="http://stackoverflow.com/questions/48726/best-javascript-i18n-techniques-ajax-dates-times-numbers-currency"
* accessdate="2012/9/9 0:13">Best JavaScript i18n techniques / Ajax -
* dates, times, numbers, currency - Stack Overflow</a>,<br />
* <a
* href="http://stackoverflow.com/questions/3084675/internationalization-in-javascript"
* accessdate="2012/9/9 0:13">Internationalization in Javascript -
* Stack Overflow</a>,<br />
* <a
* href="http://stackoverflow.com/questions/9640630/javascript-i18n-internationalization-frameworks-libraries-for-clientside-use"
* accessdate="2012/9/9 0:13">javascript i18n (internationalization)
* frameworks/libraries for clientside use - Stack Overflow</a>,<br />
* <a href="http://msdn.microsoft.com/en-us/library/txafckwd.aspx"
* accessdate="2012/9/17 23:0">Composite Formatting</a>,
* http://wiki.ecmascript.org/doku.php?id=strawman:string_format,
* http://wiki.ecmascript.org/doku.php?id=strawman:string_format_take_two
*/
function gettext(/* message */text_id/* , ...value_list */) {
// 轉換 / convert function.
function convert(text_id, domain_specified) {
// 未設定個別 domain 者,將以此訊息(text_id)顯示。
// text_id 一般應採用原文(message of original language),
// 或最常用語言;亦可以代碼(message id)表示,但須設定所有可能使用的語言。
// console.log(text_id);
var prefix, postfix;
if (library_namespace.is_debug(12)) {
console.trace(domain);
}
// 注意: 在 text_id 與所屬 domain 之 converted_text 相同的情況下,
// domain 中不會有這一筆記錄。
// 因此無法以 `text_id in domain` 來判別 fallback。
if (typeof text_id === 'function' || typeof text_id === 'object') {
using_default = true;
} else if (!(text_id in domain)) {
var matched = String(text_id).match(
PATTERN_message_with_tail_punctuation_mark);
if (matched && (matched[2] in domain)) {
prefix = matched[1];
postfix = matched[3];
text_id = matched[2];
} else {
using_default = true;
}
}
if (!using_default) {
text_id = domain[text_id];
if (prefix) {
text_id = convert_punctuation_mark(prefix, domain_name)
+ text_id;
}
if (postfix
// 預防翻譯後有結尾標點符號,但原文沒有的情況。但這情況其實應該警示。
// && !PATTERN_message_with_tail_punctuation_mark.test(text_id)
) {
text_id += convert_punctuation_mark(postfix, domain_name);
}
}
return typeof text_id === 'function' ? text_id(domain_name,
value_list, domain_specified) : text_id;
}
function try_domain(_domain_name, recover) {
var original_domain_data = [ domain_name, domain ];
domain_name = _domain_name;
// 在不明環境,如 node.js 中執行時,((gettext_texts[domain_name])) 可能為
// undefined。
domain = gettext_texts[domain_name];
if (!domain) {
if (false) {
// No 強制載入 flag here.
library_namespace.warn({
// gettext_config:{"id":"specified-domain-$1-is-not-yet-loaded.-you-may-need-to-set-the-force-flag"}
T : [ '所指定之 domain [%1] 尚未載入,若有必要請使用強制載入 flag。',
domain_name ]
});
}
domain = Object.create(null);
}
var _text = String(convert(library_namespace.is_Object(text_id) ? text_id[domain_name]
: text_id));
if (recover) {
domain_name = original_domain_data[0];
domain = original_domain_data[1];
}
return _text;
}
var value_list = arguments, length = value_list.length, using_default,
// this: 本次轉換之特殊設定。
domain_name = this && this.domain_name || gettext_domain_name,
//
domain, converted_text = try_domain(domain_name),
// 強制轉換/必須轉換 force convert. e.g., 輸入 id,因此不能以 text_id 顯示。
force_convert = using_default && this && (this.force_convert
// for DOM
|| this.getAttribute && this.getAttribute('force_convert'));
// 設定 force_convert 時,最好先 `gettext.load_domain(force_convert)`
// 以避免最後仍找不到任何一個可用的 domain。
if (force_convert) {
// force_convert: fallback_domain_name_list
if (!Array.isArray(force_convert))
force_convert = force_convert.split(',');
force_convert.some(function(_domain_name) {
_domain_name = gettext.to_standard(_domain_name);
if (!_domain_name || _domain_name === domain_name)
return;
var _text = try_domain(_domain_name, true);
if (!using_default) {
domain_name = _domain_name;
converted_text = _text;
// using the first matched
return true;
}
});
}
library_namespace
.debug('Use domain_name: ' + domain_name, 6, 'gettext');
converted_text = adapt_plural(converted_text, value_list, domain_name);
if (length <= 1) {
// assert: {String}converted_text
return converted_text;
}
var text_list = [], matched, last_index = 0,
// 允許 convert 出的結果為 object。
has_object = false,
// whole conversion specification:
// %% || %index || %domain/index
// || %\w(conversion format specifier)\d{1,2}(index)
// || %[conversion specifications@]index
//
// 警告: index 以 "|" 終結,後接數字會被視為 patten 明確終結,並且 "|" 將被吃掉。
// e.g., gettest("%1|123", 321) === "321123"
// gettest("%1||123", 321) === "321||123"
// TODO: 改成 %{index}, %{var_id}
//
// @see CeL.extract_literals()
//
// 採用 local variable,因為可能有 multithreading 的問題。
conversion_pattern = /([\s\S]*?)%(?:(%)|(?:([^%@\s\|\/]+)\/)?(?:([^%@\s\|\d]{1,3})|([^%@\|]+)@)?(\d{1,2})(\|[\|\d])?)/g;
while (matched = conversion_pattern.exec(converted_text)) {
if (matched[7]) {
// 回吐最後一個 \d
conversion_pattern.lastIndex--;
// conversion_pattern.lastIndex -= matched[7].length
// - '|'.length;
}
last_index = conversion_pattern.lastIndex;
// matched:
// 0: prefix + conversion, 1: prefix, 2: is_escaped "%",
// 3: domain_specified, 4: format, 5: object_name, 6: argument NO,
// 7: "|" + \d.
var conversion = matched[0];
if (matched[2]) {
// e.g., 'prefix%%...'
// assert: matched[3] 之後全都沒東西。
text_list.push(conversion);
continue;
}
var NO = +matched[6], format = matched[4], _matched;
if (NO < length && (!(format || (format = matched[5]))
// 有設定 {String}format 的話,就必須在 gettext.conversion 中。
|| (format in gettext.conversion))) {
if (NO === 0) {
conversion = text_id;
} else {
var domain_specified = matched[3],
//
domain_used = domain_specified
&& gettext_texts[domain_specified];
if (domain_used) {
// 避免 %0 形成 infinite loop。
var origin_domain = domain, origin_domain_name = domain_name;
library_namespace.debug('臨時改變 domain: ' + domain_name
+ '→' + domain_specified, 6, 'gettext');
domain_name = domain_specified;
domain = domain_used;
conversion = convert(value_list[NO], domain_specified);
library_namespace.debug('回存/回復 domain: ' + domain_name
+ '→' + origin_domain_name, 6, 'gettext');
domain_name = origin_domain_name;
domain = origin_domain;
} else {
if (domain_specified) {
library_namespace.warn('gettext: '
+ 'Unknown domain [' + domain_specified
+ ']');
}
conversion = convert(value_list[NO]);
}
}
if (format) {
conversion = Array.isArray(NO = gettext.conversion[format])
//
? gettext_conversion_Array(conversion, NO, format)
// assert: gettext.conversion[format] is function
: NO(conversion, domain_specified || domain_name);
}
} else if (format && matched[3]
// The same index passern as conversion_pattern
&& (_matched = matched[3].match(/(\d{1,2})/))
&& _matched[0] < length) {
// e.g., CeL.gettext('<h1>%1</h1>', 't')
format = null;
NO = _matched[0];
last_index =
// reset.
// assert: last_index === matched[0].length
// last_index - matched[0].length +
//
// 加回這次處理的部分。 1: 前導 '%'.length
1 + matched[1].length + NO.length;
conversion = convert(value_list[NO]);
} else {
library_namespace.warn('gettext: '
//
+ (NO < length ? 'Unknown format [' + format + ']'
//
: 'given too few arguments: ' + length + ' <= No. ' + NO));
}
if (typeof conversion === 'object') {
has_object = true;
text_list.push(matched[1], conversion);
} else {
// String(conversion): for Symbol value
text_list.push(matched[1] + String(conversion));
}
}
text_list.push(converted_text.slice(last_index));
return has_object ? text_list : text_list.join('');
}
var PATTERN_is_punctuation_mark = /^[,;:.?!~、,;:。?!~]$/;
// matched: [ all, header punctuation mark, text_id / message, tail
// punctuation mark ]
var PATTERN_message_with_tail_punctuation_mark = /^(\.{3,}\s*)?([\s\S]+?)(\.{3,}|…+|:\s*(%\d)?|[,;:.?!~、,;:。?!~])$/;
function trim_punctuation_marks(text) {
var matched = text.match(PATTERN_message_with_tail_punctuation_mark);
return matched ? matched[2] : text;
}
_.trim_punctuation_marks = trim_punctuation_marks;
// ------------------------------------------------------------------------
// 應對多個句子在不同語言下結合時使用。
function Sentence_combination(sentence) {
// call super constructor.
// Array.call(this);
var sentence_combination = this;
if (sentence) {
if (Array.isArray(sentence) && sentence.every(function(_sentence) {
return Array.isArray(_sentence);
})) {
// e.g., new CeL.gettext.Sentence_combination(
// [ [ 'message', p1 ], [ 'message' ] ])
sentence_combination.append(sentence);
} else {
// e.g., new CeL.gettext.Sentence_combination(
// [ 'message', p1, p2 ])
sentence_combination.push(sentence);
}
}
}
function deep_convert(text) {
if (!Array.isArray(text)) {
var converted = gettext(text);
if (converted === text && PATTERN_is_punctuation_mark.test(text)) {
// e.g., text === ','
converted = convert_punctuation_mark(text, gettext_domain_name);
}
return converted;
}
// e.g., [ '%1 elapsed.', ['%1 s', 2] ]
var converted = [ text[0] ];
for (var index = 1; index < text.length; index++) {
converted[index] = deep_convert(text[index]);
}
return gettext.apply(null, converted);
}
function Sentence_combination__converting() {
var converted_list = [];
this.forEach(function(sentence) {
sentence = deep_convert(sentence);
if (sentence)
converted_list.push(sentence);
});
return converted_list;
}
// @see CeL.data.count_word()
// 這些標點符號和下一句中間可以不用接空白字元。
// /[\u4e00-\u9fa5]/: 匹配中文字 RegExp。
// https://en.wikipedia.org/wiki/CJK_Unified_Ideographs_(Unicode_block)
// https://arc-tech.hatenablog.com/entry/2021/01/20/105620
// e.g., start quote marks
var PATTERN_no_need_to_append_tail_space = /[\s—、,;:。?!()[]{}「」『』〔〕【】〖〗〈〉《》“”‘’§(\[<{⟨‹«\u4e00-\u9fffぁ-んーァ-ヶ]$/;
// e.g., end quote marks
var PATTERN_no_need_to_add_header_space = /^[\s)\]>}⟩›»)]}」』〕】〗〉》”’‰‱]/;
function Sentence_combination__join(separator) {
// console.trace(this);
var converted_list = this.converting();
if (separator || separator === '')
return converted_list.join(separator);
for (var index = 0; index < converted_list.length;) {
var converted = converted_list[index];
// console.trace([ index, converted ]);
if (!converted
// 要處理首字母大小寫轉換,所以不直接跳出。
// || PATTERN_no_need_to_append_tail_space.test(converted)
) {
++index;
continue;
}
var next_sentence, original_index = index, must_lower_case = /[,;、,;]\s*$/
.test(converted) ? true
: /[.?!。?!]\s*$/.test(converted) ? false : undefined;
while (++index < converted_list.length) {
next_sentence = converted_list[index];
// console.trace([ converted, next_sentence, must_lower_case ]);
// 處理首字母大小寫轉換。
if (next_sentence && typeof must_lower_case === 'boolean') {
var leading_spaces = next_sentence.match(/^\s+/);
if (leading_spaces) {
leading_spaces = leading_spaces[0];
next_sentence = next_sentence
.slice(leading_spaces.length);
}
var first_char = next_sentence.charAt(0);
if (must_lower_case
^ (first_char === first_char.toLowerCase())) {
next_sentence = (must_lower_case ? first_char
.toLowerCase() : first_char.toUpperCase())
+ next_sentence.slice(1);
}
if (leading_spaces) {
next_sentence = leading_spaces + next_sentence;
}
converted_list[index] = next_sentence;
}
// 增加子句間的空格。
// 找出下一個(非空內容的)文字,檢查是否該在本token(converted_list[original_index])結尾加上空白字元。
if (next_sentence || next_sentence === 0) {
if (!PATTERN_no_need_to_append_tail_space.test(converted)
//
&& !PATTERN_no_need_to_add_header_space.test(next_sentence)) {
converted_list[original_index] += ' ';
}
break;
}
}
// console.trace([ index, converted_list[index] ]);
}
converted_list = converted_list.join('');
// TODO: upper-case the first char
return converted_list;
}
// https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Object/create
Sentence_combination.prototype
// 繼承一個父類別
= Object.assign(Object.create(Array.prototype), {
// 重新指定建構式
constructor : Sentence_combination,
converting : Sentence_combination__converting,
join : Sentence_combination__join,
toString : Sentence_combination__join
});
/**
* @example<code>
messages = new gettext.Sentence_combination();
messages.push(message, [ message ], [ message, arg_1, arg_2 ]);
messages.toString();
</code>
*/
gettext.Sentence_combination = Sentence_combination;
function append_message_tail_space(text, options) {
if (!options || typeof options === 'string' || !options.no_more_convert) {
// Treat `options` as an argument to gettext.
text = gettext.apply(null, arguments);
}
if (!text || PATTERN_no_need_to_append_tail_space.test(text)) {
return text;
}
var next_sentence = options && options.next_sentence;
return next_sentence
&& !PATTERN_no_need_to_add_header_space.test(next_sentence)
|| next_sentence === 0 ? text + ' ' : text;
}
gettext.append_message_tail_space = append_message_tail_space;
// ------------------------------------------------------------------------
// 不改變預設domain,直接取得特定domain的轉換過的文字。
// 警告:需要確保系統相應 domain resources 已載入並設定好。
gettext.in_domain = function(domain_name, text_id) {
var options = typeof domain_name === 'object' ? domain_name
//
: typeof domain_name === 'string' ? {
domain_name : gettext.to_standard(domain_name)
} : {
domain : domain_name
};
if (false && Array.isArray(text_id)) {
return gettext.apply(options, text_id);
}
if (arguments.length <= 2) {
// 沒有多餘的參數設定(e.g., %1, %2)。
return gettext.call(options, text_id);
}
var value_list = Array.prototype.slice.call(arguments);
value_list.shift();
return gettext.apply(options, value_list);
};
/**
* 檢查指定資源是否已載入,若已完成,則執行 callback 序列。
*
* @param {String}[domain_name]
* 設定當前使用之 domain name。
* @param {Integer}[type]
* 欲設定已載入/未載入之資源類型。
* @param {Boolean}[is_loaded]
* 設定/登記是否尚未載入之資源類型。
* @returns {Boolean} 此 type 是否已 loaded。
*/
function gettext_check_resources(domain_name, type, is_loaded) {
if (!domain_name)
domain_name = gettext_domain_name;
var domain = gettext_resource[domain_name];
if (!domain)
gettext_resource[domain_name] = domain = Object.create(null);
if (type)
if (type = [ , 'system', 'user' ][type]) {
if (typeof is_loaded === 'boolean') {
library_namespace.debug('登記 [' + domain_name + '] 已經載入資源 ['
+ type + ']。', 2, 'gettext_check_resources');
domain[type] = is_loaded;
}
} else
type = null;
return type ? domain[type] : domain;
}
/**
* 當設定 conversion 為 Array 時,將預設採用此 function。<br />
* 可用在單數複數形式 (plural) 之表示上。
*
* @param {Integer}amount
* 數量。
* @param {Array}conversion
* 用來轉換的 Array。
* @param {String}name
* format name。
*
* @returns {String} 轉換過的文字/句子。
*/
function gettext_conversion_Array(amount, conversion_Array, name) {
var text,
// index used.
// TODO: check if amount < 0 or amount is not integer.
index = amount < conversion_Array.length ? parseInt(amount)
: conversion_Array.length - 1;
if (index < 0) {
library_namespace.debug({
T : [ 'Negative index: %1', index ]
});
index = 1;
} else
while (index >= 0 && !(text = conversion_Array[index]))
index--;
if (!text || typeof text !== 'string') {
library_namespace.warn({
T : [ 'Nothing matched for amount [%1]', amount ]
});
return;
}
if (name)
text = text.replace(/%n/g, name);
return text.replace(/%d/g, amount);
}
/**
* 設定如何載入指定 domain resources,如語系檔。
*
* @param {String|Function}path
* (String) prefix of path to load.<br />
* function(domain){return path to load;}
*/
gettext.use_domain_location = function(path) {
if (typeof path === 'string') {
gettext_domain_location = path;
// 重設 user domain resources path。
gettext_check_resources('', 2, false);
}
return gettext_domain_location;
};
/**
* 取得當前使用之 domain name。
*
* @returns 當前使用之 domain name。
*/
gettext.get_domain_name = function() {
return gettext_domain_name;
};
gettext.is_domain_name = function(domain_name) {
return gettext_domain_name === gettext.to_standard(domain_name);
};
// force: 若 domain name 已經載入過,則再度載入。
function load_domain(domain_name, callback, force) {
var do_not_register = domain_name === plural_rules__domain_name;
if (!domain_name || !do_not_register
&& !(domain_name = gettext.to_standard(domain_name))) {
// using the default domain name.
domain_name = gettext.default_domain;
}
if (!domain_name || domain_name === gettext_domain_name && !force) {
typeof callback === 'function' && callback(domain_name);
return;
}
if (!(domain_name in gettext_texts) && !!do_not_register) {
// initialization
gettext_texts[domain_name] = Object.create(null);
}
var need_to_load = [];
// TODO: use <a href="http://en.wikipedia.org/wiki/JSONP"
// accessdate="2012/9/14 23:50">JSONP</a>
if (!gettext_check_resources(domain_name, 1)) {
library_namespace.debug('準備載入系統相應 domain resources。', 2, 'gettext');
need_to_load.push(library_namespace.get_module_path(module_name,
// resources/
CeL.env.resources_directory_name + '/' + domain_name + '.js'),
//
function() {
if (do_not_register)
return;
library_namespace.debug('Resources of module included.', 2,
'gettext');
gettext_check_resources(domain_name, 1, true);
});
}
if (typeof gettext_domain_location === 'function') {
gettext_domain_location = gettext_domain_location();
}
if (typeof gettext_domain_location === 'string'
//
&& !gettext_check_resources(domain_name, 2)) {
library_namespace.debug('準備載入 user 指定 domain resources,如語系檔。', 2,
'gettext');
need_to_load.push(typeof gettext_domain_location === 'string'
// 因 same-origin policy,採 .js 而非其他 file type 如 .json。
? gettext_domain_location + domain_name + '.js'
: gettext_domain_location(domain_name), function() {
library_namespace.debug('User-defined resources included.', 2,
'gettext');
gettext_check_resources(domain_name, 2, true);
});
}
if (need_to_load.length > 0) {
// console.trace(need_to_load);
library_namespace.debug('need_to_load: ' + need_to_load, 2,
'load_domain');
library_namespace.run(need_to_load, typeof callback === 'function'
&& function() {
library_namespace.debug('Running callback...', 2,
'gettext');
callback(domain_name);
});
} else {
library_namespace.debug('Nothing to load.');
gettext_check_resources(domain_name, 2, true);
}
}
gettext.load_domain = load_domain;
/**
* 取得/設定當前使用之 domain。
*
* @example<code>
// for i18n: define gettext() user domain resources path / location.
// gettext() will auto load (CeL.env.domain_location + language + '.js').
// e.g., resources/cmn-Hant-TW.js, resources/ja-JP.js
CeL.gettext.use_domain_location(module.filename.replace(/[^\\\/]*$/,
'resources' + CeL.env.path_separator));
CeL.gettext.use_domain('GUESS', true);
</code>
*
* @param {String}[domain_name]
* 設定當前使用之 domain name。
* @param {Function}[callback]
* 回撥函式。 callback(domain_name)
* @param {Boolean}[force]
* 強制載入 flag。即使尚未載入此 domain,亦設定之並自動載入。但是若 domain name
* 已經載入過,則不會再度載入。
*
* @returns {Object}當前使用之 domain。
*/
function use_domain(domain_name, callback, force) {
if (typeof callback === 'boolean' && force === undefined) {
// shift 掉 callback。
force = callback;
callback = undefined;
}
if (domain_name === 'GUESS') {
domain_name = guess_language();
}
if (!domain_name) {
domain_name = gettext_texts[gettext_domain_name];
typeof callback === 'function' && callback(domain_name);
// return domain used now.
return domain_name;
}
// 查驗 domain_name 是否已載入。
var is_loaded = domain_name in gettext_texts;
if (!is_loaded) {
is_loaded = gettext.to_standard(domain_name);
if (is_loaded) {
is_loaded = (domain_name = is_loaded) in gettext_texts;
}
}
if (is_loaded) {
gettext_domain_name = domain_name;
library_namespace.debug({
// gettext_config:{"id":"$1-is-loaded-setting-up-user-domain-resources-now"}
T : [ '已載入過 [%1],直接設定使用者自訂資源。', domain_name ]
}, 2, 'gettext.use_domain');
gettext_check_resources(domain_name, 2, true);
typeof callback === 'function' && callback(domain_name);
} else if (force && domain_name) {
if (library_namespace.is_WWW()
&& library_namespace.is_included('interact.DOM')) {
// 顯示使用 domain name 之訊息:此時執行,仍無法改採新 domain 顯示訊息。
library_namespace.debug({
T : [ domain_name === gettext_domain_name
// gettext_config:{"id":"force-loading-using-domain-locale-$2-($1)"}
? '強制再次載入/使用 [%2] (%1) 領域/語系。'
// gettext_config:{"id":"loading-using-domain-locale-$2-($1)"}
: '載入/使用 [%2] (%1) 領域/語系。', domain_name,
gettext.get_alias(domain_name) ]
}, 1, 'gettext.use_domain');
} else {
library_namespace.debug(
// re-load
(domain_name === gettext_domain_name ? 'FORCE ' : '')
+ 'Loading/Using domain/locale ['
+ gettext.get_alias(domain_name) + '] (' + domain_name
+ ').', 1, 'gettext.use_domain');
}
if (!(domain_name in gettext_texts)) {
// 為確保回傳的是最終的domain,先初始化。
gettext_texts[domain_name] = Object.create(null);
}
load_domain(domain_name, function() {
gettext_domain_name = domain_name;
typeof callback === 'function' && callback(domain_name);
});
} else {
if (domain_name) {
if (domain_name !== gettext_domain_name) {
library_namespace.warn({
// gettext_config:{"id":"specified-domain-$1-is-not-yet-loaded.-you-may-need-to-set-the-force-flag"}
T : [ '所指定之 domain [%1] 尚未載入,若有必要請使用強制載入 flag。',
domain_name ]
});
}
} else if (typeof callback === 'function'
&& library_namespace.is_debug())
// gettext_config:{"id":"unable-to-distinguish-domain-but-set-callback"}
library_namespace.warn('無法判別 domain,卻設定有 callback。');
// 無論如何還是執行 callback。
typeof callback === 'function' && callback(domain_name);
}
return gettext_texts[domain_name];
}
// using_domain
gettext.use_domain = use_domain;
function guess_language() {
if (library_namespace.is_WWW()) {
// http://stackoverflow.com/questions/1043339/javascript-for-detecting-browser-language-preference
return gettext.to_standard(navigator.userLanguage
|| navigator.language
// || navigator.languages && navigator.languages[0]
// IE 11
|| navigator.browserLanguage || navigator.systemLanguage);
}
function exec(command, PATTERN, mapping) {
try {
// @see https://gist.github.com/kaizhu256/a4568cb7dac2912fc5ed
// synchronously run system command in nodejs <= 0.10.x
// https://github.com/gvarsanyi/sync-exec/blob/master/js/sync-exec.js
// if (!require('child_process').execSync) { return; }
var code = require('child_process').execSync(command, {
stdio : 'pipe'
}).toString();
// console.trace([ command, code ]);
if (PATTERN)
code = code.match(PATTERN)[1];
if (mapping)
code = mapping[code];
return gettext.to_standard(code);
} catch (e) {
// TODO: handle exception
}
}
// console.trace(library_namespace.platform.is_Windows());
if (library_namespace.platform.is_Windows()) {
// TODO:
// `REG QUERY HKLM\System\CurrentControlSet\Control\Nls\Language /v
// InstallLanguage`
// https://www.lisenet.com/2014/get-windows-system-information-via-wmi-command-line-wmic/
// TODO: `wmic OS get Caption,CSDVersion,OSArchitecture,Version`
// require('os').release()
return exec(
// https://docs.microsoft.com/zh-tw/powershell/module/international/get-winsystemlocale?view=win10-ps
'PowerShell.exe -Command "& {Get-WinSystemLocale | Select-Object LCID}"',
/(\d+)[^\d]*$/, guess_language.LCID_mapping)
// WMIC is deprecated.
// https://stackoverflow.com/questions/1610337/how-can-i-find-the-current-windows-language-from-cmd
// get 非 Unicode 應用程式的語言與系統地區設定所定義的語言
|| exec('WMIC.EXE OS GET CodeSet', /(\d+)[^\d]*$/,
guess_language.code_page_mapping)
// using windows active console code page
// https://docs.microsoft.com/en-us/windows/console/console-code-pages
// CHCP may get 65001, so we do not use this at first.
|| exec('CHCP', /(\d+)[^\d]*$/,
guess_language.code_page_mapping);
}
/**
* <code>
@see https://www.itread01.com/content/1546711411.html
TODO: detect process.env.TZ: node.js 設定測試環境使用
GreenWich時間
process.env.TZ = 'Europe/London';
timezone = {
'Europe/London' : 0,
'Asia/Shanghai' : -8,
'America/New_York' : 5
};
</code>
*/
var LANG = library_namespace.env.LANG;
// e.g., LANG=zh_TW.Big5
// en_US.UTF-8
if (LANG)
return gettext.to_standard(LANG);
return exec('locale', /(?:^|\n)LANG=([^\n]+)/);
}
// https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/chcp
guess_language.code_page_mapping = {
437 : 'en-US',
866 : 'ru-RU',
932 : 'ja-JP',
936 : 'cmn-Hans-CN',
949 : 'ko-KR',
950 : 'cmn-Hant-TW',
1256 : 'arb-Arab',
54936 : 'cmn-Hans-CN'
// 65001: 'Unicode'
};
// https://zh.wikipedia.org/wiki/区域设置#列表
guess_language.LCID_mapping = {
1028 : 'cmn-Hant-TW',
1033 : 'en-US',
1041 : 'ja-JP',
1042 : 'ko-KR',
1049 : 'ru-RU',
2052 : 'cmn-Hans-CN',
2057 : 'en-GB',
3076 : 'cmn-Hant-HK',
14337 : 'arb-Arab'
};
gettext.guess_language = guess_language;
/**
* 設定欲轉換的文字格式。
*
* @param {Object}text_Object
* 文字格式。 {<br />
* text id : text for this domain }<br />
* 函數以回傳文字格式。 {<br />
* text id : function(domain name){ return text for this domain } }
* @param {String}[domain]
* 指定存入之 domain。
* @param {Boolean}[clean_and_replace]
* 是否直接覆蓋掉原先之 domain。
*/
gettext.set_text = function set_text(text_Object, domain, clean_and_replace) {
if (!library_namespace.is_Object(text_Object))
return;
if (!domain)
domain = gettext_domain_name;
// normalize domain
if (!(domain in gettext_texts))
domain = gettext.to_standard(domain);
// console.trace(domain);
if (clean_and_replace || !(domain in gettext_texts)) {
gettext_texts[domain] = text_Object;
} else {
// specify a new domain.
// gettext_texts[domain] = Object.create(null);
// CeL.set_method() 不覆蓋原有的設定。
// library_namespace.set_method(gettext_texts[domain], text_Object);
// 覆蓋原有的設定。
Object.assign(gettext_texts[domain], text_Object);
}
};
// ------------------------------------
/**
* 取得 domain 別名。 若欲取得某個語言在其他語言中的名稱,應該設定好i18n,並以gettext()取得。
*
* @param {String}[language]
* 指定之正規名稱。
* @returns {String} 主要使用之別名。
* @returns {Object} { 正規名稱 : 別名 }
*/
gettext.get_alias = function(language) {
return arguments.length > 0 ? gettext_main_alias[language in gettext_main_alias ? language
: gettext.to_standard(language)]
: gettext_main_alias;
};
/**
* 設定 domain 別名。<br />
* 本函數會改變 {Object}list!
*
* @param {Object}list
* full alias list / 別名。 = {<br />
* norm/criterion (IANA language tag) : [<br />
* 主要別名放在首個 (e.g., 當地使用之語言名稱),<br />
* 最常用之 language tag (e.g., IETF language tag),<br />
* 其他別名 / other aliases ] }
*/
gettext.set_alias = function(list) {
if (!library_namespace.is_Object(list))
return;
/** {String}normalized domain name */
var norm;
/** {String}domain alias */
var alias;
/** {Array}domain alias list */
var alias_list, i, l;
for (norm in list) {
alias_list = list[norm];
if (typeof alias_list === 'string') {
alias_list = alias_list.split('|');
} else if (!Array.isArray(alias_list)) {
library_namespace.warn([ 'gettext.set_alias: ', {
// gettext_config:{"id":"illegal-domain-alias-list-$1"}
T : [ 'Illegal domain alias list: [%1]', alias_list ]
} ]);
continue;
}
// 加入 norm 本身。
alias_list.push(norm);
for (i = 0, l = alias_list.length; i < l; i++) {
alias = alias_list[i];
if (!alias) {
continue;
}
library_namespace.debug({
// gettext_config:{"id":"adding-domain-alias-$1-→-$2"}
T : [ 'Adding domain alias [%1] → [%2]...',
//
alias, norm ]
}, 2, 'gettext.set_alias');
if (!(norm in gettext_main_alias))
gettext_main_alias[norm] = alias;
// 正規化: 不分大小寫, _ → -
alias = alias.replace(/_/g, '-').toLowerCase();
alias.split(/-/).forEach(function(token) {
if (!gettext_aliases[token])
gettext_aliases[token] = [];
if (!gettext_aliases[token].includes(norm))
gettext_aliases[token].push(norm);
});
continue;
// for fallback
while (true) {
gettext_aliases[alias] = norm;
var index = alias.lastIndexOf('-');
if (index < 1)
break;
alias = alias.slice(0, index);
}
}
}
};
/**
* 將 domain 別名正規化,轉為正規/標準名稱。<br />
* to a standard form. normalize_domain_name().
*
* TODO: fix CeL.gettext.to_standard('cmn-CN') ===
* CeL.gettext.to_standard('zh-CN')
*
* @param {String}alias
* 指定之別名。
* @param {Object}[options]
* 附加參數/設定選擇性/特殊功能與選項
*
* @returns {String} 正規名稱。
* @returns undefined : can't found.
*/
gettext.to_standard = function to_standard(alias, options) {
if (typeof alias !== 'string')
return;
if (options === true) {
options = {
get_list : true
};
} else {
options = library_namespace.setup_options(options);
}
// 正規化: 不分大小寫, _ → -
alias = alias.replace(/_/g, '-').toLowerCase();
var candidates;
alias.split(/-/)
// 通常越後面的越有特殊性。
.reverse().some(function(token) {
if (!gettext_aliases[token])
return;
// console.log(token + ': ' +
// JSON.stringify(gettext_aliases[token]));
if (!candidates) {
candidates = gettext_aliases[token];
return;
}
// 取交集。
candidates = Array.intersection(candidates,
//
gettext_aliases[token]);
// console.log('candidates: ' + JSON.stringify(candidates));
if (candidates.length < 2) {
return true;
}
});
return options.get_list ? candidates ? candidates.clone() : []
: candidates && candidates[0];
var index;
// for fallback
while (true) {
library_namespace.debug({
// gettext_config:{"id":"testing-domain-