|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 /* Portions Copyright Norbert Lindenberg 2011-2012. */ |
|
6 |
|
7 /*global JSMSG_INTL_OBJECT_NOT_INITED: false, JSMSG_INVALID_LOCALES_ELEMENT: false, |
|
8 JSMSG_INVALID_LANGUAGE_TAG: false, JSMSG_INVALID_LOCALE_MATCHER: false, |
|
9 JSMSG_INVALID_OPTION_VALUE: false, JSMSG_INVALID_DIGITS_VALUE: false, |
|
10 JSMSG_INTL_OBJECT_REINITED: false, JSMSG_INVALID_CURRENCY_CODE: false, |
|
11 JSMSG_UNDEFINED_CURRENCY: false, JSMSG_INVALID_TIME_ZONE: false, |
|
12 JSMSG_DATE_NOT_FINITE: false, |
|
13 intl_Collator_availableLocales: false, |
|
14 intl_availableCollations: false, |
|
15 intl_CompareStrings: false, |
|
16 intl_NumberFormat_availableLocales: false, |
|
17 intl_numberingSystem: false, |
|
18 intl_FormatNumber: false, |
|
19 intl_DateTimeFormat_availableLocales: false, |
|
20 intl_availableCalendars: false, |
|
21 intl_patternForSkeleton: false, |
|
22 intl_FormatDateTime: false, |
|
23 */ |
|
24 |
|
25 /* |
|
26 * The Intl module specified by standard ECMA-402, |
|
27 * ECMAScript Internationalization API Specification. |
|
28 */ |
|
29 |
|
30 |
|
31 /********** Locales, Time Zones, and Currencies **********/ |
|
32 |
|
33 |
|
34 /** |
|
35 * Convert s to upper case, but limited to characters a-z. |
|
36 * |
|
37 * Spec: ECMAScript Internationalization API Specification, 6.1. |
|
38 */ |
|
39 function toASCIIUpperCase(s) { |
|
40 assert(typeof s === "string", "toASCIIUpperCase"); |
|
41 |
|
42 // String.prototype.toUpperCase may map non-ASCII characters into ASCII, |
|
43 // so go character by character (actually code unit by code unit, but |
|
44 // since we only care about ASCII characters here, that's OK). |
|
45 var result = ""; |
|
46 for (var i = 0; i < s.length; i++) { |
|
47 var c = s[i]; |
|
48 if ("a" <= c && c <= "z") |
|
49 c = callFunction(std_String_toUpperCase, c); |
|
50 result += c; |
|
51 } |
|
52 return result; |
|
53 } |
|
54 |
|
55 |
|
56 /** |
|
57 * Regular expression matching a "Unicode locale extension sequence", which the |
|
58 * specification defines as: "any substring of a language tag that starts with |
|
59 * a separator '-' and the singleton 'u' and includes the maximum sequence of |
|
60 * following non-singleton subtags and their preceding '-' separators." |
|
61 * |
|
62 * Alternatively, this may be defined as: the components of a language tag that |
|
63 * match the extension production in RFC 5646, where the singleton component is |
|
64 * "u". |
|
65 * |
|
66 * Spec: ECMAScript Internationalization API Specification, 6.2.1. |
|
67 */ |
|
68 var unicodeLocaleExtensionSequence = "-u(-[a-z0-9]{2,8})+"; |
|
69 var unicodeLocaleExtensionSequenceRE = new RegExp(unicodeLocaleExtensionSequence); |
|
70 |
|
71 |
|
72 /** |
|
73 * Removes Unicode locale extension sequences from the given language tag. |
|
74 */ |
|
75 function removeUnicodeExtensions(locale) { |
|
76 // Don't use std_String_replace directly with a regular expression, |
|
77 // as that would set RegExp statics. |
|
78 var extensions; |
|
79 while ((extensions = regexp_exec_no_statics(unicodeLocaleExtensionSequenceRE, locale)) !== null) { |
|
80 locale = callFunction(std_String_replace, locale, extensions[0], ""); |
|
81 unicodeLocaleExtensionSequenceRE.lastIndex = 0; |
|
82 } |
|
83 return locale; |
|
84 } |
|
85 |
|
86 |
|
87 /** |
|
88 * Regular expression defining BCP 47 language tags. |
|
89 * |
|
90 * Spec: RFC 5646 section 2.1. |
|
91 */ |
|
92 var languageTagRE = (function () { |
|
93 // RFC 5234 section B.1 |
|
94 // ALPHA = %x41-5A / %x61-7A ; A-Z / a-z |
|
95 var ALPHA = "[a-zA-Z]"; |
|
96 // DIGIT = %x30-39 |
|
97 // ; 0-9 |
|
98 var DIGIT = "[0-9]"; |
|
99 |
|
100 // RFC 5646 section 2.1 |
|
101 // alphanum = (ALPHA / DIGIT) ; letters and numbers |
|
102 var alphanum = "(?:" + ALPHA + "|" + DIGIT + ")"; |
|
103 // regular = "art-lojban" ; these tags match the 'langtag' |
|
104 // / "cel-gaulish" ; production, but their subtags |
|
105 // / "no-bok" ; are not extended language |
|
106 // / "no-nyn" ; or variant subtags: their meaning |
|
107 // / "zh-guoyu" ; is defined by their registration |
|
108 // / "zh-hakka" ; and all of these are deprecated |
|
109 // / "zh-min" ; in favor of a more modern |
|
110 // / "zh-min-nan" ; subtag or sequence of subtags |
|
111 // / "zh-xiang" |
|
112 var regular = "(?:art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang)"; |
|
113 // irregular = "en-GB-oed" ; irregular tags do not match |
|
114 // / "i-ami" ; the 'langtag' production and |
|
115 // / "i-bnn" ; would not otherwise be |
|
116 // / "i-default" ; considered 'well-formed' |
|
117 // / "i-enochian" ; These tags are all valid, |
|
118 // / "i-hak" ; but most are deprecated |
|
119 // / "i-klingon" ; in favor of more modern |
|
120 // / "i-lux" ; subtags or subtag |
|
121 // / "i-mingo" ; combination |
|
122 // / "i-navajo" |
|
123 // / "i-pwn" |
|
124 // / "i-tao" |
|
125 // / "i-tay" |
|
126 // / "i-tsu" |
|
127 // / "sgn-BE-FR" |
|
128 // / "sgn-BE-NL" |
|
129 // / "sgn-CH-DE" |
|
130 var irregular = "(?:en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)"; |
|
131 // grandfathered = irregular ; non-redundant tags registered |
|
132 // / regular ; during the RFC 3066 era |
|
133 var grandfathered = "(?:" + irregular + "|" + regular + ")"; |
|
134 // privateuse = "x" 1*("-" (1*8alphanum)) |
|
135 var privateuse = "(?:x(?:-[a-z0-9]{1,8})+)"; |
|
136 // singleton = DIGIT ; 0 - 9 |
|
137 // / %x41-57 ; A - W |
|
138 // / %x59-5A ; Y - Z |
|
139 // / %x61-77 ; a - w |
|
140 // / %x79-7A ; y - z |
|
141 var singleton = "(?:" + DIGIT + "|[A-WY-Za-wy-z])"; |
|
142 // extension = singleton 1*("-" (2*8alphanum)) |
|
143 var extension = "(?:" + singleton + "(?:-" + alphanum + "{2,8})+)"; |
|
144 // variant = 5*8alphanum ; registered variants |
|
145 // / (DIGIT 3alphanum) |
|
146 var variant = "(?:" + alphanum + "{5,8}|(?:" + DIGIT + alphanum + "{3}))"; |
|
147 // region = 2ALPHA ; ISO 3166-1 code |
|
148 // / 3DIGIT ; UN M.49 code |
|
149 var region = "(?:" + ALPHA + "{2}|" + DIGIT + "{3})"; |
|
150 // script = 4ALPHA ; ISO 15924 code |
|
151 var script = "(?:" + ALPHA + "{4})"; |
|
152 // extlang = 3ALPHA ; selected ISO 639 codes |
|
153 // *2("-" 3ALPHA) ; permanently reserved |
|
154 var extlang = "(?:" + ALPHA + "{3}(?:-" + ALPHA + "{3}){0,2})"; |
|
155 // language = 2*3ALPHA ; shortest ISO 639 code |
|
156 // ["-" extlang] ; sometimes followed by |
|
157 // ; extended language subtags |
|
158 // / 4ALPHA ; or reserved for future use |
|
159 // / 5*8ALPHA ; or registered language subtag |
|
160 var language = "(?:" + ALPHA + "{2,3}(?:-" + extlang + ")?|" + ALPHA + "{4}|" + ALPHA + "{5,8})"; |
|
161 // langtag = language |
|
162 // ["-" script] |
|
163 // ["-" region] |
|
164 // *("-" variant) |
|
165 // *("-" extension) |
|
166 // ["-" privateuse] |
|
167 var langtag = language + "(?:-" + script + ")?(?:-" + region + ")?(?:-" + |
|
168 variant + ")*(?:-" + extension + ")*(?:-" + privateuse + ")?"; |
|
169 // Language-Tag = langtag ; normal language tags |
|
170 // / privateuse ; private use tag |
|
171 // / grandfathered ; grandfathered tags |
|
172 var languageTag = "^(?:" + langtag + "|" + privateuse + "|" + grandfathered + ")$"; |
|
173 |
|
174 // Language tags are case insensitive (RFC 5646 section 2.1.1). |
|
175 return new RegExp(languageTag, "i"); |
|
176 }()); |
|
177 |
|
178 |
|
179 var duplicateVariantRE = (function () { |
|
180 // RFC 5234 section B.1 |
|
181 // ALPHA = %x41-5A / %x61-7A ; A-Z / a-z |
|
182 var ALPHA = "[a-zA-Z]"; |
|
183 // DIGIT = %x30-39 |
|
184 // ; 0-9 |
|
185 var DIGIT = "[0-9]"; |
|
186 |
|
187 // RFC 5646 section 2.1 |
|
188 // alphanum = (ALPHA / DIGIT) ; letters and numbers |
|
189 var alphanum = "(?:" + ALPHA + "|" + DIGIT + ")"; |
|
190 // variant = 5*8alphanum ; registered variants |
|
191 // / (DIGIT 3alphanum) |
|
192 var variant = "(?:" + alphanum + "{5,8}|(?:" + DIGIT + alphanum + "{3}))"; |
|
193 |
|
194 // Match a langtag that contains a duplicate variant. |
|
195 var duplicateVariant = |
|
196 // Match everything in a langtag prior to any variants, and maybe some |
|
197 // of the variants as well (which makes this pattern inefficient but |
|
198 // not wrong, for our purposes); |
|
199 "(?:" + alphanum + "{2,8}-)+" + |
|
200 // a variant, parenthesised so that we can refer back to it later; |
|
201 "(" + variant + ")-" + |
|
202 // zero or more subtags at least two characters long (thus stopping |
|
203 // before extension and privateuse components); |
|
204 "(?:" + alphanum + "{2,8}-)*" + |
|
205 // and the same variant again |
|
206 "\\1" + |
|
207 // ...but not followed by any characters that would turn it into a |
|
208 // different subtag. |
|
209 "(?!" + alphanum + ")"; |
|
210 |
|
211 // Language tags are case insensitive (RFC 5646 section 2.1.1), but for |
|
212 // this regular expression that's covered by having its character classes |
|
213 // list both upper- and lower-case characters. |
|
214 return new RegExp(duplicateVariant); |
|
215 }()); |
|
216 |
|
217 |
|
218 var duplicateSingletonRE = (function () { |
|
219 // RFC 5234 section B.1 |
|
220 // ALPHA = %x41-5A / %x61-7A ; A-Z / a-z |
|
221 var ALPHA = "[a-zA-Z]"; |
|
222 // DIGIT = %x30-39 |
|
223 // ; 0-9 |
|
224 var DIGIT = "[0-9]"; |
|
225 |
|
226 // RFC 5646 section 2.1 |
|
227 // alphanum = (ALPHA / DIGIT) ; letters and numbers |
|
228 var alphanum = "(?:" + ALPHA + "|" + DIGIT + ")"; |
|
229 // singleton = DIGIT ; 0 - 9 |
|
230 // / %x41-57 ; A - W |
|
231 // / %x59-5A ; Y - Z |
|
232 // / %x61-77 ; a - w |
|
233 // / %x79-7A ; y - z |
|
234 var singleton = "(?:" + DIGIT + "|[A-WY-Za-wy-z])"; |
|
235 |
|
236 // Match a langtag that contains a duplicate singleton. |
|
237 var duplicateSingleton = |
|
238 // Match a singleton subtag, parenthesised so that we can refer back to |
|
239 // it later; |
|
240 "-(" + singleton + ")-" + |
|
241 // then zero or more subtags; |
|
242 "(?:" + alphanum + "+-)*" + |
|
243 // and the same singleton again |
|
244 "\\1" + |
|
245 // ...but not followed by any characters that would turn it into a |
|
246 // different subtag. |
|
247 "(?!" + alphanum + ")"; |
|
248 |
|
249 // Language tags are case insensitive (RFC 5646 section 2.1.1), but for |
|
250 // this regular expression that's covered by having its character classes |
|
251 // list both upper- and lower-case characters. |
|
252 return new RegExp(duplicateSingleton); |
|
253 }()); |
|
254 |
|
255 |
|
256 /** |
|
257 * Verifies that the given string is a well-formed BCP 47 language tag |
|
258 * with no duplicate variant or singleton subtags. |
|
259 * |
|
260 * Spec: ECMAScript Internationalization API Specification, 6.2.2. |
|
261 */ |
|
262 function IsStructurallyValidLanguageTag(locale) { |
|
263 assert(typeof locale === "string", "IsStructurallyValidLanguageTag"); |
|
264 if (!regexp_test_no_statics(languageTagRE, locale)) |
|
265 return false; |
|
266 |
|
267 // Before checking for duplicate variant or singleton subtags with |
|
268 // regular expressions, we have to get private use subtag sequences |
|
269 // out of the picture. |
|
270 if (callFunction(std_String_startsWith, locale, "x-")) |
|
271 return true; |
|
272 var pos = callFunction(std_String_indexOf, locale, "-x-"); |
|
273 if (pos !== -1) |
|
274 locale = callFunction(std_String_substring, locale, 0, pos); |
|
275 |
|
276 // Check for duplicate variant or singleton subtags. |
|
277 return !regexp_test_no_statics(duplicateVariantRE, locale) && |
|
278 !regexp_test_no_statics(duplicateSingletonRE, locale); |
|
279 } |
|
280 |
|
281 |
|
282 /** |
|
283 * Canonicalizes the given structurally valid BCP 47 language tag, including |
|
284 * regularized case of subtags. For example, the language tag |
|
285 * Zh-NAN-haNS-bu-variant2-Variant1-u-ca-chinese-t-Zh-laTN-x-PRIVATE, where |
|
286 * |
|
287 * Zh ; 2*3ALPHA |
|
288 * -NAN ; ["-" extlang] |
|
289 * -haNS ; ["-" script] |
|
290 * -bu ; ["-" region] |
|
291 * -variant2 ; *("-" variant) |
|
292 * -Variant1 |
|
293 * -u-ca-chinese ; *("-" extension) |
|
294 * -t-Zh-laTN |
|
295 * -x-PRIVATE ; ["-" privateuse] |
|
296 * |
|
297 * becomes nan-Hans-mm-variant2-variant1-t-zh-latn-u-ca-chinese-x-private |
|
298 * |
|
299 * Spec: ECMAScript Internationalization API Specification, 6.2.3. |
|
300 * Spec: RFC 5646, section 4.5. |
|
301 */ |
|
302 function CanonicalizeLanguageTag(locale) { |
|
303 assert(IsStructurallyValidLanguageTag(locale), "CanonicalizeLanguageTag"); |
|
304 |
|
305 // The input |
|
306 // "Zh-NAN-haNS-bu-variant2-Variant1-u-ca-chinese-t-Zh-laTN-x-PRIVATE" |
|
307 // will be used throughout this method to illustrate how it works. |
|
308 |
|
309 // Language tags are compared and processed case-insensitively, so |
|
310 // technically it's not necessary to adjust case. But for easier processing, |
|
311 // and because the canonical form for most subtags is lower case, we start |
|
312 // with lower case for all. |
|
313 // "Zh-NAN-haNS-bu-variant2-Variant1-u-ca-chinese-t-Zh-laTN-x-PRIVATE" -> |
|
314 // "zh-nan-hans-bu-variant2-variant1-u-ca-chinese-t-zh-latn-x-private" |
|
315 locale = callFunction(std_String_toLowerCase, locale); |
|
316 |
|
317 // Handle mappings for complete tags. |
|
318 if (callFunction(std_Object_hasOwnProperty, langTagMappings, locale)) |
|
319 return langTagMappings[locale]; |
|
320 |
|
321 var subtags = callFunction(std_String_split, locale, "-"); |
|
322 var i = 0; |
|
323 |
|
324 // Handle the standard part: All subtags before the first singleton or "x". |
|
325 // "zh-nan-hans-bu-variant2-variant1" |
|
326 while (i < subtags.length) { |
|
327 var subtag = subtags[i]; |
|
328 |
|
329 // If we reach the start of an extension sequence or private use part, |
|
330 // we're done with this loop. We have to check for i > 0 because for |
|
331 // irregular language tags, such as i-klingon, the single-character |
|
332 // subtag "i" is not the start of an extension sequence. |
|
333 // In the example, we break at "u". |
|
334 if (subtag.length === 1 && (i > 0 || subtag === "x")) |
|
335 break; |
|
336 |
|
337 if (subtag.length === 4) { |
|
338 // 4-character subtags are script codes; their first character |
|
339 // needs to be capitalized. "hans" -> "Hans" |
|
340 subtag = callFunction(std_String_toUpperCase, subtag[0]) + |
|
341 callFunction(std_String_substring, subtag, 1); |
|
342 } else if (i !== 0 && subtag.length === 2) { |
|
343 // 2-character subtags that are not in initial position are region |
|
344 // codes; they need to be upper case. "bu" -> "BU" |
|
345 subtag = callFunction(std_String_toUpperCase, subtag); |
|
346 } |
|
347 if (callFunction(std_Object_hasOwnProperty, langSubtagMappings, subtag)) { |
|
348 // Replace deprecated subtags with their preferred values. |
|
349 // "BU" -> "MM" |
|
350 // This has to come after we capitalize region codes because |
|
351 // otherwise some language and region codes could be confused. |
|
352 // For example, "in" is an obsolete language code for Indonesian, |
|
353 // but "IN" is the country code for India. |
|
354 // Note that the script generating langSubtagMappings makes sure |
|
355 // that no regular subtag mapping will replace an extlang code. |
|
356 subtag = langSubtagMappings[subtag]; |
|
357 } else if (callFunction(std_Object_hasOwnProperty, extlangMappings, subtag)) { |
|
358 // Replace deprecated extlang subtags with their preferred values, |
|
359 // and remove the preceding subtag if it's a redundant prefix. |
|
360 // "zh-nan" -> "nan" |
|
361 // Note that the script generating extlangMappings makes sure that |
|
362 // no extlang mapping will replace a normal language code. |
|
363 subtag = extlangMappings[subtag].preferred; |
|
364 if (i === 1 && extlangMappings[subtag].prefix === subtags[0]) { |
|
365 callFunction(std_Array_shift, subtags); |
|
366 i--; |
|
367 } |
|
368 } |
|
369 subtags[i] = subtag; |
|
370 i++; |
|
371 } |
|
372 var normal = callFunction(std_Array_join, callFunction(std_Array_slice, subtags, 0, i), "-"); |
|
373 |
|
374 // Extension sequences are sorted by their singleton characters. |
|
375 // "u-ca-chinese-t-zh-latn" -> "t-zh-latn-u-ca-chinese" |
|
376 var extensions = new List(); |
|
377 while (i < subtags.length && subtags[i] !== "x") { |
|
378 var extensionStart = i; |
|
379 i++; |
|
380 while (i < subtags.length && subtags[i].length > 1) |
|
381 i++; |
|
382 var extension = callFunction(std_Array_join, callFunction(std_Array_slice, subtags, extensionStart, i), "-"); |
|
383 extensions.push(extension); |
|
384 } |
|
385 extensions.sort(); |
|
386 |
|
387 // Private use sequences are left as is. "x-private" |
|
388 var privateUse = ""; |
|
389 if (i < subtags.length) |
|
390 privateUse = callFunction(std_Array_join, callFunction(std_Array_slice, subtags, i), "-"); |
|
391 |
|
392 // Put everything back together. |
|
393 var canonical = normal; |
|
394 if (extensions.length > 0) |
|
395 canonical += "-" + extensions.join("-"); |
|
396 if (privateUse.length > 0) { |
|
397 // Be careful of a Language-Tag that is entirely privateuse. |
|
398 if (canonical.length > 0) |
|
399 canonical += "-" + privateUse; |
|
400 else |
|
401 canonical = privateUse; |
|
402 } |
|
403 |
|
404 return canonical; |
|
405 } |
|
406 |
|
407 |
|
408 // mappings from some commonly used old-style language tags to current flavors |
|
409 // with script codes |
|
410 var oldStyleLanguageTagMappings = { |
|
411 "pa-PK": "pa-Arab-PK", |
|
412 "zh-CN": "zh-Hans-CN", |
|
413 "zh-HK": "zh-Hant-HK", |
|
414 "zh-SG": "zh-Hans-SG", |
|
415 "zh-TW": "zh-Hant-TW" |
|
416 }; |
|
417 |
|
418 |
|
419 /** |
|
420 * Returns the BCP 47 language tag for the host environment's current locale. |
|
421 * |
|
422 * Spec: ECMAScript Internationalization API Specification, 6.2.4. |
|
423 */ |
|
424 function DefaultLocale() { |
|
425 // The locale of last resort is used if none of the available locales |
|
426 // satisfies a request. "en-GB" is used based on the assumptions that |
|
427 // English is the most common second language, that both en-GB and en-US |
|
428 // are normally available in an implementation, and that en-GB is more |
|
429 // representative of the English used in other locales. |
|
430 var localeOfLastResort = "en-GB"; |
|
431 |
|
432 var locale = RuntimeDefaultLocale(); |
|
433 if (!IsStructurallyValidLanguageTag(locale)) |
|
434 return localeOfLastResort; |
|
435 |
|
436 locale = CanonicalizeLanguageTag(locale); |
|
437 if (callFunction(std_Object_hasOwnProperty, oldStyleLanguageTagMappings, locale)) |
|
438 locale = oldStyleLanguageTagMappings[locale]; |
|
439 |
|
440 if (!(collatorInternalProperties.availableLocales()[locale] && |
|
441 numberFormatInternalProperties.availableLocales()[locale] && |
|
442 dateTimeFormatInternalProperties.availableLocales()[locale])) |
|
443 { |
|
444 locale = localeOfLastResort; |
|
445 } |
|
446 return locale; |
|
447 } |
|
448 |
|
449 |
|
450 /** |
|
451 * Verifies that the given string is a well-formed ISO 4217 currency code. |
|
452 * |
|
453 * Spec: ECMAScript Internationalization API Specification, 6.3.1. |
|
454 */ |
|
455 function IsWellFormedCurrencyCode(currency) { |
|
456 var c = ToString(currency); |
|
457 var normalized = toASCIIUpperCase(c); |
|
458 if (normalized.length !== 3) |
|
459 return false; |
|
460 return !regexp_test_no_statics(/[^A-Z]/, normalized); |
|
461 } |
|
462 |
|
463 |
|
464 /********** Locale and Parameter Negotiation **********/ |
|
465 |
|
466 |
|
467 /** |
|
468 * Add old-style language tags without script code for locales that in current |
|
469 * usage would include a script subtag. Returns the availableLocales argument |
|
470 * provided. |
|
471 * |
|
472 * Spec: ECMAScript Internationalization API Specification, 9.1. |
|
473 */ |
|
474 function addOldStyleLanguageTags(availableLocales) { |
|
475 var oldStyleLocales = std_Object_getOwnPropertyNames(oldStyleLanguageTagMappings); |
|
476 for (var i = 0; i < oldStyleLocales.length; i++) { |
|
477 var oldStyleLocale = oldStyleLocales[i]; |
|
478 if (availableLocales[oldStyleLanguageTagMappings[oldStyleLocale]]) |
|
479 availableLocales[oldStyleLocale] = true; |
|
480 } |
|
481 return availableLocales; |
|
482 } |
|
483 |
|
484 |
|
485 /** |
|
486 * Canonicalizes a locale list. |
|
487 * |
|
488 * Spec: ECMAScript Internationalization API Specification, 9.2.1. |
|
489 */ |
|
490 function CanonicalizeLocaleList(locales) { |
|
491 if (locales === undefined) |
|
492 return new List(); |
|
493 var seen = new List(); |
|
494 if (typeof locales === "string") |
|
495 locales = [locales]; |
|
496 var O = ToObject(locales); |
|
497 var len = TO_UINT32(O.length); |
|
498 var k = 0; |
|
499 while (k < len) { |
|
500 // Don't call ToString(k) - SpiderMonkey is faster with integers. |
|
501 var kPresent = HasProperty(O, k); |
|
502 if (kPresent) { |
|
503 var kValue = O[k]; |
|
504 if (!(typeof kValue === "string" || IsObject(kValue))) |
|
505 ThrowError(JSMSG_INVALID_LOCALES_ELEMENT); |
|
506 var tag = ToString(kValue); |
|
507 if (!IsStructurallyValidLanguageTag(tag)) |
|
508 ThrowError(JSMSG_INVALID_LANGUAGE_TAG, tag); |
|
509 tag = CanonicalizeLanguageTag(tag); |
|
510 if (seen.indexOf(tag) === -1) |
|
511 seen.push(tag); |
|
512 } |
|
513 k++; |
|
514 } |
|
515 return seen; |
|
516 } |
|
517 |
|
518 |
|
519 /** |
|
520 * Compares a BCP 47 language tag against the locales in availableLocales |
|
521 * and returns the best available match. Uses the fallback |
|
522 * mechanism of RFC 4647, section 3.4. |
|
523 * |
|
524 * Spec: ECMAScript Internationalization API Specification, 9.2.2. |
|
525 * Spec: RFC 4647, section 3.4. |
|
526 */ |
|
527 function BestAvailableLocale(availableLocales, locale) { |
|
528 assert(IsStructurallyValidLanguageTag(locale), "invalid BestAvailableLocale locale structure"); |
|
529 assert(locale === CanonicalizeLanguageTag(locale), "non-canonical BestAvailableLocale locale"); |
|
530 assert(callFunction(std_String_indexOf, locale, "-u-") === -1, "locale shouldn't contain -u-"); |
|
531 |
|
532 var candidate = locale; |
|
533 while (true) { |
|
534 if (availableLocales[candidate]) |
|
535 return candidate; |
|
536 var pos = callFunction(std_String_lastIndexOf, candidate, "-"); |
|
537 if (pos === -1) |
|
538 return undefined; |
|
539 if (pos >= 2 && candidate[pos - 2] === "-") |
|
540 pos -= 2; |
|
541 candidate = callFunction(std_String_substring, candidate, 0, pos); |
|
542 } |
|
543 } |
|
544 |
|
545 |
|
546 /** |
|
547 * Compares a BCP 47 language priority list against the set of locales in |
|
548 * availableLocales and determines the best available language to meet the |
|
549 * request. Options specified through Unicode extension subsequences are |
|
550 * ignored in the lookup, but information about such subsequences is returned |
|
551 * separately. |
|
552 * |
|
553 * This variant is based on the Lookup algorithm of RFC 4647 section 3.4. |
|
554 * |
|
555 * Spec: ECMAScript Internationalization API Specification, 9.2.3. |
|
556 * Spec: RFC 4647, section 3.4. |
|
557 */ |
|
558 function LookupMatcher(availableLocales, requestedLocales) { |
|
559 var i = 0; |
|
560 var len = requestedLocales.length; |
|
561 var availableLocale; |
|
562 var locale, noExtensionsLocale; |
|
563 while (i < len && availableLocale === undefined) { |
|
564 locale = requestedLocales[i]; |
|
565 noExtensionsLocale = removeUnicodeExtensions(locale); |
|
566 availableLocale = BestAvailableLocale(availableLocales, noExtensionsLocale); |
|
567 i++; |
|
568 } |
|
569 |
|
570 var result = new Record(); |
|
571 if (availableLocale !== undefined) { |
|
572 result.locale = availableLocale; |
|
573 if (locale !== noExtensionsLocale) { |
|
574 var extensionMatch = regexp_exec_no_statics(unicodeLocaleExtensionSequenceRE, locale); |
|
575 var extension = extensionMatch[0]; |
|
576 var extensionIndex = extensionMatch.index; |
|
577 result.extension = extension; |
|
578 result.extensionIndex = extensionIndex; |
|
579 } |
|
580 } else { |
|
581 result.locale = DefaultLocale(); |
|
582 } |
|
583 return result; |
|
584 } |
|
585 |
|
586 |
|
587 /** |
|
588 * Compares a BCP 47 language priority list against the set of locales in |
|
589 * availableLocales and determines the best available language to meet the |
|
590 * request. Options specified through Unicode extension subsequences are |
|
591 * ignored in the lookup, but information about such subsequences is returned |
|
592 * separately. |
|
593 * |
|
594 * Spec: ECMAScript Internationalization API Specification, 9.2.4. |
|
595 */ |
|
596 function BestFitMatcher(availableLocales, requestedLocales) { |
|
597 // this implementation doesn't have anything better |
|
598 return LookupMatcher(availableLocales, requestedLocales); |
|
599 } |
|
600 |
|
601 |
|
602 /** |
|
603 * Compares a BCP 47 language priority list against availableLocales and |
|
604 * determines the best available language to meet the request. Options specified |
|
605 * through Unicode extension subsequences are negotiated separately, taking the |
|
606 * caller's relevant extensions and locale data as well as client-provided |
|
607 * options into consideration. |
|
608 * |
|
609 * Spec: ECMAScript Internationalization API Specification, 9.2.5. |
|
610 */ |
|
611 function ResolveLocale(availableLocales, requestedLocales, options, relevantExtensionKeys, localeData) { |
|
612 /*jshint laxbreak: true */ |
|
613 |
|
614 // Steps 1-3. |
|
615 var matcher = options.localeMatcher; |
|
616 var r = (matcher === "lookup") |
|
617 ? LookupMatcher(availableLocales, requestedLocales) |
|
618 : BestFitMatcher(availableLocales, requestedLocales); |
|
619 |
|
620 // Step 4. |
|
621 var foundLocale = r.locale; |
|
622 |
|
623 // Step 5.a. |
|
624 var extension = r.extension; |
|
625 var extensionIndex, extensionSubtags, extensionSubtagsLength; |
|
626 |
|
627 // Step 5. |
|
628 if (extension !== undefined) { |
|
629 // Step 5.b. |
|
630 extensionIndex = r.extensionIndex; |
|
631 |
|
632 // Steps 5.d-e. |
|
633 extensionSubtags = callFunction(std_String_split, extension, "-"); |
|
634 extensionSubtagsLength = extensionSubtags.length; |
|
635 } |
|
636 |
|
637 // Steps 6-7. |
|
638 var result = new Record(); |
|
639 result.dataLocale = foundLocale; |
|
640 |
|
641 // Step 8. |
|
642 var supportedExtension = "-u"; |
|
643 |
|
644 // Steps 9-11. |
|
645 var i = 0; |
|
646 var len = relevantExtensionKeys.length; |
|
647 while (i < len) { |
|
648 // Steps 11.a-c. |
|
649 var key = relevantExtensionKeys[i]; |
|
650 |
|
651 // In this implementation, localeData is a function, not an object. |
|
652 var foundLocaleData = localeData(foundLocale); |
|
653 var keyLocaleData = foundLocaleData[key]; |
|
654 |
|
655 // Locale data provides default value. |
|
656 // Step 11.d. |
|
657 var value = keyLocaleData[0]; |
|
658 |
|
659 // Locale tag may override. |
|
660 |
|
661 // Step 11.e. |
|
662 var supportedExtensionAddition = ""; |
|
663 |
|
664 // Step 11.f is implemented by Utilities.js. |
|
665 |
|
666 var valuePos; |
|
667 |
|
668 // Step 11.g. |
|
669 if (extensionSubtags !== undefined) { |
|
670 // Step 11.g.i. |
|
671 var keyPos = callFunction(std_Array_indexOf, extensionSubtags, key); |
|
672 |
|
673 // Step 11.g.ii. |
|
674 if (keyPos !== -1) { |
|
675 // Step 11.g.ii.1. |
|
676 if (keyPos + 1 < extensionSubtagsLength && |
|
677 extensionSubtags[keyPos + 1].length > 2) |
|
678 { |
|
679 // Step 11.g.ii.1.a. |
|
680 var requestedValue = extensionSubtags[keyPos + 1]; |
|
681 |
|
682 // Step 11.g.ii.1.b. |
|
683 valuePos = callFunction(std_Array_indexOf, keyLocaleData, requestedValue); |
|
684 |
|
685 // Step 11.g.ii.1.c. |
|
686 if (valuePos !== -1) { |
|
687 value = requestedValue; |
|
688 supportedExtensionAddition = "-" + key + "-" + value; |
|
689 } |
|
690 } else { |
|
691 // Step 11.g.ii.2. |
|
692 |
|
693 // According to the LDML spec, if there's no type value, |
|
694 // and true is an allowed value, it's used. |
|
695 |
|
696 // Step 11.g.ii.2.a. |
|
697 valuePos = callFunction(std_Array_indexOf, keyLocaleData, "true"); |
|
698 |
|
699 // Step 11.g.ii.2.b. |
|
700 if (valuePos !== -1) |
|
701 value = "true"; |
|
702 } |
|
703 } |
|
704 } |
|
705 |
|
706 // Options override all. |
|
707 |
|
708 // Step 11.h.i. |
|
709 var optionsValue = options[key]; |
|
710 |
|
711 // Step 11.h, 11.h.ii. |
|
712 if (optionsValue !== undefined && |
|
713 callFunction(std_Array_indexOf, keyLocaleData, optionsValue) !== -1) |
|
714 { |
|
715 // Step 11.h.ii.1. |
|
716 if (optionsValue !== value) { |
|
717 value = optionsValue; |
|
718 supportedExtensionAddition = ""; |
|
719 } |
|
720 } |
|
721 |
|
722 // Steps 11.i-k. |
|
723 result[key] = value; |
|
724 supportedExtension += supportedExtensionAddition; |
|
725 i++; |
|
726 } |
|
727 |
|
728 // Step 12. |
|
729 if (supportedExtension.length > 2) { |
|
730 var preExtension = callFunction(std_String_substring, foundLocale, 0, extensionIndex); |
|
731 var postExtension = callFunction(std_String_substring, foundLocale, extensionIndex); |
|
732 foundLocale = preExtension + supportedExtension + postExtension; |
|
733 } |
|
734 |
|
735 // Steps 13-14. |
|
736 result.locale = foundLocale; |
|
737 return result; |
|
738 } |
|
739 |
|
740 |
|
741 /** |
|
742 * Returns the subset of requestedLocales for which availableLocales has a |
|
743 * matching (possibly fallback) locale. Locales appear in the same order in the |
|
744 * returned list as in the input list. |
|
745 * |
|
746 * Spec: ECMAScript Internationalization API Specification, 9.2.6. |
|
747 */ |
|
748 function LookupSupportedLocales(availableLocales, requestedLocales) { |
|
749 // Steps 1-2. |
|
750 var len = requestedLocales.length; |
|
751 var subset = new List(); |
|
752 |
|
753 // Steps 3-4. |
|
754 var k = 0; |
|
755 while (k < len) { |
|
756 // Steps 4.a-b. |
|
757 var locale = requestedLocales[k]; |
|
758 var noExtensionsLocale = removeUnicodeExtensions(locale); |
|
759 |
|
760 // Step 4.c-d. |
|
761 var availableLocale = BestAvailableLocale(availableLocales, noExtensionsLocale); |
|
762 if (availableLocale !== undefined) |
|
763 subset.push(locale); |
|
764 |
|
765 // Step 4.e. |
|
766 k++; |
|
767 } |
|
768 |
|
769 // Steps 5-6. |
|
770 return subset.slice(0); |
|
771 } |
|
772 |
|
773 |
|
774 /** |
|
775 * Returns the subset of requestedLocales for which availableLocales has a |
|
776 * matching (possibly fallback) locale. Locales appear in the same order in the |
|
777 * returned list as in the input list. |
|
778 * |
|
779 * Spec: ECMAScript Internationalization API Specification, 9.2.7. |
|
780 */ |
|
781 function BestFitSupportedLocales(availableLocales, requestedLocales) { |
|
782 // don't have anything better |
|
783 return LookupSupportedLocales(availableLocales, requestedLocales); |
|
784 } |
|
785 |
|
786 |
|
787 /** |
|
788 * Returns the subset of requestedLocales for which availableLocales has a |
|
789 * matching (possibly fallback) locale. Locales appear in the same order in the |
|
790 * returned list as in the input list. |
|
791 * |
|
792 * Spec: ECMAScript Internationalization API Specification, 9.2.8. |
|
793 */ |
|
794 function SupportedLocales(availableLocales, requestedLocales, options) { |
|
795 /*jshint laxbreak: true */ |
|
796 |
|
797 // Step 1. |
|
798 var matcher; |
|
799 if (options !== undefined) { |
|
800 // Steps 1.a-b. |
|
801 options = ToObject(options); |
|
802 matcher = options.localeMatcher; |
|
803 |
|
804 // Step 1.c. |
|
805 if (matcher !== undefined) { |
|
806 matcher = ToString(matcher); |
|
807 if (matcher !== "lookup" && matcher !== "best fit") |
|
808 ThrowError(JSMSG_INVALID_LOCALE_MATCHER, matcher); |
|
809 } |
|
810 } |
|
811 |
|
812 // Steps 2-3. |
|
813 var subset = (matcher === undefined || matcher === "best fit") |
|
814 ? BestFitSupportedLocales(availableLocales, requestedLocales) |
|
815 : LookupSupportedLocales(availableLocales, requestedLocales); |
|
816 |
|
817 // Step 4. |
|
818 for (var i = 0; i < subset.length; i++) { |
|
819 _DefineValueProperty(subset, i, subset[i], |
|
820 ATTR_ENUMERABLE | ATTR_NONCONFIGURABLE | ATTR_NONWRITABLE); |
|
821 } |
|
822 _DefineValueProperty(subset, "length", subset.length, |
|
823 ATTR_NONENUMERABLE | ATTR_NONCONFIGURABLE | ATTR_NONWRITABLE); |
|
824 |
|
825 // Step 5. |
|
826 return subset; |
|
827 } |
|
828 |
|
829 |
|
830 /** |
|
831 * Extracts a property value from the provided options object, converts it to |
|
832 * the required type, checks whether it is one of a list of allowed values, |
|
833 * and fills in a fallback value if necessary. |
|
834 * |
|
835 * Spec: ECMAScript Internationalization API Specification, 9.2.9. |
|
836 */ |
|
837 function GetOption(options, property, type, values, fallback) { |
|
838 // Step 1. |
|
839 var value = options[property]; |
|
840 |
|
841 // Step 2. |
|
842 if (value !== undefined) { |
|
843 // Steps 2.a-c. |
|
844 if (type === "boolean") |
|
845 value = ToBoolean(value); |
|
846 else if (type === "string") |
|
847 value = ToString(value); |
|
848 else |
|
849 assert(false, "GetOption"); |
|
850 |
|
851 // Step 2.d. |
|
852 if (values !== undefined && callFunction(std_Array_indexOf, values, value) === -1) |
|
853 ThrowError(JSMSG_INVALID_OPTION_VALUE, property, value); |
|
854 |
|
855 // Step 2.e. |
|
856 return value; |
|
857 } |
|
858 |
|
859 // Step 3. |
|
860 return fallback; |
|
861 } |
|
862 |
|
863 /** |
|
864 * Extracts a property value from the provided options object, converts it to a |
|
865 * Number value, checks whether it is in the allowed range, and fills in a |
|
866 * fallback value if necessary. |
|
867 * |
|
868 * Spec: ECMAScript Internationalization API Specification, 9.2.10. |
|
869 */ |
|
870 function GetNumberOption(options, property, minimum, maximum, fallback) { |
|
871 assert(typeof minimum === "number", "GetNumberOption"); |
|
872 assert(typeof maximum === "number", "GetNumberOption"); |
|
873 assert(fallback === undefined || (fallback >= minimum && fallback <= maximum), "GetNumberOption"); |
|
874 |
|
875 // Step 1. |
|
876 var value = options[property]; |
|
877 |
|
878 // Step 2. |
|
879 if (value !== undefined) { |
|
880 value = ToNumber(value); |
|
881 if (std_isNaN(value) || value < minimum || value > maximum) |
|
882 ThrowError(JSMSG_INVALID_DIGITS_VALUE, value); |
|
883 return std_Math_floor(value); |
|
884 } |
|
885 |
|
886 // Step 3. |
|
887 return fallback; |
|
888 } |
|
889 |
|
890 |
|
891 /********** Property access for Intl objects **********/ |
|
892 |
|
893 |
|
894 /** |
|
895 * Set a normal public property p of o to value v, but use Object.defineProperty |
|
896 * to avoid interference from setters on Object.prototype. |
|
897 */ |
|
898 function defineProperty(o, p, v) { |
|
899 _DefineValueProperty(o, p, v, ATTR_ENUMERABLE | ATTR_CONFIGURABLE | ATTR_WRITABLE); |
|
900 } |
|
901 |
|
902 |
|
903 /** |
|
904 * Weak map used to track the initialize-as-Intl status (and, if an object has |
|
905 * been so initialized, the Intl-specific internal properties) of all objects. |
|
906 * Presence of an object as a key within this map indicates that the object has |
|
907 * its [[initializedIntlObject]] internal property set to true. The associated |
|
908 * value is an object whose structure is documented in |initializeIntlObject| |
|
909 * below. |
|
910 * |
|
911 * Ideally we'd be using private symbols for internal properties, but |
|
912 * SpiderMonkey doesn't have those yet. |
|
913 */ |
|
914 var internalsMap = new WeakMap(); |
|
915 |
|
916 |
|
917 /** |
|
918 * Set the [[initializedIntlObject]] internal property of |obj| to true. |
|
919 */ |
|
920 function initializeIntlObject(obj) { |
|
921 assert(IsObject(obj), "Non-object passed to initializeIntlObject"); |
|
922 |
|
923 // Intl-initialized objects are weird. They have [[initializedIntlObject]] |
|
924 // set on them, but they don't *necessarily* have any other properties. |
|
925 |
|
926 var internals = std_Object_create(null); |
|
927 |
|
928 // The meaning of an internals object for an object |obj| is as follows. |
|
929 // |
|
930 // If the .type is "partial", |obj| has [[initializedIntlObject]] set but |
|
931 // nothing else. No other property of |internals| can be used. (This |
|
932 // occurs when InitializeCollator or similar marks an object as |
|
933 // [[initializedIntlObject]] but fails before marking it as the appropriate |
|
934 // more-specific type ["Collator", "DateTimeFormat", "NumberFormat"].) |
|
935 // |
|
936 // Otherwise, the .type indicates the type of Intl object that |obj| is: |
|
937 // "Collator", "DateTimeFormat", or "NumberFormat" (likely with more coming |
|
938 // in future Intl specs). In these cases |obj| *conceptually* also has |
|
939 // [[initializedCollator]] or similar set, and all the other properties |
|
940 // implied by that. |
|
941 // |
|
942 // If |internals| doesn't have a "partial" .type, two additional properties |
|
943 // have meaning. The .lazyData property stores information needed to |
|
944 // compute -- without observable side effects -- the actual internal Intl |
|
945 // properties of |obj|. If it is non-null, then the actual internal |
|
946 // properties haven't been computed, and .lazyData must be processed by |
|
947 // |setInternalProperties| before internal Intl property values are |
|
948 // available. If it is null, then the .internalProps property contains an |
|
949 // object whose properties are the internal Intl properties of |obj|. |
|
950 |
|
951 internals.type = "partial"; |
|
952 internals.lazyData = null; |
|
953 internals.internalProps = null; |
|
954 |
|
955 callFunction(std_WeakMap_set, internalsMap, obj, internals); |
|
956 return internals; |
|
957 } |
|
958 |
|
959 |
|
960 /** |
|
961 * Mark |internals| as having the given type and lazy data. |
|
962 */ |
|
963 function setLazyData(internals, type, lazyData) |
|
964 { |
|
965 assert(internals.type === "partial", "can't set lazy data for anything but a newborn"); |
|
966 assert(type === "Collator" || type === "DateTimeFormat" || type == "NumberFormat", "bad type"); |
|
967 assert(IsObject(lazyData), "non-object lazy data"); |
|
968 |
|
969 // Set in reverse order so that the .type change is a barrier. |
|
970 internals.lazyData = lazyData; |
|
971 internals.type = type; |
|
972 } |
|
973 |
|
974 |
|
975 /** |
|
976 * Set the internal properties object for an |internals| object previously |
|
977 * associated with lazy data. |
|
978 */ |
|
979 function setInternalProperties(internals, internalProps) |
|
980 { |
|
981 assert(internals.type !== "partial", "newborn internals can't have computed internals"); |
|
982 assert(IsObject(internals.lazyData), "lazy data must exist already"); |
|
983 assert(IsObject(internalProps), "internalProps argument should be an object"); |
|
984 |
|
985 // Set in reverse order so that the .lazyData nulling is a barrier. |
|
986 internals.internalProps = internalProps; |
|
987 internals.lazyData = null; |
|
988 } |
|
989 |
|
990 |
|
991 /** |
|
992 * Get the existing internal properties out of a non-newborn |internals|, or |
|
993 * null if none have been computed. |
|
994 */ |
|
995 function maybeInternalProperties(internals) |
|
996 { |
|
997 assert(IsObject(internals), "non-object passed to maybeInternalProperties"); |
|
998 assert(internals.type !== "partial", "maybeInternalProperties must only be used on completely-initialized internals objects"); |
|
999 var lazyData = internals.lazyData; |
|
1000 if (lazyData) |
|
1001 return null; |
|
1002 assert(IsObject(internals.internalProps), "missing lazy data and computed internals"); |
|
1003 return internals.internalProps; |
|
1004 } |
|
1005 |
|
1006 |
|
1007 /** |
|
1008 * Return whether |obj| has an[[initializedIntlObject]] property set to true. |
|
1009 */ |
|
1010 function isInitializedIntlObject(obj) { |
|
1011 #ifdef DEBUG |
|
1012 var internals = callFunction(std_WeakMap_get, internalsMap, obj); |
|
1013 if (IsObject(internals)) { |
|
1014 assert(callFunction(std_Object_hasOwnProperty, internals, "type"), "missing type"); |
|
1015 var type = internals.type; |
|
1016 assert(type === "partial" || type === "Collator" || type === "DateTimeFormat" || type === "NumberFormat", "unexpected type"); |
|
1017 assert(callFunction(std_Object_hasOwnProperty, internals, "lazyData"), "missing lazyData"); |
|
1018 assert(callFunction(std_Object_hasOwnProperty, internals, "internalProps"), "missing internalProps"); |
|
1019 } else { |
|
1020 assert(internals === undefined, "bad mapping for |obj|"); |
|
1021 } |
|
1022 #endif |
|
1023 return callFunction(std_WeakMap_has, internalsMap, obj); |
|
1024 } |
|
1025 |
|
1026 |
|
1027 /** |
|
1028 * Check that |obj| meets the requirements for "this Collator object", "this |
|
1029 * NumberFormat object", or "this DateTimeFormat object" as used in the method |
|
1030 * with the given name. Throw a TypeError if |obj| doesn't meet these |
|
1031 * requirements. But if it does, return |obj|'s internals object (*not* the |
|
1032 * object holding its internal properties!), associated with it by |
|
1033 * |internalsMap|, with structure specified above. |
|
1034 * |
|
1035 * Spec: ECMAScript Internationalization API Specification, 10.3. |
|
1036 * Spec: ECMAScript Internationalization API Specification, 11.3. |
|
1037 * Spec: ECMAScript Internationalization API Specification, 12.3. |
|
1038 */ |
|
1039 function getIntlObjectInternals(obj, className, methodName) { |
|
1040 assert(typeof className === "string", "bad className for getIntlObjectInternals"); |
|
1041 |
|
1042 var internals = callFunction(std_WeakMap_get, internalsMap, obj); |
|
1043 assert(internals === undefined || isInitializedIntlObject(obj), "bad mapping in internalsMap"); |
|
1044 |
|
1045 if (internals === undefined || internals.type !== className) |
|
1046 ThrowError(JSMSG_INTL_OBJECT_NOT_INITED, className, methodName, className); |
|
1047 |
|
1048 return internals; |
|
1049 } |
|
1050 |
|
1051 |
|
1052 /** |
|
1053 * Get the internal properties of known-Intl object |obj|. For use only by |
|
1054 * C++ code that knows what it's doing! |
|
1055 */ |
|
1056 function getInternals(obj) |
|
1057 { |
|
1058 assert(isInitializedIntlObject(obj), "for use only on guaranteed Intl objects"); |
|
1059 |
|
1060 var internals = callFunction(std_WeakMap_get, internalsMap, obj); |
|
1061 |
|
1062 assert(internals.type !== "partial", "must have been successfully initialized"); |
|
1063 var lazyData = internals.lazyData; |
|
1064 if (!lazyData) |
|
1065 return internals.internalProps; |
|
1066 |
|
1067 var internalProps; |
|
1068 var type = internals.type; |
|
1069 if (type === "Collator") |
|
1070 internalProps = resolveCollatorInternals(lazyData) |
|
1071 else if (type === "DateTimeFormat") |
|
1072 internalProps = resolveDateTimeFormatInternals(lazyData) |
|
1073 else |
|
1074 internalProps = resolveNumberFormatInternals(lazyData); |
|
1075 setInternalProperties(internals, internalProps); |
|
1076 return internalProps; |
|
1077 } |
|
1078 |
|
1079 |
|
1080 /********** Intl.Collator **********/ |
|
1081 |
|
1082 |
|
1083 /** |
|
1084 * Mapping from Unicode extension keys for collation to options properties, |
|
1085 * their types and permissible values. |
|
1086 * |
|
1087 * Spec: ECMAScript Internationalization API Specification, 10.1.1. |
|
1088 */ |
|
1089 var collatorKeyMappings = { |
|
1090 kn: {property: "numeric", type: "boolean"}, |
|
1091 kf: {property: "caseFirst", type: "string", values: ["upper", "lower", "false"]} |
|
1092 }; |
|
1093 |
|
1094 |
|
1095 /** |
|
1096 * Compute an internal properties object from |lazyCollatorData|. |
|
1097 */ |
|
1098 function resolveCollatorInternals(lazyCollatorData) |
|
1099 { |
|
1100 assert(IsObject(lazyCollatorData), "lazy data not an object?"); |
|
1101 |
|
1102 var internalProps = std_Object_create(null); |
|
1103 |
|
1104 // Step 7. |
|
1105 internalProps.usage = lazyCollatorData.usage; |
|
1106 |
|
1107 // Step 8. |
|
1108 var Collator = collatorInternalProperties; |
|
1109 |
|
1110 // Step 9. |
|
1111 var collatorIsSorting = lazyCollatorData.usage === "sort"; |
|
1112 var localeData = collatorIsSorting |
|
1113 ? Collator.sortLocaleData |
|
1114 : Collator.searchLocaleData; |
|
1115 |
|
1116 // Compute effective locale. |
|
1117 // Step 14. |
|
1118 var relevantExtensionKeys = Collator.relevantExtensionKeys; |
|
1119 |
|
1120 // Step 15. |
|
1121 var r = ResolveLocale(Collator.availableLocales(), |
|
1122 lazyCollatorData.requestedLocales, |
|
1123 lazyCollatorData.opt, |
|
1124 relevantExtensionKeys, |
|
1125 localeData); |
|
1126 |
|
1127 // Step 16. |
|
1128 internalProps.locale = r.locale; |
|
1129 |
|
1130 // Steps 17-19. |
|
1131 var key, property, value, mapping; |
|
1132 var i = 0, len = relevantExtensionKeys.length; |
|
1133 while (i < len) { |
|
1134 // Step 19.a. |
|
1135 key = relevantExtensionKeys[i]; |
|
1136 if (key === "co") { |
|
1137 // Step 19.b. |
|
1138 property = "collation"; |
|
1139 value = r.co === null ? "default" : r.co; |
|
1140 } else { |
|
1141 // Step 19.c. |
|
1142 mapping = collatorKeyMappings[key]; |
|
1143 property = mapping.property; |
|
1144 value = r[key]; |
|
1145 if (mapping.type === "boolean") |
|
1146 value = value === "true"; |
|
1147 } |
|
1148 |
|
1149 // Step 19.d. |
|
1150 internalProps[property] = value; |
|
1151 |
|
1152 // Step 19.e. |
|
1153 i++; |
|
1154 } |
|
1155 |
|
1156 // Compute remaining collation options. |
|
1157 // Steps 21-22. |
|
1158 var s = lazyCollatorData.rawSensitivity; |
|
1159 if (s === undefined) { |
|
1160 if (collatorIsSorting) { |
|
1161 // Step 21.a. |
|
1162 s = "variant"; |
|
1163 } else { |
|
1164 // Step 21.b. |
|
1165 var dataLocale = r.dataLocale; |
|
1166 var dataLocaleData = localeData(dataLocale); |
|
1167 s = dataLocaleData.sensitivity; |
|
1168 } |
|
1169 } |
|
1170 internalProps.sensitivity = s; |
|
1171 |
|
1172 // Step 24. |
|
1173 internalProps.ignorePunctuation = lazyCollatorData.ignorePunctuation; |
|
1174 |
|
1175 // Step 25. |
|
1176 internalProps.boundFormat = undefined; |
|
1177 |
|
1178 // The caller is responsible for associating |internalProps| with the right |
|
1179 // object using |setInternalProperties|. |
|
1180 return internalProps; |
|
1181 } |
|
1182 |
|
1183 |
|
1184 /** |
|
1185 * Returns an object containing the Collator internal properties of |obj|, or |
|
1186 * throws a TypeError if |obj| isn't Collator-initialized. |
|
1187 */ |
|
1188 function getCollatorInternals(obj, methodName) { |
|
1189 var internals = getIntlObjectInternals(obj, "Collator", methodName); |
|
1190 assert(internals.type === "Collator", "bad type escaped getIntlObjectInternals"); |
|
1191 |
|
1192 // If internal properties have already been computed, use them. |
|
1193 var internalProps = maybeInternalProperties(internals); |
|
1194 if (internalProps) |
|
1195 return internalProps; |
|
1196 |
|
1197 // Otherwise it's time to fully create them. |
|
1198 internalProps = resolveCollatorInternals(internals.lazyData); |
|
1199 setInternalProperties(internals, internalProps); |
|
1200 return internalProps; |
|
1201 } |
|
1202 |
|
1203 |
|
1204 /** |
|
1205 * Initializes an object as a Collator. |
|
1206 * |
|
1207 * This method is complicated a moderate bit by its implementing initialization |
|
1208 * as a *lazy* concept. Everything that must happen now, does -- but we defer |
|
1209 * all the work we can until the object is actually used as a Collator. This |
|
1210 * later work occurs in |resolveCollatorInternals|; steps not noted here occur |
|
1211 * there. |
|
1212 * |
|
1213 * Spec: ECMAScript Internationalization API Specification, 10.1.1. |
|
1214 */ |
|
1215 function InitializeCollator(collator, locales, options) { |
|
1216 assert(IsObject(collator), "InitializeCollator"); |
|
1217 |
|
1218 // Step 1. |
|
1219 if (isInitializedIntlObject(collator)) |
|
1220 ThrowError(JSMSG_INTL_OBJECT_REINITED); |
|
1221 |
|
1222 // Step 2. |
|
1223 var internals = initializeIntlObject(collator); |
|
1224 |
|
1225 // Lazy Collator data has the following structure: |
|
1226 // |
|
1227 // { |
|
1228 // requestedLocales: List of locales, |
|
1229 // usage: "sort" / "search", |
|
1230 // opt: // opt object computed in InitializeCollator |
|
1231 // { |
|
1232 // localeMatcher: "lookup" / "best fit", |
|
1233 // kn: true / false / undefined, |
|
1234 // kf: "upper" / "lower" / "false" / undefined |
|
1235 // } |
|
1236 // rawSensitivity: "base" / "accent" / "case" / "variant" / undefined, |
|
1237 // ignorePunctuation: true / false |
|
1238 // } |
|
1239 // |
|
1240 // Note that lazy data is only installed as a final step of initialization, |
|
1241 // so every Collator lazy data object has *all* these properties, never a |
|
1242 // subset of them. |
|
1243 var lazyCollatorData = std_Object_create(null); |
|
1244 |
|
1245 // Step 3. |
|
1246 var requestedLocales = CanonicalizeLocaleList(locales); |
|
1247 lazyCollatorData.requestedLocales = requestedLocales; |
|
1248 |
|
1249 // Steps 4-5. |
|
1250 // |
|
1251 // If we ever need more speed here at startup, we should try to detect the |
|
1252 // case where |options === undefined| and Object.prototype hasn't been |
|
1253 // mucked with. (|options| is fully consumed in this method, so it's not a |
|
1254 // concern that Object.prototype might be touched between now and when |
|
1255 // |resolveCollatorInternals| is called.) For now, just keep it simple. |
|
1256 if (options === undefined) |
|
1257 options = {}; |
|
1258 else |
|
1259 options = ToObject(options); |
|
1260 |
|
1261 // Compute options that impact interpretation of locale. |
|
1262 // Step 6. |
|
1263 var u = GetOption(options, "usage", "string", ["sort", "search"], "sort"); |
|
1264 lazyCollatorData.usage = u; |
|
1265 |
|
1266 // Step 10. |
|
1267 var opt = new Record(); |
|
1268 lazyCollatorData.opt = opt; |
|
1269 |
|
1270 // Steps 11-12. |
|
1271 var matcher = GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], "best fit"); |
|
1272 opt.localeMatcher = matcher; |
|
1273 |
|
1274 // Step 13, unrolled. |
|
1275 var numericValue = GetOption(options, "numeric", "boolean", undefined, undefined); |
|
1276 if (numericValue !== undefined) |
|
1277 numericValue = callFunction(std_Boolean_toString, numericValue); |
|
1278 opt.kn = numericValue; |
|
1279 |
|
1280 var caseFirstValue = GetOption(options, "caseFirst", "string", ["upper", "lower", "false"], undefined); |
|
1281 opt.kf = caseFirstValue; |
|
1282 |
|
1283 // Compute remaining collation options. |
|
1284 // Step 20. |
|
1285 var s = GetOption(options, "sensitivity", "string", |
|
1286 ["base", "accent", "case", "variant"], undefined); |
|
1287 lazyCollatorData.rawSensitivity = s; |
|
1288 |
|
1289 // Step 23. |
|
1290 var ip = GetOption(options, "ignorePunctuation", "boolean", undefined, false); |
|
1291 lazyCollatorData.ignorePunctuation = ip; |
|
1292 |
|
1293 // Step 26. |
|
1294 // |
|
1295 // We've done everything that must be done now: mark the lazy data as fully |
|
1296 // computed and install it. |
|
1297 setLazyData(internals, "Collator", lazyCollatorData); |
|
1298 } |
|
1299 |
|
1300 |
|
1301 /** |
|
1302 * Returns the subset of the given locale list for which this locale list has a |
|
1303 * matching (possibly fallback) locale. Locales appear in the same order in the |
|
1304 * returned list as in the input list. |
|
1305 * |
|
1306 * Spec: ECMAScript Internationalization API Specification, 10.2.2. |
|
1307 */ |
|
1308 function Intl_Collator_supportedLocalesOf(locales /*, options*/) { |
|
1309 var options = arguments.length > 1 ? arguments[1] : undefined; |
|
1310 |
|
1311 var availableLocales = collatorInternalProperties.availableLocales(); |
|
1312 var requestedLocales = CanonicalizeLocaleList(locales); |
|
1313 return SupportedLocales(availableLocales, requestedLocales, options); |
|
1314 } |
|
1315 |
|
1316 |
|
1317 /** |
|
1318 * Collator internal properties. |
|
1319 * |
|
1320 * Spec: ECMAScript Internationalization API Specification, 9.1 and 10.2.3. |
|
1321 */ |
|
1322 var collatorInternalProperties = { |
|
1323 sortLocaleData: collatorSortLocaleData, |
|
1324 searchLocaleData: collatorSearchLocaleData, |
|
1325 _availableLocales: null, |
|
1326 availableLocales: function() |
|
1327 { |
|
1328 var locales = this._availableLocales; |
|
1329 if (locales) |
|
1330 return locales; |
|
1331 return (this._availableLocales = |
|
1332 addOldStyleLanguageTags(intl_Collator_availableLocales())); |
|
1333 }, |
|
1334 relevantExtensionKeys: ["co", "kn"] |
|
1335 }; |
|
1336 |
|
1337 |
|
1338 function collatorSortLocaleData(locale) { |
|
1339 var collations = intl_availableCollations(locale); |
|
1340 callFunction(std_Array_unshift, collations, null); |
|
1341 return { |
|
1342 co: collations, |
|
1343 kn: ["false", "true"] |
|
1344 }; |
|
1345 } |
|
1346 |
|
1347 |
|
1348 function collatorSearchLocaleData(locale) { |
|
1349 return { |
|
1350 co: [null], |
|
1351 kn: ["false", "true"], |
|
1352 // In theory the default sensitivity is locale dependent; |
|
1353 // in reality the CLDR/ICU default strength is always tertiary. |
|
1354 sensitivity: "variant" |
|
1355 }; |
|
1356 } |
|
1357 |
|
1358 |
|
1359 /** |
|
1360 * Function to be bound and returned by Intl.Collator.prototype.format. |
|
1361 * |
|
1362 * Spec: ECMAScript Internationalization API Specification, 12.3.2. |
|
1363 */ |
|
1364 function collatorCompareToBind(x, y) { |
|
1365 // Steps 1.a.i-ii implemented by ECMAScript declaration binding instantiation, |
|
1366 // ES5.1 10.5, step 4.d.ii. |
|
1367 |
|
1368 // Step 1.a.iii-v. |
|
1369 var X = ToString(x); |
|
1370 var Y = ToString(y); |
|
1371 return intl_CompareStrings(this, X, Y); |
|
1372 } |
|
1373 |
|
1374 |
|
1375 /** |
|
1376 * Returns a function bound to this Collator that compares x (converted to a |
|
1377 * String value) and y (converted to a String value), |
|
1378 * and returns a number less than 0 if x < y, 0 if x = y, or a number greater |
|
1379 * than 0 if x > y according to the sort order for the locale and collation |
|
1380 * options of this Collator object. |
|
1381 * |
|
1382 * Spec: ECMAScript Internationalization API Specification, 10.3.2. |
|
1383 */ |
|
1384 function Intl_Collator_compare_get() { |
|
1385 // Check "this Collator object" per introduction of section 10.3. |
|
1386 var internals = getCollatorInternals(this, "compare"); |
|
1387 |
|
1388 // Step 1. |
|
1389 if (internals.boundCompare === undefined) { |
|
1390 // Step 1.a. |
|
1391 var F = collatorCompareToBind; |
|
1392 |
|
1393 // Step 1.b-d. |
|
1394 var bc = callFunction(std_Function_bind, F, this); |
|
1395 internals.boundCompare = bc; |
|
1396 } |
|
1397 |
|
1398 // Step 2. |
|
1399 return internals.boundCompare; |
|
1400 } |
|
1401 |
|
1402 |
|
1403 /** |
|
1404 * Returns the resolved options for a Collator object. |
|
1405 * |
|
1406 * Spec: ECMAScript Internationalization API Specification, 10.3.3 and 10.4. |
|
1407 */ |
|
1408 function Intl_Collator_resolvedOptions() { |
|
1409 // Check "this Collator object" per introduction of section 10.3. |
|
1410 var internals = getCollatorInternals(this, "resolvedOptions"); |
|
1411 |
|
1412 var result = { |
|
1413 locale: internals.locale, |
|
1414 usage: internals.usage, |
|
1415 sensitivity: internals.sensitivity, |
|
1416 ignorePunctuation: internals.ignorePunctuation |
|
1417 }; |
|
1418 |
|
1419 var relevantExtensionKeys = collatorInternalProperties.relevantExtensionKeys; |
|
1420 for (var i = 0; i < relevantExtensionKeys.length; i++) { |
|
1421 var key = relevantExtensionKeys[i]; |
|
1422 var property = (key === "co") ? "collation" : collatorKeyMappings[key].property; |
|
1423 defineProperty(result, property, internals[property]); |
|
1424 } |
|
1425 return result; |
|
1426 } |
|
1427 |
|
1428 |
|
1429 /********** Intl.NumberFormat **********/ |
|
1430 |
|
1431 |
|
1432 /** |
|
1433 * NumberFormat internal properties. |
|
1434 * |
|
1435 * Spec: ECMAScript Internationalization API Specification, 9.1 and 11.2.3. |
|
1436 */ |
|
1437 var numberFormatInternalProperties = { |
|
1438 localeData: numberFormatLocaleData, |
|
1439 _availableLocales: null, |
|
1440 availableLocales: function() |
|
1441 { |
|
1442 var locales = this._availableLocales; |
|
1443 if (locales) |
|
1444 return locales; |
|
1445 return (this._availableLocales = |
|
1446 addOldStyleLanguageTags(intl_NumberFormat_availableLocales())); |
|
1447 }, |
|
1448 relevantExtensionKeys: ["nu"] |
|
1449 }; |
|
1450 |
|
1451 |
|
1452 /** |
|
1453 * Compute an internal properties object from |lazyNumberFormatData|. |
|
1454 */ |
|
1455 function resolveNumberFormatInternals(lazyNumberFormatData) { |
|
1456 assert(IsObject(lazyNumberFormatData), "lazy data not an object?"); |
|
1457 |
|
1458 var internalProps = std_Object_create(null); |
|
1459 |
|
1460 // Step 3. |
|
1461 var requestedLocales = lazyNumberFormatData.requestedLocales; |
|
1462 |
|
1463 // Compute options that impact interpretation of locale. |
|
1464 // Step 6. |
|
1465 var opt = lazyNumberFormatData.opt; |
|
1466 |
|
1467 // Compute effective locale. |
|
1468 // Step 9. |
|
1469 var NumberFormat = numberFormatInternalProperties; |
|
1470 |
|
1471 // Step 10. |
|
1472 var localeData = NumberFormat.localeData; |
|
1473 |
|
1474 // Step 11. |
|
1475 var r = ResolveLocale(NumberFormat.availableLocales(), |
|
1476 lazyNumberFormatData.requestedLocales, |
|
1477 lazyNumberFormatData.opt, |
|
1478 NumberFormat.relevantExtensionKeys, |
|
1479 localeData); |
|
1480 |
|
1481 // Steps 12-13. (Step 14 is not relevant to our implementation.) |
|
1482 internalProps.locale = r.locale; |
|
1483 internalProps.numberingSystem = r.nu; |
|
1484 |
|
1485 // Compute formatting options. |
|
1486 // Step 16. |
|
1487 var s = lazyNumberFormatData.style; |
|
1488 internalProps.style = s; |
|
1489 |
|
1490 // Steps 20, 22. |
|
1491 if (s === "currency") { |
|
1492 internalProps.currency = lazyNumberFormatData.currency; |
|
1493 internalProps.currencyDisplay = lazyNumberFormatData.currencyDisplay; |
|
1494 } |
|
1495 |
|
1496 // Step 24. |
|
1497 internalProps.minimumIntegerDigits = lazyNumberFormatData.minimumIntegerDigits; |
|
1498 |
|
1499 // Steps 27. |
|
1500 internalProps.minimumFractionDigits = lazyNumberFormatData.minimumFractionDigits; |
|
1501 |
|
1502 // Step 30. |
|
1503 internalProps.maximumFractionDigits = lazyNumberFormatData.maximumFractionDigits; |
|
1504 |
|
1505 // Step 33. |
|
1506 if ("minimumSignificantDigits" in lazyNumberFormatData) { |
|
1507 // Note: Intl.NumberFormat.prototype.resolvedOptions() exposes the |
|
1508 // actual presence (versus undefined-ness) of these properties. |
|
1509 assert("maximumSignificantDigits" in lazyNumberFormatData, "min/max sig digits mismatch"); |
|
1510 internalProps.minimumSignificantDigits = lazyNumberFormatData.minimumSignificantDigits; |
|
1511 internalProps.maximumSignificantDigits = lazyNumberFormatData.maximumSignificantDigits; |
|
1512 } |
|
1513 |
|
1514 // Step 35. |
|
1515 internalProps.useGrouping = lazyNumberFormatData.useGrouping; |
|
1516 |
|
1517 // Step 42. |
|
1518 internalProps.boundFormat = undefined; |
|
1519 |
|
1520 // The caller is responsible for associating |internalProps| with the right |
|
1521 // object using |setInternalProperties|. |
|
1522 return internalProps; |
|
1523 } |
|
1524 |
|
1525 |
|
1526 /** |
|
1527 * Returns an object containing the NumberFormat internal properties of |obj|, |
|
1528 * or throws a TypeError if |obj| isn't NumberFormat-initialized. |
|
1529 */ |
|
1530 function getNumberFormatInternals(obj, methodName) { |
|
1531 var internals = getIntlObjectInternals(obj, "NumberFormat", methodName); |
|
1532 assert(internals.type === "NumberFormat", "bad type escaped getIntlObjectInternals"); |
|
1533 |
|
1534 // If internal properties have already been computed, use them. |
|
1535 var internalProps = maybeInternalProperties(internals); |
|
1536 if (internalProps) |
|
1537 return internalProps; |
|
1538 |
|
1539 // Otherwise it's time to fully create them. |
|
1540 internalProps = resolveNumberFormatInternals(internals.lazyData); |
|
1541 setInternalProperties(internals, internalProps); |
|
1542 return internalProps; |
|
1543 } |
|
1544 |
|
1545 |
|
1546 /** |
|
1547 * Initializes an object as a NumberFormat. |
|
1548 * |
|
1549 * This method is complicated a moderate bit by its implementing initialization |
|
1550 * as a *lazy* concept. Everything that must happen now, does -- but we defer |
|
1551 * all the work we can until the object is actually used as a NumberFormat. |
|
1552 * This later work occurs in |resolveNumberFormatInternals|; steps not noted |
|
1553 * here occur there. |
|
1554 * |
|
1555 * Spec: ECMAScript Internationalization API Specification, 11.1.1. |
|
1556 */ |
|
1557 function InitializeNumberFormat(numberFormat, locales, options) { |
|
1558 assert(IsObject(numberFormat), "InitializeNumberFormat"); |
|
1559 |
|
1560 // Step 1. |
|
1561 if (isInitializedIntlObject(numberFormat)) |
|
1562 ThrowError(JSMSG_INTL_OBJECT_REINITED); |
|
1563 |
|
1564 // Step 2. |
|
1565 var internals = initializeIntlObject(numberFormat); |
|
1566 |
|
1567 // Lazy NumberFormat data has the following structure: |
|
1568 // |
|
1569 // { |
|
1570 // requestedLocales: List of locales, |
|
1571 // style: "decimal" / "percent" / "currency", |
|
1572 // |
|
1573 // // fields present only if style === "currency": |
|
1574 // currency: a well-formed currency code (IsWellFormedCurrencyCode), |
|
1575 // currencyDisplay: "code" / "symbol" / "name", |
|
1576 // |
|
1577 // opt: // opt object computed in InitializeNumberFormat |
|
1578 // { |
|
1579 // localeMatcher: "lookup" / "best fit", |
|
1580 // } |
|
1581 // |
|
1582 // minimumIntegerDigits: integer ∈ [1, 21], |
|
1583 // minimumFractionDigits: integer ∈ [0, 20], |
|
1584 // maximumFractionDigits: integer ∈ [0, 20], |
|
1585 // |
|
1586 // // optional |
|
1587 // minimumSignificantDigits: integer ∈ [1, 21], |
|
1588 // maximumSignificantDigits: integer ∈ [1, 21], |
|
1589 // |
|
1590 // useGrouping: true / false, |
|
1591 // } |
|
1592 // |
|
1593 // Note that lazy data is only installed as a final step of initialization, |
|
1594 // so every Collator lazy data object has *all* these properties, never a |
|
1595 // subset of them. |
|
1596 var lazyNumberFormatData = std_Object_create(null); |
|
1597 |
|
1598 // Step 3. |
|
1599 var requestedLocales = CanonicalizeLocaleList(locales); |
|
1600 lazyNumberFormatData.requestedLocales = requestedLocales; |
|
1601 |
|
1602 // Steps 4-5. |
|
1603 // |
|
1604 // If we ever need more speed here at startup, we should try to detect the |
|
1605 // case where |options === undefined| and Object.prototype hasn't been |
|
1606 // mucked with. (|options| is fully consumed in this method, so it's not a |
|
1607 // concern that Object.prototype might be touched between now and when |
|
1608 // |resolveNumberFormatInternals| is called.) For now just keep it simple. |
|
1609 if (options === undefined) |
|
1610 options = {}; |
|
1611 else |
|
1612 options = ToObject(options); |
|
1613 |
|
1614 // Compute options that impact interpretation of locale. |
|
1615 // Step 6. |
|
1616 var opt = new Record(); |
|
1617 lazyNumberFormatData.opt = opt; |
|
1618 |
|
1619 // Steps 7-8. |
|
1620 var matcher = GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], "best fit"); |
|
1621 opt.localeMatcher = matcher; |
|
1622 |
|
1623 // Compute formatting options. |
|
1624 // Step 15. |
|
1625 var s = GetOption(options, "style", "string", ["decimal", "percent", "currency"], "decimal"); |
|
1626 lazyNumberFormatData.style = s; |
|
1627 |
|
1628 // Steps 17-20. |
|
1629 var c = GetOption(options, "currency", "string", undefined, undefined); |
|
1630 if (c !== undefined && !IsWellFormedCurrencyCode(c)) |
|
1631 ThrowError(JSMSG_INVALID_CURRENCY_CODE, c); |
|
1632 var cDigits; |
|
1633 if (s === "currency") { |
|
1634 if (c === undefined) |
|
1635 ThrowError(JSMSG_UNDEFINED_CURRENCY); |
|
1636 |
|
1637 // Steps 20.a-c. |
|
1638 c = toASCIIUpperCase(c); |
|
1639 lazyNumberFormatData.currency = c; |
|
1640 cDigits = CurrencyDigits(c); |
|
1641 } |
|
1642 |
|
1643 // Step 21. |
|
1644 var cd = GetOption(options, "currencyDisplay", "string", ["code", "symbol", "name"], "symbol"); |
|
1645 if (s === "currency") |
|
1646 lazyNumberFormatData.currencyDisplay = cd; |
|
1647 |
|
1648 // Step 23. |
|
1649 var mnid = GetNumberOption(options, "minimumIntegerDigits", 1, 21, 1); |
|
1650 lazyNumberFormatData.minimumIntegerDigits = mnid; |
|
1651 |
|
1652 // Steps 25-26. |
|
1653 var mnfdDefault = (s === "currency") ? cDigits : 0; |
|
1654 var mnfd = GetNumberOption(options, "minimumFractionDigits", 0, 20, mnfdDefault); |
|
1655 lazyNumberFormatData.minimumFractionDigits = mnfd; |
|
1656 |
|
1657 // Steps 28-29. |
|
1658 var mxfdDefault; |
|
1659 if (s === "currency") |
|
1660 mxfdDefault = std_Math_max(mnfd, cDigits); |
|
1661 else if (s === "percent") |
|
1662 mxfdDefault = std_Math_max(mnfd, 0); |
|
1663 else |
|
1664 mxfdDefault = std_Math_max(mnfd, 3); |
|
1665 var mxfd = GetNumberOption(options, "maximumFractionDigits", mnfd, 20, mxfdDefault); |
|
1666 lazyNumberFormatData.maximumFractionDigits = mxfd; |
|
1667 |
|
1668 // Steps 31-32. |
|
1669 var mnsd = options.minimumSignificantDigits; |
|
1670 var mxsd = options.maximumSignificantDigits; |
|
1671 |
|
1672 // Step 33. |
|
1673 if (mnsd !== undefined || mxsd !== undefined) { |
|
1674 mnsd = GetNumberOption(options, "minimumSignificantDigits", 1, 21, 1); |
|
1675 mxsd = GetNumberOption(options, "maximumSignificantDigits", mnsd, 21, 21); |
|
1676 lazyNumberFormatData.minimumSignificantDigits = mnsd; |
|
1677 lazyNumberFormatData.maximumSignificantDigits = mxsd; |
|
1678 } |
|
1679 |
|
1680 // Step 34. |
|
1681 var g = GetOption(options, "useGrouping", "boolean", undefined, true); |
|
1682 lazyNumberFormatData.useGrouping = g; |
|
1683 |
|
1684 // Step 43. |
|
1685 // |
|
1686 // We've done everything that must be done now: mark the lazy data as fully |
|
1687 // computed and install it. |
|
1688 setLazyData(internals, "NumberFormat", lazyNumberFormatData); |
|
1689 } |
|
1690 |
|
1691 |
|
1692 /** |
|
1693 * Mapping from currency codes to the number of decimal digits used for them. |
|
1694 * Default is 2 digits. |
|
1695 * |
|
1696 * Spec: ISO 4217 Currency and Funds Code List. |
|
1697 * http://www.currency-iso.org/en/home/tables/table-a1.html |
|
1698 */ |
|
1699 var currencyDigits = { |
|
1700 BHD: 3, |
|
1701 BIF: 0, |
|
1702 BYR: 0, |
|
1703 CLF: 0, |
|
1704 CLP: 0, |
|
1705 DJF: 0, |
|
1706 IQD: 3, |
|
1707 GNF: 0, |
|
1708 ISK: 0, |
|
1709 JOD: 3, |
|
1710 JPY: 0, |
|
1711 KMF: 0, |
|
1712 KRW: 0, |
|
1713 KWD: 3, |
|
1714 LYD: 3, |
|
1715 OMR: 3, |
|
1716 PYG: 0, |
|
1717 RWF: 0, |
|
1718 TND: 3, |
|
1719 UGX: 0, |
|
1720 UYI: 0, |
|
1721 VND: 0, |
|
1722 VUV: 0, |
|
1723 XAF: 0, |
|
1724 XOF: 0, |
|
1725 XPF: 0 |
|
1726 }; |
|
1727 |
|
1728 |
|
1729 /** |
|
1730 * Returns the number of decimal digits to be used for the given currency. |
|
1731 * |
|
1732 * Spec: ECMAScript Internationalization API Specification, 11.1.1. |
|
1733 */ |
|
1734 function CurrencyDigits(currency) { |
|
1735 assert(typeof currency === "string", "CurrencyDigits"); |
|
1736 assert(regexp_test_no_statics(/^[A-Z]{3}$/, currency), "CurrencyDigits"); |
|
1737 |
|
1738 if (callFunction(std_Object_hasOwnProperty, currencyDigits, currency)) |
|
1739 return currencyDigits[currency]; |
|
1740 return 2; |
|
1741 } |
|
1742 |
|
1743 |
|
1744 /** |
|
1745 * Returns the subset of the given locale list for which this locale list has a |
|
1746 * matching (possibly fallback) locale. Locales appear in the same order in the |
|
1747 * returned list as in the input list. |
|
1748 * |
|
1749 * Spec: ECMAScript Internationalization API Specification, 11.2.2. |
|
1750 */ |
|
1751 function Intl_NumberFormat_supportedLocalesOf(locales /*, options*/) { |
|
1752 var options = arguments.length > 1 ? arguments[1] : undefined; |
|
1753 |
|
1754 var availableLocales = numberFormatInternalProperties.availableLocales(); |
|
1755 var requestedLocales = CanonicalizeLocaleList(locales); |
|
1756 return SupportedLocales(availableLocales, requestedLocales, options); |
|
1757 } |
|
1758 |
|
1759 |
|
1760 function getNumberingSystems(locale) { |
|
1761 // ICU doesn't have an API to determine the set of numbering systems |
|
1762 // supported for a locale; it generally pretends that any numbering system |
|
1763 // can be used with any locale. Supporting a decimal numbering system |
|
1764 // (where only the digits are replaced) is easy, so we offer them all here. |
|
1765 // Algorithmic numbering systems are typically tied to one locale, so for |
|
1766 // lack of information we don't offer them. To increase chances that |
|
1767 // other software will process output correctly, we further restrict to |
|
1768 // those decimal numbering systems explicitly listed in table 2 of |
|
1769 // the ECMAScript Internationalization API Specification, 11.3.2, which |
|
1770 // in turn are those with full specifications in version 21 of Unicode |
|
1771 // Technical Standard #35 using digits that were defined in Unicode 5.0, |
|
1772 // the Unicode version supported in Windows Vista. |
|
1773 // The one thing we can find out from ICU is the default numbering system |
|
1774 // for a locale. |
|
1775 var defaultNumberingSystem = intl_numberingSystem(locale); |
|
1776 return [ |
|
1777 defaultNumberingSystem, |
|
1778 "arab", "arabext", "bali", "beng", "deva", |
|
1779 "fullwide", "gujr", "guru", "hanidec", "khmr", |
|
1780 "knda", "laoo", "latn", "limb", "mlym", |
|
1781 "mong", "mymr", "orya", "tamldec", "telu", |
|
1782 "thai", "tibt" |
|
1783 ]; |
|
1784 } |
|
1785 |
|
1786 |
|
1787 function numberFormatLocaleData(locale) { |
|
1788 return { |
|
1789 nu: getNumberingSystems(locale) |
|
1790 }; |
|
1791 } |
|
1792 |
|
1793 |
|
1794 /** |
|
1795 * Function to be bound and returned by Intl.NumberFormat.prototype.format. |
|
1796 * |
|
1797 * Spec: ECMAScript Internationalization API Specification, 11.3.2. |
|
1798 */ |
|
1799 function numberFormatFormatToBind(value) { |
|
1800 // Steps 1.a.i implemented by ECMAScript declaration binding instantiation, |
|
1801 // ES5.1 10.5, step 4.d.ii. |
|
1802 |
|
1803 // Step 1.a.ii-iii. |
|
1804 var x = ToNumber(value); |
|
1805 return intl_FormatNumber(this, x); |
|
1806 } |
|
1807 |
|
1808 |
|
1809 /** |
|
1810 * Returns a function bound to this NumberFormat that returns a String value |
|
1811 * representing the result of calling ToNumber(value) according to the |
|
1812 * effective locale and the formatting options of this NumberFormat. |
|
1813 * |
|
1814 * Spec: ECMAScript Internationalization API Specification, 11.3.2. |
|
1815 */ |
|
1816 function Intl_NumberFormat_format_get() { |
|
1817 // Check "this NumberFormat object" per introduction of section 11.3. |
|
1818 var internals = getNumberFormatInternals(this, "format"); |
|
1819 |
|
1820 // Step 1. |
|
1821 if (internals.boundFormat === undefined) { |
|
1822 // Step 1.a. |
|
1823 var F = numberFormatFormatToBind; |
|
1824 |
|
1825 // Step 1.b-d. |
|
1826 var bf = callFunction(std_Function_bind, F, this); |
|
1827 internals.boundFormat = bf; |
|
1828 } |
|
1829 // Step 2. |
|
1830 return internals.boundFormat; |
|
1831 } |
|
1832 |
|
1833 |
|
1834 /** |
|
1835 * Returns the resolved options for a NumberFormat object. |
|
1836 * |
|
1837 * Spec: ECMAScript Internationalization API Specification, 11.3.3 and 11.4. |
|
1838 */ |
|
1839 function Intl_NumberFormat_resolvedOptions() { |
|
1840 // Check "this NumberFormat object" per introduction of section 11.3. |
|
1841 var internals = getNumberFormatInternals(this, "resolvedOptions"); |
|
1842 |
|
1843 var result = { |
|
1844 locale: internals.locale, |
|
1845 numberingSystem: internals.numberingSystem, |
|
1846 style: internals.style, |
|
1847 minimumIntegerDigits: internals.minimumIntegerDigits, |
|
1848 minimumFractionDigits: internals.minimumFractionDigits, |
|
1849 maximumFractionDigits: internals.maximumFractionDigits, |
|
1850 useGrouping: internals.useGrouping |
|
1851 }; |
|
1852 var optionalProperties = [ |
|
1853 "currency", |
|
1854 "currencyDisplay", |
|
1855 "minimumSignificantDigits", |
|
1856 "maximumSignificantDigits" |
|
1857 ]; |
|
1858 for (var i = 0; i < optionalProperties.length; i++) { |
|
1859 var p = optionalProperties[i]; |
|
1860 if (callFunction(std_Object_hasOwnProperty, internals, p)) |
|
1861 defineProperty(result, p, internals[p]); |
|
1862 } |
|
1863 return result; |
|
1864 } |
|
1865 |
|
1866 |
|
1867 /********** Intl.DateTimeFormat **********/ |
|
1868 |
|
1869 |
|
1870 /** |
|
1871 * Compute an internal properties object from |lazyDateTimeFormatData|. |
|
1872 */ |
|
1873 function resolveDateTimeFormatInternals(lazyDateTimeFormatData) { |
|
1874 assert(IsObject(lazyDateTimeFormatData), "lazy data not an object?"); |
|
1875 |
|
1876 // Lazy DateTimeFormat data has the following structure: |
|
1877 // |
|
1878 // { |
|
1879 // requestedLocales: List of locales, |
|
1880 // |
|
1881 // localeOpt: // *first* opt computed in InitializeDateTimeFormat |
|
1882 // { |
|
1883 // localeMatcher: "lookup" / "best fit", |
|
1884 // |
|
1885 // hour12: true / false, // optional |
|
1886 // } |
|
1887 // |
|
1888 // timeZone: undefined / "UTC", |
|
1889 // |
|
1890 // formatOpt: // *second* opt computed in InitializeDateTimeFormat |
|
1891 // { |
|
1892 // // all the properties/values listed in Table 3 |
|
1893 // // (weekday, era, year, month, day, &c.) |
|
1894 // } |
|
1895 // |
|
1896 // formatMatcher: "basic" / "best fit", |
|
1897 // } |
|
1898 // |
|
1899 // Note that lazy data is only installed as a final step of initialization, |
|
1900 // so every DateTimeFormat lazy data object has *all* these properties, |
|
1901 // never a subset of them. |
|
1902 |
|
1903 var internalProps = std_Object_create(null); |
|
1904 |
|
1905 // Compute effective locale. |
|
1906 // Step 8. |
|
1907 var DateTimeFormat = dateTimeFormatInternalProperties; |
|
1908 |
|
1909 // Step 9. |
|
1910 var localeData = DateTimeFormat.localeData; |
|
1911 |
|
1912 // Step 10. |
|
1913 var r = ResolveLocale(DateTimeFormat.availableLocales(), |
|
1914 lazyDateTimeFormatData.requestedLocales, |
|
1915 lazyDateTimeFormatData.localeOpt, |
|
1916 DateTimeFormat.relevantExtensionKeys, |
|
1917 localeData); |
|
1918 |
|
1919 // Steps 11-13. |
|
1920 internalProps.locale = r.locale; |
|
1921 internalProps.calendar = r.ca; |
|
1922 internalProps.numberingSystem = r.nu; |
|
1923 |
|
1924 // Compute formatting options. |
|
1925 // Step 14. |
|
1926 var dataLocale = r.dataLocale; |
|
1927 |
|
1928 // Steps 15-17. |
|
1929 internalProps.timeZone = lazyDateTimeFormatData.timeZone; |
|
1930 |
|
1931 // Step 18. |
|
1932 var formatOpt = lazyDateTimeFormatData.formatOpt; |
|
1933 |
|
1934 // Steps 27-28, more or less - see comment after this function. |
|
1935 var pattern = toBestICUPattern(dataLocale, formatOpt); |
|
1936 |
|
1937 // Step 29. |
|
1938 internalProps.pattern = pattern; |
|
1939 |
|
1940 // Step 30. |
|
1941 internalProps.boundFormat = undefined; |
|
1942 |
|
1943 // The caller is responsible for associating |internalProps| with the right |
|
1944 // object using |setInternalProperties|. |
|
1945 return internalProps; |
|
1946 } |
|
1947 |
|
1948 |
|
1949 /** |
|
1950 * Returns an object containing the DateTimeFormat internal properties of |obj|, |
|
1951 * or throws a TypeError if |obj| isn't DateTimeFormat-initialized. |
|
1952 */ |
|
1953 function getDateTimeFormatInternals(obj, methodName) { |
|
1954 var internals = getIntlObjectInternals(obj, "DateTimeFormat", methodName); |
|
1955 assert(internals.type === "DateTimeFormat", "bad type escaped getIntlObjectInternals"); |
|
1956 |
|
1957 // If internal properties have already been computed, use them. |
|
1958 var internalProps = maybeInternalProperties(internals); |
|
1959 if (internalProps) |
|
1960 return internalProps; |
|
1961 |
|
1962 // Otherwise it's time to fully create them. |
|
1963 internalProps = resolveDateTimeFormatInternals(internals.lazyData); |
|
1964 setInternalProperties(internals, internalProps); |
|
1965 return internalProps; |
|
1966 } |
|
1967 |
|
1968 /** |
|
1969 * Components of date and time formats and their values. |
|
1970 * |
|
1971 * Spec: ECMAScript Internationalization API Specification, 12.1.1. |
|
1972 */ |
|
1973 var dateTimeComponentValues = { |
|
1974 weekday: ["narrow", "short", "long"], |
|
1975 era: ["narrow", "short", "long"], |
|
1976 year: ["2-digit", "numeric"], |
|
1977 month: ["2-digit", "numeric", "narrow", "short", "long"], |
|
1978 day: ["2-digit", "numeric"], |
|
1979 hour: ["2-digit", "numeric"], |
|
1980 minute: ["2-digit", "numeric"], |
|
1981 second: ["2-digit", "numeric"], |
|
1982 timeZoneName: ["short", "long"] |
|
1983 }; |
|
1984 |
|
1985 |
|
1986 var dateTimeComponents = std_Object_getOwnPropertyNames(dateTimeComponentValues); |
|
1987 |
|
1988 |
|
1989 /** |
|
1990 * Initializes an object as a DateTimeFormat. |
|
1991 * |
|
1992 * This method is complicated a moderate bit by its implementing initialization |
|
1993 * as a *lazy* concept. Everything that must happen now, does -- but we defer |
|
1994 * all the work we can until the object is actually used as a DateTimeFormat. |
|
1995 * This later work occurs in |resolveDateTimeFormatInternals|; steps not noted |
|
1996 * here occur there. |
|
1997 * |
|
1998 * Spec: ECMAScript Internationalization API Specification, 12.1.1. |
|
1999 */ |
|
2000 function InitializeDateTimeFormat(dateTimeFormat, locales, options) { |
|
2001 assert(IsObject(dateTimeFormat), "InitializeDateTimeFormat"); |
|
2002 |
|
2003 // Step 1. |
|
2004 if (isInitializedIntlObject(dateTimeFormat)) |
|
2005 ThrowError(JSMSG_INTL_OBJECT_REINITED); |
|
2006 |
|
2007 // Step 2. |
|
2008 var internals = initializeIntlObject(dateTimeFormat); |
|
2009 |
|
2010 // Lazy DateTimeFormat data has the following structure: |
|
2011 // |
|
2012 // { |
|
2013 // requestedLocales: List of locales, |
|
2014 // |
|
2015 // localeOpt: // *first* opt computed in InitializeDateTimeFormat |
|
2016 // { |
|
2017 // localeMatcher: "lookup" / "best fit", |
|
2018 // } |
|
2019 // |
|
2020 // timeZone: undefined / "UTC", |
|
2021 // |
|
2022 // formatOpt: // *second* opt computed in InitializeDateTimeFormat |
|
2023 // { |
|
2024 // // all the properties/values listed in Table 3 |
|
2025 // // (weekday, era, year, month, day, &c.) |
|
2026 // |
|
2027 // hour12: true / false // optional |
|
2028 // } |
|
2029 // |
|
2030 // formatMatcher: "basic" / "best fit", |
|
2031 // } |
|
2032 // |
|
2033 // Note that lazy data is only installed as a final step of initialization, |
|
2034 // so every DateTimeFormat lazy data object has *all* these properties, |
|
2035 // never a subset of them. |
|
2036 var lazyDateTimeFormatData = std_Object_create(null); |
|
2037 |
|
2038 // Step 3. |
|
2039 var requestedLocales = CanonicalizeLocaleList(locales); |
|
2040 lazyDateTimeFormatData.requestedLocales = requestedLocales; |
|
2041 |
|
2042 // Step 4. |
|
2043 options = ToDateTimeOptions(options, "any", "date"); |
|
2044 |
|
2045 // Compute options that impact interpretation of locale. |
|
2046 // Step 5. |
|
2047 var localeOpt = new Record(); |
|
2048 lazyDateTimeFormatData.localeOpt = localeOpt; |
|
2049 |
|
2050 // Steps 6-7. |
|
2051 var localeMatcher = |
|
2052 GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], |
|
2053 "best fit"); |
|
2054 localeOpt.localeMatcher = localeMatcher; |
|
2055 |
|
2056 // Steps 15-17. |
|
2057 var tz = options.timeZone; |
|
2058 if (tz !== undefined) { |
|
2059 tz = toASCIIUpperCase(ToString(tz)); |
|
2060 if (tz !== "UTC") |
|
2061 ThrowError(JSMSG_INVALID_TIME_ZONE, tz); |
|
2062 } |
|
2063 lazyDateTimeFormatData.timeZone = tz; |
|
2064 |
|
2065 // Step 18. |
|
2066 var formatOpt = new Record(); |
|
2067 lazyDateTimeFormatData.formatOpt = formatOpt; |
|
2068 |
|
2069 // Step 19. |
|
2070 var i, prop; |
|
2071 for (i = 0; i < dateTimeComponents.length; i++) { |
|
2072 prop = dateTimeComponents[i]; |
|
2073 var value = GetOption(options, prop, "string", dateTimeComponentValues[prop], undefined); |
|
2074 formatOpt[prop] = value; |
|
2075 } |
|
2076 |
|
2077 // Steps 20-21 provided by ICU - see comment after this function. |
|
2078 |
|
2079 // Step 22. |
|
2080 // |
|
2081 // For some reason (ICU not exposing enough interface?) we drop the |
|
2082 // requested format matcher on the floor after this. In any case, even if |
|
2083 // doing so is justified, we have to do this work here in case it triggers |
|
2084 // getters or similar. |
|
2085 var formatMatcher = |
|
2086 GetOption(options, "formatMatcher", "string", ["basic", "best fit"], |
|
2087 "best fit"); |
|
2088 |
|
2089 // Steps 23-25 provided by ICU, more or less - see comment after this function. |
|
2090 |
|
2091 // Step 26. |
|
2092 var hr12 = GetOption(options, "hour12", "boolean", undefined, undefined); |
|
2093 |
|
2094 // Pass hr12 on to ICU. |
|
2095 if (hr12 !== undefined) |
|
2096 formatOpt.hour12 = hr12; |
|
2097 |
|
2098 // Step 31. |
|
2099 // |
|
2100 // We've done everything that must be done now: mark the lazy data as fully |
|
2101 // computed and install it. |
|
2102 setLazyData(internals, "DateTimeFormat", lazyDateTimeFormatData); |
|
2103 } |
|
2104 |
|
2105 |
|
2106 // Intl.DateTimeFormat and ICU skeletons and patterns |
|
2107 // ================================================== |
|
2108 // |
|
2109 // Different locales have different ways to display dates using the same |
|
2110 // basic components. For example, en-US might use "Sept. 24, 2012" while |
|
2111 // fr-FR might use "24 Sept. 2012". The intent of Intl.DateTimeFormat is to |
|
2112 // permit production of a format for the locale that best matches the |
|
2113 // set of date-time components and their desired representation as specified |
|
2114 // by the API client. |
|
2115 // |
|
2116 // ICU supports specification of date and time formats in three ways: |
|
2117 // |
|
2118 // 1) A style is just one of the identifiers FULL, LONG, MEDIUM, or SHORT. |
|
2119 // The date-time components included in each style and their representation |
|
2120 // are defined by ICU using CLDR locale data (CLDR is the Unicode |
|
2121 // Consortium's Common Locale Data Repository). |
|
2122 // |
|
2123 // 2) A skeleton is a string specifying which date-time components to include, |
|
2124 // and which representations to use for them. For example, "yyyyMMMMdd" |
|
2125 // specifies a year with at least four digits, a full month name, and a |
|
2126 // two-digit day. It does not specify in which order the components appear, |
|
2127 // how they are separated, the localized strings for textual components |
|
2128 // (such as weekday or month), whether the month is in format or |
|
2129 // stand-alone form¹, or the numbering system used for numeric components. |
|
2130 // All that information is filled in by ICU using CLDR locale data. |
|
2131 // ¹ The format form is the one used in formatted strings that include a |
|
2132 // day; the stand-alone form is used when not including days, e.g., in |
|
2133 // calendar headers. The two forms differ at least in some Slavic languages, |
|
2134 // e.g. Russian: "22 марта 2013 г." vs. "Март 2013". |
|
2135 // |
|
2136 // 3) A pattern is a string specifying which date-time components to include, |
|
2137 // in which order, with which separators, in which grammatical case. For |
|
2138 // example, "EEEE, d MMMM y" specifies the full localized weekday name, |
|
2139 // followed by comma and space, followed by the day, followed by space, |
|
2140 // followed by the full month name in format form, followed by space, |
|
2141 // followed by the full year. It |
|
2142 // still does not specify localized strings for textual components and the |
|
2143 // numbering system - these are determined by ICU using CLDR locale data or |
|
2144 // possibly API parameters. |
|
2145 // |
|
2146 // All actual formatting in ICU is done with patterns; styles and skeletons |
|
2147 // have to be mapped to patterns before processing. |
|
2148 // |
|
2149 // The options of DateTimeFormat most closely correspond to ICU skeletons. This |
|
2150 // implementation therefore, in the toBestICUPattern function, converts |
|
2151 // DateTimeFormat options to ICU skeletons, and then lets ICU map skeletons to |
|
2152 // actual ICU patterns. The pattern may not directly correspond to what the |
|
2153 // skeleton requests, as the mapper (UDateTimePatternGenerator) is constrained |
|
2154 // by the available locale data for the locale. The resulting ICU pattern is |
|
2155 // kept as the DateTimeFormat's [[pattern]] internal property and passed to ICU |
|
2156 // in the format method. |
|
2157 // |
|
2158 // An ICU pattern represents the information of the following DateTimeFormat |
|
2159 // internal properties described in the specification, which therefore don't |
|
2160 // exist separately in the implementation: |
|
2161 // - [[weekday]], [[era]], [[year]], [[month]], [[day]], [[hour]], [[minute]], |
|
2162 // [[second]], [[timeZoneName]] |
|
2163 // - [[hour12]] |
|
2164 // - [[hourNo0]] |
|
2165 // When needed for the resolvedOptions method, the resolveICUPattern function |
|
2166 // maps the instance's ICU pattern back to the specified properties of the |
|
2167 // object returned by resolvedOptions. |
|
2168 // |
|
2169 // ICU date-time skeletons and patterns aren't fully documented in the ICU |
|
2170 // documentation (see http://bugs.icu-project.org/trac/ticket/9627). The best |
|
2171 // documentation at this point is in UTR 35: |
|
2172 // http://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns |
|
2173 |
|
2174 |
|
2175 /** |
|
2176 * Returns an ICU pattern string for the given locale and representing the |
|
2177 * specified options as closely as possible given available locale data. |
|
2178 */ |
|
2179 function toBestICUPattern(locale, options) { |
|
2180 // Create an ICU skeleton representing the specified options. See |
|
2181 // http://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table |
|
2182 var skeleton = ""; |
|
2183 switch (options.weekday) { |
|
2184 case "narrow": |
|
2185 skeleton += "EEEEE"; |
|
2186 break; |
|
2187 case "short": |
|
2188 skeleton += "E"; |
|
2189 break; |
|
2190 case "long": |
|
2191 skeleton += "EEEE"; |
|
2192 } |
|
2193 switch (options.era) { |
|
2194 case "narrow": |
|
2195 skeleton += "GGGGG"; |
|
2196 break; |
|
2197 case "short": |
|
2198 skeleton += "G"; |
|
2199 break; |
|
2200 case "long": |
|
2201 skeleton += "GGGG"; |
|
2202 break; |
|
2203 } |
|
2204 switch (options.year) { |
|
2205 case "2-digit": |
|
2206 skeleton += "yy"; |
|
2207 break; |
|
2208 case "numeric": |
|
2209 skeleton += "y"; |
|
2210 break; |
|
2211 } |
|
2212 switch (options.month) { |
|
2213 case "2-digit": |
|
2214 skeleton += "MM"; |
|
2215 break; |
|
2216 case "numeric": |
|
2217 skeleton += "M"; |
|
2218 break; |
|
2219 case "narrow": |
|
2220 skeleton += "MMMMM"; |
|
2221 break; |
|
2222 case "short": |
|
2223 skeleton += "MMM"; |
|
2224 break; |
|
2225 case "long": |
|
2226 skeleton += "MMMM"; |
|
2227 break; |
|
2228 } |
|
2229 switch (options.day) { |
|
2230 case "2-digit": |
|
2231 skeleton += "dd"; |
|
2232 break; |
|
2233 case "numeric": |
|
2234 skeleton += "d"; |
|
2235 break; |
|
2236 } |
|
2237 var hourSkeletonChar = "j"; |
|
2238 if (options.hour12 !== undefined) { |
|
2239 if (options.hour12) |
|
2240 hourSkeletonChar = "h"; |
|
2241 else |
|
2242 hourSkeletonChar = "H"; |
|
2243 } |
|
2244 switch (options.hour) { |
|
2245 case "2-digit": |
|
2246 skeleton += hourSkeletonChar + hourSkeletonChar; |
|
2247 break; |
|
2248 case "numeric": |
|
2249 skeleton += hourSkeletonChar; |
|
2250 break; |
|
2251 } |
|
2252 switch (options.minute) { |
|
2253 case "2-digit": |
|
2254 skeleton += "mm"; |
|
2255 break; |
|
2256 case "numeric": |
|
2257 skeleton += "m"; |
|
2258 break; |
|
2259 } |
|
2260 switch (options.second) { |
|
2261 case "2-digit": |
|
2262 skeleton += "ss"; |
|
2263 break; |
|
2264 case "numeric": |
|
2265 skeleton += "s"; |
|
2266 break; |
|
2267 } |
|
2268 switch (options.timeZoneName) { |
|
2269 case "short": |
|
2270 skeleton += "z"; |
|
2271 break; |
|
2272 case "long": |
|
2273 skeleton += "zzzz"; |
|
2274 break; |
|
2275 } |
|
2276 |
|
2277 // Let ICU convert the ICU skeleton to an ICU pattern for the given locale. |
|
2278 return intl_patternForSkeleton(locale, skeleton); |
|
2279 } |
|
2280 |
|
2281 |
|
2282 /** |
|
2283 * Returns a new options object that includes the provided options (if any) |
|
2284 * and fills in default components if required components are not defined. |
|
2285 * Required can be "date", "time", or "any". |
|
2286 * Defaults can be "date", "time", or "all". |
|
2287 * |
|
2288 * Spec: ECMAScript Internationalization API Specification, 12.1.1. |
|
2289 */ |
|
2290 function ToDateTimeOptions(options, required, defaults) { |
|
2291 assert(typeof required === "string", "ToDateTimeOptions"); |
|
2292 assert(typeof defaults === "string", "ToDateTimeOptions"); |
|
2293 |
|
2294 // Steps 1-3. |
|
2295 if (options === undefined) |
|
2296 options = null; |
|
2297 else |
|
2298 options = ToObject(options); |
|
2299 options = std_Object_create(options); |
|
2300 |
|
2301 // Step 4. |
|
2302 var needDefaults = true; |
|
2303 |
|
2304 // Step 5. |
|
2305 if ((required === "date" || required === "any") && |
|
2306 (options.weekday !== undefined || options.year !== undefined || |
|
2307 options.month !== undefined || options.day !== undefined)) |
|
2308 { |
|
2309 needDefaults = false; |
|
2310 } |
|
2311 |
|
2312 // Step 6. |
|
2313 if ((required === "time" || required === "any") && |
|
2314 (options.hour !== undefined || options.minute !== undefined || |
|
2315 options.second !== undefined)) |
|
2316 { |
|
2317 needDefaults = false; |
|
2318 } |
|
2319 |
|
2320 // Step 7. |
|
2321 if (needDefaults && (defaults === "date" || defaults === "all")) { |
|
2322 // The specification says to call [[DefineOwnProperty]] with false for |
|
2323 // the Throw parameter, while Object.defineProperty uses true. For the |
|
2324 // calls here, the difference doesn't matter because we're adding |
|
2325 // properties to a new object. |
|
2326 defineProperty(options, "year", "numeric"); |
|
2327 defineProperty(options, "month", "numeric"); |
|
2328 defineProperty(options, "day", "numeric"); |
|
2329 } |
|
2330 |
|
2331 // Step 8. |
|
2332 if (needDefaults && (defaults === "time" || defaults === "all")) { |
|
2333 // See comment for step 7. |
|
2334 defineProperty(options, "hour", "numeric"); |
|
2335 defineProperty(options, "minute", "numeric"); |
|
2336 defineProperty(options, "second", "numeric"); |
|
2337 } |
|
2338 |
|
2339 // Step 9. |
|
2340 return options; |
|
2341 } |
|
2342 |
|
2343 |
|
2344 /** |
|
2345 * Compares the date and time components requested by options with the available |
|
2346 * date and time formats in formats, and selects the best match according |
|
2347 * to a specified basic matching algorithm. |
|
2348 * |
|
2349 * Spec: ECMAScript Internationalization API Specification, 12.1.1. |
|
2350 */ |
|
2351 function BasicFormatMatcher(options, formats) { |
|
2352 // Steps 1-6. |
|
2353 var removalPenalty = 120, |
|
2354 additionPenalty = 20, |
|
2355 longLessPenalty = 8, |
|
2356 longMorePenalty = 6, |
|
2357 shortLessPenalty = 6, |
|
2358 shortMorePenalty = 3; |
|
2359 |
|
2360 // Table 3. |
|
2361 var properties = ["weekday", "era", "year", "month", "day", |
|
2362 "hour", "minute", "second", "timeZoneName"]; |
|
2363 |
|
2364 // Step 11.c.vi.1. |
|
2365 var values = ["2-digit", "numeric", "narrow", "short", "long"]; |
|
2366 |
|
2367 // Steps 7-8. |
|
2368 var bestScore = -Infinity; |
|
2369 var bestFormat; |
|
2370 |
|
2371 // Steps 9-11. |
|
2372 var i = 0; |
|
2373 var len = formats.length; |
|
2374 while (i < len) { |
|
2375 // Steps 11.a-b. |
|
2376 var format = formats[i]; |
|
2377 var score = 0; |
|
2378 |
|
2379 // Step 11.c. |
|
2380 var formatProp; |
|
2381 for (var j = 0; j < properties.length; j++) { |
|
2382 var property = properties[j]; |
|
2383 |
|
2384 // Step 11.c.i. |
|
2385 var optionsProp = options[property]; |
|
2386 // Step missing from spec. |
|
2387 // https://bugs.ecmascript.org/show_bug.cgi?id=1254 |
|
2388 formatProp = undefined; |
|
2389 |
|
2390 // Steps 11.c.ii-iii. |
|
2391 if (callFunction(std_Object_hasOwnProperty, format, property)) |
|
2392 formatProp = format[property]; |
|
2393 |
|
2394 if (optionsProp === undefined && formatProp !== undefined) { |
|
2395 // Step 11.c.iv. |
|
2396 score -= additionPenalty; |
|
2397 } else if (optionsProp !== undefined && formatProp === undefined) { |
|
2398 // Step 11.c.v. |
|
2399 score -= removalPenalty; |
|
2400 } else { |
|
2401 // Step 11.c.vi. |
|
2402 var optionsPropIndex = callFunction(std_Array_indexOf, values, optionsProp); |
|
2403 var formatPropIndex = callFunction(std_Array_indexOf, values, formatProp); |
|
2404 var delta = std_Math_max(std_Math_min(formatPropIndex - optionsPropIndex, 2), -2); |
|
2405 if (delta === 2) |
|
2406 score -= longMorePenalty; |
|
2407 else if (delta === 1) |
|
2408 score -= shortMorePenalty; |
|
2409 else if (delta === -1) |
|
2410 score -= shortLessPenalty; |
|
2411 else if (delta === -2) |
|
2412 score -= longLessPenalty; |
|
2413 } |
|
2414 } |
|
2415 |
|
2416 // Step 11.d. |
|
2417 if (score > bestScore) { |
|
2418 bestScore = score; |
|
2419 bestFormat = format; |
|
2420 } |
|
2421 |
|
2422 // Step 11.e. |
|
2423 i++; |
|
2424 } |
|
2425 |
|
2426 // Step 12. |
|
2427 return bestFormat; |
|
2428 } |
|
2429 |
|
2430 |
|
2431 /** |
|
2432 * Compares the date and time components requested by options with the available |
|
2433 * date and time formats in formats, and selects the best match according |
|
2434 * to an unspecified best-fit matching algorithm. |
|
2435 * |
|
2436 * Spec: ECMAScript Internationalization API Specification, 12.1.1. |
|
2437 */ |
|
2438 function BestFitFormatMatcher(options, formats) { |
|
2439 // this implementation doesn't have anything better |
|
2440 return BasicFormatMatcher(options, formats); |
|
2441 } |
|
2442 |
|
2443 |
|
2444 /** |
|
2445 * Returns the subset of the given locale list for which this locale list has a |
|
2446 * matching (possibly fallback) locale. Locales appear in the same order in the |
|
2447 * returned list as in the input list. |
|
2448 * |
|
2449 * Spec: ECMAScript Internationalization API Specification, 12.2.2. |
|
2450 */ |
|
2451 function Intl_DateTimeFormat_supportedLocalesOf(locales /*, options*/) { |
|
2452 var options = arguments.length > 1 ? arguments[1] : undefined; |
|
2453 |
|
2454 var availableLocales = dateTimeFormatInternalProperties.availableLocales(); |
|
2455 var requestedLocales = CanonicalizeLocaleList(locales); |
|
2456 return SupportedLocales(availableLocales, requestedLocales, options); |
|
2457 } |
|
2458 |
|
2459 |
|
2460 /** |
|
2461 * DateTimeFormat internal properties. |
|
2462 * |
|
2463 * Spec: ECMAScript Internationalization API Specification, 9.1 and 12.2.3. |
|
2464 */ |
|
2465 var dateTimeFormatInternalProperties = { |
|
2466 localeData: dateTimeFormatLocaleData, |
|
2467 _availableLocales: null, |
|
2468 availableLocales: function() |
|
2469 { |
|
2470 var locales = this._availableLocales; |
|
2471 if (locales) |
|
2472 return locales; |
|
2473 return (this._availableLocales = |
|
2474 addOldStyleLanguageTags(intl_DateTimeFormat_availableLocales())); |
|
2475 }, |
|
2476 relevantExtensionKeys: ["ca", "nu"] |
|
2477 }; |
|
2478 |
|
2479 |
|
2480 function dateTimeFormatLocaleData(locale) { |
|
2481 return { |
|
2482 ca: intl_availableCalendars(locale), |
|
2483 nu: getNumberingSystems(locale) |
|
2484 }; |
|
2485 } |
|
2486 |
|
2487 |
|
2488 /** |
|
2489 * Function to be bound and returned by Intl.DateTimeFormat.prototype.format. |
|
2490 * |
|
2491 * Spec: ECMAScript Internationalization API Specification, 12.3.2. |
|
2492 */ |
|
2493 function dateTimeFormatFormatToBind() { |
|
2494 // Steps 1.a.i-ii |
|
2495 var date = arguments.length > 0 ? arguments[0] : undefined; |
|
2496 var x = (date === undefined) ? std_Date_now() : ToNumber(date); |
|
2497 |
|
2498 // Step 1.a.iii. |
|
2499 return intl_FormatDateTime(this, x); |
|
2500 } |
|
2501 |
|
2502 |
|
2503 /** |
|
2504 * Returns a function bound to this DateTimeFormat that returns a String value |
|
2505 * representing the result of calling ToNumber(date) according to the |
|
2506 * effective locale and the formatting options of this DateTimeFormat. |
|
2507 * |
|
2508 * Spec: ECMAScript Internationalization API Specification, 12.3.2. |
|
2509 */ |
|
2510 function Intl_DateTimeFormat_format_get() { |
|
2511 // Check "this DateTimeFormat object" per introduction of section 12.3. |
|
2512 var internals = getDateTimeFormatInternals(this, "format"); |
|
2513 |
|
2514 // Step 1. |
|
2515 if (internals.boundFormat === undefined) { |
|
2516 // Step 1.a. |
|
2517 var F = dateTimeFormatFormatToBind; |
|
2518 |
|
2519 // Step 1.b-d. |
|
2520 var bf = callFunction(std_Function_bind, F, this); |
|
2521 internals.boundFormat = bf; |
|
2522 } |
|
2523 |
|
2524 // Step 2. |
|
2525 return internals.boundFormat; |
|
2526 } |
|
2527 |
|
2528 |
|
2529 /** |
|
2530 * Returns the resolved options for a DateTimeFormat object. |
|
2531 * |
|
2532 * Spec: ECMAScript Internationalization API Specification, 12.3.3 and 12.4. |
|
2533 */ |
|
2534 function Intl_DateTimeFormat_resolvedOptions() { |
|
2535 // Check "this DateTimeFormat object" per introduction of section 12.3. |
|
2536 var internals = getDateTimeFormatInternals(this, "resolvedOptions"); |
|
2537 |
|
2538 var result = { |
|
2539 locale: internals.locale, |
|
2540 calendar: internals.calendar, |
|
2541 numberingSystem: internals.numberingSystem, |
|
2542 timeZone: internals.timeZone |
|
2543 }; |
|
2544 resolveICUPattern(internals.pattern, result); |
|
2545 return result; |
|
2546 } |
|
2547 |
|
2548 |
|
2549 // Table mapping ICU pattern characters back to the corresponding date-time |
|
2550 // components of DateTimeFormat. See |
|
2551 // http://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table |
|
2552 var icuPatternCharToComponent = { |
|
2553 E: "weekday", |
|
2554 G: "era", |
|
2555 y: "year", |
|
2556 M: "month", |
|
2557 L: "month", |
|
2558 d: "day", |
|
2559 h: "hour", |
|
2560 H: "hour", |
|
2561 k: "hour", |
|
2562 K: "hour", |
|
2563 m: "minute", |
|
2564 s: "second", |
|
2565 z: "timeZoneName", |
|
2566 v: "timeZoneName", |
|
2567 V: "timeZoneName" |
|
2568 }; |
|
2569 |
|
2570 |
|
2571 /** |
|
2572 * Maps an ICU pattern string to a corresponding set of date-time components |
|
2573 * and their values, and adds properties for these components to the result |
|
2574 * object, which will be returned by the resolvedOptions method. For the |
|
2575 * interpretation of ICU pattern characters, see |
|
2576 * http://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table |
|
2577 */ |
|
2578 function resolveICUPattern(pattern, result) { |
|
2579 assert(IsObject(result), "resolveICUPattern"); |
|
2580 var i = 0; |
|
2581 while (i < pattern.length) { |
|
2582 var c = pattern[i++]; |
|
2583 if (c === "'") { |
|
2584 while (i < pattern.length && pattern[i] !== "'") |
|
2585 i++; |
|
2586 i++; |
|
2587 } else { |
|
2588 var count = 1; |
|
2589 while (i < pattern.length && pattern[i] === c) { |
|
2590 i++; |
|
2591 count++; |
|
2592 } |
|
2593 var value; |
|
2594 switch (c) { |
|
2595 // "text" cases |
|
2596 case "G": |
|
2597 case "E": |
|
2598 case "z": |
|
2599 case "v": |
|
2600 case "V": |
|
2601 if (count <= 3) |
|
2602 value = "short"; |
|
2603 else if (count === 4) |
|
2604 value = "long"; |
|
2605 else |
|
2606 value = "narrow"; |
|
2607 break; |
|
2608 // "number" cases |
|
2609 case "y": |
|
2610 case "d": |
|
2611 case "h": |
|
2612 case "H": |
|
2613 case "m": |
|
2614 case "s": |
|
2615 case "k": |
|
2616 case "K": |
|
2617 if (count === 2) |
|
2618 value = "2-digit"; |
|
2619 else |
|
2620 value = "numeric"; |
|
2621 break; |
|
2622 // "text & number" cases |
|
2623 case "M": |
|
2624 case "L": |
|
2625 if (count === 1) |
|
2626 value = "numeric"; |
|
2627 else if (count === 2) |
|
2628 value = "2-digit"; |
|
2629 else if (count === 3) |
|
2630 value = "short"; |
|
2631 else if (count === 4) |
|
2632 value = "long"; |
|
2633 else |
|
2634 value = "narrow"; |
|
2635 break; |
|
2636 default: |
|
2637 // skip other pattern characters and literal text |
|
2638 } |
|
2639 if (callFunction(std_Object_hasOwnProperty, icuPatternCharToComponent, c)) |
|
2640 defineProperty(result, icuPatternCharToComponent[c], value); |
|
2641 if (c === "h" || c === "K") |
|
2642 defineProperty(result, "hour12", true); |
|
2643 else if (c === "H" || c === "k") |
|
2644 defineProperty(result, "hour12", false); |
|
2645 } |
|
2646 } |
|
2647 } |