ember-intl
Version:
Ember toolbox for internationalization.
1,119 lines (962 loc) • 755 kB
JavaScript
/**
* @license Copyright 2013 Andy Earnshaw, MIT License
*
* Implements the ECMAScript Internationalization API in ES5-compatible environments,
* following the ECMA-402 specification as closely as possible
*
* ECMA-402: http://ecma-international.org/ecma-402/1.0/
*
* CLDR format locale data should be provided using IntlPolyfill.__addLocaleData().
*/
/*jshint proto:true, eqnull:true, boss:true, laxbreak:true, newcap:false, shadow:true, funcscope:true */
/*globals global, define, exports, module, window*/
(function (global, factory) {
var IntlPolyfill = factory();
// register in -all- the module systems (at once)
if (typeof define === 'function' && define.amd)
define(IntlPolyfill);
if (typeof exports === 'object')
module.exports = IntlPolyfill;
if (!global.Intl) {
global.Intl = IntlPolyfill;
IntlPolyfill.__applyLocaleSensitivePrototypes();
}
global.IntlPolyfill = IntlPolyfill;
})(typeof global !== 'undefined' ? global : this, function() {
"use strict";
var
Intl = {},
realDefineProp = (function () {
try { return !!Object.defineProperty({}, 'a', {}); }
catch (e) { return false; }
})(),
// Need a workaround for getters in ES3
es3 = !realDefineProp && !Object.prototype.__defineGetter__,
// We use this a lot (and need it for proto-less objects)
hop = Object.prototype.hasOwnProperty,
// Naive defineProperty for compatibility
defineProperty = realDefineProp ? Object.defineProperty : function (obj, name, desc) {
if ('get' in desc && obj.__defineGetter__)
obj.__defineGetter__(name, desc.get);
else if (!hop.call(obj, name) || 'value' in desc)
obj[name] = desc.value;
},
// Array.prototype.indexOf, as good as we need it to be
arrIndexOf = Array.prototype.indexOf || function (search) {
/*jshint validthis:true */
var t = this;
if (!t.length)
return -1;
for (var i = arguments[1] || 0, max = t.length; i < max; i++) {
if (t[i] === search)
return i;
}
return -1;
},
// Create an object with the specified prototype (2nd arg required for Record)
objCreate = Object.create || function (proto, props) {
var obj;
function F() {}
F.prototype = proto;
obj = new F();
for (var k in props) {
if (hop.call(props, k))
defineProperty(obj, k, props[k]);
}
return obj;
},
// Snapshot some (hopefully still) native built-ins
arrSlice = Array.prototype.slice,
arrConcat = Array.prototype.concat,
arrPush = Array.prototype.push,
arrJoin = Array.prototype.join,
arrShift = Array.prototype.shift,
arrUnshift= Array.prototype.unshift,
// Naive Function.prototype.bind for compatibility
fnBind = Function.prototype.bind || function (thisObj) {
var fn = this,
args = arrSlice.call(arguments, 1);
// All our (presently) bound functions have either 1 or 0 arguments. By returning
// different function signatures, we can pass some tests in ES3 environments
if (fn.length === 1) {
return function (a) {
return fn.apply(thisObj, arrConcat.call(args, arrSlice.call(arguments)));
};
}
else {
return function () {
return fn.apply(thisObj, arrConcat.call(args, arrSlice.call(arguments)));
};
}
},
// Default locale is the first-added locale data for us
defaultLocale,
// Object housing internal properties for constructors
internals = objCreate(null),
// Keep internal properties internal
secret = Math.random(),
// An object map of date component keys, saves using a regex later
dateWidths = objCreate(null, { narrow:{}, short:{}, long:{} }),
// Each constructor prototype should be an instance of the constructor itself, but we
// can't initialise them as such until some locale data has been added, so this is how
// we keep track
numberFormatProtoInitialised = false,
dateTimeFormatProtoInitialised = false,
// Some regular expressions we're using
expCurrencyCode = /^[A-Z]{3}$/,
expUnicodeExSeq = /-u(?:-[0-9a-z]{2,8})+/gi, // See `extension` below
expBCP47Syntax,
expExtSequences,
expVariantDupes,
expSingletonDupes,
// IANA Subtag Registry redundant tag and subtag maps
redundantTags = {
tags: {
"art-lojban": "jbo", "i-ami": "ami", "i-bnn": "bnn", "i-hak": "hak",
"i-klingon": "tlh", "i-lux": "lb", "i-navajo": "nv", "i-pwn": "pwn",
"i-tao": "tao", "i-tay": "tay", "i-tsu": "tsu", "no-bok": "nb",
"no-nyn": "nn", "sgn-BE-FR": "sfb", "sgn-BE-NL": "vgt", "sgn-CH-DE": "sgg",
"zh-guoyu": "cmn", "zh-hakka": "hak", "zh-min-nan": "nan", "zh-xiang": "hsn",
"sgn-BR": "bzs", "sgn-CO": "csn", "sgn-DE": "gsg", "sgn-DK": "dsl",
"sgn-ES": "ssp", "sgn-FR": "fsl", "sgn-GB": "bfi", "sgn-GR": "gss",
"sgn-IE": "isg", "sgn-IT": "ise", "sgn-JP": "jsl", "sgn-MX": "mfs",
"sgn-NI": "ncs", "sgn-NL": "dse", "sgn-NO": "nsl", "sgn-PT": "psr",
"sgn-SE": "swl", "sgn-US": "ase", "sgn-ZA": "sfs", "zh-cmn": "cmn",
"zh-cmn-Hans": "cmn-Hans", "zh-cmn-Hant": "cmn-Hant", "zh-gan": "gan", "zh-wuu": "wuu",
"zh-yue": "yue"
},
subtags: {
BU: "MM", DD: "DE", FX: "FR", TP: "TL", YD: "YE", ZR: "CD", heploc: "alalc97",
'in': "id", iw: "he", ji: "yi", jw: "jv", mo: "ro", ayx: "nun", bjd: "drl",
ccq: "rki", cjr: "mom", cka: "cmr", cmk: "xch", drh: "khk", drw: "prs", gav: "dev",
hrr: "jal", ibi: "opa", kgh: "kml", lcq: "ppr", mst: "mry", myt: "mry", sca: "hle",
tie: "ras", tkk: "twm", tlw: "weo", tnf: "prs", ybd: "rki", yma: "lrr"
},
extLang: {
aao: [ "aao", "ar" ], abh: [ "abh", "ar" ], abv: [ "abv", "ar" ], acm: [ "acm", "ar" ],
acq: [ "acq", "ar" ], acw: [ "acw", "ar" ], acx: [ "acx", "ar" ], acy: [ "acy", "ar" ],
adf: [ "adf", "ar" ], ads: [ "ads", "sgn" ], aeb: [ "aeb", "ar" ], aec: [ "aec", "ar" ],
aed: [ "aed", "sgn" ], aen: [ "aen", "sgn" ], afb: [ "afb", "ar" ], afg: [ "afg", "sgn" ],
ajp: [ "ajp", "ar" ], apc: [ "apc", "ar" ], apd: [ "apd", "ar" ], arb: [ "arb", "ar" ],
arq: [ "arq", "ar" ], ars: [ "ars", "ar" ], ary: [ "ary", "ar" ], arz: [ "arz", "ar" ],
ase: [ "ase", "sgn" ], asf: [ "asf", "sgn" ], asp: [ "asp", "sgn" ], asq: [ "asq", "sgn" ],
asw: [ "asw", "sgn" ], auz: [ "auz", "ar" ], avl: [ "avl", "ar" ], ayh: [ "ayh", "ar" ],
ayl: [ "ayl", "ar" ], ayn: [ "ayn", "ar" ], ayp: [ "ayp", "ar" ], bbz: [ "bbz", "ar" ],
bfi: [ "bfi", "sgn" ], bfk: [ "bfk", "sgn" ], bjn: [ "bjn", "ms" ], bog: [ "bog", "sgn" ],
bqn: [ "bqn", "sgn" ], bqy: [ "bqy", "sgn" ], btj: [ "btj", "ms" ], bve: [ "bve", "ms" ],
bvl: [ "bvl", "sgn" ], bvu: [ "bvu", "ms" ], bzs: [ "bzs", "sgn" ], cdo: [ "cdo", "zh" ],
cds: [ "cds", "sgn" ], cjy: [ "cjy", "zh" ], cmn: [ "cmn", "zh" ], coa: [ "coa", "ms" ],
cpx: [ "cpx", "zh" ], csc: [ "csc", "sgn" ], csd: [ "csd", "sgn" ], cse: [ "cse", "sgn" ],
csf: [ "csf", "sgn" ], csg: [ "csg", "sgn" ], csl: [ "csl", "sgn" ], csn: [ "csn", "sgn" ],
csq: [ "csq", "sgn" ], csr: [ "csr", "sgn" ], czh: [ "czh", "zh" ], czo: [ "czo", "zh" ],
doq: [ "doq", "sgn" ], dse: [ "dse", "sgn" ], dsl: [ "dsl", "sgn" ], dup: [ "dup", "ms" ],
ecs: [ "ecs", "sgn" ], esl: [ "esl", "sgn" ], esn: [ "esn", "sgn" ], eso: [ "eso", "sgn" ],
eth: [ "eth", "sgn" ], fcs: [ "fcs", "sgn" ], fse: [ "fse", "sgn" ], fsl: [ "fsl", "sgn" ],
fss: [ "fss", "sgn" ], gan: [ "gan", "zh" ], gds: [ "gds", "sgn" ], gom: [ "gom", "kok" ],
gse: [ "gse", "sgn" ], gsg: [ "gsg", "sgn" ], gsm: [ "gsm", "sgn" ], gss: [ "gss", "sgn" ],
gus: [ "gus", "sgn" ], hab: [ "hab", "sgn" ], haf: [ "haf", "sgn" ], hak: [ "hak", "zh" ],
hds: [ "hds", "sgn" ], hji: [ "hji", "ms" ], hks: [ "hks", "sgn" ], hos: [ "hos", "sgn" ],
hps: [ "hps", "sgn" ], hsh: [ "hsh", "sgn" ], hsl: [ "hsl", "sgn" ], hsn: [ "hsn", "zh" ],
icl: [ "icl", "sgn" ], ils: [ "ils", "sgn" ], inl: [ "inl", "sgn" ], ins: [ "ins", "sgn" ],
ise: [ "ise", "sgn" ], isg: [ "isg", "sgn" ], isr: [ "isr", "sgn" ], jak: [ "jak", "ms" ],
jax: [ "jax", "ms" ], jcs: [ "jcs", "sgn" ], jhs: [ "jhs", "sgn" ], jls: [ "jls", "sgn" ],
jos: [ "jos", "sgn" ], jsl: [ "jsl", "sgn" ], jus: [ "jus", "sgn" ], kgi: [ "kgi", "sgn" ],
knn: [ "knn", "kok" ], kvb: [ "kvb", "ms" ], kvk: [ "kvk", "sgn" ], kvr: [ "kvr", "ms" ],
kxd: [ "kxd", "ms" ], lbs: [ "lbs", "sgn" ], lce: [ "lce", "ms" ], lcf: [ "lcf", "ms" ],
liw: [ "liw", "ms" ], lls: [ "lls", "sgn" ], lsg: [ "lsg", "sgn" ], lsl: [ "lsl", "sgn" ],
lso: [ "lso", "sgn" ], lsp: [ "lsp", "sgn" ], lst: [ "lst", "sgn" ], lsy: [ "lsy", "sgn" ],
ltg: [ "ltg", "lv" ], lvs: [ "lvs", "lv" ], lzh: [ "lzh", "zh" ], max: [ "max", "ms" ],
mdl: [ "mdl", "sgn" ], meo: [ "meo", "ms" ], mfa: [ "mfa", "ms" ], mfb: [ "mfb", "ms" ],
mfs: [ "mfs", "sgn" ], min: [ "min", "ms" ], mnp: [ "mnp", "zh" ], mqg: [ "mqg", "ms" ],
mre: [ "mre", "sgn" ], msd: [ "msd", "sgn" ], msi: [ "msi", "ms" ], msr: [ "msr", "sgn" ],
mui: [ "mui", "ms" ], mzc: [ "mzc", "sgn" ], mzg: [ "mzg", "sgn" ], mzy: [ "mzy", "sgn" ],
nan: [ "nan", "zh" ], nbs: [ "nbs", "sgn" ], ncs: [ "ncs", "sgn" ], nsi: [ "nsi", "sgn" ],
nsl: [ "nsl", "sgn" ], nsp: [ "nsp", "sgn" ], nsr: [ "nsr", "sgn" ], nzs: [ "nzs", "sgn" ],
okl: [ "okl", "sgn" ], orn: [ "orn", "ms" ], ors: [ "ors", "ms" ], pel: [ "pel", "ms" ],
pga: [ "pga", "ar" ], pks: [ "pks", "sgn" ], prl: [ "prl", "sgn" ], prz: [ "prz", "sgn" ],
psc: [ "psc", "sgn" ], psd: [ "psd", "sgn" ], pse: [ "pse", "ms" ], psg: [ "psg", "sgn" ],
psl: [ "psl", "sgn" ], pso: [ "pso", "sgn" ], psp: [ "psp", "sgn" ], psr: [ "psr", "sgn" ],
pys: [ "pys", "sgn" ], rms: [ "rms", "sgn" ], rsi: [ "rsi", "sgn" ], rsl: [ "rsl", "sgn" ],
sdl: [ "sdl", "sgn" ], sfb: [ "sfb", "sgn" ], sfs: [ "sfs", "sgn" ], sgg: [ "sgg", "sgn" ],
sgx: [ "sgx", "sgn" ], shu: [ "shu", "ar" ], slf: [ "slf", "sgn" ], sls: [ "sls", "sgn" ],
sqk: [ "sqk", "sgn" ], sqs: [ "sqs", "sgn" ], ssh: [ "ssh", "ar" ], ssp: [ "ssp", "sgn" ],
ssr: [ "ssr", "sgn" ], svk: [ "svk", "sgn" ], swc: [ "swc", "sw" ], swh: [ "swh", "sw" ],
swl: [ "swl", "sgn" ], syy: [ "syy", "sgn" ], tmw: [ "tmw", "ms" ], tse: [ "tse", "sgn" ],
tsm: [ "tsm", "sgn" ], tsq: [ "tsq", "sgn" ], tss: [ "tss", "sgn" ], tsy: [ "tsy", "sgn" ],
tza: [ "tza", "sgn" ], ugn: [ "ugn", "sgn" ], ugy: [ "ugy", "sgn" ], ukl: [ "ukl", "sgn" ],
uks: [ "uks", "sgn" ], urk: [ "urk", "ms" ], uzn: [ "uzn", "uz" ], uzs: [ "uzs", "uz" ],
vgt: [ "vgt", "sgn" ], vkk: [ "vkk", "ms" ], vkt: [ "vkt", "ms" ], vsi: [ "vsi", "sgn" ],
vsl: [ "vsl", "sgn" ], vsv: [ "vsv", "sgn" ], wuu: [ "wuu", "zh" ], xki: [ "xki", "sgn" ],
xml: [ "xml", "sgn" ], xmm: [ "xmm", "ms" ], xms: [ "xms", "sgn" ], yds: [ "yds", "sgn" ],
ysl: [ "ysl", "sgn" ], yue: [ "yue", "zh" ], zib: [ "zib", "sgn" ], zlm: [ "zlm", "ms" ],
zmi: [ "zmi", "ms" ], zsl: [ "zsl", "sgn" ], zsm: [ "zsm", "ms" ]
}
},
// Currency minor units output from tools/getISO4217data.js, formatted
currencyMinorUnits = {
BHD: 3, BYR: 0, XOF: 0, BIF: 0, XAF: 0, CLF: 0, CLP: 0, KMF: 0, DJF: 0,
XPF: 0, GNF: 0, ISK: 0, IQD: 3, JPY: 0, JOD: 3, KRW: 0, KWD: 3, LYD: 3,
OMR: 3, PYG: 0, RWF: 0, TND: 3, UGX: 0, UYI: 0, VUV: 0, VND: 0
};
/**
* Defines regular expressions for various operations related to the BCP 47 syntax,
* as defined at http://tools.ietf.org/html/bcp47#section-2.1
*/
(function () {
var
// extlang = 3ALPHA ; selected ISO 639 codes
// *2("-" 3ALPHA) ; permanently reserved
extlang = '[a-z]{3}(?:-[a-z]{3}){0,2}',
// language = 2*3ALPHA ; shortest ISO 639 code
// ["-" extlang] ; sometimes followed by
// ; extended language subtags
// / 4ALPHA ; or reserved for future use
// / 5*8ALPHA ; or registered language subtag
language = '(?:[a-z]{2,3}(?:-' + extlang + ')?|[a-z]{4}|[a-z]{5,8})',
// script = 4ALPHA ; ISO 15924 code
script = '[a-z]{4}',
// region = 2ALPHA ; ISO 3166-1 code
// / 3DIGIT ; UN M.49 code
region = '(?:[a-z]{2}|\\d{3})',
// variant = 5*8alphanum ; registered variants
// / (DIGIT 3alphanum)
variant = '(?:[a-z0-9]{5,8}|\\d[a-z0-9]{3})',
// ; Single alphanumerics
// ; "x" reserved for private use
// singleton = DIGIT ; 0 - 9
// / %x41-57 ; A - W
// / %x59-5A ; Y - Z
// / %x61-77 ; a - w
// / %x79-7A ; y - z
singleton = '[0-9a-wy-z]',
// extension = singleton 1*("-" (2*8alphanum))
extension = singleton + '(?:-[a-z0-9]{2,8})+',
// privateuse = "x" 1*("-" (1*8alphanum))
privateuse = 'x(?:-[a-z0-9]{1,8})+',
// irregular = "en-GB-oed" ; irregular tags do not match
// / "i-ami" ; the 'langtag' production and
// / "i-bnn" ; would not otherwise be
// / "i-default" ; considered 'well-formed'
// / "i-enochian" ; These tags are all valid,
// / "i-hak" ; but most are deprecated
// / "i-klingon" ; in favor of more modern
// / "i-lux" ; subtags or subtag
// / "i-mingo" ; combination
// / "i-navajo"
// / "i-pwn"
// / "i-tao"
// / "i-tay"
// / "i-tsu"
// / "sgn-BE-FR"
// / "sgn-BE-NL"
// / "sgn-CH-DE"
irregular = '(?:en-GB-oed'
+ '|i-(?:ami|bnn|default|enochian|hak|klingon|lux|mingo|navajo|pwn|tao|tay|tsu)'
+ '|sgn-(?:BE-FR|BE-NL|CH-DE))',
// regular = "art-lojban" ; these tags match the 'langtag'
// / "cel-gaulish" ; production, but their subtags
// / "no-bok" ; are not extended language
// / "no-nyn" ; or variant subtags: their meaning
// / "zh-guoyu" ; is defined by their registration
// / "zh-hakka" ; and all of these are deprecated
// / "zh-min" ; in favor of a more modern
// / "zh-min-nan" ; subtag or sequence of subtags
// / "zh-xiang"
regular = '(?:art-lojban|cel-gaulish|no-bok|no-nyn'
+ '|zh-(?:guoyu|hakka|min|min-nan|xiang))',
// grandfathered = irregular ; non-redundant tags registered
// / regular ; during the RFC 3066 era
grandfathered = '(?:' + irregular + '|' + regular + ')',
// langtag = language
// ["-" script]
// ["-" region]
// *("-" variant)
// *("-" extension)
// ["-" privateuse]
langtag = language + '(?:-' + script + ')?(?:-' + region + ')?(?:-'
+ variant + ')*(?:-' + extension + ')*(?:-' + privateuse + ')?';
// Language-Tag = langtag ; normal language tags
// / privateuse ; private use tag
// / grandfathered ; grandfathered tags
expBCP47Syntax = RegExp('^(?:'+langtag+'|'+privateuse+'|'+grandfathered+')$', 'i');
// Match duplicate variants in a language tag
expVariantDupes = RegExp('^(?!x).*?-('+variant+')-(?:\\w{4,8}-(?!x-))*\\1\\b', 'i');
// Match duplicate singletons in a language tag (except in private use)
expSingletonDupes = RegExp('^(?!x).*?-('+singleton+')-(?:\\w+-(?!x-))*\\1\\b', 'i');
// Match all extension sequences
expExtSequences = RegExp('-'+extension, 'ig');
})();
// Sect 6.2 Language Tags
// ======================
/**
* The IsStructurallyValidLanguageTag abstract operation verifies that the locale
* argument (which must be a String value)
*
* - represents a well-formed BCP 47 language tag as specified in RFC 5646 section
* 2.1, or successor,
* - does not include duplicate variant subtags, and
* - does not include duplicate singleton subtags.
*
* The abstract operation returns true if locale can be generated from the ABNF
* grammar in section 2.1 of the RFC, starting with Language-Tag, and does not
* contain duplicate variant or singleton subtags (other than as a private use
* subtag). It returns false otherwise. Terminal value characters in the grammar are
* interpreted as the Unicode equivalents of the ASCII octet values given.
*/
function /* 6.2.2 */IsStructurallyValidLanguageTag(locale) {
// represents a well-formed BCP 47 language tag as specified in RFC 5646
if (!expBCP47Syntax.test(locale))
return false;
// does not include duplicate variant subtags, and
if (expVariantDupes.test(locale))
return false;
// does not include duplicate singleton subtags.
if (expSingletonDupes.test(locale))
return false;
return true;
}
/**
* The CanonicalizeLanguageTag abstract operation returns the canonical and case-
* regularized form of the locale argument (which must be a String value that is
* a structurally valid BCP 47 language tag as verified by the
* IsStructurallyValidLanguageTag abstract operation). It takes the steps
* specified in RFC 5646 section 4.5, or successor, to bring the language tag
* into canonical form, and to regularize the case of the subtags, but does not
* take the steps to bring a language tag into “extlang form” and to reorder
* variant subtags.
* The specifications for extensions to BCP 47 language tags, such as RFC 6067,
* may include canonicalization rules for the extension subtag sequences they
* define that go beyond the canonicalization rules of RFC 5646 section 4.5.
* Implementations are allowed, but not required, to apply these additional rules.
*/
function /* 6.2.3 */CanonicalizeLanguageTag (locale) {
var match, parts;
// A language tag is in 'canonical form' when the tag is well-formed
// according to the rules in Sections 2.1 and 2.2
// Section 2.1 says all subtags use lowercase...
locale = locale.toLowerCase();
// ...with 2 exceptions: 'two-letter and four-letter subtags that neither
// appear at the start of the tag nor occur after singletons. Such two-letter
// subtags are all uppercase (as in the tags "en-CA-x-ca" or "sgn-BE-FR") and
// four-letter subtags are titlecase (as in the tag "az-Latn-x-latn").
parts = locale.split('-');
for (var i = 1, max = parts.length; i < max; i++) {
// Two-letter subtags are all uppercase
if (parts[i].length === 2)
parts[i] = parts[i].toUpperCase();
// Four-letter subtags are titlecase
else if (parts[i].length === 4)
parts[i] = parts[i].charAt(0).toUpperCase() + parts[i].slice(1);
// Is it a singleton?
else if (parts[i].length === 1 && parts[i] != 'x')
break;
}
locale = arrJoin.call(parts, '-');
// The steps laid out in RFC 5646 section 4.5 are as follows:
// 1. Extension sequences are ordered into case-insensitive ASCII order
// by singleton subtag.
if ((match = locale.match(expExtSequences)) && match.length > 1) {
// The built-in sort() sorts by ASCII order, so use that
match.sort();
// Replace all extensions with the joined, sorted array
locale = locale.replace(
RegExp('(?:' + expExtSequences.source + ')+', 'i'),
arrJoin.call(match, '')
);
}
// 2. Redundant or grandfathered tags are replaced by their 'Preferred-
// Value', if there is one.
if (hop.call(redundantTags.tags, locale))
locale = redundantTags.tags[locale];
// 3. Subtags are replaced by their 'Preferred-Value', if there is one.
// For extlangs, the original primary language subtag is also
// replaced if there is a primary language subtag in the 'Preferred-
// Value'.
parts = locale.split('-');
for (var i = 1, max = parts.length; i < max; i++) {
if (hop.call(redundantTags.subtags, parts[i]))
parts[i] = redundantTags.subtags[parts[i]];
else if (hop.call(redundantTags.extLang, parts[i])) {
parts[i] = redundantTags.extLang[parts[i]][0];
// For extlang tags, the prefix needs to be removed if it is redundant
if (i === 1 && redundantTags.extLang[parts[1]][1] === parts[0]) {
parts = arrSlice.call(parts, i++);
max -= 1;
}
}
}
return arrJoin.call(parts, '-');
}
/**
* The DefaultLocale abstract operation returns a String value representing the
* structurally valid (6.2.2) and canonicalized (6.2.3) BCP 47 language tag for the
* host environment’s current locale.
*/
function /* 6.2.4 */DefaultLocale () {
return defaultLocale;
}
// Sect 6.3 Currency Codes
// =======================
/**
* The IsWellFormedCurrencyCode abstract operation verifies that the currency argument
* (after conversion to a String value) represents a well-formed 3-letter ISO currency
* code. The following steps are taken:
*/
function /* 6.3.1 */IsWellFormedCurrencyCode(currency) {
var
// 1. Let `c` be ToString(currency)
c = String(currency),
// 2. Let `normalized` be the result of mapping c to upper case as described
// in 6.1.
normalized = toLatinUpperCase(c);
// 3. If the string length of normalized is not 3, return false.
// 4. If normalized contains any character that is not in the range "A" to "Z"
// (U+0041 to U+005A), return false.
if (expCurrencyCode.test(normalized) === false)
return false;
// 5. Return true
return true;
}
// Sect 9.2 Abstract Operations
// ============================
function /* 9.2.1 */CanonicalizeLocaleList (locales) {
// The abstract operation CanonicalizeLocaleList takes the following steps:
// 1. If locales is undefined, then a. Return a new empty List
if (locales === undefined)
return new List();
var
// 2. Let seen be a new empty List.
seen = new List(),
// 3. If locales is a String value, then
// a. Let locales be a new array created as if by the expression new
// Array(locales) where Array is the standard built-in constructor with
// that name and locales is the value of locales.
locales = typeof locales === 'string' ? [ locales ] : locales,
// 4. Let O be ToObject(locales).
O = toObject(locales),
// 5. Let lenValue be the result of calling the [[Get]] internal method of
// O with the argument "length".
// 6. Let len be ToUint32(lenValue).
len = O.length,
// 7. Let k be 0.
k = 0;
// 8. Repeat, while k < len
while (k < len) {
var
// a. Let Pk be ToString(k).
Pk = String(k),
// b. Let kPresent be the result of calling the [[HasProperty]] internal
// method of O with argument Pk.
kPresent = Pk in O;
// c. If kPresent is true, then
if (kPresent) {
var
// i. Let kValue be the result of calling the [[Get]] internal
// method of O with argument Pk.
kValue = O[Pk];
// ii. If the type of kValue is not String or Object, then throw a
// TypeError exception.
if (kValue == null || (typeof kValue !== 'string' && typeof kValue !== 'object'))
throw new TypeError('String or Object type expected');
var
// iii. Let tag be ToString(kValue).
tag = String(kValue);
// iv. If the result of calling the abstract operation
// IsStructurallyValidLanguageTag (defined in 6.2.2), passing tag as
// the argument, is false, then throw a RangeError exception.
if (!IsStructurallyValidLanguageTag(tag))
throw new RangeError("'" + tag + "' is not a structurally valid language tag");
// v. Let tag be the result of calling the abstract operation
// CanonicalizeLanguageTag (defined in 6.2.3), passing tag as the
// argument.
tag = CanonicalizeLanguageTag(tag);
// vi. If tag is not an element of seen, then append tag as the last
// element of seen.
if (arrIndexOf.call(seen, tag) === -1)
arrPush.call(seen, tag);
}
// d. Increase k by 1.
k++;
}
// 9. Return seen.
return seen;
}
/**
* The BestAvailableLocale abstract operation compares the provided argument
* locale, which must be a String value with a structurally valid and
* canonicalized BCP 47 language tag, against the locales in availableLocales and
* returns either the longest non-empty prefix of locale that is an element of
* availableLocales, or undefined if there is no such element. It uses the
* fallback mechanism of RFC 4647, section 3.4. The following steps are taken:
*/
function /* 9.2.2 */BestAvailableLocale (availableLocales, locale) {
var
// 1. Let candidate be locale
candidate = locale;
// 2. Repeat
while (true) {
// a. If availableLocales contains an element equal to candidate, then return
// candidate.
if (arrIndexOf.call(availableLocales, candidate) > -1)
return candidate;
var
// b. Let pos be the character index of the last occurrence of "-"
// (U+002D) within candidate. If that character does not occur, return
// undefined.
pos = candidate.lastIndexOf('-');
if (pos < 0)
return;
// c. If pos ≥ 2 and the character "-" occurs at index pos-2 of candidate,
// then decrease pos by 2.
if (pos >= 2 && candidate.charAt(pos - 2) == '-')
pos -= 2;
// d. Let candidate be the substring of candidate from position 0, inclusive,
// to position pos, exclusive.
candidate = candidate.substring(0, pos);
}
}
/**
* The LookupMatcher abstract operation compares requestedLocales, which must be
* a List as returned by CanonicalizeLocaleList, against the locales in
* availableLocales and determines the best available language to meet the
* request. The following steps are taken:
*/
function /* 9.2.3 */LookupMatcher (availableLocales, requestedLocales) {
var
// 1. Let i be 0.
i = 0,
// 2. Let len be the number of elements in requestedLocales.
len = requestedLocales.length,
// 3. Let availableLocale be undefined.
availableLocale;
// 4. Repeat while i < len and availableLocale is undefined:
while (i < len && !availableLocale) {
var
// a. Let locale be the element of requestedLocales at 0-origined list
// position i.
locale = requestedLocales[i],
// b. Let noExtensionsLocale be the String value that is locale with all
// Unicode locale extension sequences removed.
noExtensionsLocale = String(locale).replace(expUnicodeExSeq, ''),
// c. Let availableLocale be the result of calling the
// BestAvailableLocale abstract operation (defined in 9.2.2) with
// arguments availableLocales and noExtensionsLocale.
availableLocale = BestAvailableLocale(availableLocales, noExtensionsLocale);
// d. Increase i by 1.
i++;
}
var
// 5. Let result be a new Record.
result = new Record();
// 6. If availableLocale is not undefined, then
if (availableLocale !== undefined) {
// a. Set result.[[locale]] to availableLocale.
result['[[locale]]'] = availableLocale;
// b. If locale and noExtensionsLocale are not the same String value, then
if (String(locale) !== String(noExtensionsLocale)) {
var
// i. Let extension be the String value consisting of the first
// substring of locale that is a Unicode locale extension sequence.
extension = locale.match(expUnicodeExSeq)[0],
// ii. Let extensionIndex be the character position of the initial
// "-" of the first Unicode locale extension sequence within locale.
extensionIndex = locale.indexOf('-u-');
// iii. Set result.[[extension]] to extension.
result['[[extension]]'] = extension;
// iv. Set result.[[extensionIndex]] to extensionIndex.
result['[[extensionIndex]]'] = extensionIndex;
}
}
// 7. Else
else
// a. Set result.[[locale]] to the value returned by the DefaultLocale abstract
// operation (defined in 6.2.4).
result['[[locale]]'] = DefaultLocale();
// 8. Return result
return result;
}
/**
* The BestFitMatcher abstract operation compares requestedLocales, which must be
* a List as returned by CanonicalizeLocaleList, against the locales in
* availableLocales and determines the best available language to meet the
* request. The algorithm is implementation dependent, but should produce results
* that a typical user of the requested locales would perceive as at least as
* good as those produced by the LookupMatcher abstract operation. Options
* specified through Unicode locale extension sequences must be ignored by the
* algorithm. Information about such subsequences is returned separately.
* The abstract operation returns a record with a [[locale]] field, whose value
* is the language tag of the selected locale, which must be an element of
* availableLocales. If the language tag of the request locale that led to the
* selected locale contained a Unicode locale extension sequence, then the
* returned record also contains an [[extension]] field whose value is the first
* Unicode locale extension sequence, and an [[extensionIndex]] field whose value
* is the index of the first Unicode locale extension sequence within the request
* locale language tag.
*/
function /* 9.2.4 */BestFitMatcher (availableLocales, requestedLocales) {
return LookupMatcher(availableLocales, requestedLocales);
}
/**
* The ResolveLocale abstract operation compares a BCP 47 language priority list
* requestedLocales against the locales in availableLocales and determines the
* best available language to meet the request. availableLocales and
* requestedLocales must be provided as List values, options as a Record.
*/
function /* 9.2.5 */ResolveLocale (availableLocales, requestedLocales, options, relevantExtensionKeys, localeData) {
if (availableLocales.length === 0) {
throw new ReferenceError('No locale data has been provided for this object yet.');
}
// The following steps are taken:
var
// 1. Let matcher be the value of options.[[localeMatcher]].
matcher = options['[[localeMatcher]]'];
// 2. If matcher is "lookup", then
if (matcher === 'lookup')
var
// a. Let r be the result of calling the LookupMatcher abstract operation
// (defined in 9.2.3) with arguments availableLocales and
// requestedLocales.
r = LookupMatcher(availableLocales, requestedLocales);
// 3. Else
else
var
// a. Let r be the result of calling the BestFitMatcher abstract
// operation (defined in 9.2.4) with arguments availableLocales and
// requestedLocales.
r = BestFitMatcher(availableLocales, requestedLocales);
var
// 4. Let foundLocale be the value of r.[[locale]].
foundLocale = r['[[locale]]'];
// 5. If r has an [[extension]] field, then
if (hop.call(r, '[[extension]]'))
var
// a. Let extension be the value of r.[[extension]].
extension = r['[[extension]]'],
// b. Let extensionIndex be the value of r.[[extensionIndex]].
extensionIndex = r['[[extensionIndex]]'],
// c. Let split be the standard built-in function object defined in ES5,
// 15.5.4.14.
split = String.prototype.split,
// d. Let extensionSubtags be the result of calling the [[Call]] internal
// method of split with extension as the this value and an argument
// list containing the single item "-".
extensionSubtags = split.call(extension, '-'),
// e. Let extensionSubtagsLength be the result of calling the [[Get]]
// internal method of extensionSubtags with argument "length".
extensionSubtagsLength = extensionSubtags.length;
var
// 6. Let result be a new Record.
result = new Record();
// 7. Set result.[[dataLocale]] to foundLocale.
result['[[dataLocale]]'] = foundLocale;
var
// 8. Let supportedExtension be "-u".
supportedExtension = '-u',
// 9. Let i be 0.
i = 0,
// 10. Let len be the result of calling the [[Get]] internal method of
// relevantExtensionKeys with argument "length".
len = relevantExtensionKeys.length;
// 11 Repeat while i < len:
while (i < len) {
var
// a. Let key be the result of calling the [[Get]] internal method of
// relevantExtensionKeys with argument ToString(i).
key = relevantExtensionKeys[i],
// b. Let foundLocaleData be the result of calling the [[Get]] internal
// method of localeData with the argument foundLocale.
foundLocaleData = localeData[foundLocale],
// c. Let keyLocaleData be the result of calling the [[Get]] internal
// method of foundLocaleData with the argument key.
keyLocaleData = foundLocaleData[key],
// d. Let value be the result of calling the [[Get]] internal method of
// keyLocaleData with argument "0".
value = keyLocaleData['0'],
// e. Let supportedExtensionAddition be "".
supportedExtensionAddition = '',
// f. Let indexOf be the standard built-in function object defined in
// ES5, 15.4.4.14.
indexOf = arrIndexOf;
// g. If extensionSubtags is not undefined, then
if (extensionSubtags !== undefined) {
var
// i. Let keyPos be the result of calling the [[Call]] internal
// method of indexOf with extensionSubtags as the this value and
// an argument list containing the single item key.
keyPos = indexOf.call(extensionSubtags, key);
// ii. If keyPos ≠ -1, then
if (keyPos !== -1) {
// 1. If keyPos + 1 < extensionSubtagsLength and the length of the
// result of calling the [[Get]] internal method of
// extensionSubtags with argument ToString(keyPos +1) is greater
// than 2, then
if (keyPos + 1 < extensionSubtagsLength
&& extensionSubtags[keyPos + 1].length > 2) {
var
// a. Let requestedValue be the result of calling the [[Get]]
// internal method of extensionSubtags with argument
// ToString(keyPos + 1).
requestedValue = extensionSubtags[keyPos + 1],
// b. Let valuePos be the result of calling the [[Call]]
// internal method of indexOf with keyLocaleData as the
// this value and an argument list containing the single
// item requestedValue.
valuePos = indexOf.call(keyLocaleData, requestedValue);
// c. If valuePos ≠ -1, then
if (valuePos !== -1)
var
// i. Let value be requestedValue.
value = requestedValue,
// ii. Let supportedExtensionAddition be the
// concatenation of "-", key, "-", and value.
supportedExtensionAddition = '-' + key + '-' + value;
}
// 2. Else
else {
var
// a. Let valuePos be the result of calling the [[Call]]
// internal method of indexOf with keyLocaleData as the this
// value and an argument list containing the single item
// "true".
valuePos = indexOf(keyLocaleData, 'true');
// b. If valuePos ≠ -1, then
if (valuePos !== -1)
var
// i. Let value be "true".
value = 'true';
}
}
}
// h. If options has a field [[<key>]], then
if (hop.call(options, '[[' + key + ']]')) {
var
// i. Let optionsValue be the value of options.[[<key>]].
optionsValue = options['[[' + key + ']]'];
// ii. If the result of calling the [[Call]] internal method of indexOf
// with keyLocaleData as the this value and an argument list
// containing the single item optionsValue is not -1, then
if (indexOf.call(keyLocaleData, optionsValue) !== -1) {
// 1. If optionsValue is not equal to value, then
if (optionsValue !== value) {
// a. Let value be optionsValue.
value = optionsValue;
// b. Let supportedExtensionAddition be "".
supportedExtensionAddition = '';
}
}
}
// i. Set result.[[<key>]] to value.
result['[[' + key + ']]'] = value;
// j. Append supportedExtensionAddition to supportedExtension.
supportedExtension += supportedExtensionAddition;
// k. Increase i by 1.
i++;
}
// 12. If the length of supportedExtension is greater than 2, then
if (supportedExtension.length > 2) {
var
// a. Let preExtension be the substring of foundLocale from position 0,
// inclusive, to position extensionIndex, exclusive.
preExtension = foundLocale.substring(0, extensionIndex),
// b. Let postExtension be the substring of foundLocale from position
// extensionIndex to the end of the string.
postExtension = foundLocale.substring(extensionIndex),
// c. Let foundLocale be the concatenation of preExtension,
// supportedExtension, and postExtension.
foundLocale = preExtension + supportedExtension + postExtension;
}
// 13. Set result.[[locale]] to foundLocale.
result['[[locale]]'] = foundLocale;
// 14. Return result.
return result;
}
/**
* The LookupSupportedLocales abstract operation returns the subset of the
* provided BCP 47 language priority list requestedLocales for which
* availableLocales has a matching locale when using the BCP 47 Lookup algorithm.
* Locales appear in the same order in the returned list as in requestedLocales.
* The following steps are taken:
*/
function /* 9.2.6 */LookupSupportedLocales (availableLocales, requestedLocales) {
var
// 1. Let len be the number of elements in requestedLocales.
len = requestedLocales.length,
// 2. Let subset be a new empty List.
subset = new List(),
// 3. Let k be 0.
k = 0;
// 4. Repeat while k < len
while (k < len) {
var
// a. Let locale be the element of requestedLocales at 0-origined list
// position k.
locale = requestedLocales[k],
// b. Let noExtensionsLocale be the String value that is locale with all
// Unicode locale extension sequences removed.
noExtensionsLocale = String(locale).replace(expUnicodeExSeq, ''),
// c. Let availableLocale be the result of calling the
// BestAvailableLocale abstract operation (defined in 9.2.2) with
// arguments availableLocales and noExtensionsLocale.
availableLocale = BestAvailableLocale(availableLocales, noExtensionsLocale);
// d. If availableLocale is not undefined, then append locale to the end of
// subset.
if (availableLocale !== undefined)
arrPush.call(subset, locale);
// e. Increment k by 1.
k++;
}
var
// 5. Let subsetArray be a new Array object whose elements are the same
// values in the same order as the elements of subset.
subsetArray = arrSlice.call(subset);
// 6. Return subsetArray.
return subsetArray;
}
/**
* The BestFitSupportedLocales abstract operation returns the subset of the
* provided BCP 47 language priority list requestedLocales for which
* availableLocales has a matching locale when using the Best Fit Matcher
* algorithm. Locales appear in the same order in the returned list as in
* requestedLocales. The steps taken are implementation dependent.
*/
function /*9.2.7 */BestFitSupportedLocales (availableLocales, requestedLocales) {
// ###TODO: implement this function as described by the specification###
return LookupSupportedLocales(availableLocales, requestedLocales);
}
/**
* The SupportedLocales abstract operation returns the subset of the provided BCP
* 47 language priority list requestedLocales for which availableLocales has a
* matching locale. Two algorithms are available to match the locales: the Lookup
* algorithm described in RFC 4647 section 3.4, and an implementation dependent
* best-fit algorithm. Locales appear in the same order in the returned list as
* in requestedLocales. The following steps are taken:
*/
function /*9.2.8 */SupportedLocales (availableLocales, requestedLocales, options) {
// 1. If options is not undefined, then
if (options !== undefined) {
var
// a. Let options be ToObject(options).
options = new Record(toObject(options)),
// b. Let matcher be the result of calling the [[Get]] internal method of
// options with argument "localeMatcher".
matcher = options.localeMatcher;
// c. If matcher is not undefined, then
if (matcher !== undefined) {
// i. Let matcher be ToString(matcher).
matcher = String(matcher);
// ii. If matcher is not "lookup" or "best fit", then throw a RangeError
// exception.
if (matcher !== 'lookup' && matcher !== 'best fit')
throw new RangeError('matcher should be "lookup" or "best fit"');
}
}
// 2. If matcher is undefined or "best fit", then
if (matcher === undefined || matcher === 'best fit')
var
// a. Let subset be the result of calling the BestFitSupportedLocales
// abstract operation (defined in 9.2.7) with arguments
// availableLocales and requestedLocales.
subset = BestFitSupportedLocales(availableLocales, requestedLocales);
// 3. Else
else
var
// a. Let subset be the result of calling the LookupSupportedLocales
// abstract operation (defined in 9.2.6) with arguments
// availableLocales and requestedLocales.
subset = LookupSupportedLocales(availableLocales, requestedLocales);
// 4. For each named own property name P of subset,
for (var P in subset) {
if (!hop.call(subset, P))
continue;
// a. Let desc be the result of calling the [[GetOwnProperty]] internal
// method of subset with P.
// b. Set desc.[[Writable]] to false.
// c. Set desc.[[Configurable]] to false.
// d. Call the [[DefineOwnProperty]] internal method of subset with P, desc,
// and true as arguments.
defineProperty(subset, P, {
writable: false, configurable: false, value: subset[P]
});
}
// "Freeze" the array so no new elements can be added
defineProperty(subset, 'length', { writable: false });
// 5. Return subset
return subset;
}
/**
* The GetOption abstract operation extracts the value of the property named
* property from the provided options object, converts it to the required type,
* checks whether it is one of a List of allowed values, and fills in a fallback
* value if necessary.
*/
function /*9.2.9 */GetOption (options, property, type, values, fallback) {
var
// 1. Let value be the result of calling the [[Get]] internal method of
// options with argument property.
value = options[property];
// 2. If value is not undefined, then
if (value !== undefined) {
// a. Assert: type is "boolean" or "string".
// b. If type is "boolean", then let value be ToBoolean(value).
// c. If type is "string", then let value be ToString(value).
value = type === 'boolean' ? Boolean(value)
: (type === 'string' ? String(value) : value);
// d. If values is not undefined, then
if (values !== undefined) {
// i. If values does not contain an element equal to value, then throw a
// RangeError exception.
if (arrIndexOf.call(values, value) === -1)
throw new RangeError("'" + value + "' is not an allowed value for `" + property +'`');
}
// e. Return value.
return value;
}
// Else return fallback.
return fallback;
}
/**
* The GetNumberOption abstract operation extracts a property value from the
* provided options object, converts it to a Number value, checks whether it is
* in the allowed range, and fills in a fallback value if necessary.
*/
function /* 9.2.10 */GetNumberOption (options, property, minimum, maximum, fallback) {
var
// 1. Let value be the result of calling the [[Get]] internal method of
// options with argument property.
value = options[property];
// 2. If value is not undefined, then
if (value !== undefined) {
// a. Let value be ToNumber(value).
value = Number(value);
// b. If value is NaN or less than minimum or greater than maximum, throw a
// RangeError exception.
if (isNaN(value) || value < minimum || value > maximum)
throw new RangeError('Value is not a number or outside accepted range');
// c. Return floor(value).
return Math.floor(value);
}
// 3. Else return fallback.
return fallback;
}
// 11.1 The Intl.NumberFormat constructor
// ======================================
// Define the NumberFormat constructor internally so it cannot be tainted
function NumberFormatConstructor () {
var locales = arguments[0];
var options = arguments[1];
if (!this || this === Intl) {
return new Intl.NumberFormat(locales, options);
}
return InitializeNumberFormat(toObject(this), locales, options);
}
defineProperty(Intl, 'NumberFormat', {
configurable: true,
writable: true,
value: NumberFormatConstructor
});
// Must explicitly set prototypes as unwritable
defineProperty(Intl.NumberFormat, 'prototype', {
writable: false
});
/**
* The abstract operation InitializeNumberFormat ac