i18n.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516
  1. define("dojo/i18n", ["./_base/kernel", "require", "./has", "./_base/array", "./_base/config", "./_base/lang", "./_base/xhr", "./json"],
  2. function(dojo, require, has, array, config, lang, xhr, json) {
  3. // module:
  4. // dojo/i18n
  5. // summary:
  6. // This module implements the !dojo/i18n plugin and the v1.6- i18n API
  7. // description:
  8. // We choose to include our own plugin to leverage functionality already contained in dojo
  9. // and thereby reduce the size of the plugin compared to various loader implementations. Also, this
  10. // allows foreign AMD loaders to be used without their plugins.
  11. has.add("dojo-preload-i18n-Api",
  12. // if true, define the preload localizations machinery
  13. 1
  14. );
  15. true || has.add("dojo-v1x-i18n-Api",
  16. // if true, define the v1.x i18n functions
  17. 1
  18. );
  19. var
  20. thisModule= dojo.i18n=
  21. // the dojo.i18n module
  22. {},
  23. nlsRe=
  24. // regexp for reconstructing the master bundle name from parts of the regexp match
  25. // nlsRe.exec("foo/bar/baz/nls/en-ca/foo") gives:
  26. // ["foo/bar/baz/nls/en-ca/foo", "foo/bar/baz/nls/", "/", "/", "en-ca", "foo"]
  27. // nlsRe.exec("foo/bar/baz/nls/foo") gives:
  28. // ["foo/bar/baz/nls/foo", "foo/bar/baz/nls/", "/", "/", "foo", ""]
  29. // so, if match[5] is blank, it means this is the top bundle definition.
  30. // courtesy of http://requirejs.org
  31. /(^.*(^|\/)nls)(\/|$)([^\/]*)\/?([^\/]*)/,
  32. getAvailableLocales= function(
  33. root,
  34. locale,
  35. bundlePath,
  36. bundleName
  37. ){
  38. // return a vector of module ids containing all available locales with respect to the target locale
  39. // For example, assuming:
  40. // * the root bundle indicates specific bundles for "fr" and "fr-ca",
  41. // * bundlePath is "myPackage/nls"
  42. // * bundleName is "myBundle"
  43. // Then a locale argument of "fr-ca" would return
  44. // ["myPackage/nls/myBundle", "myPackage/nls/fr/myBundle", "myPackage/nls/fr-ca/myBundle"]
  45. // Notice that bundles are returned least-specific to most-specific, starting with the root.
  46. //
  47. // If root===false indicates we're working with a pre-AMD i18n bundle that doesn't tell about the available locales;
  48. // therefore, assume everything is available and get 404 errors that indicate a particular localization is not available
  49. //
  50. for(var result= [bundlePath + bundleName], localeParts= locale.split("-"), current= "", i= 0; i<localeParts.length; i++){
  51. current+= (current ? "-" : "") + localeParts[i];
  52. if(!root || root[current]){
  53. result.push(bundlePath + current + "/" + bundleName);
  54. }
  55. }
  56. return result;
  57. },
  58. cache= {},
  59. getL10nName= dojo.getL10nName = function(moduleName, bundleName, locale){
  60. locale = locale ? locale.toLowerCase() : dojo.locale;
  61. moduleName = "dojo/i18n!" + moduleName.replace(/\./g, "/");
  62. bundleName = bundleName.replace(/\./g, "/");
  63. return (/root/i.test(locale)) ?
  64. (moduleName + "/nls/" + bundleName) :
  65. (moduleName + "/nls/" + locale + "/" + bundleName);
  66. },
  67. doLoad = function(require, bundlePathAndName, bundlePath, bundleName, locale, load){
  68. // get the root bundle which instructs which other bundles are required to construct the localized bundle
  69. require([bundlePathAndName], function(root){
  70. var current = lang.clone(root.root || root.ROOT),// 1.6 built bundle defined ROOT
  71. availableLocales= getAvailableLocales(!root._v1x && root, locale, bundlePath, bundleName);
  72. require(availableLocales, function(){
  73. for (var i= 1; i<availableLocales.length; i++){
  74. current= lang.mixin(lang.clone(current), arguments[i]);
  75. }
  76. // target may not have been resolve (e.g., maybe only "fr" exists when "fr-ca" was requested)
  77. var target= bundlePathAndName + "/" + locale;
  78. cache[target]= current;
  79. load();
  80. });
  81. });
  82. },
  83. normalize = function(id, toAbsMid){
  84. // id may be relative
  85. // preload has form *preload*<path>/nls/<module>*<flattened locales> and
  86. // therefore never looks like a relative
  87. return /^\./.test(id) ? toAbsMid(id) : id;
  88. },
  89. getLocalesToLoad = function(targetLocale){
  90. var list = config.extraLocale || [];
  91. list = lang.isArray(list) ? list : [list];
  92. list.push(targetLocale);
  93. return list;
  94. },
  95. load = function(id, require, load){
  96. //
  97. // id is in one of the following formats
  98. //
  99. // 1. <path>/nls/<bundle>
  100. // => load the bundle, localized to config.locale; load all bundles localized to
  101. // config.extraLocale (if any); return the loaded bundle localized to config.locale.
  102. //
  103. // 2. <path>/nls/<locale>/<bundle>
  104. // => load then return the bundle localized to <locale>
  105. //
  106. // 3. *preload*<path>/nls/<module>*<JSON array of available locales>
  107. // => for config.locale and all config.extraLocale, load all bundles found
  108. // in the best-matching bundle rollup. A value of 1 is returned, which
  109. // is meaningless other than to say the plugin is executing the requested
  110. // preloads
  111. //
  112. // In cases 1 and 2, <path> is always normalized to an absolute module id upon entry; see
  113. // normalize. In case 3, it <path> is assumed to be absolue; this is arranged by the builder.
  114. //
  115. // To load a bundle means to insert the bundle into the plugin's cache and publish the bundle
  116. // value to the loader. Given <path>, <bundle>, and a particular <locale>, the cache key
  117. //
  118. // <path>/nls/<bundle>/<locale>
  119. //
  120. // will hold the value. Similarly, then plugin will publish this value to the loader by
  121. //
  122. // define("<path>/nls/<bundle>/<locale>", <bundle-value>);
  123. //
  124. // Given this algorithm, other machinery can provide fast load paths be preplacing
  125. // values in the plugin's cache, which is public. When a load is demanded the
  126. // cache is inspected before starting any loading. Explicitly placing values in the plugin
  127. // cache is an advanced/experimental feature that should not be needed; use at your own risk.
  128. //
  129. // For the normal AMD algorithm, the root bundle is loaded first, which instructs the
  130. // plugin what additional localized bundles are required for a particular locale. These
  131. // additional locales are loaded and a mix of the root and each progressively-specific
  132. // locale is returned. For example:
  133. //
  134. // 1. The client demands "dojo/i18n!some/path/nls/someBundle
  135. //
  136. // 2. The loader demands load(some/path/nls/someBundle)
  137. //
  138. // 3. This plugin require's "some/path/nls/someBundle", which is the root bundle.
  139. //
  140. // 4. Assuming config.locale is "ab-cd-ef" and the root bundle indicates that localizations
  141. // are available for "ab" and "ab-cd-ef" (note the missing "ab-cd", then the plugin
  142. // requires "some/path/nls/ab/someBundle" and "some/path/nls/ab-cd-ef/someBundle"
  143. //
  144. // 5. Upon receiving all required bundles, the plugin constructs the value of the bundle
  145. // ab-cd-ef as...
  146. //
  147. // mixin(mixin(mixin({}, require("some/path/nls/someBundle"),
  148. // require("some/path/nls/ab/someBundle")),
  149. // require("some/path/nls/ab-cd-ef/someBundle"));
  150. //
  151. // This value is inserted into the cache and published to the loader at the
  152. // key/module-id some/path/nls/someBundle/ab-cd-ef.
  153. //
  154. // The special preload signature (case 3) instructs the plugin to stop servicing all normal requests
  155. // (further preload requests will be serviced) until all ongoing preloading has completed.
  156. //
  157. // The preload signature instructs the plugin that a special rollup module is available that contains
  158. // one or more flattened, localized bundles. The JSON array of available locales indicates which locales
  159. // are available. Here is an example:
  160. //
  161. // *preload*some/path/nls/someModule*["root", "ab", "ab-cd-ef"]
  162. //
  163. // This indicates the following rollup modules are available:
  164. //
  165. // some/path/nls/someModule_ROOT
  166. // some/path/nls/someModule_ab
  167. // some/path/nls/someModule_ab-cd-ef
  168. //
  169. // Each of these modules is a normal AMD module that contains one or more flattened bundles in a hash.
  170. // For example, assume someModule contained the bundles some/bundle/path/someBundle and
  171. // some/bundle/path/someOtherBundle, then some/path/nls/someModule_ab would be expressed as folllows:
  172. //
  173. // define({
  174. // some/bundle/path/someBundle:<value of someBundle, flattened with respect to locale ab>,
  175. // some/bundle/path/someOtherBundle:<value of someOtherBundle, flattened with respect to locale ab>,
  176. // });
  177. //
  178. // E.g., given this design, preloading for locale=="ab" can execute the following algorithm:
  179. //
  180. // require(["some/path/nls/someModule_ab"], function(rollup){
  181. // for(var p in rollup){
  182. // var id = p + "/ab",
  183. // cache[id] = rollup[p];
  184. // define(id, rollup[p]);
  185. // }
  186. // });
  187. //
  188. // Similarly, if "ab-cd" is requested, the algorithm can determine that "ab" is the best available and
  189. // load accordingly.
  190. //
  191. // The builder will write such rollups for every layer if a non-empty localeList profile property is
  192. // provided. Further, the builder will include the following cache entry in the cache associated with
  193. // any layer.
  194. //
  195. // "*now":function(r){r(['dojo/i18n!*preload*<path>/nls/<module>*<JSON array of available locales>']);}
  196. //
  197. // The *now special cache module instructs the loader to apply the provided function to context-require
  198. // with respect to the particular layer being defined. This causes the plugin to hold all normal service
  199. // requests until all preloading is complete.
  200. //
  201. // Notice that this algorithm is rarely better than the standard AMD load algorithm. Consider the normal case
  202. // where the target locale has a single segment and a layer depends on a single bundle:
  203. //
  204. // Without Preloads:
  205. //
  206. // 1. Layer loads root bundle.
  207. // 2. bundle is demanded; plugin loads single localized bundle.
  208. //
  209. // With Preloads:
  210. //
  211. // 1. Layer causes preloading of target bundle.
  212. // 2. bundle is demanded; service is delayed until preloading complete; bundle is returned.
  213. //
  214. // In each case a single transaction is required to load the target bundle. In cases where multiple bundles
  215. // are required and/or the locale has multiple segments, preloads still requires a single transaction whereas
  216. // the normal path requires an additional transaction for each additional bundle/locale-segment. However all
  217. // of these additional transactions can be done concurrently. Owing to this analysis, the entire preloading
  218. // algorithm can be discard during a build by setting the has feature dojo-preload-i18n-Api to false.
  219. //
  220. if(has("dojo-preload-i18n-Api")){
  221. var split = id.split("*"),
  222. preloadDemand = split[1]=="preload";
  223. if(preloadDemand){
  224. if(!cache[id]){
  225. // use cache[id] to prevent multiple preloads of the same preload; this shouldn't happen, but
  226. // who knows what over-aggressive human optimizers may attempt
  227. cache[id] = 1;
  228. preloadL10n(split[2], json.parse(split[3]), 1);
  229. }
  230. // don't stall the loader!
  231. load(1);
  232. }
  233. if(preloadDemand || waitForPreloads(id, require, load)){
  234. return;
  235. }
  236. }
  237. var match= nlsRe.exec(id),
  238. bundlePath= match[1] + "/",
  239. bundleName= match[5] || match[4],
  240. bundlePathAndName= bundlePath + bundleName,
  241. localeSpecified = (match[5] && match[4]),
  242. targetLocale= localeSpecified || dojo.locale,
  243. loadTarget= bundlePathAndName + "/" + targetLocale,
  244. loadList = localeSpecified ? [targetLocale] : getLocalesToLoad(targetLocale),
  245. remaining = loadList.length,
  246. finish = function(){
  247. if(!--remaining){
  248. load(lang.delegate(cache[loadTarget]));
  249. }
  250. };
  251. array.forEach(loadList, function(locale){
  252. var target = bundlePathAndName + "/" + locale;
  253. if(has("dojo-preload-i18n-Api")){
  254. checkForLegacyModules(target);
  255. }
  256. if(!cache[target]){
  257. doLoad(require, bundlePathAndName, bundlePath, bundleName, locale, finish);
  258. }else{
  259. finish();
  260. }
  261. });
  262. };
  263. if(has("dojo-unit-tests")){
  264. var unitTests = thisModule.unitTests = [];
  265. }
  266. if(has("dojo-preload-i18n-Api") || 1){
  267. var normalizeLocale = thisModule.normalizeLocale= function(locale){
  268. var result = locale ? locale.toLowerCase() : dojo.locale;
  269. return result == "root" ? "ROOT" : result;
  270. },
  271. isXd = function(mid){
  272. return (1 && 1) ?
  273. require.isXdUrl(require.toUrl(mid + ".js")) :
  274. true;
  275. },
  276. preloading = 0,
  277. preloadWaitQueue = [],
  278. preloadL10n = thisModule._preloadLocalizations = function(/*String*/bundlePrefix, /*Array*/localesGenerated, /*boolean*/ guaranteedAmdFormat){
  279. // summary:
  280. // Load available flattened resource bundles associated with a particular module for dojo.locale and all dojo.config.extraLocale (if any)
  281. //
  282. // descirption:
  283. // Only called by built layer files. The entire locale hierarchy is loaded. For example,
  284. // if locale=="ab-cd", then ROOT, "ab", and "ab-cd" are loaded. This is different than v1.6-
  285. // in that the v1.6- would lonly load ab-cd...which was *always* flattened.
  286. //
  287. // If guaranteedAmdFormat is true, then the module can be loaded with require thereby circumventing the detection algorithm
  288. // and the extra possible extra transaction.
  289. //
  290. function forEachLocale(locale, func){
  291. // given locale= "ab-cd-ef", calls func on "ab-cd-ef", "ab-cd", "ab", "ROOT"; stops calling the first time func returns truthy
  292. var parts = locale.split("-");
  293. while(parts.length){
  294. if(func(parts.join("-"))){
  295. return true;
  296. }
  297. parts.pop();
  298. }
  299. return func("ROOT");
  300. }
  301. function preload(locale){
  302. locale = normalizeLocale(locale);
  303. forEachLocale(locale, function(loc){
  304. if(array.indexOf(localesGenerated, loc)>=0){
  305. var mid = bundlePrefix.replace(/\./g, "/")+"_"+loc;
  306. preloading++;
  307. (isXd(mid) || guaranteedAmdFormat ? require : syncRequire)([mid], function(rollup){
  308. for(var p in rollup){
  309. cache[p + "/" + loc] = rollup[p];
  310. }
  311. --preloading;
  312. while(!preloading && preloadWaitQueue.length){
  313. load.apply(null, preloadWaitQueue.shift());
  314. }
  315. });
  316. return true;
  317. }
  318. return false;
  319. });
  320. }
  321. preload();
  322. array.forEach(dojo.config.extraLocale, preload);
  323. },
  324. waitForPreloads = function(id, require, load){
  325. if(preloading){
  326. preloadWaitQueue.push([id, require, load]);
  327. }
  328. return preloading;
  329. };
  330. }
  331. if(1){
  332. // this code path assumes the dojo loader and won't work with a standard AMD loader
  333. var evalBundle=
  334. // use the function ctor to keep the minifiers away (also come close to global scope, but this is secondary)
  335. new Function(
  336. "__bundle", // the bundle to evalutate
  337. "__checkForLegacyModules", // a function that checks if __bundle defined __mid in the global space
  338. "__mid", // the mid that __bundle is intended to define
  339. // returns one of:
  340. // 1 => the bundle was an AMD bundle
  341. // a legacy bundle object that is the value of __mid
  342. // instance of Error => could not figure out how to evaluate bundle
  343. // used to detect when __bundle calls define
  344. "var define = function(){define.called = 1;},"
  345. + " require = function(){define.called = 1;};"
  346. + "try{"
  347. + "define.called = 0;"
  348. + "eval(__bundle);"
  349. + "if(define.called==1)"
  350. // bundle called define; therefore signal it's an AMD bundle
  351. + "return 1;"
  352. + "if((__checkForLegacyModules = __checkForLegacyModules(__mid)))"
  353. // bundle was probably a v1.6- built NLS flattened NLS bundle that defined __mid in the global space
  354. + "return __checkForLegacyModules;"
  355. + "}catch(e){}"
  356. // evaulating the bundle was *neither* an AMD *nor* a legacy flattened bundle
  357. // either way, re-eval *after* surrounding with parentheses
  358. + "try{"
  359. + "return eval('('+__bundle+')');"
  360. + "}catch(e){"
  361. + "return e;"
  362. + "}"
  363. ),
  364. syncRequire= function(deps, callback){
  365. var results= [];
  366. array.forEach(deps, function(mid){
  367. var url= require.toUrl(mid + ".js");
  368. function load(text){
  369. var result = evalBundle(text, checkForLegacyModules, mid);
  370. if(result===1){
  371. // the bundle was an AMD module; re-inject it through the normal AMD path
  372. // we gotta do this since it could be an anonymous module and simply evaluating
  373. // the text here won't provide the loader with the context to know what
  374. // module is being defined()'d. With browser caching, this should be free; further
  375. // this entire code path can be circumvented by using the AMD format to begin with
  376. require([mid], function(bundle){
  377. results.push(cache[url]= bundle);
  378. });
  379. }else{
  380. if(result instanceof Error){
  381. console.error("failed to evaluate i18n bundle; url=" + url, result);
  382. result = {};
  383. }
  384. // nls/<locale>/<bundle-name> indicates not the root.
  385. results.push(cache[url] = (/nls\/[^\/]+\/[^\/]+$/.test(url) ? result : {root:result, _v1x:1}));
  386. }
  387. }
  388. if(cache[url]){
  389. results.push(cache[url]);
  390. }else{
  391. var bundle = require.syncLoadNls(mid);
  392. // need to check for legacy module here because there might be a legacy module for a
  393. // less specific locale (which was not looked up during the first checkForLegacyModules
  394. // call in load()).
  395. // Also need to reverse the locale and the module name in the mid because syncRequire
  396. // deps parameters uses the AMD style package/nls/locale/module while legacy code uses
  397. // package/nls/module/locale.
  398. if(!bundle){
  399. bundle = checkForLegacyModules(mid.replace(/nls\/([^\/]*)\/([^\/]*)$/, "nls/$2/$1"));
  400. }
  401. if(bundle){
  402. results.push(bundle);
  403. }else{
  404. if(!xhr){
  405. try{
  406. require.getText(url, true, load);
  407. }catch(e){
  408. results.push(cache[url]= {});
  409. }
  410. }else{
  411. xhr.get({
  412. url:url,
  413. sync:true,
  414. load:load,
  415. error:function(){
  416. results.push(cache[url]= {});
  417. }
  418. });
  419. }
  420. }
  421. }
  422. });
  423. callback && callback.apply(null, results);
  424. },
  425. checkForLegacyModules = function(target){
  426. // legacy code may have already loaded [e.g] the raw bundle x/y/z at x.y.z; when true, push into the cache
  427. for(var result, names = target.split("/"), object = dojo.global[names[0]], i = 1; object && i<names.length-1; object = object[names[i++]]){}
  428. if(object){
  429. result = object[names[i]];
  430. if(!result){
  431. // fallback for incorrect bundle build of 1.6
  432. result = object[names[i].replace(/-/g,"_")];
  433. }
  434. if(result){
  435. cache[target] = result;
  436. }
  437. }
  438. return result;
  439. };
  440. thisModule.getLocalization= function(moduleName, bundleName, locale){
  441. var result,
  442. l10nName= getL10nName(moduleName, bundleName, locale).substring(10);
  443. load(l10nName, (!isXd(l10nName) ? syncRequire : require), function(result_){ result= result_; });
  444. return result;
  445. };
  446. if(has("dojo-unit-tests")){
  447. unitTests.push(function(doh){
  448. doh.register("tests.i18n.unit", function(t){
  449. var check;
  450. check = evalBundle("{prop:1}");
  451. t.is({prop:1}, check); t.is(undefined, check[1]);
  452. check = evalBundle("({prop:1})");
  453. t.is({prop:1}, check); t.is(undefined, check[1]);
  454. check = evalBundle("{'prop-x':1}");
  455. t.is({'prop-x':1}, check); t.is(undefined, check[1]);
  456. check = evalBundle("({'prop-x':1})");
  457. t.is({'prop-x':1}, check); t.is(undefined, check[1]);
  458. check = evalBundle("define({'prop-x':1})");
  459. t.is(1, check);
  460. check = evalBundle("this is total nonsense and should throw an error");
  461. t.is(check instanceof Error, true);
  462. });
  463. });
  464. }
  465. }
  466. return lang.mixin(thisModule, {
  467. dynamic:true,
  468. normalize:normalize,
  469. load:load,
  470. cache:cache
  471. });
  472. });