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

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

Issue 18487004: Import the v8-i18n extension into v8 (Closed) Base URL: https://v8.googlecode.com/svn/branches/bleeding_edge
Patch Set: Created 7 years, 5 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
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 %FunctionRemovePrototype(boundMethod);
77 this[internalName] = boundMethod;
78 }
79 return this[internalName];
80 }
81
82 %FunctionRemovePrototype(getter);
83
84 Object.defineProperty(obj.prototype, methodName, {
85 get: getter,
86 enumerable: false,
87 configurable: true
88 });
89 }
90
91
92 /**
93 * Returns an intersection of locales and service supported locales.
94 * Parameter locales is treated as a priority list.
95 */
96 function supportedLocalesOf(service, locales, options) {
97 if (service.match(SERVICE_RE) === null) {
98 throw new Error('Internal error, wrong service type: ' + service);
99 }
100
101 // Provide defaults if matcher was not specified.
102 if (options === undefined) {
103 options = {};
104 } else {
105 options = toObject(options);
106 }
107
108 var matcher = options.localeMatcher;
109 if (matcher !== undefined) {
110 matcher = String(matcher);
111 if (matcher !== 'lookup' && matcher !== 'best fit') {
112 throw new RangeError('Illegal value for localeMatcher:' + matcher);
113 }
114 } else {
115 matcher = 'best fit';
116 }
117
118 var requestedLocales = initializeLocaleList(locales);
119
120 // Cache these, they don't ever change per service.
121 if (AVAILABLE_LOCALES[service] === undefined) {
122 AVAILABLE_LOCALES[service] = getAvailableLocalesOf(service);
123 }
124
125 // Use either best fit or lookup algorithm to match locales.
126 if (matcher === 'best fit') {
127 return initializeLocaleList(bestFitSupportedLocalesOf(
128 requestedLocales, AVAILABLE_LOCALES[service]));
129 }
130
131 return initializeLocaleList(lookupSupportedLocalesOf(
132 requestedLocales, AVAILABLE_LOCALES[service]));
133 }
134
135
136 /**
137 * Returns the subset of the provided BCP 47 language priority list for which
138 * this service has a matching locale when using the BCP 47 Lookup algorithm.
139 * Locales appear in the same order in the returned list as in the input list.
140 */
141 function lookupSupportedLocalesOf(requestedLocales, availableLocales) {
142 var matchedLocales = [];
143 for (var i = 0; i < requestedLocales.length; ++i) {
144 // Remove -u- extension.
145 var locale = requestedLocales[i].replace(UNICODE_EXTENSION_RE, '');
146 do {
147 if (availableLocales[locale] !== undefined) {
148 // Push requested locale not the resolved one.
149 matchedLocales.push(requestedLocales[i]);
150 break;
151 }
152 // Truncate locale if possible, if not break.
153 var pos = locale.lastIndexOf('-');
154 if (pos === -1) {
155 break;
156 }
157 locale = locale.substring(0, pos);
158 } while (true);
159 }
160
161 return matchedLocales;
162 }
163
164
165 /**
166 * Returns the subset of the provided BCP 47 language priority list for which
167 * this service has a matching locale when using the implementation
168 * dependent algorithm.
169 * Locales appear in the same order in the returned list as in the input list.
170 */
171 function bestFitSupportedLocalesOf(requestedLocales, availableLocales) {
172 // TODO(cira): implement better best fit algorithm.
173 return lookupSupportedLocalesOf(requestedLocales, availableLocales);
174 }
175
176
177 /**
178 * Returns a getOption function that extracts property value for given
179 * options object. If property is missing it returns defaultValue. If value
180 * is out of range for that property it throws RangeError.
181 */
182 function getGetOption(options, caller) {
183 if (options === undefined) {
184 throw new Error('Internal ' + caller + ' error. ' +
185 'Default options are missing.');
186 }
187
188 function getOption(property, type, values, defaultValue) {
189 if (options[property] !== undefined) {
190 var value = options[property];
191 switch (type) {
192 case 'boolean':
193 value = Boolean(value);
194 break;
195 case 'string':
196 value = String(value);
197 break;
198 case 'number':
199 value = Number(value);
200 break;
201 default:
202 throw new Error('Internal error. Wrong value type.');
203 }
204 if (values !== undefined && values.indexOf(value) === -1) {
205 throw new RangeError('Value ' + value + ' out of range for ' + caller +
206 ' options property ' + property);
207 }
208
209 return value;
210 }
211
212 return defaultValue;
213 }
214
215 return getOption;
216 }
217
218
219 /**
220 * Compares a BCP 47 language priority list requestedLocales against the locales
221 * in availableLocales and determines the best available language to meet the
222 * request. Two algorithms are available to match the locales: the Lookup
223 * algorithm described in RFC 4647 section 3.4, and an implementation dependent
224 * best-fit algorithm. Independent of the locale matching algorithm, options
225 * specified through Unicode locale extension sequences are negotiated
226 * separately, taking the caller's relevant extension keys and locale data as
227 * well as client-provided options into consideration. Returns an object with
228 * a locale property whose value is the language tag of the selected locale,
229 * and properties for each key in relevantExtensionKeys providing the selected
230 * value for that key.
231 */
232 function resolveLocale(service, requestedLocales, options) {
233 requestedLocales = initializeLocaleList(requestedLocales);
234
235 var getOption = getGetOption(options, service);
236 var matcher = getOption('localeMatcher', 'string',
237 ['lookup', 'best fit'], 'best fit');
238 var resolved;
239 if (matcher === 'lookup') {
240 resolved = lookupMatcher(service, requestedLocales);
241 } else {
242 resolved = bestFitMatcher(service, requestedLocales);
243 }
244
245 return resolved;
246 }
247
248
249 /**
250 * Returns best matched supported locale and extension info using basic
251 * lookup algorithm.
252 */
253 function lookupMatcher(service, requestedLocales) {
254 native function NativeJSGetDefaultICULocale();
255
256 if (service.match(SERVICE_RE) === null) {
257 throw new Error('Internal error, wrong service type: ' + service);
258 }
259
260 // Cache these, they don't ever change per service.
261 if (AVAILABLE_LOCALES[service] === undefined) {
262 AVAILABLE_LOCALES[service] = getAvailableLocalesOf(service);
263 }
264
265 for (var i = 0; i < requestedLocales.length; ++i) {
266 // Remove all extensions.
267 var locale = requestedLocales[i].replace(ANY_EXTENSION_RE, '');
268 do {
269 if (AVAILABLE_LOCALES[service][locale] !== undefined) {
270 // Return the resolved locale and extension.
271 var extensionMatch = requestedLocales[i].match(UNICODE_EXTENSION_RE);
272 var extension = (extensionMatch === null) ? '' : extensionMatch[0];
273 return {'locale': locale, 'extension': extension, 'position': i};
274 }
275 // Truncate locale if possible.
276 var pos = locale.lastIndexOf('-');
277 if (pos === -1) {
278 break;
279 }
280 locale = locale.substring(0, pos);
281 } while (true);
282 }
283
284 // Didn't find a match, return default.
285 if (DEFAULT_ICU_LOCALE === undefined) {
286 DEFAULT_ICU_LOCALE = NativeJSGetDefaultICULocale();
287 }
288
289 return {'locale': DEFAULT_ICU_LOCALE, 'extension': '', 'position': -1};
290 }
291
292
293 /**
294 * Returns best matched supported locale and extension info using
295 * implementation dependend algorithm.
296 */
297 function bestFitMatcher(service, requestedLocales) {
298 // TODO(cira): implement better best fit algorithm.
299 return lookupMatcher(service, requestedLocales);
300 }
301
302
303 /**
304 * Parses Unicode extension into key - value map.
305 * Returns empty object if the extension string is invalid.
306 * We are not concerned with the validity of the values at this point.
307 */
308 function parseExtension(extension) {
309 var extensionSplit = extension.split('-');
310
311 // Assume ['', 'u', ...] input, but don't throw.
312 if (extensionSplit.length <= 2 ||
313 (extensionSplit[0] !== '' && extensionSplit[1] !== 'u')) {
314 return {};
315 }
316
317 // Key is {2}alphanum, value is {3,8}alphanum.
318 // Some keys may not have explicit values (booleans).
319 var extensionMap = {};
320 var previousKey = undefined;
321 for (var i = 2; i < extensionSplit.length; ++i) {
322 var length = extensionSplit[i].length;
323 var element = extensionSplit[i];
324 if (length === 2) {
325 extensionMap[element] = undefined;
326 previousKey = element;
327 } else if (length >= 3 && length <=8 && previousKey !== undefined) {
328 extensionMap[previousKey] = element;
329 previousKey = undefined;
330 } else {
331 // There is a value that's too long, or that doesn't have a key.
332 return {};
333 }
334 }
335
336 return extensionMap;
337 }
338
339
340 /**
341 * Converts parameter to an Object if possible.
342 */
343 function toObject(value) {
344 if (value === undefined || value === null) {
345 throw new TypeError('Value cannot be converted to an Object.');
346 }
347
348 return Object(value);
349 }
350
351
352 /**
353 * Populates internalOptions object with boolean key-value pairs
354 * from extensionMap and options.
355 * Returns filtered extension (number and date format constructors use
356 * Unicode extensions for passing parameters to ICU).
357 * It's used for extension-option pairs only, e.g. kn-normalization, but not
358 * for 'sensitivity' since it doesn't have extension equivalent.
359 * Extensions like nu and ca don't have options equivalent, so we place
360 * undefined in the map.property to denote that.
361 */
362 function setOptions(inOptions, extensionMap, keyValues, getOption, outOptions) {
363 var extension = '';
364
365 function updateExtension(key, value) {
366 return '-' + key + '-' + String(value);
367 }
368
369 function updateProperty(property, type, value) {
370 if (type === 'boolean' && (typeof value === 'string')) {
371 value = (value === 'true') ? true : false;
372 }
373
374 if (property !== undefined) {
375 defineWEProperty(outOptions, property, value);
376 }
377 }
378
379 for (var key in keyValues) {
380 if (keyValues.hasOwnProperty(key)) {
381 var value = undefined;
382 var map = keyValues[key];
383 if (map.property !== undefined) {
384 // This may return true if user specifies numeric: 'false', since
385 // Boolean('nonempty') === true.
386 value = getOption(map.property, map.type, map.values);
387 }
388 if (value !== undefined) {
389 updateProperty(map.property, map.type, value);
390 extension += updateExtension(key, value);
391 continue;
392 }
393 // User options didn't have it, check Unicode extension.
394 // Here we want to convert strings 'true', 'false' into proper Boolean
395 // values (not a user error).
396 if (extensionMap.hasOwnProperty(key)) {
397 value = extensionMap[key];
398 if (value !== undefined) {
399 updateProperty(map.property, map.type, value);
400 extension += updateExtension(key, value);
401 } else if (map.type === 'boolean') {
402 // Boolean keys are allowed not to have values in Unicode extension.
403 // Those default to true.
404 updateProperty(map.property, map.type, true);
405 extension += updateExtension(key, true);
406 }
407 }
408 }
409 }
410
411 return extension === ''? '' : '-u' + extension;
412 }
413
414
415 /**
416 * Converts all OwnProperties into
417 * configurable: false, writable: false, enumerable: true.
418 */
419 function freezeArray(array) {
420 array.forEach(function(element, index) {
421 Object.defineProperty(array, index, {value: element,
422 configurable: false,
423 writable: false,
424 enumerable: true});
425 });
426
427 Object.defineProperty(array, 'length', {value: array.length,
428 writable: false});
429
430 return array;
431 }
432
433
434 /**
435 * It's sometimes desireable to leave user requested locale instead of ICU
436 * supported one (zh-TW is equivalent to zh-Hant-TW, so we should keep shorter
437 * one, if that was what user requested).
438 * This function returns user specified tag if its maximized form matches ICU
439 * resolved locale. If not we return ICU result.
440 */
441 function getOptimalLanguageTag(original, resolved) {
442 // Returns Array<Object>, where each object has maximized and base properties.
443 // Maximized: zh -> zh-Hans-CN
444 // Base: zh-CN-u-ca-gregory -> zh-CN
445 native function NativeJSGetLanguageTagVariants();
446
447 // Take care of grandfathered or simple cases.
448 if (original === resolved) {
449 return original;
450 }
451
452 var locales = NativeJSGetLanguageTagVariants([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 native function NativeJSAvailableLocalesOf();
471 var available = NativeJSAvailableLocalesOf(service);
472
473 for (var i in available) {
474 if (available.hasOwnProperty(i)) {
475 var parts = i.match(/^([a-z]{2,3})-([A-Z][a-z]{3})-([A-Z]{2})$/);
476 if (parts !== null) {
477 // Build xx-ZZ. We don't care about the actual value,
478 // as long it's not undefined.
479 available[parts[1] + '-' + parts[3]] = null;
480 }
481 }
482 }
483
484 return available;
485 }
486
487
488 /**
489 * Defines a property and sets writable and enumerable to true.
490 * Configurable is false by default.
491 */
492 function defineWEProperty(object, property, value) {
493 Object.defineProperty(object, property,
494 {value: value, writable: true, enumerable: true});
495 }
496
497
498 /**
499 * Adds property to an object if the value is not undefined.
500 * Sets configurable descriptor to false.
501 */
502 function addWEPropertyIfDefined(object, property, value) {
503 if (value !== undefined) {
504 defineWEProperty(object, property, value);
505 }
506 }
507
508
509 /**
510 * Defines a property and sets writable, enumerable and configurable to true.
511 */
512 function defineWECProperty(object, property, value) {
513 Object.defineProperty(object, property,
514 {value: value,
515 writable: true,
516 enumerable: true,
517 configurable: true});
518 }
519
520
521 /**
522 * Adds property to an object if the value is not undefined.
523 * Sets all descriptors to true.
524 */
525 function addWECPropertyIfDefined(object, property, value) {
526 if (value !== undefined) {
527 defineWECProperty(object, property, value);
528 }
529 }
530
531
532 /**
533 * Returns titlecased word, aMeRricA -> America.
534 */
535 function toTitleCaseWord(word) {
536 return word.substr(0, 1).toUpperCase() + word.substr(1).toLowerCase();
537 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698