Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(10)

Side by Side Diff: src/extensions/i18n/i18n-utils.js

Issue 148503002: A64: Synchronize with r15545. (Closed) Base URL: https://v8.googlecode.com/svn/branches/experimental/a64
Patch Set: Created 6 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « src/extensions/i18n/i18n-utils.cc ('k') | src/extensions/i18n/locale.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 native function NativeJSGetDefaultICULocale();
259
260 if (service.match(SERVICE_RE) === null) {
261 throw new Error('Internal error, wrong service type: ' + service);
262 }
263
264 // Cache these, they don't ever change per service.
265 if (AVAILABLE_LOCALES[service] === undefined) {
266 AVAILABLE_LOCALES[service] = getAvailableLocalesOf(service);
267 }
268
269 for (var i = 0; i < requestedLocales.length; ++i) {
270 // Remove all extensions.
271 var locale = requestedLocales[i].replace(ANY_EXTENSION_RE, '');
272 do {
273 if (AVAILABLE_LOCALES[service][locale] !== undefined) {
274 // Return the resolved locale and extension.
275 var extensionMatch = requestedLocales[i].match(UNICODE_EXTENSION_RE);
276 var extension = (extensionMatch === null) ? '' : extensionMatch[0];
277 return {'locale': locale, 'extension': extension, 'position': i};
278 }
279 // Truncate locale if possible.
280 var pos = locale.lastIndexOf('-');
281 if (pos === -1) {
282 break;
283 }
284 locale = locale.substring(0, pos);
285 } while (true);
286 }
287
288 // Didn't find a match, return default.
289 if (DEFAULT_ICU_LOCALE === undefined) {
290 DEFAULT_ICU_LOCALE = NativeJSGetDefaultICULocale();
291 }
292
293 return {'locale': DEFAULT_ICU_LOCALE, 'extension': '', 'position': -1};
294 }
295
296
297 /**
298 * Returns best matched supported locale and extension info using
299 * implementation dependend algorithm.
300 */
301 function bestFitMatcher(service, requestedLocales) {
302 // TODO(cira): implement better best fit algorithm.
303 return lookupMatcher(service, requestedLocales);
304 }
305
306
307 /**
308 * Parses Unicode extension into key - value map.
309 * Returns empty object if the extension string is invalid.
310 * We are not concerned with the validity of the values at this point.
311 */
312 function parseExtension(extension) {
313 var extensionSplit = extension.split('-');
314
315 // Assume ['', 'u', ...] input, but don't throw.
316 if (extensionSplit.length <= 2 ||
317 (extensionSplit[0] !== '' && extensionSplit[1] !== 'u')) {
318 return {};
319 }
320
321 // Key is {2}alphanum, value is {3,8}alphanum.
322 // Some keys may not have explicit values (booleans).
323 var extensionMap = {};
324 var previousKey = undefined;
325 for (var i = 2; i < extensionSplit.length; ++i) {
326 var length = extensionSplit[i].length;
327 var element = extensionSplit[i];
328 if (length === 2) {
329 extensionMap[element] = undefined;
330 previousKey = element;
331 } else if (length >= 3 && length <=8 && previousKey !== undefined) {
332 extensionMap[previousKey] = element;
333 previousKey = undefined;
334 } else {
335 // There is a value that's too long, or that doesn't have a key.
336 return {};
337 }
338 }
339
340 return extensionMap;
341 }
342
343
344 /**
345 * Converts parameter to an Object if possible.
346 */
347 function toObject(value) {
348 if (value === undefined || value === null) {
349 throw new TypeError('Value cannot be converted to an Object.');
350 }
351
352 return Object(value);
353 }
354
355
356 /**
357 * Populates internalOptions object with boolean key-value pairs
358 * from extensionMap and options.
359 * Returns filtered extension (number and date format constructors use
360 * Unicode extensions for passing parameters to ICU).
361 * It's used for extension-option pairs only, e.g. kn-normalization, but not
362 * for 'sensitivity' since it doesn't have extension equivalent.
363 * Extensions like nu and ca don't have options equivalent, so we place
364 * undefined in the map.property to denote that.
365 */
366 function setOptions(inOptions, extensionMap, keyValues, getOption, outOptions) {
367 var extension = '';
368
369 var updateExtension = function updateExtension(key, value) {
370 return '-' + key + '-' + String(value);
371 }
372
373 var updateProperty = function updateProperty(property, type, value) {
374 if (type === 'boolean' && (typeof value === 'string')) {
375 value = (value === 'true') ? true : false;
376 }
377
378 if (property !== undefined) {
379 defineWEProperty(outOptions, property, value);
380 }
381 }
382
383 for (var key in keyValues) {
384 if (keyValues.hasOwnProperty(key)) {
385 var value = undefined;
386 var map = keyValues[key];
387 if (map.property !== undefined) {
388 // This may return true if user specifies numeric: 'false', since
389 // Boolean('nonempty') === true.
390 value = getOption(map.property, map.type, map.values);
391 }
392 if (value !== undefined) {
393 updateProperty(map.property, map.type, value);
394 extension += updateExtension(key, value);
395 continue;
396 }
397 // User options didn't have it, check Unicode extension.
398 // Here we want to convert strings 'true', 'false' into proper Boolean
399 // values (not a user error).
400 if (extensionMap.hasOwnProperty(key)) {
401 value = extensionMap[key];
402 if (value !== undefined) {
403 updateProperty(map.property, map.type, value);
404 extension += updateExtension(key, value);
405 } else if (map.type === 'boolean') {
406 // Boolean keys are allowed not to have values in Unicode extension.
407 // Those default to true.
408 updateProperty(map.property, map.type, true);
409 extension += updateExtension(key, true);
410 }
411 }
412 }
413 }
414
415 return extension === ''? '' : '-u' + extension;
416 }
417
418
419 /**
420 * Converts all OwnProperties into
421 * configurable: false, writable: false, enumerable: true.
422 */
423 function freezeArray(array) {
424 array.forEach(function(element, index) {
425 Object.defineProperty(array, index, {value: element,
426 configurable: false,
427 writable: false,
428 enumerable: true});
429 });
430
431 Object.defineProperty(array, 'length', {value: array.length,
432 writable: false});
433
434 return array;
435 }
436
437
438 /**
439 * It's sometimes desireable to leave user requested locale instead of ICU
440 * supported one (zh-TW is equivalent to zh-Hant-TW, so we should keep shorter
441 * one, if that was what user requested).
442 * This function returns user specified tag if its maximized form matches ICU
443 * resolved locale. If not we return ICU result.
444 */
445 function getOptimalLanguageTag(original, resolved) {
446 // Returns Array<Object>, where each object has maximized and base properties.
447 // Maximized: zh -> zh-Hans-CN
448 // Base: zh-CN-u-ca-gregory -> zh-CN
449 native function NativeJSGetLanguageTagVariants();
450
451 // Take care of grandfathered or simple cases.
452 if (original === resolved) {
453 return original;
454 }
455
456 var locales = NativeJSGetLanguageTagVariants([original, resolved]);
457 if (locales[0].maximized !== locales[1].maximized) {
458 return resolved;
459 }
460
461 // Preserve extensions of resolved locale, but swap base tags with original.
462 var resolvedBase = new RegExp('^' + locales[1].base);
463 return resolved.replace(resolvedBase, locales[0].base);
464 }
465
466
467 /**
468 * Returns an Object that contains all of supported locales for a given
469 * service.
470 * In addition to the supported locales we add xx-ZZ locale for each xx-Yyyy-ZZ
471 * that is supported. This is required by the spec.
472 */
473 function getAvailableLocalesOf(service) {
474 native function NativeJSAvailableLocalesOf();
475 var available = NativeJSAvailableLocalesOf(service);
476
477 for (var i in available) {
478 if (available.hasOwnProperty(i)) {
479 var parts = i.match(/^([a-z]{2,3})-([A-Z][a-z]{3})-([A-Z]{2})$/);
480 if (parts !== null) {
481 // Build xx-ZZ. We don't care about the actual value,
482 // as long it's not undefined.
483 available[parts[1] + '-' + parts[3]] = null;
484 }
485 }
486 }
487
488 return available;
489 }
490
491
492 /**
493 * Defines a property and sets writable and enumerable to true.
494 * Configurable is false by default.
495 */
496 function defineWEProperty(object, property, value) {
497 Object.defineProperty(object, property,
498 {value: value, writable: true, enumerable: true});
499 }
500
501
502 /**
503 * Adds property to an object if the value is not undefined.
504 * Sets configurable descriptor to false.
505 */
506 function addWEPropertyIfDefined(object, property, value) {
507 if (value !== undefined) {
508 defineWEProperty(object, property, value);
509 }
510 }
511
512
513 /**
514 * Defines a property and sets writable, enumerable and configurable to true.
515 */
516 function defineWECProperty(object, property, value) {
517 Object.defineProperty(object, property,
518 {value: value,
519 writable: true,
520 enumerable: true,
521 configurable: true});
522 }
523
524
525 /**
526 * Adds property to an object if the value is not undefined.
527 * Sets all descriptors to true.
528 */
529 function addWECPropertyIfDefined(object, property, value) {
530 if (value !== undefined) {
531 defineWECProperty(object, property, value);
532 }
533 }
534
535
536 /**
537 * Returns titlecased word, aMeRricA -> America.
538 */
539 function toTitleCaseWord(word) {
540 return word.substr(0, 1).toUpperCase() + word.substr(1).toLowerCase();
541 }
OLDNEW
« no previous file with comments | « src/extensions/i18n/i18n-utils.cc ('k') | src/extensions/i18n/locale.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698