common.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727
  1. /**
  2. * Commonly used routines throughout ECMA-402 package. Also referred to in the standard as "Abstract Operations"
  3. */
  4. define(["./List", "./Record",
  5. "requirejs-text/text!../cldr/config/availableLocales.json",
  6. "requirejs-text/text!../cldr/supplemental/aliases.json",
  7. "requirejs-text/text!../cldr/supplemental/localeAliases.json",
  8. "requirejs-text/text!../cldr/supplemental/parentLocales.json",
  9. "requirejs-text/text!../cldr/supplemental/likelySubtags.json",
  10. "requirejs-text/text!../cldr/supplemental/calendarPreferenceData.json",
  11. ],
  12. function (List, Record, availableLocalesJson, aliasesJson, localeAliasesJson,
  13. parentLocalesJson, likelySubtagsJson, calendarPreferenceDataJson) {
  14. var aliases = JSON.parse(aliasesJson).supplemental.metadata.alias;
  15. var localeAliases = JSON.parse(localeAliasesJson).supplemental.metadata.alias;
  16. var parentLocales = JSON.parse(parentLocalesJson).supplemental.parentLocales.parentLocale;
  17. var likelySubtags = JSON.parse(likelySubtagsJson).supplemental.likelySubtags;
  18. var calendarPreferenceData = JSON.parse(calendarPreferenceDataJson).supplemental.calendarPreferenceData;
  19. var common = {
  20. unicodeLocaleExtensions : /-u(-[a-z0-9]{2,8})+/g,
  21. /**
  22. * Utility function to convert identifier strings to upper case as defined in ECMA-402 Section 6.1
  23. *
  24. * @param {String} identifier The string to be converted
  25. * @returns {String} The converted string
  26. * @private
  27. */
  28. _toUpperCaseIdentifier : function (identifier) {
  29. var match = /[a-z]/g;
  30. return identifier.replace(match, function (m) {
  31. return m.toUpperCase();
  32. }); // String
  33. },
  34. /**
  35. * Utility function to convert identifier strings to lower case as defined in ECMA-402 Section 6.1
  36. *
  37. * @param {String} identifier The string to be converted
  38. * @returns {String} The converted string
  39. * @private
  40. */
  41. _toLowerCaseIdentifier : function (identifier) {
  42. var match = /[A-Z]/g;
  43. return identifier.replace(match, function (m) {
  44. return m.toLowerCase();
  45. }); // String
  46. },
  47. /**
  48. * Utility function to retrieve the appropriate region code given a locale identifier.
  49. * If just a language tag is given, then the likely subtags data from CLDR is checked
  50. * to find the most likely region code.
  51. *
  52. * @param {String} locale The locale identifier
  53. * @returns {String} The 2 letter region code
  54. * @private
  55. */
  56. _getRegion : function (locale) {
  57. var region = "001";
  58. var regionPos = locale.search(/(?:-)([A-Z]{2})(?=(-|$))/);
  59. if (regionPos >= 0) {
  60. region = locale.substr(regionPos + 1, 2);
  61. } else {
  62. var likelySubtag = likelySubtags[locale];
  63. if (likelySubtag) {
  64. region = likelySubtag.substr(-2);
  65. }
  66. }
  67. return region;
  68. },
  69. /**
  70. * Utility function to determine the supported calendars for a given region.
  71. * Calendar preference data from CLDR is used to determine which locales are used
  72. * in a given region.
  73. *
  74. * @param {String} region The 2 letter region code
  75. * @returns {String[]} An array containing the supported calendars for this region, in order of preference.
  76. * @private
  77. */
  78. _getSupportedCalendars : function (region) {
  79. var supportedCalendars = [ "gregory", "buddhist", "hebrew", "japanese", "roc",
  80. "islamic", "islamic-civil", "islamic-tbla", "islamic-umalqura"];
  81. var calendarPreferences = [];
  82. if (calendarPreferenceData[region]) {
  83. var prefs = calendarPreferenceData[region].toString().split(" ");
  84. prefs.forEach(function (pref) {
  85. var thisPref = pref.replace("gregorian", "gregory");
  86. if (supportedCalendars.indexOf(thisPref) !== -1) {
  87. calendarPreferences.push(thisPref);
  88. }
  89. });
  90. }
  91. /* Gregorian should always be supported */
  92. if (calendarPreferences.indexOf("gregory") === -1) {
  93. calendarPreferences.push("gregory");
  94. }
  95. return calendarPreferences;
  96. },
  97. /**
  98. * IsStructurallyValidLanguageTag abstract operation as defined in ECMA-402 Section 6.2.2
  99. *
  100. * @param {String} locale The language tag to check
  101. * @returns {Boolean} Returns true if the string is a structurally valid language tag.
  102. * @private
  103. */
  104. isStructurallyValidLanguageTag : function (locale) {
  105. if (typeof locale !== "string") {
  106. return false; // Boolean
  107. }
  108. var identifier = this._toLowerCaseIdentifier(locale);
  109. var langtag = new RegExp(
  110. "^([a-z]{2,3}(-[a-z]{3}){0,3}|[a-z]{4,8})" + // language
  111. "(-[a-z]{4})?" + // script
  112. "(-([a-z]{2}|\\d{3}))?" + // territory
  113. "(-([a-z0-9]{5,8}|\\d[a-z0-9]{3}))*" + // variant
  114. "(-[a-wyz0-9](-[a-z0-9]{2,8})+)*(-x(-[a-z0-9]{1,8})+)?$"); // extension
  115. var privateuse = /x(-[a-z0-9]{1,8})+/;
  116. var grandfathered = new RegExp(
  117. "en-gb-oed|(i-(ami|bnn|default|enochian|hak|klingon|lux|mingo|navajo|pwn|tao|tay|tsu))|" +
  118. "sgn-((be-(fr|nl))|(ch-de))");
  119. if (privateuse.test(identifier) || grandfathered.test(identifier)) {
  120. return true; // Boolean
  121. }
  122. /**
  123. * Utility function to determine whether the given element is a unique variant
  124. * within the context of a BCP 47 compliant language tag.
  125. *
  126. * @param {String} element The element tag
  127. * @returns {Boolean} Returns true if the given element is a unique variant.
  128. * @private
  129. */
  130. function _isUniqueVariant(element) {
  131. var firstSingletonPosition = identifier.search(/-[a-z0-9]-/);
  132. if (firstSingletonPosition > 0) {
  133. return identifier.indexOf(element) > firstSingletonPosition
  134. || identifier.indexOf(element) === identifier.lastIndexOf(element,
  135. firstSingletonPosition); // Boolean
  136. }
  137. return identifier.indexOf(element) === identifier.lastIndexOf(element); // Boolean
  138. }
  139. /**
  140. * Utility function to determine whether the given element is a unique singleton
  141. * within the context of a BCP 47 compliant language tag.
  142. *
  143. * @param {String} element The element tag
  144. * @returns {Boolean} Returns true if the given element is a unique singleton.
  145. * @private
  146. */
  147. function _isUniqueSingleton(element) {
  148. var firstXPosition = identifier.search(/-x-/);
  149. if (firstXPosition > 0) {
  150. return identifier.indexOf(element) === identifier.lastIndexOf(element, firstXPosition);
  151. }
  152. return identifier.indexOf(element) === identifier.lastIndexOf(element); // Boolean
  153. }
  154. if (langtag.test(identifier)) { // represents a well-formed BCP 47 language tag
  155. var varianttag = /-[a-z0-9]{5,8}|\d[a-z0-9]{3}/g;
  156. var variants = varianttag.exec(identifier);
  157. var singletontag = /-[a-wyz0-9]-/g;
  158. var singletons = singletontag.exec(identifier);
  159. var variantsOK = !variants || variants.every(_isUniqueVariant); // has no duplicate variant tags
  160. var singletonsOK = !singletons || singletons.every(_isUniqueSingleton); // has no duplicate
  161. // singleton tags
  162. return variantsOK && singletonsOK;
  163. }
  164. return false; // Boolean
  165. },
  166. /**
  167. * CanonicalizeLanguageTag abstract operation as defined in ECMA-402 Section 6.2.3
  168. *
  169. * @param {String} locale The structurally valid language tag to canonicalize
  170. * @returns {String} The canonical and case regularized form of the language tag.
  171. * @private
  172. */
  173. CanonicalizeLanguageTag : function (locale) {
  174. var result = locale.toLowerCase();
  175. var firstSingletonPosition = result.search(/(^|-)[a-z0-9]-/);
  176. var languageTag = /^([a-z]{2,3}(-[a-z]{3}){0,3}|[a-z]{4,8})(?=(-|$))/;
  177. var scriptTag = /(?:-)([a-z]{4})(?=(-|$))/;
  178. var regionTag = /(?:-)([a-z]{2})(?=(-|$))/g;
  179. var variantTag = /(?:-)([a-z0-9]{5,8}|\d[a-z0-9]{3})/;
  180. var extlangTag = /^([a-z]{2,3}(-[a-z]{3}))(?=(-|$))/;
  181. /* Canonicalize the Language Tag */
  182. result = result.replace(languageTag, function (m) {
  183. var lookupAlias = aliases.languageAlias[m];
  184. if (lookupAlias && lookupAlias._reason !== "macrolanguage") {
  185. m = lookupAlias._replacement ? lookupAlias._replacement : m;
  186. }
  187. return m;
  188. }); // String
  189. // Canonicalize the Script Tag
  190. result = result.replace(scriptTag, function (m) {
  191. if (firstSingletonPosition === -1 || result.indexOf(m) < firstSingletonPosition) {
  192. m = m.substring(0, 2).toUpperCase() + m.substring(2);
  193. var script = m.substring(1);
  194. var lookupAlias = aliases.scriptAlias[script];
  195. if (lookupAlias) {
  196. m = lookupAlias._replacement ? "-" + lookupAlias._replacement : m;
  197. }
  198. }
  199. return m;
  200. }); // String
  201. // Canonicalize the Region Tag
  202. result = result.replace(regionTag, function (m) {
  203. if (firstSingletonPosition === -1 || result.indexOf(m) < firstSingletonPosition) {
  204. m = m.toUpperCase();
  205. var region = m.substring(1);
  206. var lookupAlias = aliases.territoryAlias[region];
  207. if (lookupAlias) {
  208. var repl = lookupAlias._replacement;
  209. if (repl.indexOf(" ") >= 0) {
  210. repl = repl.substring(0, repl.indexOf(" "));
  211. }
  212. m = repl ? "-" + repl : m;
  213. }
  214. }
  215. return m;
  216. }); // String
  217. // Canonicalize the Variant Tag
  218. result = result.replace(variantTag, function (m) {
  219. // Variant tags are upper case in CLDR's data.
  220. var variant = common._toUpperCaseIdentifier(m.substring(1));
  221. var lookupAlias = aliases.variantAlias[variant];
  222. if (lookupAlias) {
  223. var repl = lookupAlias._replacement;
  224. m = repl ? "-" + common._toLowerCaseIdentifier(repl) : m;
  225. }
  226. return m;
  227. }); // String
  228. // Canonicalize any whole tag combinations or grandfathered tags
  229. result = result.replace(result, function (m) {
  230. var lookupAlias = aliases.languageAlias[m];
  231. if (lookupAlias && lookupAlias._reason !== "macrolanguage") {
  232. m = lookupAlias._replacement ? lookupAlias._replacement : m;
  233. }
  234. return m;
  235. }); // String
  236. // Remove the prefix if an extlang tag exists
  237. if (extlangTag.test(result)) {
  238. result = result.replace(/^[a-z]{2,3}-/, "");
  239. }
  240. return result; // String
  241. },
  242. /**
  243. * DefaultLocale abstract operation as defined in ECMA-402 Section 6.2.4
  244. *
  245. * @returns {String} A string value representing the structurally valid (6.2.2)
  246. * and canonicalized (6.2.3) BCP 47 language tag for the host environment’s current locale.
  247. * @private
  248. */
  249. DefaultLocale : function () {
  250. var result;
  251. var global = (function () {return this; })();
  252. var navigator = global.navigator;
  253. if (navigator && this.isStructurallyValidLanguageTag(navigator.language)) {
  254. result = this.BestFitAvailableLocale(this.availableLocalesList, this
  255. .CanonicalizeLanguageTag(navigator.language));
  256. }
  257. if (!result && navigator && this.isStructurallyValidLanguageTag(navigator.userLanguage)) {
  258. result = this.BestFitAvailableLocale(this.availableLocalesList, this
  259. .CanonicalizeLanguageTag(navigator.userLanguage));
  260. }
  261. if (!result) {
  262. result = "root";
  263. }
  264. return result;
  265. },
  266. /**
  267. * IsWellFormedCurrencyCode abstract operation as defined in ECMA-402 Section 6.3.1
  268. *
  269. * @param {String} currency The currency code to check
  270. * @returns {Boolean} Returns true if the string is a well formed currency code.
  271. * @private
  272. */
  273. IsWellFormedCurrencyCode : function (currency) {
  274. var wellFormed = /^[A-Za-z]{3}$/;
  275. return wellFormed.test(currency.toString()); // Boolean
  276. },
  277. /**
  278. * CanonicalizeLocaleList abstract operation as defined in ECMA-402 Section 9.2.1
  279. *
  280. * @param {*} locales The list of locales to canonicalize
  281. * @returns {Object} The canonicalized list of locales, as a "List" object.
  282. * @private
  283. */
  284. CanonicalizeLocaleList : function (locales) {
  285. if (locales === undefined) {
  286. return new List();
  287. }
  288. if (locales === null) {
  289. throw new TypeError("Locale list can not be null");
  290. }
  291. var seen = new List();
  292. if (typeof locales === "string") {
  293. locales = new Array(locales);
  294. }
  295. var O = Object(locales);
  296. var lenValue = O.length;
  297. var len = lenValue >>> 0; // Convert to unsigned 32-bit integer
  298. for (var k = 0; k < len ; k++) {
  299. var Pk = k.toString();
  300. var kPresent = Pk in O;
  301. if (kPresent) {
  302. var kValue = O[Pk];
  303. if (typeof kValue !== "string" && typeof kValue !== "object") {
  304. throw new TypeError(kValue + " must be a string or an object.");
  305. }
  306. var tag = kValue.toString();
  307. if (!this.isStructurallyValidLanguageTag(tag)) {
  308. throw new RangeError(tag + " is not a structurally valid language tag.");
  309. }
  310. tag = this.CanonicalizeLanguageTag(tag);
  311. if (seen.indexOf(tag) < 0) {
  312. seen.push(tag);
  313. }
  314. }
  315. }
  316. return seen;
  317. },
  318. /**
  319. * BestAvailableLocale abstract operation as defined in ECMA-402 Section 9.2.2
  320. *
  321. * @param {List} availableLocales The canonicalized list of available locales
  322. * @param {String} locale The locale identifier to check
  323. * @returns {String} The best available locale, using the fallback mechanism of RFC 4647, section 3.4.
  324. * @private
  325. */
  326. BestAvailableLocale : function (availableLocales, locale) {
  327. var candidate = locale;
  328. while (true) {
  329. if (availableLocales.indexOf(candidate) >= 0) {
  330. return candidate;
  331. }
  332. var pos = candidate.lastIndexOf("-");
  333. if (pos < 0) {
  334. return undefined;
  335. }
  336. if (pos >= 2 && candidate.charAt(pos - 2) === "-") {
  337. pos -= 2;
  338. }
  339. candidate = candidate.substring(0, pos);
  340. }
  341. },
  342. /**
  343. * LookupMatcher abstract operation as defined in ECMA-402 Section 9.2.3
  344. *
  345. * @param {List} availableLocales The canonicalized list of available locales
  346. * @param {List} requestedLocales The canonicalized list of requested locales
  347. * @returns {String} The best available locale identifier to meet the request
  348. * @private
  349. */
  350. LookupMatcher : function (availableLocales, requestedLocales) {
  351. var i = 0;
  352. var len = requestedLocales.length;
  353. var availableLocale = null;
  354. var locale = null;
  355. var noExtensionsLocale = null;
  356. while (i < len && availableLocale === null) {
  357. locale = requestedLocales[i];
  358. noExtensionsLocale = locale.replace(this.unicodeLocaleExtensions, "");
  359. availableLocale = this.BestAvailableLocale(availableLocales, noExtensionsLocale);
  360. i++;
  361. }
  362. var result = new Record();
  363. if (availableLocale) {
  364. result.set("locale", availableLocale);
  365. if (locale !== noExtensionsLocale) {
  366. result.set("extension", locale.match(this.unicodeLocaleExtensions)[0]);
  367. result.set("extensionIndex", locale.search(this.unicodeLocaleExtensions));
  368. }
  369. } else {
  370. result.set("locale", this.DefaultLocale());
  371. }
  372. return result;
  373. },
  374. /**
  375. * BestFitAvailableLocale abstract operation.
  376. *
  377. * Algorithm is similar to BestAvailableLocale, as in Section 9.2.2
  378. * except that the following additional operations are performed:
  379. * 1). CLDR macrolanguage replacements are done ( i.e. "cmn" becomes "zh" )
  380. * 2). Known locale aliases, such as zh-TW = zh-Hant-TW, are resolved,
  381. * 3). Explicit parent locales from CLDR's supplemental data are also considered.
  382. *
  383. * @param {List} availableLocales The canonicalized list of available locales
  384. * @param {String} locale The locale identifier to check
  385. * @returns {String} The best fit available locale, using CLDR's locale fallback mechanism.
  386. * @private
  387. */
  388. BestFitAvailableLocale : function (availableLocales, locale) {
  389. var candidate = locale;
  390. while (true) {
  391. var langtag = candidate.substring(0, candidate.indexOf("-"));
  392. var lookupAlias = aliases.languageAlias[langtag];
  393. if (lookupAlias && lookupAlias._reason === "macrolanguage") {
  394. candidate = candidate.replace(langtag, lookupAlias._replacement);
  395. }
  396. lookupAlias = localeAliases.localeAlias[candidate];
  397. if (lookupAlias) {
  398. candidate = lookupAlias._replacement;
  399. }
  400. if (availableLocales.indexOf(candidate) >= 0) {
  401. return candidate;
  402. }
  403. var parentLocale = parentLocales[candidate];
  404. if (parentLocale) {
  405. candidate = parentLocale;
  406. } else {
  407. var pos = candidate.lastIndexOf("-");
  408. if (pos < 0) {
  409. return undefined;
  410. }
  411. if (pos >= 2 && candidate.charAt(pos - 2) === "-") {
  412. pos -= 2;
  413. }
  414. candidate = candidate.substring(0, pos);
  415. }
  416. }
  417. },
  418. /**
  419. * BestFitMatcher abstract operation as defined in ECMA-402 Section 9.2.4
  420. *
  421. * @param {List} availableLocales The canonicalized list of available locales
  422. * @param {List} requestedLocales The canonicalized list of requested locales
  423. * @returns {String} The best available locale identifier to meet the request
  424. * @private
  425. */
  426. BestFitMatcher : function (availableLocales, requestedLocales) {
  427. var i = 0;
  428. var len = requestedLocales.length;
  429. var availableLocale = null;
  430. var locale = null;
  431. var noExtensionsLocale = null;
  432. while (i < len && availableLocale === null) {
  433. locale = requestedLocales[i];
  434. noExtensionsLocale = locale.replace(this.unicodeLocaleExtensions, "");
  435. availableLocale = this.BestFitAvailableLocale(availableLocales, noExtensionsLocale);
  436. i++;
  437. }
  438. var result = new Record();
  439. if (availableLocale) {
  440. result.set("locale", availableLocale);
  441. if (locale !== noExtensionsLocale) {
  442. result.set("extension", locale.match(this.unicodeLocaleExtensions)[0]);
  443. result.set("extensionIndex", locale.search(this.unicodeLocaleExtensions));
  444. }
  445. } else {
  446. result.set("locale", this.DefaultLocale());
  447. }
  448. return result;
  449. },
  450. /**
  451. * ResolveLocale abstract operation as defined in ECMA-402 Section 9.2.5
  452. *
  453. * Compares a BCP 47 language priority list requestedLocales against the locales in availableLocales
  454. * and determines the best available language to meet the request.
  455. *
  456. * @param {List} availableLocales The canonicalized list of available locales
  457. * @param {List} requestedLocales The canonicalized list of requested locales
  458. * @param {Record} options Locale matching options (for example, "lookup" vs. "best fit" algorithm)
  459. * @param {String[]} relevantExtensionKeys Array of relevant -u extension keys for the match
  460. * @param {Object} localeData Hash table containing the preloaded locale data.
  461. * @returns {Record} The locale information regarding best available locale that meets the request
  462. * @private
  463. */
  464. /* jshint maxcomplexity: 14 */
  465. ResolveLocale : function (availableLocales, requestedLocales, options, relevantExtensionKeys,
  466. localeData) {
  467. var matcher = options.localeMatcher;
  468. var r = matcher === "lookup" ? this.LookupMatcher(availableLocales, requestedLocales) : this
  469. .BestFitMatcher(availableLocales, requestedLocales);
  470. var foundLocale = r.locale;
  471. var extension = "";
  472. var extensionSubtags = [];
  473. var extensionSubtagsLength = 0;
  474. var extensionIndex = 0;
  475. if (r.extension !== undefined) {
  476. extension = r.extension;
  477. extensionIndex = r.extensionIndex;
  478. extensionSubtags = extension.split("-");
  479. extensionSubtagsLength = extensionSubtags.length;
  480. }
  481. var result = new Record();
  482. result.set("dataLocale", foundLocale);
  483. var supportedExtension = "-u";
  484. var i = 0;
  485. var len = relevantExtensionKeys.length;
  486. while (i < len) {
  487. var key = relevantExtensionKeys[String(i)];
  488. var foundLocaleData = localeData[foundLocale];
  489. var keyLocaleData = foundLocaleData[key];
  490. var value = keyLocaleData["0"];
  491. var supportedExtensionAddition = "";
  492. if (typeof extensionSubtags !== "undefined") {
  493. var keyPos = extensionSubtags.indexOf(key);
  494. var valuePos;
  495. if (keyPos !== -1) {
  496. if (keyPos + 1 < extensionSubtagsLength
  497. && extensionSubtags[String(keyPos + 1)].length > 2) {
  498. var requestedValue = extensionSubtags[String(keyPos + 1)];
  499. // fix for islamic-civil, islamic-umalqura & islamic-tbla calendars
  500. if (requestedValue === "islamic" && extensionSubtags[String(keyPos + 2)]) {
  501. requestedValue += "-" + extensionSubtags[String(keyPos + 2)];
  502. }
  503. valuePos = keyLocaleData.indexOf(requestedValue);
  504. if (valuePos !== -1) {
  505. value = requestedValue;
  506. supportedExtensionAddition = "-" + key + "-" + value;
  507. }
  508. } else {
  509. valuePos = keyLocaleData.indexOf("true");
  510. if (valuePos !== -1) {
  511. value = "true";
  512. }
  513. }
  514. }
  515. }
  516. var optionsValue = options[key];
  517. if (optionsValue !== undefined) {
  518. if (keyLocaleData.indexOf(optionsValue) !== -1) {
  519. if (optionsValue !== value) {
  520. value = optionsValue;
  521. supportedExtensionAddition = "";
  522. }
  523. }
  524. }
  525. result.set(key, value);
  526. supportedExtension += supportedExtensionAddition;
  527. i++;
  528. }
  529. if (supportedExtension.length > 2) {
  530. var preExtension = foundLocale.substring(0, extensionIndex);
  531. var postExtension = foundLocale.substring(extensionIndex);
  532. foundLocale = preExtension + supportedExtension + postExtension;
  533. }
  534. result.set("locale", foundLocale);
  535. return result;
  536. },
  537. /* jshint maxcomplexity: 10 */
  538. /**
  539. * LookupSupportedLocales abstract operation as defined in ECMA-402 Section 9.2.6
  540. *
  541. * Returns the subset of the provided BCP 47 language priority list requestedLocales for which
  542. * availableLocales has a matching locale when using the BCP 47 lookup algorithm.
  543. * Locales appear in the same order in the returned list as in requestedLocales.
  544. *
  545. * @param {List} availableLocales The canonicalized list of available locales
  546. * @param {List} requestedLocales The canonicalized list of requested locales
  547. * @returns {String[]} An array containing a list of matching locales
  548. * @private
  549. */
  550. LookupSupportedLocales : function (availableLocales, requestedLocales) {
  551. var len = requestedLocales.length;
  552. var subset = new List();
  553. var k = 0;
  554. while (k < len) {
  555. var locale = requestedLocales[k];
  556. var noExtensionsLocale = locale.replace(this.unicodeLocaleExtensions, "");
  557. var availableLocale = this.BestAvailableLocale(availableLocales, noExtensionsLocale);
  558. if (availableLocale !== undefined) {
  559. subset.push(locale);
  560. }
  561. k++;
  562. }
  563. var subsetArray = subset.toArray();
  564. return subsetArray;
  565. },
  566. /**
  567. * BestFitSupportedLocales abstract operation as defined in ECMA-402 Section 9.2.7
  568. *
  569. * Returns the subset of the provided BCP 47 language priority list requestedLocales for which
  570. * availableLocales has a matching locale when using the best fit matcher algorithm.
  571. * Locales appear in the same order in the returned list as in requestedLocales.
  572. *
  573. * @param {List} availableLocales The canonicalized list of available locales
  574. * @param {List} requestedLocales The canonicalized list of requested locales
  575. * @returns {String[]} An array containing a list of matching locales
  576. * @private
  577. */
  578. BestFitSupportedLocales : function (availableLocales, requestedLocales) {
  579. var len = requestedLocales.length;
  580. var subset = new List();
  581. var k = 0;
  582. while (k < len) {
  583. var locale = requestedLocales[k];
  584. var noExtensionsLocale = locale.replace(this.unicodeLocaleExtensions, "");
  585. var availableLocale = this.BestFitAvailableLocale(availableLocales, noExtensionsLocale);
  586. if (availableLocale !== undefined) {
  587. subset.push(locale);
  588. }
  589. k++;
  590. }
  591. var subsetArray = subset.toArray();
  592. return subsetArray;
  593. },
  594. /**
  595. * SupportedLocales abstract operation as defined in ECMA-402 Section 9.2.8
  596. *
  597. * Returns the subset of the provided BCP 47 language priority list requestedLocales for which
  598. * availableLocales has a matching locale. Two algorithms are available to match the locales:
  599. * the Lookup algorithm described in RFC 4647 section 3.4, and an implementation dependent
  600. * best-fit algorithm. Locales appear in the same order in the returned list as in requestedLocales.
  601. *
  602. * @param {List} availableLocales The canonicalized list of available locales
  603. * @param {List} requestedLocales The canonicalized list of requested locales
  604. * @param {Object} options Specifies which lookup algorithm to use
  605. * @returns {String[]} An array containing a list of matching locales
  606. * @private
  607. */
  608. SupportedLocales : function (availableLocales, requestedLocales, options) {
  609. var matcher;
  610. var subset;
  611. if (options !== undefined) {
  612. options = Object(options);
  613. matcher = options.localeMatcher;
  614. if (matcher !== undefined) {
  615. matcher = String(matcher);
  616. if (matcher !== "lookup" && matcher !== "best fit") {
  617. throw new RangeError("Matching algorithm must be 'lookup' or 'best fit'.");
  618. }
  619. }
  620. }
  621. if (matcher === undefined || matcher === "best fit") {
  622. subset = this.BestFitSupportedLocales(availableLocales, requestedLocales);
  623. } else {
  624. subset = this.LookupSupportedLocales(availableLocales, requestedLocales);
  625. }
  626. for (var P in Object.getOwnPropertyNames(subset)) {
  627. var desc = Object.getOwnPropertyDescriptor(subset, P);
  628. if (desc !== undefined) {
  629. desc.writable = false;
  630. desc.configurable = false;
  631. Object.defineProperty(subset, P, desc);
  632. }
  633. }
  634. Object.defineProperty(subset, "length", {
  635. writable : false,
  636. configurable : false
  637. });
  638. return subset;
  639. },
  640. /**
  641. * GetOption abstract operation as defined in ECMA-402 Section 9.2.9
  642. *
  643. * Extracts the value of the named property from the provided options object,
  644. * converts it to the required type, checks whether it is one of a List of allowed values,
  645. * and fills in a fallback value if necessary.
  646. *
  647. * @param {Object} options The object containing the options to search
  648. * @param {String} property The property to retrieve
  649. * @param {String} type The type of the resulting option value
  650. * @param {Array} values The list of allowed values
  651. * @param {*} fallback The fallback value
  652. * @returns {*} The resulting value as described above.
  653. * @private
  654. */
  655. GetOption : function (options, property, type, values, fallback) {
  656. var value = options[property];
  657. if (value !== undefined) {
  658. if (type === "boolean") {
  659. value = Boolean(value);
  660. }
  661. if (type === "string") {
  662. value = String(value);
  663. }
  664. if (values !== undefined) {
  665. for (var v in values) {
  666. if (values[v] === value) {
  667. return value;
  668. }
  669. }
  670. throw new RangeError("The specified value " + value + " for property " + property
  671. + " is invalid.");
  672. }
  673. return value;
  674. }
  675. return fallback;
  676. },
  677. /**
  678. * GetNumberOption abstract operation as defined in ECMA-402 Section 9.2.10
  679. *
  680. * Extracts the value of the named property from the provided options object,
  681. * converts it to a Number value, checks whether it is in the allowed range,
  682. * and fills in a fallback value if necessary.
  683. *
  684. * @param {Object} options The object containing the options to search
  685. * @param {String} property The property to retrieve
  686. * @param {Number} minimum The minimum numeric value for this option
  687. * @param {Number} maximum The maximum numeric value for this option
  688. * @param {*} fallback The fallback value
  689. * @returns {Number} The resulting value as described above.
  690. * @private
  691. */
  692. GetNumberOption : function (options, property, minimum, maximum, fallback) {
  693. var value = options[property];
  694. if (value !== undefined) {
  695. value = Number(value);
  696. if (isNaN(value) || value < minimum || value > maximum) {
  697. throw new RangeError("The specified number value " + value + " is not in the allowed range");
  698. }
  699. return Math.floor(value);
  700. }
  701. return fallback;
  702. }
  703. };
  704. common.availableLocalesList = common.CanonicalizeLocaleList(JSON.parse(availableLocalesJson).availableLocales);
  705. return common;
  706. });