cldr.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662
  1. /**
  2. * CLDR JavaScript Library v0.4.3
  3. * http://jquery.com/
  4. *
  5. * Copyright 2013 Rafael Xavier de Souza
  6. * Released under the MIT license
  7. * http://jquery.org/license
  8. *
  9. * Date: 2015-08-24T01:00Z
  10. */
  11. /*!
  12. * CLDR JavaScript Library v0.4.3 2015-08-24T01:00Z MIT license © Rafael Xavier
  13. * http://git.io/h4lmVg
  14. */
  15. (function( root, factory ) {
  16. if ( typeof define === "function" && define.amd ) {
  17. // AMD.
  18. define( factory );
  19. } else if ( typeof module === "object" && typeof module.exports === "object" ) {
  20. // Node. CommonJS.
  21. module.exports = factory();
  22. } else {
  23. // Global
  24. root.Cldr = factory();
  25. }
  26. }( this, function() {
  27. var arrayIsArray = Array.isArray || function( obj ) {
  28. return Object.prototype.toString.call( obj ) === "[object Array]";
  29. };
  30. var pathNormalize = function( path, attributes ) {
  31. if ( arrayIsArray( path ) ) {
  32. path = path.join( "/" );
  33. }
  34. if ( typeof path !== "string" ) {
  35. throw new Error( "invalid path \"" + path + "\"" );
  36. }
  37. // 1: Ignore leading slash `/`
  38. // 2: Ignore leading `cldr/`
  39. path = path
  40. .replace( /^\// , "" ) /* 1 */
  41. .replace( /^cldr\// , "" ); /* 2 */
  42. // Replace {attribute}'s
  43. path = path.replace( /{[a-zA-Z]+}/g, function( name ) {
  44. name = name.replace( /^{([^}]*)}$/, "$1" );
  45. return attributes[ name ];
  46. });
  47. return path.split( "/" );
  48. };
  49. var arraySome = function( array, callback ) {
  50. var i, length;
  51. if ( array.some ) {
  52. return array.some( callback );
  53. }
  54. for ( i = 0, length = array.length; i < length; i++ ) {
  55. if ( callback( array[ i ], i, array ) ) {
  56. return true;
  57. }
  58. }
  59. return false;
  60. };
  61. /**
  62. * Return the maximized language id as defined in
  63. * http://www.unicode.org/reports/tr35/#Likely_Subtags
  64. * 1. Canonicalize.
  65. * 1.1 Make sure the input locale is in canonical form: uses the right
  66. * separator, and has the right casing.
  67. * TODO Right casing? What df? It seems languages are lowercase, scripts are
  68. * Capitalized, territory is uppercase. I am leaving this as an exercise to
  69. * the user.
  70. *
  71. * 1.2 Replace any deprecated subtags with their canonical values using the
  72. * <alias> data in supplemental metadata. Use the first value in the
  73. * replacement list, if it exists. Language tag replacements may have multiple
  74. * parts, such as "sh" ➞ "sr_Latn" or mo" ➞ "ro_MD". In such a case, the
  75. * original script and/or region are retained if there is one. Thus
  76. * "sh_Arab_AQ" ➞ "sr_Arab_AQ", not "sr_Latn_AQ".
  77. * TODO What <alias> data?
  78. *
  79. * 1.3 If the tag is grandfathered (see <variable id="$grandfathered"
  80. * type="choice"> in the supplemental data), then return it.
  81. * TODO grandfathered?
  82. *
  83. * 1.4 Remove the script code 'Zzzz' and the region code 'ZZ' if they occur.
  84. * 1.5 Get the components of the cleaned-up source tag (languages, scripts,
  85. * and regions), plus any variants and extensions.
  86. * 2. Lookup. Lookup each of the following in order, and stop on the first
  87. * match:
  88. * 2.1 languages_scripts_regions
  89. * 2.2 languages_regions
  90. * 2.3 languages_scripts
  91. * 2.4 languages
  92. * 2.5 und_scripts
  93. * 3. Return
  94. * 3.1 If there is no match, either return an error value, or the match for
  95. * "und" (in APIs where a valid language tag is required).
  96. * 3.2 Otherwise there is a match = languagem_scriptm_regionm
  97. * 3.3 Let xr = xs if xs is not empty, and xm otherwise.
  98. * 3.4 Return the language tag composed of languager _ scriptr _ regionr +
  99. * variants + extensions.
  100. *
  101. * @subtags [Array] normalized language id subtags tuple (see init.js).
  102. */
  103. var coreLikelySubtags = function( Cldr, cldr, subtags, options ) {
  104. var match, matchFound,
  105. language = subtags[ 0 ],
  106. script = subtags[ 1 ],
  107. sep = Cldr.localeSep,
  108. territory = subtags[ 2 ];
  109. options = options || {};
  110. // Skip if (language, script, territory) is not empty [3.3]
  111. if ( language !== "und" && script !== "Zzzz" && territory !== "ZZ" ) {
  112. return [ language, script, territory ];
  113. }
  114. // Skip if no supplemental likelySubtags data is present
  115. if ( typeof cldr.get( "supplemental/likelySubtags" ) === "undefined" ) {
  116. return;
  117. }
  118. // [2]
  119. matchFound = arraySome([
  120. [ language, script, territory ],
  121. [ language, territory ],
  122. [ language, script ],
  123. [ language ],
  124. [ "und", script ]
  125. ], function( test ) {
  126. return match = !(/\b(Zzzz|ZZ)\b/).test( test.join( sep ) ) /* [1.4] */ && cldr.get( [ "supplemental/likelySubtags", test.join( sep ) ] );
  127. });
  128. // [3]
  129. if ( matchFound ) {
  130. // [3.2 .. 3.4]
  131. match = match.split( sep );
  132. return [
  133. language !== "und" ? language : match[ 0 ],
  134. script !== "Zzzz" ? script : match[ 1 ],
  135. territory !== "ZZ" ? territory : match[ 2 ]
  136. ];
  137. } else if ( options.force ) {
  138. // [3.1.2]
  139. return cldr.get( "supplemental/likelySubtags/und" ).split( sep );
  140. } else {
  141. // [3.1.1]
  142. return;
  143. }
  144. };
  145. /**
  146. * Given a locale, remove any fields that Add Likely Subtags would add.
  147. * http://www.unicode.org/reports/tr35/#Likely_Subtags
  148. * 1. First get max = AddLikelySubtags(inputLocale). If an error is signaled,
  149. * return it.
  150. * 2. Remove the variants from max.
  151. * 3. Then for trial in {language, language _ region, language _ script}. If
  152. * AddLikelySubtags(trial) = max, then return trial + variants.
  153. * 4. If you do not get a match, return max + variants.
  154. *
  155. * @maxLanguageId [Array] maxLanguageId tuple (see init.js).
  156. */
  157. var coreRemoveLikelySubtags = function( Cldr, cldr, maxLanguageId ) {
  158. var match, matchFound,
  159. language = maxLanguageId[ 0 ],
  160. script = maxLanguageId[ 1 ],
  161. territory = maxLanguageId[ 2 ];
  162. // [3]
  163. matchFound = arraySome([
  164. [ [ language, "Zzzz", "ZZ" ], [ language ] ],
  165. [ [ language, "Zzzz", territory ], [ language, territory ] ],
  166. [ [ language, script, "ZZ" ], [ language, script ] ]
  167. ], function( test ) {
  168. var result = coreLikelySubtags( Cldr, cldr, test[ 0 ] );
  169. match = test[ 1 ];
  170. return result && result[ 0 ] === maxLanguageId[ 0 ] &&
  171. result[ 1 ] === maxLanguageId[ 1 ] &&
  172. result[ 2 ] === maxLanguageId[ 2 ];
  173. });
  174. // [4]
  175. return matchFound ? match : maxLanguageId;
  176. };
  177. /**
  178. * subtags( locale )
  179. *
  180. * @locale [String]
  181. */
  182. var coreSubtags = function( locale ) {
  183. var aux, unicodeLanguageId,
  184. subtags = [];
  185. locale = locale.replace( /_/, "-" );
  186. // Unicode locale extensions.
  187. aux = locale.split( "-u-" );
  188. if ( aux[ 1 ] ) {
  189. aux[ 1 ] = aux[ 1 ].split( "-t-" );
  190. locale = aux[ 0 ] + ( aux[ 1 ][ 1 ] ? "-t-" + aux[ 1 ][ 1 ] : "");
  191. subtags[ 4 /* unicodeLocaleExtensions */ ] = aux[ 1 ][ 0 ];
  192. }
  193. // TODO normalize transformed extensions. Currently, skipped.
  194. // subtags[ x ] = locale.split( "-t-" )[ 1 ];
  195. unicodeLanguageId = locale.split( "-t-" )[ 0 ];
  196. // unicode_language_id = "root"
  197. // | unicode_language_subtag
  198. // (sep unicode_script_subtag)?
  199. // (sep unicode_region_subtag)?
  200. // (sep unicode_variant_subtag)* ;
  201. //
  202. // Although unicode_language_subtag = alpha{2,8}, I'm using alpha{2,3}. Because, there's no language on CLDR lengthier than 3.
  203. aux = unicodeLanguageId.match( /^(([a-z]{2,3})(-([A-Z][a-z]{3}))?(-([A-Z]{2}|[0-9]{3}))?)(-[a-zA-Z0-9]{5,8}|[0-9][a-zA-Z0-9]{3})*$|^(root)$/ );
  204. if ( aux === null ) {
  205. return [ "und", "Zzzz", "ZZ" ];
  206. }
  207. subtags[ 0 /* language */ ] = aux[ 9 ] /* root */ || aux[ 2 ] || "und";
  208. subtags[ 1 /* script */ ] = aux[ 4 ] || "Zzzz";
  209. subtags[ 2 /* territory */ ] = aux[ 6 ] || "ZZ";
  210. subtags[ 3 /* variant */ ] = aux[ 7 ];
  211. // 0: language
  212. // 1: script
  213. // 2: territory (aka region)
  214. // 3: variant
  215. // 4: unicodeLocaleExtensions
  216. return subtags;
  217. };
  218. var arrayForEach = function( array, callback ) {
  219. var i, length;
  220. if ( array.forEach ) {
  221. return array.forEach( callback );
  222. }
  223. for ( i = 0, length = array.length; i < length; i++ ) {
  224. callback( array[ i ], i, array );
  225. }
  226. };
  227. /**
  228. * bundleLookup( minLanguageId )
  229. *
  230. * @Cldr [Cldr class]
  231. *
  232. * @cldr [Cldr instance]
  233. *
  234. * @minLanguageId [String] requested languageId after applied remove likely subtags.
  235. */
  236. var bundleLookup = function( Cldr, cldr, minLanguageId ) {
  237. var availableBundleMap = Cldr._availableBundleMap,
  238. availableBundleMapQueue = Cldr._availableBundleMapQueue;
  239. if ( availableBundleMapQueue.length ) {
  240. arrayForEach( availableBundleMapQueue, function( bundle ) {
  241. var existing, maxBundle, minBundle, subtags;
  242. subtags = coreSubtags( bundle );
  243. maxBundle = coreLikelySubtags( Cldr, cldr, subtags, { force: true } ) || subtags;
  244. minBundle = coreRemoveLikelySubtags( Cldr, cldr, maxBundle );
  245. minBundle = minBundle.join( Cldr.localeSep );
  246. existing = availableBundleMapQueue[ minBundle ];
  247. if ( existing && existing.length < bundle.length ) {
  248. return;
  249. }
  250. availableBundleMap[ minBundle ] = bundle;
  251. });
  252. Cldr._availableBundleMapQueue = [];
  253. }
  254. return availableBundleMap[ minLanguageId ] || null;
  255. };
  256. var objectKeys = function( object ) {
  257. var i,
  258. result = [];
  259. if ( Object.keys ) {
  260. return Object.keys( object );
  261. }
  262. for ( i in object ) {
  263. result.push( i );
  264. }
  265. return result;
  266. };
  267. var createError = function( code, attributes ) {
  268. var error, message;
  269. message = code + ( attributes && JSON ? ": " + JSON.stringify( attributes ) : "" );
  270. error = new Error( message );
  271. error.code = code;
  272. // extend( error, attributes );
  273. arrayForEach( objectKeys( attributes ), function( attribute ) {
  274. error[ attribute ] = attributes[ attribute ];
  275. });
  276. return error;
  277. };
  278. var validate = function( code, check, attributes ) {
  279. if ( !check ) {
  280. throw createError( code, attributes );
  281. }
  282. };
  283. var validatePresence = function( value, name ) {
  284. validate( "E_MISSING_PARAMETER", typeof value !== "undefined", {
  285. name: name
  286. });
  287. };
  288. var validateType = function( value, name, check, expected ) {
  289. validate( "E_INVALID_PAR_TYPE", check, {
  290. expected: expected,
  291. name: name,
  292. value: value
  293. });
  294. };
  295. var validateTypePath = function( value, name ) {
  296. validateType( value, name, typeof value === "string" || arrayIsArray( value ), "String or Array" );
  297. };
  298. /**
  299. * Function inspired by jQuery Core, but reduced to our use case.
  300. */
  301. var isPlainObject = function( obj ) {
  302. return obj !== null && "" + obj === "[object Object]";
  303. };
  304. var validateTypePlainObject = function( value, name ) {
  305. validateType( value, name, typeof value === "undefined" || isPlainObject( value ), "Plain Object" );
  306. };
  307. var validateTypeString = function( value, name ) {
  308. validateType( value, name, typeof value === "string", "a string" );
  309. };
  310. // @path: normalized path
  311. var resourceGet = function( data, path ) {
  312. var i,
  313. node = data,
  314. length = path.length;
  315. for ( i = 0; i < length - 1; i++ ) {
  316. node = node[ path[ i ] ];
  317. if ( !node ) {
  318. return undefined;
  319. }
  320. }
  321. return node[ path[ i ] ];
  322. };
  323. /**
  324. * setAvailableBundles( Cldr, json )
  325. *
  326. * @Cldr [Cldr class]
  327. *
  328. * @json resolved/unresolved cldr data.
  329. *
  330. * Set available bundles queue based on passed json CLDR data. Considers a bundle as any String at /main/{bundle}.
  331. */
  332. var coreSetAvailableBundles = function( Cldr, json ) {
  333. var bundle,
  334. availableBundleMapQueue = Cldr._availableBundleMapQueue,
  335. main = resourceGet( json, [ "main" ] );
  336. if ( main ) {
  337. for ( bundle in main ) {
  338. if ( main.hasOwnProperty( bundle ) && bundle !== "root" ) {
  339. availableBundleMapQueue.push( bundle );
  340. }
  341. }
  342. }
  343. };
  344. var alwaysArray = function( somethingOrArray ) {
  345. return arrayIsArray( somethingOrArray ) ? somethingOrArray : [ somethingOrArray ];
  346. };
  347. var jsonMerge = (function() {
  348. // Returns new deeply merged JSON.
  349. //
  350. // Eg.
  351. // merge( { a: { b: 1, c: 2 } }, { a: { b: 3, d: 4 } } )
  352. // -> { a: { b: 3, c: 2, d: 4 } }
  353. //
  354. // @arguments JSON's
  355. //
  356. var merge = function() {
  357. var destination = {},
  358. sources = [].slice.call( arguments, 0 );
  359. arrayForEach( sources, function( source ) {
  360. var prop;
  361. for ( prop in source ) {
  362. if ( prop in destination && typeof destination[ prop ] === "object" && !arrayIsArray( destination[ prop ] ) ) {
  363. // Merge Objects
  364. destination[ prop ] = merge( destination[ prop ], source[ prop ] );
  365. } else {
  366. // Set new values
  367. destination[ prop ] = source[ prop ];
  368. }
  369. }
  370. });
  371. return destination;
  372. };
  373. return merge;
  374. }());
  375. /**
  376. * load( Cldr, source, jsons )
  377. *
  378. * @Cldr [Cldr class]
  379. *
  380. * @source [Object]
  381. *
  382. * @jsons [arguments]
  383. */
  384. var coreLoad = function( Cldr, source, jsons ) {
  385. var i, j, json;
  386. validatePresence( jsons[ 0 ], "json" );
  387. // Support arbitrary parameters, e.g., `Cldr.load({...}, {...})`.
  388. for ( i = 0; i < jsons.length; i++ ) {
  389. // Support array parameters, e.g., `Cldr.load([{...}, {...}])`.
  390. json = alwaysArray( jsons[ i ] );
  391. for ( j = 0; j < json.length; j++ ) {
  392. validateTypePlainObject( json[ j ], "json" );
  393. source = jsonMerge( source, json[ j ] );
  394. coreSetAvailableBundles( Cldr, json[ j ] );
  395. }
  396. }
  397. return source;
  398. };
  399. var itemGetResolved = function( Cldr, path, attributes ) {
  400. // Resolve path
  401. var normalizedPath = pathNormalize( path, attributes );
  402. return resourceGet( Cldr._resolved, normalizedPath );
  403. };
  404. /**
  405. * new Cldr()
  406. */
  407. var Cldr = function( locale ) {
  408. this.init( locale );
  409. };
  410. // Build optimization hack to avoid duplicating functions across modules.
  411. Cldr._alwaysArray = alwaysArray;
  412. Cldr._coreLoad = coreLoad;
  413. Cldr._createError = createError;
  414. Cldr._itemGetResolved = itemGetResolved;
  415. Cldr._jsonMerge = jsonMerge;
  416. Cldr._pathNormalize = pathNormalize;
  417. Cldr._resourceGet = resourceGet;
  418. Cldr._validatePresence = validatePresence;
  419. Cldr._validateType = validateType;
  420. Cldr._validateTypePath = validateTypePath;
  421. Cldr._validateTypePlainObject = validateTypePlainObject;
  422. Cldr._availableBundleMap = {};
  423. Cldr._availableBundleMapQueue = [];
  424. Cldr._resolved = {};
  425. // Allow user to override locale separator "-" (default) | "_". According to http://www.unicode.org/reports/tr35/#Unicode_language_identifier, both "-" and "_" are valid locale separators (eg. "en_GB", "en-GB"). According to http://unicode.org/cldr/trac/ticket/6786 its usage must be consistent throughout the data set.
  426. Cldr.localeSep = "-";
  427. /**
  428. * Cldr.load( json [, json, ...] )
  429. *
  430. * @json [JSON] CLDR data or [Array] Array of @json's.
  431. *
  432. * Load resolved cldr data.
  433. */
  434. Cldr.load = function() {
  435. Cldr._resolved = coreLoad( Cldr, Cldr._resolved, arguments );
  436. };
  437. /**
  438. * .init() automatically run on instantiation/construction.
  439. */
  440. Cldr.prototype.init = function( locale ) {
  441. var attributes, language, maxLanguageId, minLanguageId, script, subtags, territory, unicodeLocaleExtensions, variant,
  442. sep = Cldr.localeSep;
  443. validatePresence( locale, "locale" );
  444. validateTypeString( locale, "locale" );
  445. subtags = coreSubtags( locale );
  446. unicodeLocaleExtensions = subtags[ 4 ];
  447. variant = subtags[ 3 ];
  448. // Normalize locale code.
  449. // Get (or deduce) the "triple subtags": language, territory (also aliased as region), and script subtags.
  450. // Get the variant subtags (calendar, collation, currency, etc).
  451. // refs:
  452. // - http://www.unicode.org/reports/tr35/#Field_Definitions
  453. // - http://www.unicode.org/reports/tr35/#Language_and_Locale_IDs
  454. // - http://www.unicode.org/reports/tr35/#Unicode_locale_identifier
  455. // When a locale id does not specify a language, or territory (region), or script, they are obtained by Likely Subtags.
  456. maxLanguageId = coreLikelySubtags( Cldr, this, subtags, { force: true } ) || subtags;
  457. language = maxLanguageId[ 0 ];
  458. script = maxLanguageId[ 1 ];
  459. territory = maxLanguageId[ 2 ];
  460. minLanguageId = coreRemoveLikelySubtags( Cldr, this, maxLanguageId ).join( sep );
  461. // Set attributes
  462. this.attributes = attributes = {
  463. bundle: bundleLookup( Cldr, this, minLanguageId ),
  464. // Unicode Language Id
  465. minlanguageId: minLanguageId,
  466. maxLanguageId: maxLanguageId.join( sep ),
  467. // Unicode Language Id Subtabs
  468. language: language,
  469. script: script,
  470. territory: territory,
  471. region: territory, /* alias */
  472. variant: variant
  473. };
  474. // Unicode locale extensions.
  475. unicodeLocaleExtensions && ( "-" + unicodeLocaleExtensions ).replace( /-[a-z]{3,8}|(-[a-z]{2})-([a-z]{3,8})/g, function( attribute, key, type ) {
  476. if ( key ) {
  477. // Extension is in the `keyword` form.
  478. attributes[ "u" + key ] = type;
  479. } else {
  480. // Extension is in the `attribute` form.
  481. attributes[ "u" + attribute ] = true;
  482. }
  483. });
  484. this.locale = locale;
  485. };
  486. /**
  487. * .get()
  488. */
  489. Cldr.prototype.get = function( path ) {
  490. validatePresence( path, "path" );
  491. validateTypePath( path, "path" );
  492. return itemGetResolved( Cldr, path, this.attributes );
  493. };
  494. /**
  495. * .main()
  496. */
  497. Cldr.prototype.main = function( path ) {
  498. validatePresence( path, "path" );
  499. validateTypePath( path, "path" );
  500. validate( "E_MISSING_BUNDLE", this.attributes.bundle !== null, {
  501. locale: this.locale
  502. });
  503. path = alwaysArray( path );
  504. return this.get( [ "main/{bundle}" ].concat( path ) );
  505. };
  506. return Cldr;
  507. }));