| OLD | NEW |
| (Empty) |
| 1 // Copyright 2013 the V8 project authors. All rights reserved. | |
| 2 // Redistribution and use in source and binary forms, with or without | |
| 3 // modification, are permitted provided that the following conditions are | |
| 4 // met: | |
| 5 // | |
| 6 // * Redistributions of source code must retain the above copyright | |
| 7 // notice, this list of conditions and the following disclaimer. | |
| 8 // * Redistributions in binary form must reproduce the above | |
| 9 // copyright notice, this list of conditions and the following | |
| 10 // disclaimer in the documentation and/or other materials provided | |
| 11 // with the distribution. | |
| 12 // * Neither the name of Google Inc. nor the names of its | |
| 13 // contributors may be used to endorse or promote products derived | |
| 14 // from this software without specific prior written permission. | |
| 15 // | |
| 16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 // limitations under the License. | |
| 28 | |
| 29 // ECMAScript 402 API implementation is broken into separate files for | |
| 30 // each service. The build system combines them together into one | |
| 31 // Intl namespace. | |
| 32 | |
| 33 /** | |
| 34 * Adds bound method to the prototype of the given object. | |
| 35 */ | |
| 36 function addBoundMethod(obj, methodName, implementation, length) { | |
| 37 function getter() { | |
| 38 if (!this || typeof this !== 'object' || | |
| 39 this.__initializedIntlObject === undefined) { | |
| 40 throw new TypeError('Method ' + methodName + ' called on a ' + | |
| 41 'non-object or on a wrong type of object.'); | |
| 42 } | |
| 43 var internalName = '__bound' + methodName + '__'; | |
| 44 if (this[internalName] === undefined) { | |
| 45 var that = this; | |
| 46 var boundMethod; | |
| 47 if (length === undefined || length === 2) { | |
| 48 boundMethod = function(x, y) { | |
| 49 if (%_IsConstructCall()) { | |
| 50 throw new TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR); | |
| 51 } | |
| 52 return implementation(that, x, y); | |
| 53 } | |
| 54 } else if (length === 1) { | |
| 55 boundMethod = function(x) { | |
| 56 if (%_IsConstructCall()) { | |
| 57 throw new TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR); | |
| 58 } | |
| 59 return implementation(that, x); | |
| 60 } | |
| 61 } else { | |
| 62 boundMethod = function() { | |
| 63 if (%_IsConstructCall()) { | |
| 64 throw new TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR); | |
| 65 } | |
| 66 // DateTimeFormat.format needs to be 0 arg method, but can stil | |
| 67 // receive optional dateValue param. If one was provided, pass it | |
| 68 // along. | |
| 69 if (arguments.length > 0) { | |
| 70 return implementation(that, arguments[0]); | |
| 71 } else { | |
| 72 return implementation(that); | |
| 73 } | |
| 74 } | |
| 75 } | |
| 76 %FunctionSetName(boundMethod, internalName); | |
| 77 %FunctionRemovePrototype(boundMethod); | |
| 78 %SetNativeFlag(boundMethod); | |
| 79 this[internalName] = boundMethod; | |
| 80 } | |
| 81 return this[internalName]; | |
| 82 } | |
| 83 | |
| 84 %FunctionSetName(getter, methodName); | |
| 85 %FunctionRemovePrototype(getter); | |
| 86 %SetNativeFlag(getter); | |
| 87 | |
| 88 Object.defineProperty(obj.prototype, methodName, { | |
| 89 get: getter, | |
| 90 enumerable: false, | |
| 91 configurable: true | |
| 92 }); | |
| 93 } | |
| 94 | |
| 95 | |
| 96 /** | |
| 97 * Returns an intersection of locales and service supported locales. | |
| 98 * Parameter locales is treated as a priority list. | |
| 99 */ | |
| 100 function supportedLocalesOf(service, locales, options) { | |
| 101 if (service.match(SERVICE_RE) === null) { | |
| 102 throw new Error('Internal error, wrong service type: ' + service); | |
| 103 } | |
| 104 | |
| 105 // Provide defaults if matcher was not specified. | |
| 106 if (options === undefined) { | |
| 107 options = {}; | |
| 108 } else { | |
| 109 options = toObject(options); | |
| 110 } | |
| 111 | |
| 112 var matcher = options.localeMatcher; | |
| 113 if (matcher !== undefined) { | |
| 114 matcher = String(matcher); | |
| 115 if (matcher !== 'lookup' && matcher !== 'best fit') { | |
| 116 throw new RangeError('Illegal value for localeMatcher:' + matcher); | |
| 117 } | |
| 118 } else { | |
| 119 matcher = 'best fit'; | |
| 120 } | |
| 121 | |
| 122 var requestedLocales = initializeLocaleList(locales); | |
| 123 | |
| 124 // Cache these, they don't ever change per service. | |
| 125 if (AVAILABLE_LOCALES[service] === undefined) { | |
| 126 AVAILABLE_LOCALES[service] = getAvailableLocalesOf(service); | |
| 127 } | |
| 128 | |
| 129 // Use either best fit or lookup algorithm to match locales. | |
| 130 if (matcher === 'best fit') { | |
| 131 return initializeLocaleList(bestFitSupportedLocalesOf( | |
| 132 requestedLocales, AVAILABLE_LOCALES[service])); | |
| 133 } | |
| 134 | |
| 135 return initializeLocaleList(lookupSupportedLocalesOf( | |
| 136 requestedLocales, AVAILABLE_LOCALES[service])); | |
| 137 } | |
| 138 | |
| 139 | |
| 140 /** | |
| 141 * Returns the subset of the provided BCP 47 language priority list for which | |
| 142 * this service has a matching locale when using the BCP 47 Lookup algorithm. | |
| 143 * Locales appear in the same order in the returned list as in the input list. | |
| 144 */ | |
| 145 function lookupSupportedLocalesOf(requestedLocales, availableLocales) { | |
| 146 var matchedLocales = []; | |
| 147 for (var i = 0; i < requestedLocales.length; ++i) { | |
| 148 // Remove -u- extension. | |
| 149 var locale = requestedLocales[i].replace(UNICODE_EXTENSION_RE, ''); | |
| 150 do { | |
| 151 if (availableLocales[locale] !== undefined) { | |
| 152 // Push requested locale not the resolved one. | |
| 153 matchedLocales.push(requestedLocales[i]); | |
| 154 break; | |
| 155 } | |
| 156 // Truncate locale if possible, if not break. | |
| 157 var pos = locale.lastIndexOf('-'); | |
| 158 if (pos === -1) { | |
| 159 break; | |
| 160 } | |
| 161 locale = locale.substring(0, pos); | |
| 162 } while (true); | |
| 163 } | |
| 164 | |
| 165 return matchedLocales; | |
| 166 } | |
| 167 | |
| 168 | |
| 169 /** | |
| 170 * Returns the subset of the provided BCP 47 language priority list for which | |
| 171 * this service has a matching locale when using the implementation | |
| 172 * dependent algorithm. | |
| 173 * Locales appear in the same order in the returned list as in the input list. | |
| 174 */ | |
| 175 function bestFitSupportedLocalesOf(requestedLocales, availableLocales) { | |
| 176 // TODO(cira): implement better best fit algorithm. | |
| 177 return lookupSupportedLocalesOf(requestedLocales, availableLocales); | |
| 178 } | |
| 179 | |
| 180 | |
| 181 /** | |
| 182 * Returns a getOption function that extracts property value for given | |
| 183 * options object. If property is missing it returns defaultValue. If value | |
| 184 * is out of range for that property it throws RangeError. | |
| 185 */ | |
| 186 function getGetOption(options, caller) { | |
| 187 if (options === undefined) { | |
| 188 throw new Error('Internal ' + caller + ' error. ' + | |
| 189 'Default options are missing.'); | |
| 190 } | |
| 191 | |
| 192 var getOption = function getOption(property, type, values, defaultValue) { | |
| 193 if (options[property] !== undefined) { | |
| 194 var value = options[property]; | |
| 195 switch (type) { | |
| 196 case 'boolean': | |
| 197 value = Boolean(value); | |
| 198 break; | |
| 199 case 'string': | |
| 200 value = String(value); | |
| 201 break; | |
| 202 case 'number': | |
| 203 value = Number(value); | |
| 204 break; | |
| 205 default: | |
| 206 throw new Error('Internal error. Wrong value type.'); | |
| 207 } | |
| 208 if (values !== undefined && values.indexOf(value) === -1) { | |
| 209 throw new RangeError('Value ' + value + ' out of range for ' + caller + | |
| 210 ' options property ' + property); | |
| 211 } | |
| 212 | |
| 213 return value; | |
| 214 } | |
| 215 | |
| 216 return defaultValue; | |
| 217 } | |
| 218 | |
| 219 return getOption; | |
| 220 } | |
| 221 | |
| 222 | |
| 223 /** | |
| 224 * Compares a BCP 47 language priority list requestedLocales against the locales | |
| 225 * in availableLocales and determines the best available language to meet the | |
| 226 * request. Two algorithms are available to match the locales: the Lookup | |
| 227 * algorithm described in RFC 4647 section 3.4, and an implementation dependent | |
| 228 * best-fit algorithm. Independent of the locale matching algorithm, options | |
| 229 * specified through Unicode locale extension sequences are negotiated | |
| 230 * separately, taking the caller's relevant extension keys and locale data as | |
| 231 * well as client-provided options into consideration. Returns an object with | |
| 232 * a locale property whose value is the language tag of the selected locale, | |
| 233 * and properties for each key in relevantExtensionKeys providing the selected | |
| 234 * value for that key. | |
| 235 */ | |
| 236 function resolveLocale(service, requestedLocales, options) { | |
| 237 requestedLocales = initializeLocaleList(requestedLocales); | |
| 238 | |
| 239 var getOption = getGetOption(options, service); | |
| 240 var matcher = getOption('localeMatcher', 'string', | |
| 241 ['lookup', 'best fit'], 'best fit'); | |
| 242 var resolved; | |
| 243 if (matcher === 'lookup') { | |
| 244 resolved = lookupMatcher(service, requestedLocales); | |
| 245 } else { | |
| 246 resolved = bestFitMatcher(service, requestedLocales); | |
| 247 } | |
| 248 | |
| 249 return resolved; | |
| 250 } | |
| 251 | |
| 252 | |
| 253 /** | |
| 254 * Returns best matched supported locale and extension info using basic | |
| 255 * lookup algorithm. | |
| 256 */ | |
| 257 function lookupMatcher(service, requestedLocales) { | |
| 258 if (service.match(SERVICE_RE) === null) { | |
| 259 throw new Error('Internal error, wrong service type: ' + service); | |
| 260 } | |
| 261 | |
| 262 // Cache these, they don't ever change per service. | |
| 263 if (AVAILABLE_LOCALES[service] === undefined) { | |
| 264 AVAILABLE_LOCALES[service] = getAvailableLocalesOf(service); | |
| 265 } | |
| 266 | |
| 267 for (var i = 0; i < requestedLocales.length; ++i) { | |
| 268 // Remove all extensions. | |
| 269 var locale = requestedLocales[i].replace(ANY_EXTENSION_RE, ''); | |
| 270 do { | |
| 271 if (AVAILABLE_LOCALES[service][locale] !== undefined) { | |
| 272 // Return the resolved locale and extension. | |
| 273 var extensionMatch = requestedLocales[i].match(UNICODE_EXTENSION_RE); | |
| 274 var extension = (extensionMatch === null) ? '' : extensionMatch[0]; | |
| 275 return {'locale': locale, 'extension': extension, 'position': i}; | |
| 276 } | |
| 277 // Truncate locale if possible. | |
| 278 var pos = locale.lastIndexOf('-'); | |
| 279 if (pos === -1) { | |
| 280 break; | |
| 281 } | |
| 282 locale = locale.substring(0, pos); | |
| 283 } while (true); | |
| 284 } | |
| 285 | |
| 286 // Didn't find a match, return default. | |
| 287 if (DEFAULT_ICU_LOCALE === undefined) { | |
| 288 DEFAULT_ICU_LOCALE = %GetDefaultICULocale(); | |
| 289 } | |
| 290 | |
| 291 return {'locale': DEFAULT_ICU_LOCALE, 'extension': '', 'position': -1}; | |
| 292 } | |
| 293 | |
| 294 | |
| 295 /** | |
| 296 * Returns best matched supported locale and extension info using | |
| 297 * implementation dependend algorithm. | |
| 298 */ | |
| 299 function bestFitMatcher(service, requestedLocales) { | |
| 300 // TODO(cira): implement better best fit algorithm. | |
| 301 return lookupMatcher(service, requestedLocales); | |
| 302 } | |
| 303 | |
| 304 | |
| 305 /** | |
| 306 * Parses Unicode extension into key - value map. | |
| 307 * Returns empty object if the extension string is invalid. | |
| 308 * We are not concerned with the validity of the values at this point. | |
| 309 */ | |
| 310 function parseExtension(extension) { | |
| 311 var extensionSplit = extension.split('-'); | |
| 312 | |
| 313 // Assume ['', 'u', ...] input, but don't throw. | |
| 314 if (extensionSplit.length <= 2 || | |
| 315 (extensionSplit[0] !== '' && extensionSplit[1] !== 'u')) { | |
| 316 return {}; | |
| 317 } | |
| 318 | |
| 319 // Key is {2}alphanum, value is {3,8}alphanum. | |
| 320 // Some keys may not have explicit values (booleans). | |
| 321 var extensionMap = {}; | |
| 322 var previousKey = undefined; | |
| 323 for (var i = 2; i < extensionSplit.length; ++i) { | |
| 324 var length = extensionSplit[i].length; | |
| 325 var element = extensionSplit[i]; | |
| 326 if (length === 2) { | |
| 327 extensionMap[element] = undefined; | |
| 328 previousKey = element; | |
| 329 } else if (length >= 3 && length <=8 && previousKey !== undefined) { | |
| 330 extensionMap[previousKey] = element; | |
| 331 previousKey = undefined; | |
| 332 } else { | |
| 333 // There is a value that's too long, or that doesn't have a key. | |
| 334 return {}; | |
| 335 } | |
| 336 } | |
| 337 | |
| 338 return extensionMap; | |
| 339 } | |
| 340 | |
| 341 | |
| 342 /** | |
| 343 * Converts parameter to an Object if possible. | |
| 344 */ | |
| 345 function toObject(value) { | |
| 346 if (value === undefined || value === null) { | |
| 347 throw new TypeError('Value cannot be converted to an Object.'); | |
| 348 } | |
| 349 | |
| 350 return Object(value); | |
| 351 } | |
| 352 | |
| 353 | |
| 354 /** | |
| 355 * Populates internalOptions object with boolean key-value pairs | |
| 356 * from extensionMap and options. | |
| 357 * Returns filtered extension (number and date format constructors use | |
| 358 * Unicode extensions for passing parameters to ICU). | |
| 359 * It's used for extension-option pairs only, e.g. kn-normalization, but not | |
| 360 * for 'sensitivity' since it doesn't have extension equivalent. | |
| 361 * Extensions like nu and ca don't have options equivalent, so we place | |
| 362 * undefined in the map.property to denote that. | |
| 363 */ | |
| 364 function setOptions(inOptions, extensionMap, keyValues, getOption, outOptions) { | |
| 365 var extension = ''; | |
| 366 | |
| 367 var updateExtension = function updateExtension(key, value) { | |
| 368 return '-' + key + '-' + String(value); | |
| 369 } | |
| 370 | |
| 371 var updateProperty = function updateProperty(property, type, value) { | |
| 372 if (type === 'boolean' && (typeof value === 'string')) { | |
| 373 value = (value === 'true') ? true : false; | |
| 374 } | |
| 375 | |
| 376 if (property !== undefined) { | |
| 377 defineWEProperty(outOptions, property, value); | |
| 378 } | |
| 379 } | |
| 380 | |
| 381 for (var key in keyValues) { | |
| 382 if (keyValues.hasOwnProperty(key)) { | |
| 383 var value = undefined; | |
| 384 var map = keyValues[key]; | |
| 385 if (map.property !== undefined) { | |
| 386 // This may return true if user specifies numeric: 'false', since | |
| 387 // Boolean('nonempty') === true. | |
| 388 value = getOption(map.property, map.type, map.values); | |
| 389 } | |
| 390 if (value !== undefined) { | |
| 391 updateProperty(map.property, map.type, value); | |
| 392 extension += updateExtension(key, value); | |
| 393 continue; | |
| 394 } | |
| 395 // User options didn't have it, check Unicode extension. | |
| 396 // Here we want to convert strings 'true', 'false' into proper Boolean | |
| 397 // values (not a user error). | |
| 398 if (extensionMap.hasOwnProperty(key)) { | |
| 399 value = extensionMap[key]; | |
| 400 if (value !== undefined) { | |
| 401 updateProperty(map.property, map.type, value); | |
| 402 extension += updateExtension(key, value); | |
| 403 } else if (map.type === 'boolean') { | |
| 404 // Boolean keys are allowed not to have values in Unicode extension. | |
| 405 // Those default to true. | |
| 406 updateProperty(map.property, map.type, true); | |
| 407 extension += updateExtension(key, true); | |
| 408 } | |
| 409 } | |
| 410 } | |
| 411 } | |
| 412 | |
| 413 return extension === ''? '' : '-u' + extension; | |
| 414 } | |
| 415 | |
| 416 | |
| 417 /** | |
| 418 * Converts all OwnProperties into | |
| 419 * configurable: false, writable: false, enumerable: true. | |
| 420 */ | |
| 421 function freezeArray(array) { | |
| 422 array.forEach(function(element, index) { | |
| 423 Object.defineProperty(array, index, {value: element, | |
| 424 configurable: false, | |
| 425 writable: false, | |
| 426 enumerable: true}); | |
| 427 }); | |
| 428 | |
| 429 Object.defineProperty(array, 'length', {value: array.length, | |
| 430 writable: false}); | |
| 431 | |
| 432 return array; | |
| 433 } | |
| 434 | |
| 435 | |
| 436 /** | |
| 437 * It's sometimes desireable to leave user requested locale instead of ICU | |
| 438 * supported one (zh-TW is equivalent to zh-Hant-TW, so we should keep shorter | |
| 439 * one, if that was what user requested). | |
| 440 * This function returns user specified tag if its maximized form matches ICU | |
| 441 * resolved locale. If not we return ICU result. | |
| 442 */ | |
| 443 function getOptimalLanguageTag(original, resolved) { | |
| 444 // Returns Array<Object>, where each object has maximized and base properties. | |
| 445 // Maximized: zh -> zh-Hans-CN | |
| 446 // Base: zh-CN-u-ca-gregory -> zh-CN | |
| 447 // Take care of grandfathered or simple cases. | |
| 448 if (original === resolved) { | |
| 449 return original; | |
| 450 } | |
| 451 | |
| 452 var locales = %GetLanguageTagVariants([original, resolved]); | |
| 453 if (locales[0].maximized !== locales[1].maximized) { | |
| 454 return resolved; | |
| 455 } | |
| 456 | |
| 457 // Preserve extensions of resolved locale, but swap base tags with original. | |
| 458 var resolvedBase = new RegExp('^' + locales[1].base); | |
| 459 return resolved.replace(resolvedBase, locales[0].base); | |
| 460 } | |
| 461 | |
| 462 | |
| 463 /** | |
| 464 * Returns an Object that contains all of supported locales for a given | |
| 465 * service. | |
| 466 * In addition to the supported locales we add xx-ZZ locale for each xx-Yyyy-ZZ | |
| 467 * that is supported. This is required by the spec. | |
| 468 */ | |
| 469 function getAvailableLocalesOf(service) { | |
| 470 var available = %AvailableLocalesOf(service); | |
| 471 | |
| 472 for (var i in available) { | |
| 473 if (available.hasOwnProperty(i)) { | |
| 474 var parts = i.match(/^([a-z]{2,3})-([A-Z][a-z]{3})-([A-Z]{2})$/); | |
| 475 if (parts !== null) { | |
| 476 // Build xx-ZZ. We don't care about the actual value, | |
| 477 // as long it's not undefined. | |
| 478 available[parts[1] + '-' + parts[3]] = null; | |
| 479 } | |
| 480 } | |
| 481 } | |
| 482 | |
| 483 return available; | |
| 484 } | |
| 485 | |
| 486 | |
| 487 /** | |
| 488 * Defines a property and sets writable and enumerable to true. | |
| 489 * Configurable is false by default. | |
| 490 */ | |
| 491 function defineWEProperty(object, property, value) { | |
| 492 Object.defineProperty(object, property, | |
| 493 {value: value, writable: true, enumerable: true}); | |
| 494 } | |
| 495 | |
| 496 | |
| 497 /** | |
| 498 * Adds property to an object if the value is not undefined. | |
| 499 * Sets configurable descriptor to false. | |
| 500 */ | |
| 501 function addWEPropertyIfDefined(object, property, value) { | |
| 502 if (value !== undefined) { | |
| 503 defineWEProperty(object, property, value); | |
| 504 } | |
| 505 } | |
| 506 | |
| 507 | |
| 508 /** | |
| 509 * Defines a property and sets writable, enumerable and configurable to true. | |
| 510 */ | |
| 511 function defineWECProperty(object, property, value) { | |
| 512 Object.defineProperty(object, property, | |
| 513 {value: value, | |
| 514 writable: true, | |
| 515 enumerable: true, | |
| 516 configurable: true}); | |
| 517 } | |
| 518 | |
| 519 | |
| 520 /** | |
| 521 * Adds property to an object if the value is not undefined. | |
| 522 * Sets all descriptors to true. | |
| 523 */ | |
| 524 function addWECPropertyIfDefined(object, property, value) { | |
| 525 if (value !== undefined) { | |
| 526 defineWECProperty(object, property, value); | |
| 527 } | |
| 528 } | |
| 529 | |
| 530 | |
| 531 /** | |
| 532 * Returns titlecased word, aMeRricA -> America. | |
| 533 */ | |
| 534 function toTitleCaseWord(word) { | |
| 535 return word.substr(0, 1).toUpperCase() + word.substr(1).toLowerCase(); | |
| 536 } | |
| OLD | NEW |