i18n.js revision 342c50ce1624b485728b9a4fc41d8bbf37eb46cf
1// Copyright 2013 the V8 project authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5// ECMAScript 402 API implementation. 6 7/** 8 * Intl object is a single object that has some named properties, 9 * all of which are constructors. 10 */ 11(function(global, utils) { 12 13"use strict"; 14 15%CheckIsBootstrapping(); 16 17// ------------------------------------------------------------------- 18// Imports 19 20var ArrayIndexOf; 21var ArrayJoin; 22var ArrayPush; 23var IsFinite; 24var IsNaN; 25var GlobalBoolean = global.Boolean; 26var GlobalDate = global.Date; 27var GlobalNumber = global.Number; 28var GlobalRegExp = global.RegExp; 29var GlobalString = global.String; 30var MakeError; 31var MakeRangeError; 32var MakeTypeError; 33var MathFloor; 34var ObjectDefineProperties = utils.ImportNow("ObjectDefineProperties"); 35var ObjectDefineProperty = utils.ImportNow("ObjectDefineProperty"); 36var patternSymbol = utils.ImportNow("intl_pattern_symbol"); 37var RegExpTest; 38var resolvedSymbol = utils.ImportNow("intl_resolved_symbol"); 39var StringIndexOf; 40var StringLastIndexOf; 41var StringMatch; 42var StringReplace; 43var StringSplit; 44var StringSubstr; 45var StringSubstring; 46 47utils.Import(function(from) { 48 ArrayIndexOf = from.ArrayIndexOf; 49 ArrayJoin = from.ArrayJoin; 50 ArrayPush = from.ArrayPush; 51 IsFinite = from.IsFinite; 52 IsNaN = from.IsNaN; 53 MakeError = from.MakeError; 54 MakeRangeError = from.MakeRangeError; 55 MakeTypeError = from.MakeTypeError; 56 MathFloor = from.MathFloor; 57 RegExpTest = from.RegExpTest; 58 StringIndexOf = from.StringIndexOf; 59 StringLastIndexOf = from.StringLastIndexOf; 60 StringMatch = from.StringMatch; 61 StringReplace = from.StringReplace; 62 StringSplit = from.StringSplit; 63 StringSubstr = from.StringSubstr; 64 StringSubstring = from.StringSubstring; 65}); 66 67// ------------------------------------------------------------------- 68 69var Intl = {}; 70 71%AddNamedProperty(global, "Intl", Intl, DONT_ENUM); 72 73/** 74 * Caches available locales for each service. 75 */ 76var AVAILABLE_LOCALES = { 77 'collator': UNDEFINED, 78 'numberformat': UNDEFINED, 79 'dateformat': UNDEFINED, 80 'breakiterator': UNDEFINED 81}; 82 83/** 84 * Caches default ICU locale. 85 */ 86var DEFAULT_ICU_LOCALE = UNDEFINED; 87 88/** 89 * Unicode extension regular expression. 90 */ 91var UNICODE_EXTENSION_RE = UNDEFINED; 92 93function GetUnicodeExtensionRE() { 94 if (IS_UNDEFINED(UNDEFINED)) { 95 UNICODE_EXTENSION_RE = new GlobalRegExp('-u(-[a-z0-9]{2,8})+', 'g'); 96 } 97 return UNICODE_EXTENSION_RE; 98} 99 100/** 101 * Matches any Unicode extension. 102 */ 103var ANY_EXTENSION_RE = UNDEFINED; 104 105function GetAnyExtensionRE() { 106 if (IS_UNDEFINED(ANY_EXTENSION_RE)) { 107 ANY_EXTENSION_RE = new GlobalRegExp('-[a-z0-9]{1}-.*', 'g'); 108 } 109 return ANY_EXTENSION_RE; 110} 111 112/** 113 * Replace quoted text (single quote, anything but the quote and quote again). 114 */ 115var QUOTED_STRING_RE = UNDEFINED; 116 117function GetQuotedStringRE() { 118 if (IS_UNDEFINED(QUOTED_STRING_RE)) { 119 QUOTED_STRING_RE = new GlobalRegExp("'[^']+'", 'g'); 120 } 121 return QUOTED_STRING_RE; 122} 123 124/** 125 * Matches valid service name. 126 */ 127var SERVICE_RE = UNDEFINED; 128 129function GetServiceRE() { 130 if (IS_UNDEFINED(SERVICE_RE)) { 131 SERVICE_RE = 132 new GlobalRegExp('^(collator|numberformat|dateformat|breakiterator)$'); 133 } 134 return SERVICE_RE; 135} 136 137/** 138 * Validates a language tag against bcp47 spec. 139 * Actual value is assigned on first run. 140 */ 141var LANGUAGE_TAG_RE = UNDEFINED; 142 143function GetLanguageTagRE() { 144 if (IS_UNDEFINED(LANGUAGE_TAG_RE)) { 145 BuildLanguageTagREs(); 146 } 147 return LANGUAGE_TAG_RE; 148} 149 150/** 151 * Helps find duplicate variants in the language tag. 152 */ 153var LANGUAGE_VARIANT_RE = UNDEFINED; 154 155function GetLanguageVariantRE() { 156 if (IS_UNDEFINED(LANGUAGE_VARIANT_RE)) { 157 BuildLanguageTagREs(); 158 } 159 return LANGUAGE_VARIANT_RE; 160} 161 162/** 163 * Helps find duplicate singletons in the language tag. 164 */ 165var LANGUAGE_SINGLETON_RE = UNDEFINED; 166 167function GetLanguageSingletonRE() { 168 if (IS_UNDEFINED(LANGUAGE_SINGLETON_RE)) { 169 BuildLanguageTagREs(); 170 } 171 return LANGUAGE_SINGLETON_RE; 172} 173 174/** 175 * Matches valid IANA time zone names. 176 */ 177var TIMEZONE_NAME_CHECK_RE = UNDEFINED; 178 179function GetTimezoneNameCheckRE() { 180 if (IS_UNDEFINED(TIMEZONE_NAME_CHECK_RE)) { 181 TIMEZONE_NAME_CHECK_RE = new GlobalRegExp( 182 '^([A-Za-z]+)/([A-Za-z_-]+)((?:\/[A-Za-z_-]+)+)*$'); 183 } 184 return TIMEZONE_NAME_CHECK_RE; 185} 186 187/** 188 * Matches valid location parts of IANA time zone names. 189 */ 190var TIMEZONE_NAME_LOCATION_PART_RE = UNDEFINED; 191 192function GetTimezoneNameLocationPartRE() { 193 if (IS_UNDEFINED(TIMEZONE_NAME_LOCATION_PART_RE)) { 194 TIMEZONE_NAME_LOCATION_PART_RE = 195 new GlobalRegExp('^([A-Za-z]+)((?:[_-][A-Za-z]+)+)*$'); 196 } 197 return TIMEZONE_NAME_LOCATION_PART_RE; 198} 199 200/** 201 * Adds bound method to the prototype of the given object. 202 */ 203function addBoundMethod(obj, methodName, implementation, length) { 204 %CheckIsBootstrapping(); 205 function getter() { 206 if (!%IsInitializedIntlObject(this)) { 207 throw MakeTypeError(kMethodCalledOnWrongObject, methodName); 208 } 209 var internalName = '__bound' + methodName + '__'; 210 if (IS_UNDEFINED(this[internalName])) { 211 var that = this; 212 var boundMethod; 213 if (IS_UNDEFINED(length) || length === 2) { 214 boundMethod = function(x, y) { 215 if (!IS_UNDEFINED(new.target)) { 216 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor); 217 } 218 return implementation(that, x, y); 219 } 220 } else if (length === 1) { 221 boundMethod = function(x) { 222 if (!IS_UNDEFINED(new.target)) { 223 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor); 224 } 225 return implementation(that, x); 226 } 227 } else { 228 boundMethod = function() { 229 if (!IS_UNDEFINED(new.target)) { 230 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor); 231 } 232 // DateTimeFormat.format needs to be 0 arg method, but can stil 233 // receive optional dateValue param. If one was provided, pass it 234 // along. 235 if (arguments.length > 0) { 236 return implementation(that, arguments[0]); 237 } else { 238 return implementation(that); 239 } 240 } 241 } 242 %FunctionSetName(boundMethod, internalName); 243 %FunctionRemovePrototype(boundMethod); 244 %SetNativeFlag(boundMethod); 245 this[internalName] = boundMethod; 246 } 247 return this[internalName]; 248 } 249 250 %FunctionSetName(getter, methodName); 251 %FunctionRemovePrototype(getter); 252 %SetNativeFlag(getter); 253 254 ObjectDefineProperty(obj.prototype, methodName, { 255 get: getter, 256 enumerable: false, 257 configurable: true 258 }); 259} 260 261 262/** 263 * Returns an intersection of locales and service supported locales. 264 * Parameter locales is treated as a priority list. 265 */ 266function supportedLocalesOf(service, locales, options) { 267 if (IS_NULL(%_Call(StringMatch, service, GetServiceRE()))) { 268 throw MakeError(kWrongServiceType, service); 269 } 270 271 // Provide defaults if matcher was not specified. 272 if (IS_UNDEFINED(options)) { 273 options = {}; 274 } else { 275 options = TO_OBJECT(options); 276 } 277 278 var matcher = options.localeMatcher; 279 if (!IS_UNDEFINED(matcher)) { 280 matcher = GlobalString(matcher); 281 if (matcher !== 'lookup' && matcher !== 'best fit') { 282 throw MakeRangeError(kLocaleMatcher, matcher); 283 } 284 } else { 285 matcher = 'best fit'; 286 } 287 288 var requestedLocales = initializeLocaleList(locales); 289 290 // Cache these, they don't ever change per service. 291 if (IS_UNDEFINED(AVAILABLE_LOCALES[service])) { 292 AVAILABLE_LOCALES[service] = getAvailableLocalesOf(service); 293 } 294 295 // Use either best fit or lookup algorithm to match locales. 296 if (matcher === 'best fit') { 297 return initializeLocaleList(bestFitSupportedLocalesOf( 298 requestedLocales, AVAILABLE_LOCALES[service])); 299 } 300 301 return initializeLocaleList(lookupSupportedLocalesOf( 302 requestedLocales, AVAILABLE_LOCALES[service])); 303} 304 305 306/** 307 * Returns the subset of the provided BCP 47 language priority list for which 308 * this service has a matching locale when using the BCP 47 Lookup algorithm. 309 * Locales appear in the same order in the returned list as in the input list. 310 */ 311function lookupSupportedLocalesOf(requestedLocales, availableLocales) { 312 var matchedLocales = []; 313 for (var i = 0; i < requestedLocales.length; ++i) { 314 // Remove -u- extension. 315 var locale = %_Call(StringReplace, 316 requestedLocales[i], 317 GetUnicodeExtensionRE(), 318 ''); 319 do { 320 if (!IS_UNDEFINED(availableLocales[locale])) { 321 // Push requested locale not the resolved one. 322 %_Call(ArrayPush, matchedLocales, requestedLocales[i]); 323 break; 324 } 325 // Truncate locale if possible, if not break. 326 var pos = %_Call(StringLastIndexOf, locale, '-'); 327 if (pos === -1) { 328 break; 329 } 330 locale = %_Call(StringSubstring, locale, 0, pos); 331 } while (true); 332 } 333 334 return matchedLocales; 335} 336 337 338/** 339 * Returns the subset of the provided BCP 47 language priority list for which 340 * this service has a matching locale when using the implementation 341 * dependent algorithm. 342 * Locales appear in the same order in the returned list as in the input list. 343 */ 344function bestFitSupportedLocalesOf(requestedLocales, availableLocales) { 345 // TODO(cira): implement better best fit algorithm. 346 return lookupSupportedLocalesOf(requestedLocales, availableLocales); 347} 348 349 350/** 351 * Returns a getOption function that extracts property value for given 352 * options object. If property is missing it returns defaultValue. If value 353 * is out of range for that property it throws RangeError. 354 */ 355function getGetOption(options, caller) { 356 if (IS_UNDEFINED(options)) throw MakeError(kDefaultOptionsMissing, caller); 357 358 var getOption = function getOption(property, type, values, defaultValue) { 359 if (!IS_UNDEFINED(options[property])) { 360 var value = options[property]; 361 switch (type) { 362 case 'boolean': 363 value = GlobalBoolean(value); 364 break; 365 case 'string': 366 value = GlobalString(value); 367 break; 368 case 'number': 369 value = GlobalNumber(value); 370 break; 371 default: 372 throw MakeError(kWrongValueType); 373 } 374 375 if (!IS_UNDEFINED(values) && %_Call(ArrayIndexOf, values, value) === -1) { 376 throw MakeRangeError(kValueOutOfRange, value, caller, property); 377 } 378 379 return value; 380 } 381 382 return defaultValue; 383 } 384 385 return getOption; 386} 387 388 389/** 390 * Compares a BCP 47 language priority list requestedLocales against the locales 391 * in availableLocales and determines the best available language to meet the 392 * request. Two algorithms are available to match the locales: the Lookup 393 * algorithm described in RFC 4647 section 3.4, and an implementation dependent 394 * best-fit algorithm. Independent of the locale matching algorithm, options 395 * specified through Unicode locale extension sequences are negotiated 396 * separately, taking the caller's relevant extension keys and locale data as 397 * well as client-provided options into consideration. Returns an object with 398 * a locale property whose value is the language tag of the selected locale, 399 * and properties for each key in relevantExtensionKeys providing the selected 400 * value for that key. 401 */ 402function resolveLocale(service, requestedLocales, options) { 403 requestedLocales = initializeLocaleList(requestedLocales); 404 405 var getOption = getGetOption(options, service); 406 var matcher = getOption('localeMatcher', 'string', 407 ['lookup', 'best fit'], 'best fit'); 408 var resolved; 409 if (matcher === 'lookup') { 410 resolved = lookupMatcher(service, requestedLocales); 411 } else { 412 resolved = bestFitMatcher(service, requestedLocales); 413 } 414 415 return resolved; 416} 417 418 419/** 420 * Returns best matched supported locale and extension info using basic 421 * lookup algorithm. 422 */ 423function lookupMatcher(service, requestedLocales) { 424 if (IS_NULL(%_Call(StringMatch, service, GetServiceRE()))) { 425 throw MakeError(kWrongServiceType, service); 426 } 427 428 // Cache these, they don't ever change per service. 429 if (IS_UNDEFINED(AVAILABLE_LOCALES[service])) { 430 AVAILABLE_LOCALES[service] = getAvailableLocalesOf(service); 431 } 432 433 for (var i = 0; i < requestedLocales.length; ++i) { 434 // Remove all extensions. 435 var locale = %_Call(StringReplace, requestedLocales[i], 436 GetAnyExtensionRE(), ''); 437 do { 438 if (!IS_UNDEFINED(AVAILABLE_LOCALES[service][locale])) { 439 // Return the resolved locale and extension. 440 var extensionMatch = 441 %_Call(StringMatch, requestedLocales[i], GetUnicodeExtensionRE()); 442 var extension = IS_NULL(extensionMatch) ? '' : extensionMatch[0]; 443 return {'locale': locale, 'extension': extension, 'position': i}; 444 } 445 // Truncate locale if possible. 446 var pos = %_Call(StringLastIndexOf, locale, '-'); 447 if (pos === -1) { 448 break; 449 } 450 locale = %_Call(StringSubstring, locale, 0, pos); 451 } while (true); 452 } 453 454 // Didn't find a match, return default. 455 if (IS_UNDEFINED(DEFAULT_ICU_LOCALE)) { 456 DEFAULT_ICU_LOCALE = %GetDefaultICULocale(); 457 } 458 459 return {'locale': DEFAULT_ICU_LOCALE, 'extension': '', 'position': -1}; 460} 461 462 463/** 464 * Returns best matched supported locale and extension info using 465 * implementation dependend algorithm. 466 */ 467function bestFitMatcher(service, requestedLocales) { 468 // TODO(cira): implement better best fit algorithm. 469 return lookupMatcher(service, requestedLocales); 470} 471 472 473/** 474 * Parses Unicode extension into key - value map. 475 * Returns empty object if the extension string is invalid. 476 * We are not concerned with the validity of the values at this point. 477 */ 478function parseExtension(extension) { 479 var extensionSplit = %_Call(StringSplit, extension, '-'); 480 481 // Assume ['', 'u', ...] input, but don't throw. 482 if (extensionSplit.length <= 2 || 483 (extensionSplit[0] !== '' && extensionSplit[1] !== 'u')) { 484 return {}; 485 } 486 487 // Key is {2}alphanum, value is {3,8}alphanum. 488 // Some keys may not have explicit values (booleans). 489 var extensionMap = {}; 490 var previousKey = UNDEFINED; 491 for (var i = 2; i < extensionSplit.length; ++i) { 492 var length = extensionSplit[i].length; 493 var element = extensionSplit[i]; 494 if (length === 2) { 495 extensionMap[element] = UNDEFINED; 496 previousKey = element; 497 } else if (length >= 3 && length <=8 && !IS_UNDEFINED(previousKey)) { 498 extensionMap[previousKey] = element; 499 previousKey = UNDEFINED; 500 } else { 501 // There is a value that's too long, or that doesn't have a key. 502 return {}; 503 } 504 } 505 506 return extensionMap; 507} 508 509 510/** 511 * Populates internalOptions object with boolean key-value pairs 512 * from extensionMap and options. 513 * Returns filtered extension (number and date format constructors use 514 * Unicode extensions for passing parameters to ICU). 515 * It's used for extension-option pairs only, e.g. kn-normalization, but not 516 * for 'sensitivity' since it doesn't have extension equivalent. 517 * Extensions like nu and ca don't have options equivalent, so we place 518 * undefined in the map.property to denote that. 519 */ 520function setOptions(inOptions, extensionMap, keyValues, getOption, outOptions) { 521 var extension = ''; 522 523 var updateExtension = function updateExtension(key, value) { 524 return '-' + key + '-' + GlobalString(value); 525 } 526 527 var updateProperty = function updateProperty(property, type, value) { 528 if (type === 'boolean' && (typeof value === 'string')) { 529 value = (value === 'true') ? true : false; 530 } 531 532 if (!IS_UNDEFINED(property)) { 533 defineWEProperty(outOptions, property, value); 534 } 535 } 536 537 for (var key in keyValues) { 538 if (%HasOwnProperty(keyValues, key)) { 539 var value = UNDEFINED; 540 var map = keyValues[key]; 541 if (!IS_UNDEFINED(map.property)) { 542 // This may return true if user specifies numeric: 'false', since 543 // Boolean('nonempty') === true. 544 value = getOption(map.property, map.type, map.values); 545 } 546 if (!IS_UNDEFINED(value)) { 547 updateProperty(map.property, map.type, value); 548 extension += updateExtension(key, value); 549 continue; 550 } 551 // User options didn't have it, check Unicode extension. 552 // Here we want to convert strings 'true', 'false' into proper Boolean 553 // values (not a user error). 554 if (%HasOwnProperty(extensionMap, key)) { 555 value = extensionMap[key]; 556 if (!IS_UNDEFINED(value)) { 557 updateProperty(map.property, map.type, value); 558 extension += updateExtension(key, value); 559 } else if (map.type === 'boolean') { 560 // Boolean keys are allowed not to have values in Unicode extension. 561 // Those default to true. 562 updateProperty(map.property, map.type, true); 563 extension += updateExtension(key, true); 564 } 565 } 566 } 567 } 568 569 return extension === ''? '' : '-u' + extension; 570} 571 572 573/** 574 * Converts all OwnProperties into 575 * configurable: false, writable: false, enumerable: true. 576 */ 577function freezeArray(array) { 578 var l = array.length; 579 for (var i = 0; i < l; i++) { 580 if (i in array) { 581 ObjectDefineProperty(array, i, {value: array[i], 582 configurable: false, 583 writable: false, 584 enumerable: true}); 585 } 586 } 587 588 ObjectDefineProperty(array, 'length', {value: l, writable: false}); 589 return array; 590} 591 592 593/** 594 * It's sometimes desireable to leave user requested locale instead of ICU 595 * supported one (zh-TW is equivalent to zh-Hant-TW, so we should keep shorter 596 * one, if that was what user requested). 597 * This function returns user specified tag if its maximized form matches ICU 598 * resolved locale. If not we return ICU result. 599 */ 600function getOptimalLanguageTag(original, resolved) { 601 // Returns Array<Object>, where each object has maximized and base properties. 602 // Maximized: zh -> zh-Hans-CN 603 // Base: zh-CN-u-ca-gregory -> zh-CN 604 // Take care of grandfathered or simple cases. 605 if (original === resolved) { 606 return original; 607 } 608 609 var locales = %GetLanguageTagVariants([original, resolved]); 610 if (locales[0].maximized !== locales[1].maximized) { 611 return resolved; 612 } 613 614 // Preserve extensions of resolved locale, but swap base tags with original. 615 var resolvedBase = new GlobalRegExp('^' + locales[1].base); 616 return %_Call(StringReplace, resolved, resolvedBase, locales[0].base); 617} 618 619 620/** 621 * Returns an Object that contains all of supported locales for a given 622 * service. 623 * In addition to the supported locales we add xx-ZZ locale for each xx-Yyyy-ZZ 624 * that is supported. This is required by the spec. 625 */ 626function getAvailableLocalesOf(service) { 627 var available = %AvailableLocalesOf(service); 628 629 for (var i in available) { 630 if (%HasOwnProperty(available, i)) { 631 var parts = 632 %_Call(StringMatch, i, /^([a-z]{2,3})-([A-Z][a-z]{3})-([A-Z]{2})$/); 633 if (parts !== null) { 634 // Build xx-ZZ. We don't care about the actual value, 635 // as long it's not undefined. 636 available[parts[1] + '-' + parts[3]] = null; 637 } 638 } 639 } 640 641 return available; 642} 643 644 645/** 646 * Defines a property and sets writable and enumerable to true. 647 * Configurable is false by default. 648 */ 649function defineWEProperty(object, property, value) { 650 ObjectDefineProperty(object, property, 651 {value: value, writable: true, enumerable: true}); 652} 653 654 655/** 656 * Adds property to an object if the value is not undefined. 657 * Sets configurable descriptor to false. 658 */ 659function addWEPropertyIfDefined(object, property, value) { 660 if (!IS_UNDEFINED(value)) { 661 defineWEProperty(object, property, value); 662 } 663} 664 665 666/** 667 * Defines a property and sets writable, enumerable and configurable to true. 668 */ 669function defineWECProperty(object, property, value) { 670 ObjectDefineProperty(object, property, {value: value, 671 writable: true, 672 enumerable: true, 673 configurable: true}); 674} 675 676 677/** 678 * Adds property to an object if the value is not undefined. 679 * Sets all descriptors to true. 680 */ 681function addWECPropertyIfDefined(object, property, value) { 682 if (!IS_UNDEFINED(value)) { 683 defineWECProperty(object, property, value); 684 } 685} 686 687 688/** 689 * Returns titlecased word, aMeRricA -> America. 690 */ 691function toTitleCaseWord(word) { 692 return %StringToUpperCase(%_Call(StringSubstr, word, 0, 1)) + 693 %StringToLowerCase(%_Call(StringSubstr, word, 1)); 694} 695 696/** 697 * Returns titlecased location, bueNos_airES -> Buenos_Aires 698 * or ho_cHi_minH -> Ho_Chi_Minh. It is locale-agnostic and only 699 * deals with ASCII only characters. 700 * 'of', 'au' and 'es' are special-cased and lowercased. 701 */ 702function toTitleCaseTimezoneLocation(location) { 703 var match = %_Call(StringMatch, location, GetTimezoneNameLocationPartRE()); 704 if (IS_NULL(match)) throw MakeRangeError(kExpectedLocation, location); 705 706 var result = toTitleCaseWord(match[1]); 707 if (!IS_UNDEFINED(match[2]) && 2 < match.length) { 708 // The first character is a separator, '_' or '-'. 709 // None of IANA zone names has both '_' and '-'. 710 var separator = %_Call(StringSubstring, match[2], 0, 1); 711 var parts = %_Call(StringSplit, match[2], separator); 712 for (var i = 1; i < parts.length; i++) { 713 var part = parts[i] 714 var lowercasedPart = %StringToLowerCase(part); 715 result = result + separator + 716 ((lowercasedPart !== 'es' && 717 lowercasedPart !== 'of' && lowercasedPart !== 'au') ? 718 toTitleCaseWord(part) : lowercasedPart); 719 } 720 } 721 return result; 722} 723 724/** 725 * Canonicalizes the language tag, or throws in case the tag is invalid. 726 */ 727function canonicalizeLanguageTag(localeID) { 728 // null is typeof 'object' so we have to do extra check. 729 if (typeof localeID !== 'string' && typeof localeID !== 'object' || 730 IS_NULL(localeID)) { 731 throw MakeTypeError(kLanguageID); 732 } 733 734 var localeString = GlobalString(localeID); 735 736 if (isValidLanguageTag(localeString) === false) { 737 throw MakeRangeError(kInvalidLanguageTag, localeString); 738 } 739 740 // This call will strip -kn but not -kn-true extensions. 741 // ICU bug filled - http://bugs.icu-project.org/trac/ticket/9265. 742 // TODO(cira): check if -u-kn-true-kc-true-kh-true still throws after 743 // upgrade to ICU 4.9. 744 var tag = %CanonicalizeLanguageTag(localeString); 745 if (tag === 'invalid-tag') { 746 throw MakeRangeError(kInvalidLanguageTag, localeString); 747 } 748 749 return tag; 750} 751 752 753/** 754 * Returns an array where all locales are canonicalized and duplicates removed. 755 * Throws on locales that are not well formed BCP47 tags. 756 */ 757function initializeLocaleList(locales) { 758 var seen = []; 759 if (IS_UNDEFINED(locales)) { 760 // Constructor is called without arguments. 761 seen = []; 762 } else { 763 // We allow single string localeID. 764 if (typeof locales === 'string') { 765 %_Call(ArrayPush, seen, canonicalizeLanguageTag(locales)); 766 return freezeArray(seen); 767 } 768 769 var o = TO_OBJECT(locales); 770 var len = TO_UINT32(o.length); 771 772 for (var k = 0; k < len; k++) { 773 if (k in o) { 774 var value = o[k]; 775 776 var tag = canonicalizeLanguageTag(value); 777 778 if (%_Call(ArrayIndexOf, seen, tag) === -1) { 779 %_Call(ArrayPush, seen, tag); 780 } 781 } 782 } 783 } 784 785 return freezeArray(seen); 786} 787 788 789/** 790 * Validates the language tag. Section 2.2.9 of the bcp47 spec 791 * defines a valid tag. 792 * 793 * ICU is too permissible and lets invalid tags, like 794 * hant-cmn-cn, through. 795 * 796 * Returns false if the language tag is invalid. 797 */ 798function isValidLanguageTag(locale) { 799 // Check if it's well-formed, including grandfadered tags. 800 if (!%_Call(RegExpTest, GetLanguageTagRE(), locale)) { 801 return false; 802 } 803 804 // Just return if it's a x- form. It's all private. 805 if (%_Call(StringIndexOf, locale, 'x-') === 0) { 806 return true; 807 } 808 809 // Check if there are any duplicate variants or singletons (extensions). 810 811 // Remove private use section. 812 locale = %_Call(StringSplit, locale, /-x-/)[0]; 813 814 // Skip language since it can match variant regex, so we start from 1. 815 // We are matching i-klingon here, but that's ok, since i-klingon-klingon 816 // is not valid and would fail LANGUAGE_TAG_RE test. 817 var variants = []; 818 var extensions = []; 819 var parts = %_Call(StringSplit, locale, /-/); 820 for (var i = 1; i < parts.length; i++) { 821 var value = parts[i]; 822 if (%_Call(RegExpTest, GetLanguageVariantRE(), value) && 823 extensions.length === 0) { 824 if (%_Call(ArrayIndexOf, variants, value) === -1) { 825 %_Call(ArrayPush, variants, value); 826 } else { 827 return false; 828 } 829 } 830 831 if (%_Call(RegExpTest, GetLanguageSingletonRE(), value)) { 832 if (%_Call(ArrayIndexOf, extensions, value) === -1) { 833 %_Call(ArrayPush, extensions, value); 834 } else { 835 return false; 836 } 837 } 838 } 839 840 return true; 841 } 842 843 844/** 845 * Builds a regular expresion that validates the language tag 846 * against bcp47 spec. 847 * Uses http://tools.ietf.org/html/bcp47, section 2.1, ABNF. 848 * Runs on load and initializes the global REs. 849 */ 850function BuildLanguageTagREs() { 851 var alpha = '[a-zA-Z]'; 852 var digit = '[0-9]'; 853 var alphanum = '(' + alpha + '|' + digit + ')'; 854 var regular = '(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|' + 855 'zh-min|zh-min-nan|zh-xiang)'; 856 var irregular = '(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|' + 857 'i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|' + 858 'i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)'; 859 var grandfathered = '(' + irregular + '|' + regular + ')'; 860 var privateUse = '(x(-' + alphanum + '{1,8})+)'; 861 862 var singleton = '(' + digit + '|[A-WY-Za-wy-z])'; 863 LANGUAGE_SINGLETON_RE = new GlobalRegExp('^' + singleton + '$', 'i'); 864 865 var extension = '(' + singleton + '(-' + alphanum + '{2,8})+)'; 866 867 var variant = '(' + alphanum + '{5,8}|(' + digit + alphanum + '{3}))'; 868 LANGUAGE_VARIANT_RE = new GlobalRegExp('^' + variant + '$', 'i'); 869 870 var region = '(' + alpha + '{2}|' + digit + '{3})'; 871 var script = '(' + alpha + '{4})'; 872 var extLang = '(' + alpha + '{3}(-' + alpha + '{3}){0,2})'; 873 var language = '(' + alpha + '{2,3}(-' + extLang + ')?|' + alpha + '{4}|' + 874 alpha + '{5,8})'; 875 var langTag = language + '(-' + script + ')?(-' + region + ')?(-' + 876 variant + ')*(-' + extension + ')*(-' + privateUse + ')?'; 877 878 var languageTag = 879 '^(' + langTag + '|' + privateUse + '|' + grandfathered + ')$'; 880 LANGUAGE_TAG_RE = new GlobalRegExp(languageTag, 'i'); 881} 882 883var resolvedAccessor = { 884 get() { 885 %IncrementUseCounter(kIntlResolved); 886 return this[resolvedSymbol]; 887 }, 888 set(value) { 889 this[resolvedSymbol] = value; 890 } 891}; 892 893/** 894 * Initializes the given object so it's a valid Collator instance. 895 * Useful for subclassing. 896 */ 897function initializeCollator(collator, locales, options) { 898 if (%IsInitializedIntlObject(collator)) { 899 throw MakeTypeError(kReinitializeIntl, "Collator"); 900 } 901 902 if (IS_UNDEFINED(options)) { 903 options = {}; 904 } 905 906 var getOption = getGetOption(options, 'collator'); 907 908 var internalOptions = {}; 909 910 defineWEProperty(internalOptions, 'usage', getOption( 911 'usage', 'string', ['sort', 'search'], 'sort')); 912 913 var sensitivity = getOption('sensitivity', 'string', 914 ['base', 'accent', 'case', 'variant']); 915 if (IS_UNDEFINED(sensitivity) && internalOptions.usage === 'sort') { 916 sensitivity = 'variant'; 917 } 918 defineWEProperty(internalOptions, 'sensitivity', sensitivity); 919 920 defineWEProperty(internalOptions, 'ignorePunctuation', getOption( 921 'ignorePunctuation', 'boolean', UNDEFINED, false)); 922 923 var locale = resolveLocale('collator', locales, options); 924 925 // ICU can't take kb, kc... parameters through localeID, so we need to pass 926 // them as options. 927 // One exception is -co- which has to be part of the extension, but only for 928 // usage: sort, and its value can't be 'standard' or 'search'. 929 var extensionMap = parseExtension(locale.extension); 930 931 /** 932 * Map of Unicode extensions to option properties, and their values and types, 933 * for a collator. 934 */ 935 var COLLATOR_KEY_MAP = { 936 'kn': {'property': 'numeric', 'type': 'boolean'}, 937 'kf': {'property': 'caseFirst', 'type': 'string', 938 'values': ['false', 'lower', 'upper']} 939 }; 940 941 setOptions( 942 options, extensionMap, COLLATOR_KEY_MAP, getOption, internalOptions); 943 944 var collation = 'default'; 945 var extension = ''; 946 if (%HasOwnProperty(extensionMap, 'co') && internalOptions.usage === 'sort') { 947 948 /** 949 * Allowed -u-co- values. List taken from: 950 * http://unicode.org/repos/cldr/trunk/common/bcp47/collation.xml 951 */ 952 var ALLOWED_CO_VALUES = [ 953 'big5han', 'dict', 'direct', 'ducet', 'gb2312', 'phonebk', 'phonetic', 954 'pinyin', 'reformed', 'searchjl', 'stroke', 'trad', 'unihan', 'zhuyin' 955 ]; 956 957 if (%_Call(ArrayIndexOf, ALLOWED_CO_VALUES, extensionMap.co) !== -1) { 958 extension = '-u-co-' + extensionMap.co; 959 // ICU can't tell us what the collation is, so save user's input. 960 collation = extensionMap.co; 961 } 962 } else if (internalOptions.usage === 'search') { 963 extension = '-u-co-search'; 964 } 965 defineWEProperty(internalOptions, 'collation', collation); 966 967 var requestedLocale = locale.locale + extension; 968 969 // We define all properties C++ code may produce, to prevent security 970 // problems. If malicious user decides to redefine Object.prototype.locale 971 // we can't just use plain x.locale = 'us' or in C++ Set("locale", "us"). 972 // ObjectDefineProperties will either succeed defining or throw an error. 973 var resolved = ObjectDefineProperties({}, { 974 caseFirst: {writable: true}, 975 collation: {value: internalOptions.collation, writable: true}, 976 ignorePunctuation: {writable: true}, 977 locale: {writable: true}, 978 numeric: {writable: true}, 979 requestedLocale: {value: requestedLocale, writable: true}, 980 sensitivity: {writable: true}, 981 strength: {writable: true}, 982 usage: {value: internalOptions.usage, writable: true} 983 }); 984 985 var internalCollator = %CreateCollator(requestedLocale, 986 internalOptions, 987 resolved); 988 989 // Writable, configurable and enumerable are set to false by default. 990 %MarkAsInitializedIntlObjectOfType(collator, 'collator', internalCollator); 991 collator[resolvedSymbol] = resolved; 992 ObjectDefineProperty(collator, 'resolved', resolvedAccessor); 993 994 return collator; 995} 996 997 998/** 999 * Constructs Intl.Collator object given optional locales and options 1000 * parameters. 1001 * 1002 * @constructor 1003 */ 1004%AddNamedProperty(Intl, 'Collator', function() { 1005 var locales = arguments[0]; 1006 var options = arguments[1]; 1007 1008 if (!this || this === Intl) { 1009 // Constructor is called as a function. 1010 return new Intl.Collator(locales, options); 1011 } 1012 1013 return initializeCollator(TO_OBJECT(this), locales, options); 1014 }, 1015 DONT_ENUM 1016); 1017 1018 1019/** 1020 * Collator resolvedOptions method. 1021 */ 1022%AddNamedProperty(Intl.Collator.prototype, 'resolvedOptions', function() { 1023 if (!IS_UNDEFINED(new.target)) { 1024 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor); 1025 } 1026 1027 if (!%IsInitializedIntlObjectOfType(this, 'collator')) { 1028 throw MakeTypeError(kResolvedOptionsCalledOnNonObject, "Collator"); 1029 } 1030 1031 var coll = this; 1032 var locale = getOptimalLanguageTag(coll[resolvedSymbol].requestedLocale, 1033 coll[resolvedSymbol].locale); 1034 1035 return { 1036 locale: locale, 1037 usage: coll[resolvedSymbol].usage, 1038 sensitivity: coll[resolvedSymbol].sensitivity, 1039 ignorePunctuation: coll[resolvedSymbol].ignorePunctuation, 1040 numeric: coll[resolvedSymbol].numeric, 1041 caseFirst: coll[resolvedSymbol].caseFirst, 1042 collation: coll[resolvedSymbol].collation 1043 }; 1044 }, 1045 DONT_ENUM 1046); 1047%FunctionSetName(Intl.Collator.prototype.resolvedOptions, 'resolvedOptions'); 1048%FunctionRemovePrototype(Intl.Collator.prototype.resolvedOptions); 1049%SetNativeFlag(Intl.Collator.prototype.resolvedOptions); 1050 1051 1052/** 1053 * Returns the subset of the given locale list for which this locale list 1054 * has a matching (possibly fallback) locale. Locales appear in the same 1055 * order in the returned list as in the input list. 1056 * Options are optional parameter. 1057 */ 1058%AddNamedProperty(Intl.Collator, 'supportedLocalesOf', function(locales) { 1059 if (!IS_UNDEFINED(new.target)) { 1060 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor); 1061 } 1062 1063 return supportedLocalesOf('collator', locales, arguments[1]); 1064 }, 1065 DONT_ENUM 1066); 1067%FunctionSetName(Intl.Collator.supportedLocalesOf, 'supportedLocalesOf'); 1068%FunctionRemovePrototype(Intl.Collator.supportedLocalesOf); 1069%SetNativeFlag(Intl.Collator.supportedLocalesOf); 1070 1071 1072/** 1073 * When the compare method is called with two arguments x and y, it returns a 1074 * Number other than NaN that represents the result of a locale-sensitive 1075 * String comparison of x with y. 1076 * The result is intended to order String values in the sort order specified 1077 * by the effective locale and collation options computed during construction 1078 * of this Collator object, and will be negative, zero, or positive, depending 1079 * on whether x comes before y in the sort order, the Strings are equal under 1080 * the sort order, or x comes after y in the sort order, respectively. 1081 */ 1082function compare(collator, x, y) { 1083 return %InternalCompare(%GetImplFromInitializedIntlObject(collator), 1084 GlobalString(x), GlobalString(y)); 1085}; 1086 1087 1088addBoundMethod(Intl.Collator, 'compare', compare, 2); 1089 1090/** 1091 * Verifies that the input is a well-formed ISO 4217 currency code. 1092 * Don't uppercase to test. It could convert invalid code into a valid one. 1093 * For example \u00DFP (Eszett+P) becomes SSP. 1094 */ 1095function isWellFormedCurrencyCode(currency) { 1096 return typeof currency == "string" && 1097 currency.length == 3 && 1098 %_Call(StringMatch, currency, /[^A-Za-z]/) == null; 1099} 1100 1101 1102/** 1103 * Returns the valid digit count for a property, or throws RangeError on 1104 * a value out of the range. 1105 */ 1106function getNumberOption(options, property, min, max, fallback) { 1107 var value = options[property]; 1108 if (!IS_UNDEFINED(value)) { 1109 value = GlobalNumber(value); 1110 if (IsNaN(value) || value < min || value > max) { 1111 throw MakeRangeError(kPropertyValueOutOfRange, property); 1112 } 1113 return MathFloor(value); 1114 } 1115 1116 return fallback; 1117} 1118 1119var patternAccessor = { 1120 get() { 1121 %IncrementUseCounter(kIntlPattern); 1122 return this[patternSymbol]; 1123 }, 1124 set(value) { 1125 this[patternSymbol] = value; 1126 } 1127}; 1128 1129/** 1130 * Initializes the given object so it's a valid NumberFormat instance. 1131 * Useful for subclassing. 1132 */ 1133function initializeNumberFormat(numberFormat, locales, options) { 1134 if (%IsInitializedIntlObject(numberFormat)) { 1135 throw MakeTypeError(kReinitializeIntl, "NumberFormat"); 1136 } 1137 1138 if (IS_UNDEFINED(options)) { 1139 options = {}; 1140 } 1141 1142 var getOption = getGetOption(options, 'numberformat'); 1143 1144 var locale = resolveLocale('numberformat', locales, options); 1145 1146 var internalOptions = {}; 1147 defineWEProperty(internalOptions, 'style', getOption( 1148 'style', 'string', ['decimal', 'percent', 'currency'], 'decimal')); 1149 1150 var currency = getOption('currency', 'string'); 1151 if (!IS_UNDEFINED(currency) && !isWellFormedCurrencyCode(currency)) { 1152 throw MakeRangeError(kInvalidCurrencyCode, currency); 1153 } 1154 1155 if (internalOptions.style === 'currency' && IS_UNDEFINED(currency)) { 1156 throw MakeTypeError(kCurrencyCode); 1157 } 1158 1159 var currencyDisplay = getOption( 1160 'currencyDisplay', 'string', ['code', 'symbol', 'name'], 'symbol'); 1161 if (internalOptions.style === 'currency') { 1162 defineWEProperty(internalOptions, 'currency', %StringToUpperCase(currency)); 1163 defineWEProperty(internalOptions, 'currencyDisplay', currencyDisplay); 1164 } 1165 1166 // Digit ranges. 1167 var mnid = getNumberOption(options, 'minimumIntegerDigits', 1, 21, 1); 1168 defineWEProperty(internalOptions, 'minimumIntegerDigits', mnid); 1169 1170 var mnfd = options['minimumFractionDigits']; 1171 var mxfd = options['maximumFractionDigits']; 1172 if (!IS_UNDEFINED(mnfd) || internalOptions.style !== 'currency') { 1173 mnfd = getNumberOption(options, 'minimumFractionDigits', 0, 20, 0); 1174 defineWEProperty(internalOptions, 'minimumFractionDigits', mnfd); 1175 } 1176 1177 if (!IS_UNDEFINED(mxfd) || internalOptions.style !== 'currency') { 1178 var min_mxfd = internalOptions.style === 'percent' ? 0 : 3; 1179 mnfd = IS_UNDEFINED(mnfd) ? 0 : mnfd; 1180 var fallback_limit = (mnfd > min_mxfd) ? mnfd : min_mxfd; 1181 mxfd = getNumberOption(options, 'maximumFractionDigits', mnfd, 20, fallback_limit); 1182 defineWEProperty(internalOptions, 'maximumFractionDigits', mxfd); 1183 } 1184 1185 var mnsd = options['minimumSignificantDigits']; 1186 var mxsd = options['maximumSignificantDigits']; 1187 if (!IS_UNDEFINED(mnsd) || !IS_UNDEFINED(mxsd)) { 1188 mnsd = getNumberOption(options, 'minimumSignificantDigits', 1, 21, 0); 1189 defineWEProperty(internalOptions, 'minimumSignificantDigits', mnsd); 1190 1191 mxsd = getNumberOption(options, 'maximumSignificantDigits', mnsd, 21, 21); 1192 defineWEProperty(internalOptions, 'maximumSignificantDigits', mxsd); 1193 } 1194 1195 // Grouping. 1196 defineWEProperty(internalOptions, 'useGrouping', getOption( 1197 'useGrouping', 'boolean', UNDEFINED, true)); 1198 1199 // ICU prefers options to be passed using -u- extension key/values for 1200 // number format, so we need to build that. 1201 var extensionMap = parseExtension(locale.extension); 1202 1203 /** 1204 * Map of Unicode extensions to option properties, and their values and types, 1205 * for a number format. 1206 */ 1207 var NUMBER_FORMAT_KEY_MAP = { 1208 'nu': {'property': UNDEFINED, 'type': 'string'} 1209 }; 1210 1211 var extension = setOptions(options, extensionMap, NUMBER_FORMAT_KEY_MAP, 1212 getOption, internalOptions); 1213 1214 var requestedLocale = locale.locale + extension; 1215 var resolved = ObjectDefineProperties({}, { 1216 currency: {writable: true}, 1217 currencyDisplay: {writable: true}, 1218 locale: {writable: true}, 1219 maximumFractionDigits: {writable: true}, 1220 minimumFractionDigits: {writable: true}, 1221 minimumIntegerDigits: {writable: true}, 1222 numberingSystem: {writable: true}, 1223 pattern: patternAccessor, 1224 requestedLocale: {value: requestedLocale, writable: true}, 1225 style: {value: internalOptions.style, writable: true}, 1226 useGrouping: {writable: true} 1227 }); 1228 if (%HasOwnProperty(internalOptions, 'minimumSignificantDigits')) { 1229 defineWEProperty(resolved, 'minimumSignificantDigits', UNDEFINED); 1230 } 1231 if (%HasOwnProperty(internalOptions, 'maximumSignificantDigits')) { 1232 defineWEProperty(resolved, 'maximumSignificantDigits', UNDEFINED); 1233 } 1234 var formatter = %CreateNumberFormat(requestedLocale, 1235 internalOptions, 1236 resolved); 1237 1238 if (internalOptions.style === 'currency') { 1239 ObjectDefineProperty(resolved, 'currencyDisplay', {value: currencyDisplay, 1240 writable: true}); 1241 } 1242 1243 %MarkAsInitializedIntlObjectOfType(numberFormat, 'numberformat', formatter); 1244 numberFormat[resolvedSymbol] = resolved; 1245 ObjectDefineProperty(numberFormat, 'resolved', resolvedAccessor); 1246 1247 return numberFormat; 1248} 1249 1250 1251/** 1252 * Constructs Intl.NumberFormat object given optional locales and options 1253 * parameters. 1254 * 1255 * @constructor 1256 */ 1257%AddNamedProperty(Intl, 'NumberFormat', function() { 1258 var locales = arguments[0]; 1259 var options = arguments[1]; 1260 1261 if (!this || this === Intl) { 1262 // Constructor is called as a function. 1263 return new Intl.NumberFormat(locales, options); 1264 } 1265 1266 return initializeNumberFormat(TO_OBJECT(this), locales, options); 1267 }, 1268 DONT_ENUM 1269); 1270 1271 1272/** 1273 * NumberFormat resolvedOptions method. 1274 */ 1275%AddNamedProperty(Intl.NumberFormat.prototype, 'resolvedOptions', function() { 1276 if (!IS_UNDEFINED(new.target)) { 1277 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor); 1278 } 1279 1280 if (!%IsInitializedIntlObjectOfType(this, 'numberformat')) { 1281 throw MakeTypeError(kResolvedOptionsCalledOnNonObject, "NumberFormat"); 1282 } 1283 1284 var format = this; 1285 var locale = getOptimalLanguageTag(format[resolvedSymbol].requestedLocale, 1286 format[resolvedSymbol].locale); 1287 1288 var result = { 1289 locale: locale, 1290 numberingSystem: format[resolvedSymbol].numberingSystem, 1291 style: format[resolvedSymbol].style, 1292 useGrouping: format[resolvedSymbol].useGrouping, 1293 minimumIntegerDigits: format[resolvedSymbol].minimumIntegerDigits, 1294 minimumFractionDigits: format[resolvedSymbol].minimumFractionDigits, 1295 maximumFractionDigits: format[resolvedSymbol].maximumFractionDigits, 1296 }; 1297 1298 if (result.style === 'currency') { 1299 defineWECProperty(result, 'currency', format[resolvedSymbol].currency); 1300 defineWECProperty(result, 'currencyDisplay', 1301 format[resolvedSymbol].currencyDisplay); 1302 } 1303 1304 if (%HasOwnProperty(format[resolvedSymbol], 'minimumSignificantDigits')) { 1305 defineWECProperty(result, 'minimumSignificantDigits', 1306 format[resolvedSymbol].minimumSignificantDigits); 1307 } 1308 1309 if (%HasOwnProperty(format[resolvedSymbol], 'maximumSignificantDigits')) { 1310 defineWECProperty(result, 'maximumSignificantDigits', 1311 format[resolvedSymbol].maximumSignificantDigits); 1312 } 1313 1314 return result; 1315 }, 1316 DONT_ENUM 1317); 1318%FunctionSetName(Intl.NumberFormat.prototype.resolvedOptions, 1319 'resolvedOptions'); 1320%FunctionRemovePrototype(Intl.NumberFormat.prototype.resolvedOptions); 1321%SetNativeFlag(Intl.NumberFormat.prototype.resolvedOptions); 1322 1323 1324/** 1325 * Returns the subset of the given locale list for which this locale list 1326 * has a matching (possibly fallback) locale. Locales appear in the same 1327 * order in the returned list as in the input list. 1328 * Options are optional parameter. 1329 */ 1330%AddNamedProperty(Intl.NumberFormat, 'supportedLocalesOf', function(locales) { 1331 if (!IS_UNDEFINED(new.target)) { 1332 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor); 1333 } 1334 1335 return supportedLocalesOf('numberformat', locales, arguments[1]); 1336 }, 1337 DONT_ENUM 1338); 1339%FunctionSetName(Intl.NumberFormat.supportedLocalesOf, 'supportedLocalesOf'); 1340%FunctionRemovePrototype(Intl.NumberFormat.supportedLocalesOf); 1341%SetNativeFlag(Intl.NumberFormat.supportedLocalesOf); 1342 1343 1344/** 1345 * Returns a String value representing the result of calling ToNumber(value) 1346 * according to the effective locale and the formatting options of this 1347 * NumberFormat. 1348 */ 1349function formatNumber(formatter, value) { 1350 // Spec treats -0 and +0 as 0. 1351 var number = TO_NUMBER(value) + 0; 1352 1353 return %InternalNumberFormat(%GetImplFromInitializedIntlObject(formatter), 1354 number); 1355} 1356 1357 1358/** 1359 * Returns a Number that represents string value that was passed in. 1360 */ 1361function parseNumber(formatter, value) { 1362 return %InternalNumberParse(%GetImplFromInitializedIntlObject(formatter), 1363 GlobalString(value)); 1364} 1365 1366 1367addBoundMethod(Intl.NumberFormat, 'format', formatNumber, 1); 1368addBoundMethod(Intl.NumberFormat, 'v8Parse', parseNumber, 1); 1369 1370/** 1371 * Returns a string that matches LDML representation of the options object. 1372 */ 1373function toLDMLString(options) { 1374 var getOption = getGetOption(options, 'dateformat'); 1375 1376 var ldmlString = ''; 1377 1378 var option = getOption('weekday', 'string', ['narrow', 'short', 'long']); 1379 ldmlString += appendToLDMLString( 1380 option, {narrow: 'EEEEE', short: 'EEE', long: 'EEEE'}); 1381 1382 option = getOption('era', 'string', ['narrow', 'short', 'long']); 1383 ldmlString += appendToLDMLString( 1384 option, {narrow: 'GGGGG', short: 'GGG', long: 'GGGG'}); 1385 1386 option = getOption('year', 'string', ['2-digit', 'numeric']); 1387 ldmlString += appendToLDMLString(option, {'2-digit': 'yy', 'numeric': 'y'}); 1388 1389 option = getOption('month', 'string', 1390 ['2-digit', 'numeric', 'narrow', 'short', 'long']); 1391 ldmlString += appendToLDMLString(option, {'2-digit': 'MM', 'numeric': 'M', 1392 'narrow': 'MMMMM', 'short': 'MMM', 'long': 'MMMM'}); 1393 1394 option = getOption('day', 'string', ['2-digit', 'numeric']); 1395 ldmlString += appendToLDMLString( 1396 option, {'2-digit': 'dd', 'numeric': 'd'}); 1397 1398 var hr12 = getOption('hour12', 'boolean'); 1399 option = getOption('hour', 'string', ['2-digit', 'numeric']); 1400 if (IS_UNDEFINED(hr12)) { 1401 ldmlString += appendToLDMLString(option, {'2-digit': 'jj', 'numeric': 'j'}); 1402 } else if (hr12 === true) { 1403 ldmlString += appendToLDMLString(option, {'2-digit': 'hh', 'numeric': 'h'}); 1404 } else { 1405 ldmlString += appendToLDMLString(option, {'2-digit': 'HH', 'numeric': 'H'}); 1406 } 1407 1408 option = getOption('minute', 'string', ['2-digit', 'numeric']); 1409 ldmlString += appendToLDMLString(option, {'2-digit': 'mm', 'numeric': 'm'}); 1410 1411 option = getOption('second', 'string', ['2-digit', 'numeric']); 1412 ldmlString += appendToLDMLString(option, {'2-digit': 'ss', 'numeric': 's'}); 1413 1414 option = getOption('timeZoneName', 'string', ['short', 'long']); 1415 ldmlString += appendToLDMLString(option, {short: 'z', long: 'zzzz'}); 1416 1417 return ldmlString; 1418} 1419 1420 1421/** 1422 * Returns either LDML equivalent of the current option or empty string. 1423 */ 1424function appendToLDMLString(option, pairs) { 1425 if (!IS_UNDEFINED(option)) { 1426 return pairs[option]; 1427 } else { 1428 return ''; 1429 } 1430} 1431 1432 1433/** 1434 * Returns object that matches LDML representation of the date. 1435 */ 1436function fromLDMLString(ldmlString) { 1437 // First remove '' quoted text, so we lose 'Uhr' strings. 1438 ldmlString = %_Call(StringReplace, ldmlString, GetQuotedStringRE(), ''); 1439 1440 var options = {}; 1441 var match = %_Call(StringMatch, ldmlString, /E{3,5}/g); 1442 options = appendToDateTimeObject( 1443 options, 'weekday', match, {EEEEE: 'narrow', EEE: 'short', EEEE: 'long'}); 1444 1445 match = %_Call(StringMatch, ldmlString, /G{3,5}/g); 1446 options = appendToDateTimeObject( 1447 options, 'era', match, {GGGGG: 'narrow', GGG: 'short', GGGG: 'long'}); 1448 1449 match = %_Call(StringMatch, ldmlString, /y{1,2}/g); 1450 options = appendToDateTimeObject( 1451 options, 'year', match, {y: 'numeric', yy: '2-digit'}); 1452 1453 match = %_Call(StringMatch, ldmlString, /M{1,5}/g); 1454 options = appendToDateTimeObject(options, 'month', match, {MM: '2-digit', 1455 M: 'numeric', MMMMM: 'narrow', MMM: 'short', MMMM: 'long'}); 1456 1457 // Sometimes we get L instead of M for month - standalone name. 1458 match = %_Call(StringMatch, ldmlString, /L{1,5}/g); 1459 options = appendToDateTimeObject(options, 'month', match, {LL: '2-digit', 1460 L: 'numeric', LLLLL: 'narrow', LLL: 'short', LLLL: 'long'}); 1461 1462 match = %_Call(StringMatch, ldmlString, /d{1,2}/g); 1463 options = appendToDateTimeObject( 1464 options, 'day', match, {d: 'numeric', dd: '2-digit'}); 1465 1466 match = %_Call(StringMatch, ldmlString, /h{1,2}/g); 1467 if (match !== null) { 1468 options['hour12'] = true; 1469 } 1470 options = appendToDateTimeObject( 1471 options, 'hour', match, {h: 'numeric', hh: '2-digit'}); 1472 1473 match = %_Call(StringMatch, ldmlString, /H{1,2}/g); 1474 if (match !== null) { 1475 options['hour12'] = false; 1476 } 1477 options = appendToDateTimeObject( 1478 options, 'hour', match, {H: 'numeric', HH: '2-digit'}); 1479 1480 match = %_Call(StringMatch, ldmlString, /m{1,2}/g); 1481 options = appendToDateTimeObject( 1482 options, 'minute', match, {m: 'numeric', mm: '2-digit'}); 1483 1484 match = %_Call(StringMatch, ldmlString, /s{1,2}/g); 1485 options = appendToDateTimeObject( 1486 options, 'second', match, {s: 'numeric', ss: '2-digit'}); 1487 1488 match = %_Call(StringMatch, ldmlString, /z|zzzz/g); 1489 options = appendToDateTimeObject( 1490 options, 'timeZoneName', match, {z: 'short', zzzz: 'long'}); 1491 1492 return options; 1493} 1494 1495 1496function appendToDateTimeObject(options, option, match, pairs) { 1497 if (IS_NULL(match)) { 1498 if (!%HasOwnProperty(options, option)) { 1499 defineWEProperty(options, option, UNDEFINED); 1500 } 1501 return options; 1502 } 1503 1504 var property = match[0]; 1505 defineWEProperty(options, option, pairs[property]); 1506 1507 return options; 1508} 1509 1510 1511/** 1512 * Returns options with at least default values in it. 1513 */ 1514function toDateTimeOptions(options, required, defaults) { 1515 if (IS_UNDEFINED(options)) { 1516 options = {}; 1517 } else { 1518 options = TO_OBJECT(options); 1519 } 1520 1521 var needsDefault = true; 1522 if ((required === 'date' || required === 'any') && 1523 (!IS_UNDEFINED(options.weekday) || !IS_UNDEFINED(options.year) || 1524 !IS_UNDEFINED(options.month) || !IS_UNDEFINED(options.day))) { 1525 needsDefault = false; 1526 } 1527 1528 if ((required === 'time' || required === 'any') && 1529 (!IS_UNDEFINED(options.hour) || !IS_UNDEFINED(options.minute) || 1530 !IS_UNDEFINED(options.second))) { 1531 needsDefault = false; 1532 } 1533 1534 if (needsDefault && (defaults === 'date' || defaults === 'all')) { 1535 ObjectDefineProperty(options, 'year', {value: 'numeric', 1536 writable: true, 1537 enumerable: true, 1538 configurable: true}); 1539 ObjectDefineProperty(options, 'month', {value: 'numeric', 1540 writable: true, 1541 enumerable: true, 1542 configurable: true}); 1543 ObjectDefineProperty(options, 'day', {value: 'numeric', 1544 writable: true, 1545 enumerable: true, 1546 configurable: true}); 1547 } 1548 1549 if (needsDefault && (defaults === 'time' || defaults === 'all')) { 1550 ObjectDefineProperty(options, 'hour', {value: 'numeric', 1551 writable: true, 1552 enumerable: true, 1553 configurable: true}); 1554 ObjectDefineProperty(options, 'minute', {value: 'numeric', 1555 writable: true, 1556 enumerable: true, 1557 configurable: true}); 1558 ObjectDefineProperty(options, 'second', {value: 'numeric', 1559 writable: true, 1560 enumerable: true, 1561 configurable: true}); 1562 } 1563 1564 return options; 1565} 1566 1567 1568/** 1569 * Initializes the given object so it's a valid DateTimeFormat instance. 1570 * Useful for subclassing. 1571 */ 1572function initializeDateTimeFormat(dateFormat, locales, options) { 1573 1574 if (%IsInitializedIntlObject(dateFormat)) { 1575 throw MakeTypeError(kReinitializeIntl, "DateTimeFormat"); 1576 } 1577 1578 if (IS_UNDEFINED(options)) { 1579 options = {}; 1580 } 1581 1582 var locale = resolveLocale('dateformat', locales, options); 1583 1584 options = toDateTimeOptions(options, 'any', 'date'); 1585 1586 var getOption = getGetOption(options, 'dateformat'); 1587 1588 // We implement only best fit algorithm, but still need to check 1589 // if the formatMatcher values are in range. 1590 var matcher = getOption('formatMatcher', 'string', 1591 ['basic', 'best fit'], 'best fit'); 1592 1593 // Build LDML string for the skeleton that we pass to the formatter. 1594 var ldmlString = toLDMLString(options); 1595 1596 // Filter out supported extension keys so we know what to put in resolved 1597 // section later on. 1598 // We need to pass calendar and number system to the method. 1599 var tz = canonicalizeTimeZoneID(options.timeZone); 1600 1601 // ICU prefers options to be passed using -u- extension key/values, so 1602 // we need to build that. 1603 var internalOptions = {}; 1604 var extensionMap = parseExtension(locale.extension); 1605 1606 /** 1607 * Map of Unicode extensions to option properties, and their values and types, 1608 * for a date/time format. 1609 */ 1610 var DATETIME_FORMAT_KEY_MAP = { 1611 'ca': {'property': UNDEFINED, 'type': 'string'}, 1612 'nu': {'property': UNDEFINED, 'type': 'string'} 1613 }; 1614 1615 var extension = setOptions(options, extensionMap, DATETIME_FORMAT_KEY_MAP, 1616 getOption, internalOptions); 1617 1618 var requestedLocale = locale.locale + extension; 1619 var resolved = ObjectDefineProperties({}, { 1620 calendar: {writable: true}, 1621 day: {writable: true}, 1622 era: {writable: true}, 1623 hour12: {writable: true}, 1624 hour: {writable: true}, 1625 locale: {writable: true}, 1626 minute: {writable: true}, 1627 month: {writable: true}, 1628 numberingSystem: {writable: true}, 1629 [patternSymbol]: {writable: true}, 1630 pattern: patternAccessor, 1631 requestedLocale: {value: requestedLocale, writable: true}, 1632 second: {writable: true}, 1633 timeZone: {writable: true}, 1634 timeZoneName: {writable: true}, 1635 tz: {value: tz, writable: true}, 1636 weekday: {writable: true}, 1637 year: {writable: true} 1638 }); 1639 1640 var formatter = %CreateDateTimeFormat( 1641 requestedLocale, {skeleton: ldmlString, timeZone: tz}, resolved); 1642 1643 if (resolved.timeZone === "Etc/Unknown") { 1644 throw MakeRangeError(kUnsupportedTimeZone, tz); 1645 } 1646 1647 %MarkAsInitializedIntlObjectOfType(dateFormat, 'dateformat', formatter); 1648 dateFormat[resolvedSymbol] = resolved; 1649 ObjectDefineProperty(dateFormat, 'resolved', resolvedAccessor); 1650 1651 return dateFormat; 1652} 1653 1654 1655/** 1656 * Constructs Intl.DateTimeFormat object given optional locales and options 1657 * parameters. 1658 * 1659 * @constructor 1660 */ 1661%AddNamedProperty(Intl, 'DateTimeFormat', function() { 1662 var locales = arguments[0]; 1663 var options = arguments[1]; 1664 1665 if (!this || this === Intl) { 1666 // Constructor is called as a function. 1667 return new Intl.DateTimeFormat(locales, options); 1668 } 1669 1670 return initializeDateTimeFormat(TO_OBJECT(this), locales, options); 1671 }, 1672 DONT_ENUM 1673); 1674 1675 1676/** 1677 * DateTimeFormat resolvedOptions method. 1678 */ 1679%AddNamedProperty(Intl.DateTimeFormat.prototype, 'resolvedOptions', function() { 1680 if (!IS_UNDEFINED(new.target)) { 1681 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor); 1682 } 1683 1684 if (!%IsInitializedIntlObjectOfType(this, 'dateformat')) { 1685 throw MakeTypeError(kResolvedOptionsCalledOnNonObject, "DateTimeFormat"); 1686 } 1687 1688 /** 1689 * Maps ICU calendar names into LDML type. 1690 */ 1691 var ICU_CALENDAR_MAP = { 1692 'gregorian': 'gregory', 1693 'japanese': 'japanese', 1694 'buddhist': 'buddhist', 1695 'roc': 'roc', 1696 'persian': 'persian', 1697 'islamic-civil': 'islamicc', 1698 'islamic': 'islamic', 1699 'hebrew': 'hebrew', 1700 'chinese': 'chinese', 1701 'indian': 'indian', 1702 'coptic': 'coptic', 1703 'ethiopic': 'ethiopic', 1704 'ethiopic-amete-alem': 'ethioaa' 1705 }; 1706 1707 var format = this; 1708 var fromPattern = fromLDMLString(format[resolvedSymbol][patternSymbol]); 1709 var userCalendar = ICU_CALENDAR_MAP[format[resolvedSymbol].calendar]; 1710 if (IS_UNDEFINED(userCalendar)) { 1711 // Use ICU name if we don't have a match. It shouldn't happen, but 1712 // it would be too strict to throw for this. 1713 userCalendar = format[resolvedSymbol].calendar; 1714 } 1715 1716 var locale = getOptimalLanguageTag(format[resolvedSymbol].requestedLocale, 1717 format[resolvedSymbol].locale); 1718 1719 var result = { 1720 locale: locale, 1721 numberingSystem: format[resolvedSymbol].numberingSystem, 1722 calendar: userCalendar, 1723 timeZone: format[resolvedSymbol].timeZone 1724 }; 1725 1726 addWECPropertyIfDefined(result, 'timeZoneName', fromPattern.timeZoneName); 1727 addWECPropertyIfDefined(result, 'era', fromPattern.era); 1728 addWECPropertyIfDefined(result, 'year', fromPattern.year); 1729 addWECPropertyIfDefined(result, 'month', fromPattern.month); 1730 addWECPropertyIfDefined(result, 'day', fromPattern.day); 1731 addWECPropertyIfDefined(result, 'weekday', fromPattern.weekday); 1732 addWECPropertyIfDefined(result, 'hour12', fromPattern.hour12); 1733 addWECPropertyIfDefined(result, 'hour', fromPattern.hour); 1734 addWECPropertyIfDefined(result, 'minute', fromPattern.minute); 1735 addWECPropertyIfDefined(result, 'second', fromPattern.second); 1736 1737 return result; 1738 }, 1739 DONT_ENUM 1740); 1741%FunctionSetName(Intl.DateTimeFormat.prototype.resolvedOptions, 1742 'resolvedOptions'); 1743%FunctionRemovePrototype(Intl.DateTimeFormat.prototype.resolvedOptions); 1744%SetNativeFlag(Intl.DateTimeFormat.prototype.resolvedOptions); 1745 1746 1747/** 1748 * Returns the subset of the given locale list for which this locale list 1749 * has a matching (possibly fallback) locale. Locales appear in the same 1750 * order in the returned list as in the input list. 1751 * Options are optional parameter. 1752 */ 1753%AddNamedProperty(Intl.DateTimeFormat, 'supportedLocalesOf', function(locales) { 1754 if (!IS_UNDEFINED(new.target)) { 1755 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor); 1756 } 1757 1758 return supportedLocalesOf('dateformat', locales, arguments[1]); 1759 }, 1760 DONT_ENUM 1761); 1762%FunctionSetName(Intl.DateTimeFormat.supportedLocalesOf, 'supportedLocalesOf'); 1763%FunctionRemovePrototype(Intl.DateTimeFormat.supportedLocalesOf); 1764%SetNativeFlag(Intl.DateTimeFormat.supportedLocalesOf); 1765 1766 1767/** 1768 * Returns a String value representing the result of calling ToNumber(date) 1769 * according to the effective locale and the formatting options of this 1770 * DateTimeFormat. 1771 */ 1772function formatDate(formatter, dateValue) { 1773 var dateMs; 1774 if (IS_UNDEFINED(dateValue)) { 1775 dateMs = %DateCurrentTime(); 1776 } else { 1777 dateMs = TO_NUMBER(dateValue); 1778 } 1779 1780 if (!IsFinite(dateMs)) throw MakeRangeError(kDateRange); 1781 1782 return %InternalDateFormat(%GetImplFromInitializedIntlObject(formatter), 1783 new GlobalDate(dateMs)); 1784} 1785 1786 1787/** 1788 * Returns a Date object representing the result of calling ToString(value) 1789 * according to the effective locale and the formatting options of this 1790 * DateTimeFormat. 1791 * Returns undefined if date string cannot be parsed. 1792 */ 1793function parseDate(formatter, value) { 1794 return %InternalDateParse(%GetImplFromInitializedIntlObject(formatter), 1795 GlobalString(value)); 1796} 1797 1798 1799// 0 because date is optional argument. 1800addBoundMethod(Intl.DateTimeFormat, 'format', formatDate, 0); 1801addBoundMethod(Intl.DateTimeFormat, 'v8Parse', parseDate, 1); 1802 1803 1804/** 1805 * Returns canonical Area/Location(/Location) name, or throws an exception 1806 * if the zone name is invalid IANA name. 1807 */ 1808function canonicalizeTimeZoneID(tzID) { 1809 // Skip undefined zones. 1810 if (IS_UNDEFINED(tzID)) { 1811 return tzID; 1812 } 1813 1814 // Special case handling (UTC, GMT). 1815 var upperID = %StringToUpperCase(tzID); 1816 if (upperID === 'UTC' || upperID === 'GMT' || 1817 upperID === 'ETC/UTC' || upperID === 'ETC/GMT') { 1818 return 'UTC'; 1819 } 1820 1821 // TODO(jshin): Add support for Etc/GMT[+-]([1-9]|1[0-2]) 1822 1823 // We expect only _, '-' and / beside ASCII letters. 1824 // All inputs should conform to Area/Location(/Location)* from now on. 1825 var match = %_Call(StringMatch, tzID, GetTimezoneNameCheckRE()); 1826 if (IS_NULL(match)) throw MakeRangeError(kExpectedTimezoneID, tzID); 1827 1828 var result = toTitleCaseTimezoneLocation(match[1]) + '/' + 1829 toTitleCaseTimezoneLocation(match[2]); 1830 1831 if (!IS_UNDEFINED(match[3]) && 3 < match.length) { 1832 var locations = %_Call(StringSplit, match[3], '/'); 1833 // The 1st element is empty. Starts with i=1. 1834 for (var i = 1; i < locations.length; i++) { 1835 result = result + '/' + toTitleCaseTimezoneLocation(locations[i]); 1836 } 1837 } 1838 1839 return result; 1840} 1841 1842/** 1843 * Initializes the given object so it's a valid BreakIterator instance. 1844 * Useful for subclassing. 1845 */ 1846function initializeBreakIterator(iterator, locales, options) { 1847 if (%IsInitializedIntlObject(iterator)) { 1848 throw MakeTypeError(kReinitializeIntl, "v8BreakIterator"); 1849 } 1850 1851 if (IS_UNDEFINED(options)) { 1852 options = {}; 1853 } 1854 1855 var getOption = getGetOption(options, 'breakiterator'); 1856 1857 var internalOptions = {}; 1858 1859 defineWEProperty(internalOptions, 'type', getOption( 1860 'type', 'string', ['character', 'word', 'sentence', 'line'], 'word')); 1861 1862 var locale = resolveLocale('breakiterator', locales, options); 1863 var resolved = ObjectDefineProperties({}, { 1864 requestedLocale: {value: locale.locale, writable: true}, 1865 type: {value: internalOptions.type, writable: true}, 1866 locale: {writable: true} 1867 }); 1868 1869 var internalIterator = %CreateBreakIterator(locale.locale, 1870 internalOptions, 1871 resolved); 1872 1873 %MarkAsInitializedIntlObjectOfType(iterator, 'breakiterator', 1874 internalIterator); 1875 iterator[resolvedSymbol] = resolved; 1876 ObjectDefineProperty(iterator, 'resolved', resolvedAccessor); 1877 1878 return iterator; 1879} 1880 1881 1882/** 1883 * Constructs Intl.v8BreakIterator object given optional locales and options 1884 * parameters. 1885 * 1886 * @constructor 1887 */ 1888%AddNamedProperty(Intl, 'v8BreakIterator', function() { 1889 var locales = arguments[0]; 1890 var options = arguments[1]; 1891 1892 if (!this || this === Intl) { 1893 // Constructor is called as a function. 1894 return new Intl.v8BreakIterator(locales, options); 1895 } 1896 1897 return initializeBreakIterator(TO_OBJECT(this), locales, options); 1898 }, 1899 DONT_ENUM 1900); 1901 1902 1903/** 1904 * BreakIterator resolvedOptions method. 1905 */ 1906%AddNamedProperty(Intl.v8BreakIterator.prototype, 'resolvedOptions', 1907 function() { 1908 if (!IS_UNDEFINED(new.target)) { 1909 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor); 1910 } 1911 1912 if (!%IsInitializedIntlObjectOfType(this, 'breakiterator')) { 1913 throw MakeTypeError(kResolvedOptionsCalledOnNonObject, "v8BreakIterator"); 1914 } 1915 1916 var segmenter = this; 1917 var locale = 1918 getOptimalLanguageTag(segmenter[resolvedSymbol].requestedLocale, 1919 segmenter[resolvedSymbol].locale); 1920 1921 return { 1922 locale: locale, 1923 type: segmenter[resolvedSymbol].type 1924 }; 1925 }, 1926 DONT_ENUM 1927); 1928%FunctionSetName(Intl.v8BreakIterator.prototype.resolvedOptions, 1929 'resolvedOptions'); 1930%FunctionRemovePrototype(Intl.v8BreakIterator.prototype.resolvedOptions); 1931%SetNativeFlag(Intl.v8BreakIterator.prototype.resolvedOptions); 1932 1933 1934/** 1935 * Returns the subset of the given locale list for which this locale list 1936 * has a matching (possibly fallback) locale. Locales appear in the same 1937 * order in the returned list as in the input list. 1938 * Options are optional parameter. 1939 */ 1940%AddNamedProperty(Intl.v8BreakIterator, 'supportedLocalesOf', 1941 function(locales) { 1942 if (!IS_UNDEFINED(new.target)) { 1943 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor); 1944 } 1945 1946 return supportedLocalesOf('breakiterator', locales, arguments[1]); 1947 }, 1948 DONT_ENUM 1949); 1950%FunctionSetName(Intl.v8BreakIterator.supportedLocalesOf, 'supportedLocalesOf'); 1951%FunctionRemovePrototype(Intl.v8BreakIterator.supportedLocalesOf); 1952%SetNativeFlag(Intl.v8BreakIterator.supportedLocalesOf); 1953 1954 1955/** 1956 * Adopts text to segment using the iterator. Old text, if present, 1957 * gets discarded. 1958 */ 1959function adoptText(iterator, text) { 1960 %BreakIteratorAdoptText(%GetImplFromInitializedIntlObject(iterator), 1961 GlobalString(text)); 1962} 1963 1964 1965/** 1966 * Returns index of the first break in the string and moves current pointer. 1967 */ 1968function first(iterator) { 1969 return %BreakIteratorFirst(%GetImplFromInitializedIntlObject(iterator)); 1970} 1971 1972 1973/** 1974 * Returns the index of the next break and moves the pointer. 1975 */ 1976function next(iterator) { 1977 return %BreakIteratorNext(%GetImplFromInitializedIntlObject(iterator)); 1978} 1979 1980 1981/** 1982 * Returns index of the current break. 1983 */ 1984function current(iterator) { 1985 return %BreakIteratorCurrent(%GetImplFromInitializedIntlObject(iterator)); 1986} 1987 1988 1989/** 1990 * Returns type of the current break. 1991 */ 1992function breakType(iterator) { 1993 return %BreakIteratorBreakType(%GetImplFromInitializedIntlObject(iterator)); 1994} 1995 1996 1997addBoundMethod(Intl.v8BreakIterator, 'adoptText', adoptText, 1); 1998addBoundMethod(Intl.v8BreakIterator, 'first', first, 0); 1999addBoundMethod(Intl.v8BreakIterator, 'next', next, 0); 2000addBoundMethod(Intl.v8BreakIterator, 'current', current, 0); 2001addBoundMethod(Intl.v8BreakIterator, 'breakType', breakType, 0); 2002 2003// Save references to Intl objects and methods we use, for added security. 2004var savedObjects = { 2005 'collator': Intl.Collator, 2006 'numberformat': Intl.NumberFormat, 2007 'dateformatall': Intl.DateTimeFormat, 2008 'dateformatdate': Intl.DateTimeFormat, 2009 'dateformattime': Intl.DateTimeFormat 2010}; 2011 2012 2013// Default (created with undefined locales and options parameters) collator, 2014// number and date format instances. They'll be created as needed. 2015var defaultObjects = { 2016 'collator': UNDEFINED, 2017 'numberformat': UNDEFINED, 2018 'dateformatall': UNDEFINED, 2019 'dateformatdate': UNDEFINED, 2020 'dateformattime': UNDEFINED, 2021}; 2022 2023 2024/** 2025 * Returns cached or newly created instance of a given service. 2026 * We cache only default instances (where no locales or options are provided). 2027 */ 2028function cachedOrNewService(service, locales, options, defaults) { 2029 var useOptions = (IS_UNDEFINED(defaults)) ? options : defaults; 2030 if (IS_UNDEFINED(locales) && IS_UNDEFINED(options)) { 2031 if (IS_UNDEFINED(defaultObjects[service])) { 2032 defaultObjects[service] = new savedObjects[service](locales, useOptions); 2033 } 2034 return defaultObjects[service]; 2035 } 2036 return new savedObjects[service](locales, useOptions); 2037} 2038 2039 2040function OverrideFunction(object, name, f) { 2041 %CheckIsBootstrapping(); 2042 ObjectDefineProperty(object, name, { value: f, 2043 writeable: true, 2044 configurable: true, 2045 enumerable: false }); 2046 %FunctionSetName(f, name); 2047 %FunctionRemovePrototype(f); 2048 %SetNativeFlag(f); 2049} 2050 2051/** 2052 * Compares this and that, and returns less than 0, 0 or greater than 0 value. 2053 * Overrides the built-in method. 2054 */ 2055OverrideFunction(GlobalString.prototype, 'localeCompare', function(that) { 2056 if (!IS_UNDEFINED(new.target)) { 2057 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor); 2058 } 2059 2060 if (IS_NULL_OR_UNDEFINED(this)) { 2061 throw MakeTypeError(kMethodInvokedOnNullOrUndefined); 2062 } 2063 2064 var locales = arguments[1]; 2065 var options = arguments[2]; 2066 var collator = cachedOrNewService('collator', locales, options); 2067 return compare(collator, this, that); 2068 } 2069); 2070 2071 2072/** 2073 * Unicode normalization. This method is called with one argument that 2074 * specifies the normalization form. 2075 * If none is specified, "NFC" is assumed. 2076 * If the form is not one of "NFC", "NFD", "NFKC", or "NFKD", then throw 2077 * a RangeError Exception. 2078 */ 2079 2080OverrideFunction(GlobalString.prototype, 'normalize', function() { 2081 if (!IS_UNDEFINED(new.target)) { 2082 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor); 2083 } 2084 2085 CHECK_OBJECT_COERCIBLE(this, "String.prototype.normalize"); 2086 var s = TO_STRING(this); 2087 2088 var formArg = arguments[0]; 2089 var form = IS_UNDEFINED(formArg) ? 'NFC' : TO_STRING(formArg); 2090 2091 var NORMALIZATION_FORMS = ['NFC', 'NFD', 'NFKC', 'NFKD']; 2092 2093 var normalizationForm = %_Call(ArrayIndexOf, NORMALIZATION_FORMS, form); 2094 if (normalizationForm === -1) { 2095 throw MakeRangeError(kNormalizationForm, 2096 %_Call(ArrayJoin, NORMALIZATION_FORMS, ', ')); 2097 } 2098 2099 return %StringNormalize(s, normalizationForm); 2100 } 2101); 2102 2103 2104/** 2105 * Formats a Number object (this) using locale and options values. 2106 * If locale or options are omitted, defaults are used. 2107 */ 2108OverrideFunction(GlobalNumber.prototype, 'toLocaleString', function() { 2109 if (!IS_UNDEFINED(new.target)) { 2110 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor); 2111 } 2112 2113 if (!(this instanceof GlobalNumber) && typeof(this) !== 'number') { 2114 throw MakeTypeError(kMethodInvokedOnWrongType, "Number"); 2115 } 2116 2117 var locales = arguments[0]; 2118 var options = arguments[1]; 2119 var numberFormat = cachedOrNewService('numberformat', locales, options); 2120 return formatNumber(numberFormat, this); 2121 } 2122); 2123 2124 2125/** 2126 * Returns actual formatted date or fails if date parameter is invalid. 2127 */ 2128function toLocaleDateTime(date, locales, options, required, defaults, service) { 2129 if (!(date instanceof GlobalDate)) { 2130 throw MakeTypeError(kMethodInvokedOnWrongType, "Date"); 2131 } 2132 2133 if (IsNaN(date)) return 'Invalid Date'; 2134 2135 var internalOptions = toDateTimeOptions(options, required, defaults); 2136 2137 var dateFormat = 2138 cachedOrNewService(service, locales, options, internalOptions); 2139 2140 return formatDate(dateFormat, date); 2141} 2142 2143 2144/** 2145 * Formats a Date object (this) using locale and options values. 2146 * If locale or options are omitted, defaults are used - both date and time are 2147 * present in the output. 2148 */ 2149OverrideFunction(GlobalDate.prototype, 'toLocaleString', function() { 2150 if (!IS_UNDEFINED(new.target)) { 2151 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor); 2152 } 2153 2154 var locales = arguments[0]; 2155 var options = arguments[1]; 2156 return toLocaleDateTime( 2157 this, locales, options, 'any', 'all', 'dateformatall'); 2158 } 2159); 2160 2161 2162/** 2163 * Formats a Date object (this) using locale and options values. 2164 * If locale or options are omitted, defaults are used - only date is present 2165 * in the output. 2166 */ 2167OverrideFunction(GlobalDate.prototype, 'toLocaleDateString', function() { 2168 if (!IS_UNDEFINED(new.target)) { 2169 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor); 2170 } 2171 2172 var locales = arguments[0]; 2173 var options = arguments[1]; 2174 return toLocaleDateTime( 2175 this, locales, options, 'date', 'date', 'dateformatdate'); 2176 } 2177); 2178 2179 2180/** 2181 * Formats a Date object (this) using locale and options values. 2182 * If locale or options are omitted, defaults are used - only time is present 2183 * in the output. 2184 */ 2185OverrideFunction(GlobalDate.prototype, 'toLocaleTimeString', function() { 2186 if (!IS_UNDEFINED(new.target)) { 2187 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor); 2188 } 2189 2190 var locales = arguments[0]; 2191 var options = arguments[1]; 2192 return toLocaleDateTime( 2193 this, locales, options, 'time', 'time', 'dateformattime'); 2194 } 2195); 2196 2197}) 2198