PageRenderTime 52ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/files/i18next/1.6.0/i18next-1.6.0.js

https://gitlab.com/Mirros/jsdelivr
JavaScript | 1426 lines | 921 code | 126 blank | 379 comment | 236 complexity | 7797d8d8a63740443ca95ff8495ec0ce MD5 | raw file
  1. // i18next, v1.6.0
  2. // Copyright (c)2013 Jan Mühlemann (jamuhl).
  3. // Distributed under MIT license
  4. // http://i18next.com
  5. (function() {
  6. // add indexOf to non ECMA-262 standard compliant browsers
  7. if (!Array.prototype.indexOf) {
  8. Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
  9. "use strict";
  10. if (this == null) {
  11. throw new TypeError();
  12. }
  13. var t = Object(this);
  14. var len = t.length >>> 0;
  15. if (len === 0) {
  16. return -1;
  17. }
  18. var n = 0;
  19. if (arguments.length > 0) {
  20. n = Number(arguments[1]);
  21. if (n != n) { // shortcut for verifying if it's NaN
  22. n = 0;
  23. } else if (n != 0 && n != Infinity && n != -Infinity) {
  24. n = (n > 0 || -1) * Math.floor(Math.abs(n));
  25. }
  26. }
  27. if (n >= len) {
  28. return -1;
  29. }
  30. var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
  31. for (; k < len; k++) {
  32. if (k in t && t[k] === searchElement) {
  33. return k;
  34. }
  35. }
  36. return -1;
  37. }
  38. }
  39. var root = this
  40. , $ = root.jQuery
  41. , i18n = {}
  42. , resStore = {}
  43. , currentLng
  44. , replacementCounter = 0
  45. , languages = [];
  46. // Export the i18next object for **CommonJS**.
  47. // If we're not in CommonJS, add `i18n` to the
  48. // global object or to jquery.
  49. if (typeof module !== 'undefined' && module.exports) {
  50. module.exports = i18n;
  51. } else {
  52. if ($) {
  53. $.i18n = $.i18n || i18n;
  54. }
  55. root.i18n = root.i18n || i18n;
  56. }
  57. // defaults
  58. var o = {
  59. lng: undefined,
  60. load: 'all',
  61. preload: [],
  62. lowerCaseLng: false,
  63. returnObjectTrees: false,
  64. fallbackLng: 'dev',
  65. detectLngQS: 'setLng',
  66. ns: 'translation',
  67. fallbackToDefaultNS: false,
  68. nsseparator: ':',
  69. keyseparator: '.',
  70. selectorAttr: 'data-i18n',
  71. debug: false,
  72. resGetPath: 'locales/__lng__/__ns__.json',
  73. resPostPath: 'locales/add/__lng__/__ns__',
  74. getAsync: true,
  75. postAsync: true,
  76. resStore: undefined,
  77. useLocalStorage: false,
  78. localStorageExpirationTime: 7*24*60*60*1000,
  79. dynamicLoad: false,
  80. sendMissing: false,
  81. sendMissingTo: 'fallback', // current | all
  82. sendType: 'POST',
  83. interpolationPrefix: '__',
  84. interpolationSuffix: '__',
  85. reusePrefix: '$t(',
  86. reuseSuffix: ')',
  87. pluralSuffix: '_plural',
  88. pluralNotFound: ['plural_not_found', Math.random()].join(''),
  89. contextNotFound: ['context_not_found', Math.random()].join(''),
  90. setJqueryExt: true,
  91. defaultValueFromContent: true,
  92. useDataAttrOptions: false,
  93. cookieExpirationTime: undefined,
  94. useCookie: true,
  95. cookieName: 'i18next',
  96. postProcess: undefined
  97. };
  98. function _extend(target, source) {
  99. if (!source || typeof source === 'function') {
  100. return target;
  101. }
  102. for (var attr in source) { target[attr] = source[attr]; }
  103. return target;
  104. }
  105. function _each(object, callback, args) {
  106. var name, i = 0,
  107. length = object.length,
  108. isObj = length === undefined || typeof object === "function";
  109. if (args) {
  110. if (isObj) {
  111. for (name in object) {
  112. if (callback.apply(object[name], args) === false) {
  113. break;
  114. }
  115. }
  116. } else {
  117. for ( ; i < length; ) {
  118. if (callback.apply(object[i++], args) === false) {
  119. break;
  120. }
  121. }
  122. }
  123. // A special, fast, case for the most common use of each
  124. } else {
  125. if (isObj) {
  126. for (name in object) {
  127. if (callback.call(object[name], name, object[name]) === false) {
  128. break;
  129. }
  130. }
  131. } else {
  132. for ( ; i < length; ) {
  133. if (callback.call(object[i], i, object[i++]) === false) {
  134. break;
  135. }
  136. }
  137. }
  138. }
  139. return object;
  140. }
  141. function _ajax(options) {
  142. // v0.5.0 of https://github.com/goloroden/http.js
  143. var getXhr = function (callback) {
  144. // Use the native XHR object if the browser supports it.
  145. if (window.XMLHttpRequest) {
  146. return callback(null, new XMLHttpRequest());
  147. } else if (window.ActiveXObject) {
  148. // In Internet Explorer check for ActiveX versions of the XHR object.
  149. try {
  150. return callback(null, new ActiveXObject("Msxml2.XMLHTTP"));
  151. } catch (e) {
  152. return callback(null, new ActiveXObject("Microsoft.XMLHTTP"));
  153. }
  154. }
  155. // If no XHR support was found, throw an error.
  156. return callback(new Error());
  157. };
  158. var encodeUsingUrlEncoding = function (data) {
  159. if(typeof data === 'string') {
  160. return data;
  161. }
  162. var result = [];
  163. for(var dataItem in data) {
  164. if(data.hasOwnProperty(dataItem)) {
  165. result.push(encodeURIComponent(dataItem) + '=' + encodeURIComponent(data[dataItem]));
  166. }
  167. }
  168. return result.join('&');
  169. };
  170. var utf8 = function (text) {
  171. text = text.replace(/\r\n/g, '\n');
  172. var result = '';
  173. for(var i = 0; i < text.length; i++) {
  174. var c = text.charCodeAt(i);
  175. if(c < 128) {
  176. result += String.fromCharCode(c);
  177. } else if((c > 127) && (c < 2048)) {
  178. result += String.fromCharCode((c >> 6) | 192);
  179. result += String.fromCharCode((c & 63) | 128);
  180. } else {
  181. result += String.fromCharCode((c >> 12) | 224);
  182. result += String.fromCharCode(((c >> 6) & 63) | 128);
  183. result += String.fromCharCode((c & 63) | 128);
  184. }
  185. }
  186. return result;
  187. };
  188. var base64 = function (text) {
  189. var keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
  190. text = utf8(text);
  191. var result = '',
  192. chr1, chr2, chr3,
  193. enc1, enc2, enc3, enc4,
  194. i = 0;
  195. do {
  196. chr1 = text.charCodeAt(i++);
  197. chr2 = text.charCodeAt(i++);
  198. chr3 = text.charCodeAt(i++);
  199. enc1 = chr1 >> 2;
  200. enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
  201. enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
  202. enc4 = chr3 & 63;
  203. if(isNaN(chr2)) {
  204. enc3 = enc4 = 64;
  205. } else if(isNaN(chr3)) {
  206. enc4 = 64;
  207. }
  208. result +=
  209. keyStr.charAt(enc1) +
  210. keyStr.charAt(enc2) +
  211. keyStr.charAt(enc3) +
  212. keyStr.charAt(enc4);
  213. chr1 = chr2 = chr3 = '';
  214. enc1 = enc2 = enc3 = enc4 = '';
  215. } while(i < text.length);
  216. return result;
  217. };
  218. var mergeHeaders = function () {
  219. // Use the first header object as base.
  220. var result = arguments[0];
  221. // Iterate through the remaining header objects and add them.
  222. for(var i = 1; i < arguments.length; i++) {
  223. var currentHeaders = arguments[i];
  224. for(var header in currentHeaders) {
  225. if(currentHeaders.hasOwnProperty(header)) {
  226. result[header] = currentHeaders[header];
  227. }
  228. }
  229. }
  230. // Return the merged headers.
  231. return result;
  232. };
  233. var ajax = function (method, url, options, callback) {
  234. // Adjust parameters.
  235. if(typeof options === 'function') {
  236. callback = options;
  237. options = {};
  238. }
  239. // Set default parameter values.
  240. options.cache = options.cache || false;
  241. options.data = options.data || {};
  242. options.headers = options.headers || {};
  243. options.jsonp = options.jsonp || false;
  244. options.async = options.async === undefined ? true : options.async;
  245. // Merge the various header objects.
  246. var headers = mergeHeaders({
  247. 'accept': '*/*',
  248. 'content-type': 'application/x-www-form-urlencoded;charset=UTF-8'
  249. }, ajax.headers, options.headers);
  250. // Encode the data according to the content-type.
  251. var payload;
  252. if (headers['content-type'] === 'application/json') {
  253. payload = JSON.stringify(options.data);
  254. } else {
  255. payload = encodeUsingUrlEncoding(options.data);
  256. }
  257. // Specially prepare GET requests: Setup the query string, handle caching and make a JSONP call
  258. // if neccessary.
  259. if(method === 'GET') {
  260. // Setup the query string.
  261. var queryString = [];
  262. if(payload) {
  263. queryString.push(payload);
  264. payload = null;
  265. }
  266. // Handle caching.
  267. if(!options.cache) {
  268. queryString.push('_=' + (new Date()).getTime());
  269. }
  270. // If neccessary prepare the query string for a JSONP call.
  271. if(options.jsonp) {
  272. queryString.push('callback=' + options.jsonp);
  273. queryString.push('jsonp=' + options.jsonp);
  274. }
  275. // Merge the query string and attach it to the url.
  276. queryString = queryString.join('&');
  277. if (queryString.length > 1) {
  278. if (url.indexOf('?') > -1) {
  279. url += '&' + queryString;
  280. } else {
  281. url += '?' + queryString;
  282. }
  283. }
  284. // Make a JSONP call if neccessary.
  285. if(options.jsonp) {
  286. var head = document.getElementsByTagName('head')[0];
  287. var script = document.createElement('script');
  288. script.type = 'text/javascript';
  289. script.src = url;
  290. head.appendChild(script);
  291. return;
  292. }
  293. }
  294. // Since we got here, it is no JSONP request, so make a normal XHR request.
  295. getXhr(function (err, xhr) {
  296. if(err) return callback(err);
  297. // Open the request.
  298. xhr.open(method, url, options.async);
  299. // Set the request headers.
  300. for(var header in headers) {
  301. if(headers.hasOwnProperty(header)) {
  302. xhr.setRequestHeader(header, headers[header]);
  303. }
  304. }
  305. // Handle the request events.
  306. xhr.onreadystatechange = function () {
  307. if(xhr.readyState === 4) {
  308. var data = xhr.responseText || '';
  309. // If no callback is given, return.
  310. if(!callback) {
  311. return;
  312. }
  313. // Return an object that provides access to the data as text and JSON.
  314. callback(xhr.status, {
  315. text: function () {
  316. return data;
  317. },
  318. json: function () {
  319. return JSON.parse(data);
  320. }
  321. });
  322. }
  323. };
  324. // Actually send the XHR request.
  325. xhr.send(payload);
  326. });
  327. };
  328. // Define the external interface.
  329. var http = {
  330. authBasic: function (username, password) {
  331. ajax.headers['Authorization'] = 'Basic ' + base64(username + ':' + password);
  332. },
  333. connect: function (url, options, callback) {
  334. return ajax('CONNECT', url, options, callback);
  335. },
  336. del: function (url, options, callback) {
  337. return ajax('DELETE', url, options, callback);
  338. },
  339. get: function (url, options, callback) {
  340. return ajax('GET', url, options, callback);
  341. },
  342. head: function (url, options, callback) {
  343. return ajax('HEAD', url, options, callback);
  344. },
  345. headers: function (headers) {
  346. ajax.headers = headers || {};
  347. },
  348. isAllowed: function (url, verb, callback) {
  349. this.options(url, function (status, data) {
  350. callback(data.text().indexOf(verb) !== -1);
  351. });
  352. },
  353. options: function (url, options, callback) {
  354. return ajax('OPTIONS', url, options, callback);
  355. },
  356. patch: function (url, options, callback) {
  357. return ajax('PATCH', url, options, callback);
  358. },
  359. post: function (url, options, callback) {
  360. return ajax('POST', url, options, callback);
  361. },
  362. put: function (url, options, callback) {
  363. return ajax('PUT', url, options, callback);
  364. },
  365. trace: function (url, options, callback) {
  366. return ajax('TRACE', url, options, callback);
  367. }
  368. };
  369. var methode = options.type ? options.type.toLowerCase() : 'get';
  370. http[methode](options.url, options, function (status, data) {
  371. if (status === 200) {
  372. options.success(data.json(), status, null);
  373. } else {
  374. options.error(data.text(), status, null);
  375. }
  376. });
  377. }
  378. var _cookie = {
  379. create: function(name,value,minutes) {
  380. var expires;
  381. if (minutes) {
  382. var date = new Date();
  383. date.setTime(date.getTime()+(minutes*60*1000));
  384. expires = "; expires="+date.toGMTString();
  385. }
  386. else expires = "";
  387. document.cookie = name+"="+value+expires+"; path=/";
  388. },
  389. read: function(name) {
  390. var nameEQ = name + "=";
  391. var ca = document.cookie.split(';');
  392. for(var i=0;i < ca.length;i++) {
  393. var c = ca[i];
  394. while (c.charAt(0)==' ') c = c.substring(1,c.length);
  395. if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length,c.length);
  396. }
  397. return null;
  398. },
  399. remove: function(name) {
  400. this.create(name,"",-1);
  401. }
  402. };
  403. var cookie_noop = {
  404. create: function(name,value,minutes) {},
  405. read: function(name) { return null; },
  406. remove: function(name) {}
  407. };
  408. // move dependent functions to a container so that
  409. // they can be overriden easier in no jquery environment (node.js)
  410. var f = {
  411. extend: $ ? $.extend : _extend,
  412. each: $ ? $.each : _each,
  413. ajax: $ ? $.ajax : _ajax,
  414. cookie: typeof document !== 'undefined' ? _cookie : cookie_noop,
  415. detectLanguage: detectLanguage,
  416. log: function(str) {
  417. if (o.debug && typeof console !== "undefined") console.log(str);
  418. },
  419. toLanguages: function(lng) {
  420. var languages = [];
  421. if (typeof lng === 'string' && lng.indexOf('-') > -1) {
  422. var parts = lng.split('-');
  423. lng = o.lowerCaseLng ?
  424. parts[0].toLowerCase() + '-' + parts[1].toLowerCase() :
  425. parts[0].toLowerCase() + '-' + parts[1].toUpperCase();
  426. if (o.load !== 'unspecific') languages.push(lng);
  427. if (o.load !== 'current') languages.push(parts[0]);
  428. } else {
  429. languages.push(lng);
  430. }
  431. if (languages.indexOf(o.fallbackLng) === -1 && o.fallbackLng) languages.push(o.fallbackLng);
  432. return languages;
  433. },
  434. regexEscape: function(str) {
  435. return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
  436. }
  437. };
  438. function init(options, cb) {
  439. if (typeof options === 'function') {
  440. cb = options;
  441. options = {};
  442. }
  443. options = options || {};
  444. // override defaults with passed in options
  445. f.extend(o, options);
  446. // create namespace object if namespace is passed in as string
  447. if (typeof o.ns == 'string') {
  448. o.ns = { namespaces: [o.ns], defaultNs: o.ns};
  449. }
  450. // escape prefix/suffix
  451. o.interpolationPrefixEscaped = f.regexEscape(o.interpolationPrefix);
  452. o.interpolationSuffixEscaped = f.regexEscape(o.interpolationSuffix);
  453. if (!o.lng) o.lng = f.detectLanguage();
  454. if (o.lng) {
  455. // set cookie with lng set (as detectLanguage will set cookie on need)
  456. if (o.useCookie) f.cookie.create(o.cookieName, o.lng, o.cookieExpirationTime);
  457. } else {
  458. o.lng = o.fallbackLng;
  459. if (o.useCookie) f.cookie.remove(o.cookieName);
  460. }
  461. languages = f.toLanguages(o.lng);
  462. currentLng = languages[0];
  463. f.log('currentLng set to: ' + currentLng);
  464. pluralExtensions.setCurrentLng(currentLng);
  465. // add JQuery extensions
  466. if ($ && o.setJqueryExt) addJqueryFunct();
  467. // jQuery deferred
  468. var deferred;
  469. if ($ && $.Deferred) {
  470. deferred = $.Deferred();
  471. }
  472. // return immidiatly if res are passed in
  473. if (o.resStore) {
  474. resStore = o.resStore;
  475. if (cb) cb(translate);
  476. if (deferred) deferred.resolve();
  477. if (deferred) return deferred.promise();
  478. return;
  479. }
  480. // languages to load
  481. var lngsToLoad = f.toLanguages(o.lng);
  482. if (typeof o.preload === 'string') o.preload = [o.preload];
  483. for (var i = 0, l = o.preload.length; i < l; i++) {
  484. var pres = f.toLanguages(o.preload[i]);
  485. for (var y = 0, len = pres.length; y < len; y++) {
  486. if (lngsToLoad.indexOf(pres[y]) < 0) {
  487. lngsToLoad.push(pres[y]);
  488. }
  489. }
  490. }
  491. // else load them
  492. i18n.sync.load(lngsToLoad, o, function(err, store) {
  493. resStore = store;
  494. if (cb) cb(translate);
  495. if (deferred) deferred.resolve();
  496. });
  497. if (deferred) return deferred.promise();
  498. }
  499. function preload(lngs, cb) {
  500. if (typeof lngs === 'string') lngs = [lngs];
  501. for (var i = 0, l = lngs.length; i < l; i++) {
  502. if (o.preload.indexOf(lngs[i]) < 0) {
  503. o.preload.push(lngs[i]);
  504. }
  505. }
  506. return init(cb);
  507. }
  508. function addResourceBundle(lng, ns, resources) {
  509. if (typeof ns !== 'string') {
  510. resources = ns;
  511. ns = o.ns.defaultNs;
  512. }
  513. resStore[lng] = resStore[lng] || {};
  514. resStore[lng][ns] = resStore[lng][ns] || {};
  515. f.extend(resStore[lng][ns], resources);
  516. }
  517. function setDefaultNamespace(ns) {
  518. o.ns.defaultNs = ns;
  519. }
  520. function loadNamespace(namespace, cb) {
  521. loadNamespaces([namespace], cb);
  522. }
  523. function loadNamespaces(namespaces, cb) {
  524. var opts = {
  525. dynamicLoad: o.dynamicLoad,
  526. resGetPath: o.resGetPath,
  527. getAsync: o.getAsync,
  528. ns: { namespaces: namespaces, defaultNs: ''} /* new namespaces to load */
  529. };
  530. // languages to load
  531. var lngsToLoad = f.toLanguages(o.lng);
  532. if (typeof o.preload === 'string') o.preload = [o.preload];
  533. for (var i = 0, l = o.preload.length; i < l; i++) {
  534. var pres = f.toLanguages(o.preload[i]);
  535. for (var y = 0, len = pres.length; y < len; y++) {
  536. if (lngsToLoad.indexOf(pres[y]) < 0) {
  537. lngsToLoad.push(pres[y]);
  538. }
  539. }
  540. }
  541. // check if we have to load
  542. var lngNeedLoad = [];
  543. for (var a = 0, lenA = lngsToLoad.length; a < lenA; a++) {
  544. var needLoad = false;
  545. var resSet = resStore[lngsToLoad[a]];
  546. if (resSet) {
  547. for (var b = 0, lenB = namespaces.length; b < lenB; b++) {
  548. if (!resSet[namespaces[b]]) needLoad = true;
  549. }
  550. } else {
  551. needLoad = true;
  552. }
  553. if (needLoad) lngNeedLoad.push(lngsToLoad[a]);
  554. }
  555. if (lngNeedLoad.length) {
  556. i18n.sync._fetch(lngNeedLoad, opts, function(err, store) {
  557. var todo = namespaces.length * lngNeedLoad.length;
  558. // load each file individual
  559. f.each(namespaces, function(nsIndex, nsValue) {
  560. f.each(lngNeedLoad, function(lngIndex, lngValue) {
  561. resStore[lngValue] = resStore[lngValue] || {};
  562. resStore[lngValue][nsValue] = store[lngValue][nsValue];
  563. todo--; // wait for all done befor callback
  564. if (todo === 0 && cb) {
  565. if (o.useLocalStorage) i18n.sync._storeLocal(resStore);
  566. cb();
  567. }
  568. });
  569. });
  570. });
  571. } else {
  572. if (cb) cb();
  573. }
  574. }
  575. function setLng(lng, cb) {
  576. return init({lng: lng}, cb);
  577. }
  578. function lng() {
  579. return currentLng;
  580. }
  581. function addJqueryFunct() {
  582. // $.t shortcut
  583. $.t = $.t || translate;
  584. function parse(ele, key, options) {
  585. if (key.length === 0) return;
  586. var attr = 'text';
  587. if (key.indexOf('[') === 0) {
  588. var parts = key.split(']');
  589. key = parts[1];
  590. attr = parts[0].substr(1, parts[0].length-1);
  591. }
  592. if (key.indexOf(';') === key.length-1) {
  593. key = key.substr(0, key.length-2);
  594. }
  595. var optionsToUse;
  596. if (attr === 'html') {
  597. optionsToUse = o.defaultValueFromContent ? $.extend({ defaultValue: ele.html() }, options) : options;
  598. ele.html($.t(key, optionsToUse));
  599. }
  600. else if (attr === 'text') {
  601. optionsToUse = o.defaultValueFromContent ? $.extend({ defaultValue: ele.text() }, options) : options;
  602. ele.text($.t(key, optionsToUse));
  603. } else {
  604. optionsToUse = o.defaultValueFromContent ? $.extend({ defaultValue: ele.attr(attr) }, options) : options;
  605. ele.attr(attr, $.t(key, optionsToUse));
  606. }
  607. }
  608. function localize(ele, options) {
  609. var key = ele.attr(o.selectorAttr);
  610. if (!key) return;
  611. var target = ele
  612. , targetSelector = ele.data("i18n-target");
  613. if (targetSelector) {
  614. target = ele.find(targetSelector) || ele;
  615. }
  616. if (!options && o.useDataAttrOptions === true) {
  617. options = ele.data("i18n-options");
  618. }
  619. options = options || {};
  620. if (key.indexOf(';') <= key.length-1) {
  621. var keys = key.split(';');
  622. $.each(keys, function(m, k) {
  623. parse(target, k, options);
  624. });
  625. } else {
  626. parse(target, k, options);
  627. }
  628. if (o.useDataAttrOptions === true) ele.data("i18n-options", options);
  629. }
  630. // fn
  631. $.fn.i18n = function (options) {
  632. return this.each(function() {
  633. // localize element itself
  634. localize($(this), options);
  635. // localize childs
  636. var elements = $(this).find('[' + o.selectorAttr + ']');
  637. elements.each(function() {
  638. localize($(this), options);
  639. });
  640. });
  641. };
  642. }
  643. function applyReplacement(str, replacementHash, nestedKey, options) {
  644. options = options || replacementHash; // first call uses replacement hash combined with options
  645. if (str.indexOf(options.interpolationPrefix || o.interpolationPrefix) < 0) return str;
  646. var prefix = options.interpolationPrefix ? f.regexEscape(options.interpolationPrefix) : o.interpolationPrefixEscaped
  647. , suffix = options.interpolationSuffix ? f.regexEscape(options.interpolationSuffix) : o.interpolationSuffixEscaped;
  648. f.each(replacementHash, function(key, value) {
  649. if (typeof value === 'object' && value !== null) {
  650. str = applyReplacement(str, value, nestedKey ? nestedKey + o.keyseparator + key : key, options);
  651. } else {
  652. str = str.replace(new RegExp([prefix, nestedKey ? nestedKey + o.keyseparator + key : key, suffix].join(''), 'g'), value);
  653. }
  654. });
  655. return str;
  656. }
  657. function applyReuse(translated, options) {
  658. var comma = ',';
  659. var options_open = '{';
  660. var options_close = '}';
  661. var opts = f.extend({}, options);
  662. delete opts.postProcess;
  663. while (translated.indexOf(o.reusePrefix) != -1) {
  664. replacementCounter++;
  665. if (replacementCounter > o.maxRecursion) { break; } // safety net for too much recursion
  666. var index_of_opening = translated.indexOf(o.reusePrefix);
  667. var index_of_end_of_closing = translated.indexOf(o.reuseSuffix, index_of_opening) + o.reuseSuffix.length;
  668. var token = translated.substring(index_of_opening, index_of_end_of_closing);
  669. var token_without_symbols = token.replace(o.reusePrefix, '').replace(o.reuseSuffix, '');
  670. if (token_without_symbols.indexOf(comma) != -1) {
  671. var index_of_token_end_of_closing = token_without_symbols.indexOf(comma);
  672. if (token_without_symbols.indexOf(options_open, index_of_token_end_of_closing) != -1 && token_without_symbols.indexOf(options_close, index_of_token_end_of_closing) != -1) {
  673. var index_of_opts_opening = token_without_symbols.indexOf(options_open, index_of_token_end_of_closing);
  674. var index_of_opts_end_of_closing = token_without_symbols.indexOf(options_close, index_of_opts_opening) + options_close.length;
  675. try {
  676. opts = f.extend(opts, JSON.parse(token_without_symbols.substring(index_of_opts_opening, index_of_opts_end_of_closing)));
  677. token_without_symbols = token_without_symbols.substring(0, index_of_token_end_of_closing);
  678. } catch (e) {
  679. }
  680. }
  681. }
  682. var translated_token = _translate(token_without_symbols, opts);
  683. translated = translated.replace(token, translated_token);
  684. }
  685. return translated;
  686. }
  687. function hasContext(options) {
  688. return (options.context && typeof options.context == 'string');
  689. }
  690. function needsPlural(options) {
  691. return (options.count !== undefined && typeof options.count != 'string' && options.count !== 1);
  692. }
  693. function translate(key, options){
  694. replacementCounter = 0;
  695. return _translate(key, options);
  696. }
  697. function _translate(key, options){
  698. options = options || {};
  699. if (!resStore) { return notfound; } // no resStore to translate from
  700. var optionWithoutCount, translated
  701. , notfound = options.defaultValue || key
  702. , lngs = languages;
  703. if (options.lng) {
  704. lngs = f.toLanguages(options.lng);
  705. if (!resStore[lngs[0]]) {
  706. var oldAsync = o.getAsync;
  707. o.getAsync = false;
  708. i18n.sync.load(lngs, o, function(err, store) {
  709. f.extend(resStore, store);
  710. o.getAsync = oldAsync;
  711. });
  712. }
  713. }
  714. var ns = options.ns || o.ns.defaultNs;
  715. if (key.indexOf(o.nsseparator) > -1) {
  716. var parts = key.split(o.nsseparator);
  717. ns = parts[0];
  718. key = parts[1];
  719. }
  720. if (hasContext(options)) {
  721. optionWithoutCount = f.extend({}, options);
  722. delete optionWithoutCount.context;
  723. optionWithoutCount.defaultValue = o.contextNotFound;
  724. var contextKey = ns + o.nsseparator + key + '_' + options.context;
  725. translated = translate(contextKey, optionWithoutCount);
  726. if (translated != o.contextNotFound) {
  727. return applyReplacement(translated, { context: options.context }); // apply replacement for context only
  728. } // else continue translation with original/nonContext key
  729. }
  730. if (needsPlural(options)) {
  731. optionWithoutCount = f.extend({}, options);
  732. delete optionWithoutCount.count;
  733. optionWithoutCount.defaultValue = o.pluralNotFound;
  734. var pluralKey = ns + o.nsseparator + key + o.pluralSuffix;
  735. var pluralExtension = pluralExtensions.get(currentLng, options.count);
  736. if (pluralExtension >= 0) {
  737. pluralKey = pluralKey + '_' + pluralExtension;
  738. } else if (pluralExtension === 1) {
  739. pluralKey = ns + o.nsseparator + key; // singular
  740. }
  741. translated = translate(pluralKey, optionWithoutCount);
  742. if (translated != o.pluralNotFound) {
  743. return applyReplacement(translated, { count: options.count }); // apply replacement for count only
  744. } // else continue translation with original/singular key
  745. }
  746. var found;
  747. var keys = key.split(o.keyseparator);
  748. for (var i = 0, len = lngs.length; i < len; i++ ) {
  749. if (found) break;
  750. var l = lngs[i];
  751. var x = 0;
  752. var value = resStore[l] && resStore[l][ns];
  753. while (keys[x]) {
  754. value = value && value[keys[x]];
  755. x++;
  756. }
  757. if (value !== undefined) {
  758. if (typeof value === 'string') {
  759. value = applyReplacement(value, options);
  760. value = applyReuse(value, options);
  761. } else if (Object.prototype.toString.apply(value) === '[object Array]' && !o.returnObjectTrees && !options.returnObjectTrees) {
  762. value = value.join('\n');
  763. value = applyReplacement(value, options);
  764. value = applyReuse(value, options);
  765. } else {
  766. if (!o.returnObjectTrees && !options.returnObjectTrees) {
  767. value = 'key \'' + ns + ':' + key + ' (' + l + ')\' ' +
  768. 'returned a object instead of string.';
  769. f.log(value);
  770. } else {
  771. var copy = {}; // apply child translation on a copy
  772. for (var m in value) {
  773. // apply translation on childs
  774. copy[m] = _translate(ns + o.nsseparator + key + o.keyseparator + m, options);
  775. }
  776. value = copy;
  777. }
  778. }
  779. found = value;
  780. }
  781. }
  782. if (found === undefined && o.fallbackToDefaultNS) {
  783. found = _translate(key, options);
  784. }
  785. if (found === undefined && o.sendMissing) {
  786. if (options.lng) {
  787. sync.postMissing(lngs[0], ns, key, notfound, lngs);
  788. } else {
  789. sync.postMissing(o.lng, ns, key, notfound, lngs);
  790. }
  791. }
  792. var postProcessor = options.postProcess || o.postProcess;
  793. if (found !== undefined && postProcessor) {
  794. if (postProcessors[postProcessor]) {
  795. found = postProcessors[postProcessor](found, key, options);
  796. }
  797. }
  798. if (found === undefined) {
  799. notfound = applyReplacement(notfound, options);
  800. notfound = applyReuse(notfound, options);
  801. }
  802. return (found !== undefined) ? found : notfound;
  803. }
  804. function detectLanguage() {
  805. var detectedLng;
  806. // get from qs
  807. var qsParm = [];
  808. if (typeof window !== 'undefined') {
  809. (function() {
  810. var query = window.location.search.substring(1);
  811. var parms = query.split('&');
  812. for (var i=0; i<parms.length; i++) {
  813. var pos = parms[i].indexOf('=');
  814. if (pos > 0) {
  815. var key = parms[i].substring(0,pos);
  816. var val = parms[i].substring(pos+1);
  817. qsParm[key] = val;
  818. }
  819. }
  820. })();
  821. if (qsParm[o.detectLngQS]) {
  822. detectedLng = qsParm[o.detectLngQS];
  823. }
  824. }
  825. // get from cookie
  826. if (!detectedLng && typeof document !== 'undefined' && o.useCookie ) {
  827. var c = f.cookie.read(o.cookieName);
  828. if (c) detectedLng = c;
  829. }
  830. // get from navigator
  831. if (!detectedLng && typeof navigator !== 'undefined') {
  832. detectedLng = (navigator.language) ? navigator.language : navigator.userLanguage;
  833. }
  834. return detectedLng;
  835. }
  836. var sync = {
  837. load: function(lngs, options, cb) {
  838. if (options.useLocalStorage) {
  839. sync._loadLocal(lngs, options, function(err, store) {
  840. var missingLngs = [];
  841. for (var i = 0, len = lngs.length; i < len; i++) {
  842. if (!store[lngs[i]]) missingLngs.push(lngs[i]);
  843. }
  844. if (missingLngs.length > 0) {
  845. sync._fetch(missingLngs, options, function(err, fetched) {
  846. f.extend(store, fetched);
  847. sync._storeLocal(fetched);
  848. cb(null, store);
  849. });
  850. } else {
  851. cb(null, store);
  852. }
  853. });
  854. } else {
  855. sync._fetch(lngs, options, function(err, store){
  856. cb(null, store);
  857. });
  858. }
  859. },
  860. _loadLocal: function(lngs, options, cb) {
  861. var store = {}
  862. , nowMS = new Date().getTime();
  863. if(window.localStorage) {
  864. var todo = lngs.length;
  865. f.each(lngs, function(key, lng) {
  866. var local = window.localStorage.getItem('res_' + lng);
  867. if (local) {
  868. local = JSON.parse(local);
  869. if (local.i18nStamp && local.i18nStamp + options.localStorageExpirationTime > nowMS) {
  870. store[lng] = local;
  871. }
  872. }
  873. todo--; // wait for all done befor callback
  874. if (todo === 0) cb(null, store);
  875. });
  876. }
  877. },
  878. _storeLocal: function(store) {
  879. if(window.localStorage) {
  880. for (var m in store) {
  881. store[m].i18nStamp = new Date().getTime();
  882. window.localStorage.setItem('res_' + m, JSON.stringify(store[m]));
  883. }
  884. }
  885. return;
  886. },
  887. _fetch: function(lngs, options, cb) {
  888. var ns = options.ns
  889. , store = {};
  890. if (!options.dynamicLoad) {
  891. var todo = ns.namespaces.length * lngs.length
  892. , errors;
  893. // load each file individual
  894. f.each(ns.namespaces, function(nsIndex, nsValue) {
  895. f.each(lngs, function(lngIndex, lngValue) {
  896. // Call this once our translation has returned.
  897. var loadComplete = function(err, data) {
  898. if (err) {
  899. errors = errors || [];
  900. errors.push(err);
  901. }
  902. store[lngValue] = store[lngValue] || {};
  903. store[lngValue][nsValue] = data;
  904. todo--; // wait for all done befor callback
  905. if (todo === 0) cb(errors, store);
  906. };
  907. if(typeof options.customLoad == 'function'){
  908. // Use the specified custom callback.
  909. options.customLoad(lngValue, nsValue, options, loadComplete);
  910. } else {
  911. //~ // Use our inbuilt sync.
  912. sync._fetchOne(lngValue, nsValue, options, loadComplete);
  913. }
  914. });
  915. });
  916. } else {
  917. var url = applyReplacement(options.resGetPath, { lng: lngs.join('+'), ns: ns.namespaces.join('+') });
  918. // load all needed stuff once
  919. f.ajax({
  920. url: url,
  921. success: function(data, status, xhr) {
  922. f.log('loaded: ' + url);
  923. cb(null, data);
  924. },
  925. error : function(xhr, status, error) {
  926. f.log('failed loading: ' + url);
  927. cb('failed loading resource.json error: ' + error);
  928. },
  929. dataType: "json",
  930. async : options.getAsync
  931. });
  932. }
  933. },
  934. _fetchOne: function(lng, ns, options, done) {
  935. var url = applyReplacement(options.resGetPath, { lng: lng, ns: ns });
  936. f.ajax({
  937. url: url,
  938. success: function(data, status, xhr) {
  939. f.log('loaded: ' + url);
  940. done(null, data);
  941. },
  942. error : function(xhr, status, error) {
  943. f.log('failed loading: ' + url);
  944. done(error, {});
  945. },
  946. dataType: "json",
  947. async : options.getAsync
  948. });
  949. },
  950. postMissing: function(lng, ns, key, defaultValue, lngs) {
  951. var payload = {};
  952. payload[key] = defaultValue;
  953. var urls = [];
  954. if (o.sendMissingTo === 'fallback') {
  955. urls.push({lng: o.fallbackLng, url: applyReplacement(o.resPostPath, { lng: o.fallbackLng, ns: ns })});
  956. } else if (o.sendMissingTo === 'current') {
  957. urls.push({lng: lng, url: applyReplacement(o.resPostPath, { lng: lng, ns: ns })});
  958. } else if (o.sendMissingTo === 'all') {
  959. for (var i = 0, l = lngs.length; i < l; i++) {
  960. urls.push({lng: lngs[i], url: applyReplacement(o.resPostPath, { lng: lngs[i], ns: ns })});
  961. }
  962. }
  963. for (var y = 0, len = urls.length; y < len; y++) {
  964. var item = urls[y];
  965. f.ajax({
  966. url: item.url,
  967. type: o.sendType,
  968. data: payload,
  969. success: function(data, status, xhr) {
  970. f.log('posted missing key \'' + key + '\' to: ' + item.url);
  971. // add key to resStore
  972. var keys = key.split('.');
  973. var x = 0;
  974. var value = resStore[item.lng][ns];
  975. while (keys[x]) {
  976. if (x === keys.length - 1) {
  977. value = value[keys[x]] = defaultValue;
  978. } else {
  979. value = value[keys[x]] = value[keys[x]] || {};
  980. }
  981. x++;
  982. }
  983. },
  984. error : function(xhr, status, error) {
  985. f.log('failed posting missing key \'' + key + '\' to: ' + item.url);
  986. },
  987. dataType: "json",
  988. async : o.postAsync
  989. });
  990. }
  991. }
  992. };
  993. // definition http://translate.sourceforge.net/wiki/l10n/pluralforms
  994. var pluralExtensions = {
  995. rules: {
  996. "ach": {
  997. "name": "Acholi",
  998. "numbers": [
  999. 1,
  1000. 2
  1001. ],
  1002. "plurals": function(n) { return Number(n > 1); }
  1003. },
  1004. "af": {
  1005. "name": "Afrikaans",
  1006. "numbers": [
  1007. 1,
  1008. 2
  1009. ],
  1010. "plurals": function(n) { return Number(n != 1); }
  1011. },
  1012. "ak": {
  1013. "name": "Akan",
  1014. "numbers": [
  1015. 1,
  1016. 2
  1017. ],
  1018. "plurals": function(n) { return Number(n > 1); }
  1019. },
  1020. "am": {
  1021. "name": "Amharic",
  1022. "numbers": [
  1023. 1,
  1024. 2
  1025. ],
  1026. "plurals": function(n) { return Number(n > 1); }
  1027. },
  1028. "an": {
  1029. "name": "Aragonese",
  1030. "numbers": [
  1031. 1,
  1032. 2
  1033. ],
  1034. "plurals": function(n) { return Number(n != 1); }
  1035. },
  1036. "ar": {
  1037. "name": "Arabic",
  1038. "numbers": [
  1039. 0,
  1040. 1,
  1041. 2,
  1042. 3,
  1043. 11,
  1044. 100
  1045. ],
  1046. "plurals": function(n) { return Number(n===0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5); }
  1047. },
  1048. "arn": {
  1049. "name": "Mapudungun",
  1050. "numbers": [
  1051. 1,
  1052. 2
  1053. ],
  1054. "plurals": function(n) { return Number(n > 1); }
  1055. },
  1056. "ast": {
  1057. "name": "Asturian",
  1058. "numbers": [
  1059. 1,
  1060. 2
  1061. ],
  1062. "plurals": function(n) { return Number(n != 1); }
  1063. },
  1064. "ay": {
  1065. "name": "Aymar\u00e1",
  1066. "numbers": [
  1067. 1
  1068. ],
  1069. "plurals": function(n) { return 0; }
  1070. },
  1071. "az": {
  1072. "name": "Azerbaijani",
  1073. "numbers": [
  1074. 1,
  1075. 2
  1076. ],
  1077. "plurals": function(n) { return Number(n != 1); }
  1078. },
  1079. "be": {
  1080. "name": "Belarusian",
  1081. "numbers": [
  1082. 1,
  1083. 2,
  1084. 5
  1085. ],
  1086. "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); }
  1087. },
  1088. "bg": {
  1089. "name": "Bulgarian",
  1090. "numbers": [
  1091. 1,
  1092. 2
  1093. ],
  1094. "plurals": function(n) { return Number(n != 1); }
  1095. },
  1096. "bn": {
  1097. "name": "Bengali",
  1098. "numbers": [
  1099. 1,
  1100. 2
  1101. ],
  1102. "plurals": function(n) { return Number(n != 1); }
  1103. },
  1104. "bo": {
  1105. "name": "Tibetan",
  1106. "numbers": [
  1107. 1
  1108. ],
  1109. "plurals": function(n) { return 0; }
  1110. },
  1111. "br": {
  1112. "name": "Breton",
  1113. "numbers": [
  1114. 1,
  1115. 2
  1116. ],
  1117. "plurals": function(n) { return Number(n > 1); }
  1118. },
  1119. "bs": {
  1120. "name": "Bosnian",
  1121. "numbers": [
  1122. 1,
  1123. 2,
  1124. 5
  1125. ],
  1126. "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); }
  1127. },
  1128. "ca": {
  1129. "name": "Catalan",
  1130. "numbers": [
  1131. 1,
  1132. 2
  1133. ],
  1134. "plurals": function(n) { return Number(n != 1); }
  1135. },
  1136. "cgg": {
  1137. "name": "Chiga",
  1138. "numbers": [
  1139. 1
  1140. ],
  1141. "plurals": function(n) { return 0; }
  1142. },
  1143. "cs": {
  1144. "name": "Czech",
  1145. "numbers": [
  1146. 1,
  1147. 2,
  1148. 5
  1149. ],
  1150. "plurals": function(n) { return Number((n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2); }
  1151. },
  1152. "csb": {
  1153. "name": "Kashubian",
  1154. "numbers": [
  1155. 1,
  1156. 2,
  1157. 5
  1158. ],
  1159. "plurals": function(n) { return Number(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); }
  1160. },
  1161. "cy": {
  1162. "name": "Welsh",
  1163. "numbers": [
  1164. 1,
  1165. 2,
  1166. 3,
  1167. 8
  1168. ],
  1169. "plurals": function(n) { return Number((n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3); }
  1170. },
  1171. "da": {
  1172. "name": "Danish",
  1173. "numbers": [
  1174. 1,
  1175. 2
  1176. ],
  1177. "plurals": function(n) { return Number(n != 1); }
  1178. },
  1179. "de": {
  1180. "name": "German",
  1181. "numbers": [
  1182. 1,
  1183. 2
  1184. ],
  1185. "plurals": function(n) { return Number(n != 1); }
  1186. },
  1187. "dz": {
  1188. "name": "Dzongkha",
  1189. "numbers": [
  1190. 1
  1191. ],
  1192. "plurals": function(n) { return 0; }
  1193. },
  1194. "el": {
  1195. "name": "Greek",
  1196. "numbers": [
  1197. 1,
  1198. 2
  1199. ],
  1200. "plurals": function(n) { return Number(n != 1); }
  1201. },
  1202. "en": {
  1203. "name": "English",
  1204. "numbers": [
  1205. 1,
  1206. 2
  1207. ],
  1208. "plurals": function(n) { return Number(n != 1); }
  1209. },
  1210. "eo": {
  1211. "name": "Esperanto",
  1212. "numbers": [
  1213. 1,
  1214. 2
  1215. ],
  1216. "plurals": function(n) { return Number(n != 1); }
  1217. },
  1218. "es": {
  1219. "name": "Spanish",
  1220. "numbers": [
  1221. 1,
  1222. 2
  1223. ],
  1224. "plurals": function(n) { return Number(n != 1); }
  1225. },
  1226. "es_ar": {
  1227. "name": "Argentinean Spanish",
  1228. "numbers": [
  1229. 1,
  1230. 2
  1231. ],
  1232. "plurals": function(n) { return Number(n != 1); }
  1233. },
  1234. "et": {
  1235. "name": "Estonian",
  1236. "numbers": [
  1237. 1,
  1238. 2
  1239. ],
  1240. "plura