i18next.bundled.js 87 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382
  1. const isString = obj => typeof obj === 'string';
  2. const defer = () => {
  3. let res;
  4. let rej;
  5. const promise = new Promise((resolve, reject) => {
  6. res = resolve;
  7. rej = reject;
  8. });
  9. promise.resolve = res;
  10. promise.reject = rej;
  11. return promise;
  12. };
  13. const makeString = object => {
  14. if (object == null) return '';
  15. return '' + object;
  16. };
  17. const copy = (a, s, t) => {
  18. a.forEach(m => {
  19. if (s[m]) t[m] = s[m];
  20. });
  21. };
  22. const lastOfPathSeparatorRegExp = /###/g;
  23. const cleanKey = key => key && key.indexOf('###') > -1 ? key.replace(lastOfPathSeparatorRegExp, '.') : key;
  24. const canNotTraverseDeeper = object => !object || isString(object);
  25. const getLastOfPath = (object, path, Empty) => {
  26. const stack = !isString(path) ? path : path.split('.');
  27. let stackIndex = 0;
  28. while (stackIndex < stack.length - 1) {
  29. if (canNotTraverseDeeper(object)) return {};
  30. const key = cleanKey(stack[stackIndex]);
  31. if (!object[key] && Empty) object[key] = new Empty();
  32. if (Object.prototype.hasOwnProperty.call(object, key)) {
  33. object = object[key];
  34. } else {
  35. object = {};
  36. }
  37. ++stackIndex;
  38. }
  39. if (canNotTraverseDeeper(object)) return {};
  40. return {
  41. obj: object,
  42. k: cleanKey(stack[stackIndex])
  43. };
  44. };
  45. const setPath = (object, path, newValue) => {
  46. const {
  47. obj,
  48. k
  49. } = getLastOfPath(object, path, Object);
  50. if (obj !== undefined || path.length === 1) {
  51. obj[k] = newValue;
  52. return;
  53. }
  54. let e = path[path.length - 1];
  55. let p = path.slice(0, path.length - 1);
  56. let last = getLastOfPath(object, p, Object);
  57. while (last.obj === undefined && p.length) {
  58. e = `${p[p.length - 1]}.${e}`;
  59. p = p.slice(0, p.length - 1);
  60. last = getLastOfPath(object, p, Object);
  61. if (last && last.obj && typeof last.obj[`${last.k}.${e}`] !== 'undefined') {
  62. last.obj = undefined;
  63. }
  64. }
  65. last.obj[`${last.k}.${e}`] = newValue;
  66. };
  67. const pushPath = (object, path, newValue, concat) => {
  68. const {
  69. obj,
  70. k
  71. } = getLastOfPath(object, path, Object);
  72. obj[k] = obj[k] || [];
  73. obj[k].push(newValue);
  74. };
  75. const getPath = (object, path) => {
  76. const {
  77. obj,
  78. k
  79. } = getLastOfPath(object, path);
  80. if (!obj) return undefined;
  81. return obj[k];
  82. };
  83. const getPathWithDefaults = (data, defaultData, key) => {
  84. const value = getPath(data, key);
  85. if (value !== undefined) {
  86. return value;
  87. }
  88. return getPath(defaultData, key);
  89. };
  90. const deepExtend = (target, source, overwrite) => {
  91. for (const prop in source) {
  92. if (prop !== '__proto__' && prop !== 'constructor') {
  93. if (prop in target) {
  94. if (isString(target[prop]) || target[prop] instanceof String || isString(source[prop]) || source[prop] instanceof String) {
  95. if (overwrite) target[prop] = source[prop];
  96. } else {
  97. deepExtend(target[prop], source[prop], overwrite);
  98. }
  99. } else {
  100. target[prop] = source[prop];
  101. }
  102. }
  103. }
  104. return target;
  105. };
  106. const regexEscape = str => str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
  107. var _entityMap = {
  108. '&': '&amp;',
  109. '<': '&lt;',
  110. '>': '&gt;',
  111. '"': '&quot;',
  112. "'": '&#39;',
  113. '/': '&#x2F;'
  114. };
  115. const escape = data => {
  116. if (isString(data)) {
  117. return data.replace(/[&<>"'\/]/g, s => _entityMap[s]);
  118. }
  119. return data;
  120. };
  121. class RegExpCache {
  122. constructor(capacity) {
  123. this.capacity = capacity;
  124. this.regExpMap = new Map();
  125. this.regExpQueue = [];
  126. }
  127. getRegExp(pattern) {
  128. const regExpFromCache = this.regExpMap.get(pattern);
  129. if (regExpFromCache !== undefined) {
  130. return regExpFromCache;
  131. }
  132. const regExpNew = new RegExp(pattern);
  133. if (this.regExpQueue.length === this.capacity) {
  134. this.regExpMap.delete(this.regExpQueue.shift());
  135. }
  136. this.regExpMap.set(pattern, regExpNew);
  137. this.regExpQueue.push(pattern);
  138. return regExpNew;
  139. }
  140. }
  141. const chars = [' ', ',', '?', '!', ';'];
  142. const looksLikeObjectPathRegExpCache = new RegExpCache(20);
  143. const looksLikeObjectPath = (key, nsSeparator, keySeparator) => {
  144. nsSeparator = nsSeparator || '';
  145. keySeparator = keySeparator || '';
  146. const possibleChars = chars.filter(c => nsSeparator.indexOf(c) < 0 && keySeparator.indexOf(c) < 0);
  147. if (possibleChars.length === 0) return true;
  148. const r = looksLikeObjectPathRegExpCache.getRegExp(`(${possibleChars.map(c => c === '?' ? '\\?' : c).join('|')})`);
  149. let matched = !r.test(key);
  150. if (!matched) {
  151. const ki = key.indexOf(keySeparator);
  152. if (ki > 0 && !r.test(key.substring(0, ki))) {
  153. matched = true;
  154. }
  155. }
  156. return matched;
  157. };
  158. const deepFind = function (obj, path) {
  159. let keySeparator = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '.';
  160. if (!obj) return undefined;
  161. if (obj[path]) return obj[path];
  162. const tokens = path.split(keySeparator);
  163. let current = obj;
  164. for (let i = 0; i < tokens.length;) {
  165. if (!current || typeof current !== 'object') {
  166. return undefined;
  167. }
  168. let next;
  169. let nextPath = '';
  170. for (let j = i; j < tokens.length; ++j) {
  171. if (j !== i) {
  172. nextPath += keySeparator;
  173. }
  174. nextPath += tokens[j];
  175. next = current[nextPath];
  176. if (next !== undefined) {
  177. if (['string', 'number', 'boolean'].indexOf(typeof next) > -1 && j < tokens.length - 1) {
  178. continue;
  179. }
  180. i += j - i + 1;
  181. break;
  182. }
  183. }
  184. current = next;
  185. }
  186. return current;
  187. };
  188. const getCleanedCode = code => code && code.replace('_', '-');
  189. const consoleLogger = {
  190. type: 'logger',
  191. log(args) {
  192. this.output('log', args);
  193. },
  194. warn(args) {
  195. this.output('warn', args);
  196. },
  197. error(args) {
  198. this.output('error', args);
  199. },
  200. output(type, args) {
  201. if (console && console[type]) console[type].apply(console, args);
  202. }
  203. };
  204. class Logger {
  205. constructor(concreteLogger) {
  206. let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  207. this.init(concreteLogger, options);
  208. }
  209. init(concreteLogger) {
  210. let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  211. this.prefix = options.prefix || 'i18next:';
  212. this.logger = concreteLogger || consoleLogger;
  213. this.options = options;
  214. this.debug = options.debug;
  215. }
  216. log() {
  217. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  218. args[_key] = arguments[_key];
  219. }
  220. return this.forward(args, 'log', '', true);
  221. }
  222. warn() {
  223. for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
  224. args[_key2] = arguments[_key2];
  225. }
  226. return this.forward(args, 'warn', '', true);
  227. }
  228. error() {
  229. for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
  230. args[_key3] = arguments[_key3];
  231. }
  232. return this.forward(args, 'error', '');
  233. }
  234. deprecate() {
  235. for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
  236. args[_key4] = arguments[_key4];
  237. }
  238. return this.forward(args, 'warn', 'WARNING DEPRECATED: ', true);
  239. }
  240. forward(args, lvl, prefix, debugOnly) {
  241. if (debugOnly && !this.debug) return null;
  242. if (isString(args[0])) args[0] = `${prefix}${this.prefix} ${args[0]}`;
  243. return this.logger[lvl](args);
  244. }
  245. create(moduleName) {
  246. return new Logger(this.logger, {
  247. ...{
  248. prefix: `${this.prefix}:${moduleName}:`
  249. },
  250. ...this.options
  251. });
  252. }
  253. clone(options) {
  254. options = options || this.options;
  255. options.prefix = options.prefix || this.prefix;
  256. return new Logger(this.logger, options);
  257. }
  258. }
  259. var baseLogger = new Logger();
  260. class EventEmitter {
  261. constructor() {
  262. this.observers = {};
  263. }
  264. on(events, listener) {
  265. events.split(' ').forEach(event => {
  266. if (!this.observers[event]) this.observers[event] = new Map();
  267. const numListeners = this.observers[event].get(listener) || 0;
  268. this.observers[event].set(listener, numListeners + 1);
  269. });
  270. return this;
  271. }
  272. off(event, listener) {
  273. if (!this.observers[event]) return;
  274. if (!listener) {
  275. delete this.observers[event];
  276. return;
  277. }
  278. this.observers[event].delete(listener);
  279. }
  280. emit(event) {
  281. for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
  282. args[_key - 1] = arguments[_key];
  283. }
  284. if (this.observers[event]) {
  285. const cloned = Array.from(this.observers[event].entries());
  286. cloned.forEach(_ref => {
  287. let [observer, numTimesAdded] = _ref;
  288. for (let i = 0; i < numTimesAdded; i++) {
  289. observer(...args);
  290. }
  291. });
  292. }
  293. if (this.observers['*']) {
  294. const cloned = Array.from(this.observers['*'].entries());
  295. cloned.forEach(_ref2 => {
  296. let [observer, numTimesAdded] = _ref2;
  297. for (let i = 0; i < numTimesAdded; i++) {
  298. observer.apply(observer, [event, ...args]);
  299. }
  300. });
  301. }
  302. }
  303. }
  304. class ResourceStore extends EventEmitter {
  305. constructor(data) {
  306. let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
  307. ns: ['translation'],
  308. defaultNS: 'translation'
  309. };
  310. super();
  311. this.data = data || {};
  312. this.options = options;
  313. if (this.options.keySeparator === undefined) {
  314. this.options.keySeparator = '.';
  315. }
  316. if (this.options.ignoreJSONStructure === undefined) {
  317. this.options.ignoreJSONStructure = true;
  318. }
  319. }
  320. addNamespaces(ns) {
  321. if (this.options.ns.indexOf(ns) < 0) {
  322. this.options.ns.push(ns);
  323. }
  324. }
  325. removeNamespaces(ns) {
  326. const index = this.options.ns.indexOf(ns);
  327. if (index > -1) {
  328. this.options.ns.splice(index, 1);
  329. }
  330. }
  331. getResource(lng, ns, key) {
  332. let options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
  333. const keySeparator = options.keySeparator !== undefined ? options.keySeparator : this.options.keySeparator;
  334. const ignoreJSONStructure = options.ignoreJSONStructure !== undefined ? options.ignoreJSONStructure : this.options.ignoreJSONStructure;
  335. let path;
  336. if (lng.indexOf('.') > -1) {
  337. path = lng.split('.');
  338. } else {
  339. path = [lng, ns];
  340. if (key) {
  341. if (Array.isArray(key)) {
  342. path.push(...key);
  343. } else if (isString(key) && keySeparator) {
  344. path.push(...key.split(keySeparator));
  345. } else {
  346. path.push(key);
  347. }
  348. }
  349. }
  350. const result = getPath(this.data, path);
  351. if (!result && !ns && !key && lng.indexOf('.') > -1) {
  352. lng = path[0];
  353. ns = path[1];
  354. key = path.slice(2).join('.');
  355. }
  356. if (result || !ignoreJSONStructure || !isString(key)) return result;
  357. return deepFind(this.data && this.data[lng] && this.data[lng][ns], key, keySeparator);
  358. }
  359. addResource(lng, ns, key, value) {
  360. let options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {
  361. silent: false
  362. };
  363. const keySeparator = options.keySeparator !== undefined ? options.keySeparator : this.options.keySeparator;
  364. let path = [lng, ns];
  365. if (key) path = path.concat(keySeparator ? key.split(keySeparator) : key);
  366. if (lng.indexOf('.') > -1) {
  367. path = lng.split('.');
  368. value = ns;
  369. ns = path[1];
  370. }
  371. this.addNamespaces(ns);
  372. setPath(this.data, path, value);
  373. if (!options.silent) this.emit('added', lng, ns, key, value);
  374. }
  375. addResources(lng, ns, resources) {
  376. let options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {
  377. silent: false
  378. };
  379. for (const m in resources) {
  380. if (isString(resources[m]) || Array.isArray(resources[m])) this.addResource(lng, ns, m, resources[m], {
  381. silent: true
  382. });
  383. }
  384. if (!options.silent) this.emit('added', lng, ns, resources);
  385. }
  386. addResourceBundle(lng, ns, resources, deep, overwrite) {
  387. let options = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : {
  388. silent: false,
  389. skipCopy: false
  390. };
  391. let path = [lng, ns];
  392. if (lng.indexOf('.') > -1) {
  393. path = lng.split('.');
  394. deep = resources;
  395. resources = ns;
  396. ns = path[1];
  397. }
  398. this.addNamespaces(ns);
  399. let pack = getPath(this.data, path) || {};
  400. if (!options.skipCopy) resources = JSON.parse(JSON.stringify(resources));
  401. if (deep) {
  402. deepExtend(pack, resources, overwrite);
  403. } else {
  404. pack = {
  405. ...pack,
  406. ...resources
  407. };
  408. }
  409. setPath(this.data, path, pack);
  410. if (!options.silent) this.emit('added', lng, ns, resources);
  411. }
  412. removeResourceBundle(lng, ns) {
  413. if (this.hasResourceBundle(lng, ns)) {
  414. delete this.data[lng][ns];
  415. }
  416. this.removeNamespaces(ns);
  417. this.emit('removed', lng, ns);
  418. }
  419. hasResourceBundle(lng, ns) {
  420. return this.getResource(lng, ns) !== undefined;
  421. }
  422. getResourceBundle(lng, ns) {
  423. if (!ns) ns = this.options.defaultNS;
  424. if (this.options.compatibilityAPI === 'v1') return {
  425. ...{},
  426. ...this.getResource(lng, ns)
  427. };
  428. return this.getResource(lng, ns);
  429. }
  430. getDataByLanguage(lng) {
  431. return this.data[lng];
  432. }
  433. hasLanguageSomeTranslations(lng) {
  434. const data = this.getDataByLanguage(lng);
  435. const n = data && Object.keys(data) || [];
  436. return !!n.find(v => data[v] && Object.keys(data[v]).length > 0);
  437. }
  438. toJSON() {
  439. return this.data;
  440. }
  441. }
  442. var postProcessor = {
  443. processors: {},
  444. addPostProcessor(module) {
  445. this.processors[module.name] = module;
  446. },
  447. handle(processors, value, key, options, translator) {
  448. processors.forEach(processor => {
  449. if (this.processors[processor]) value = this.processors[processor].process(value, key, options, translator);
  450. });
  451. return value;
  452. }
  453. };
  454. const checkedLoadedFor = {};
  455. class Translator extends EventEmitter {
  456. constructor(services) {
  457. let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  458. super();
  459. copy(['resourceStore', 'languageUtils', 'pluralResolver', 'interpolator', 'backendConnector', 'i18nFormat', 'utils'], services, this);
  460. this.options = options;
  461. if (this.options.keySeparator === undefined) {
  462. this.options.keySeparator = '.';
  463. }
  464. this.logger = baseLogger.create('translator');
  465. }
  466. changeLanguage(lng) {
  467. if (lng) this.language = lng;
  468. }
  469. exists(key) {
  470. let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
  471. interpolation: {}
  472. };
  473. if (key === undefined || key === null) {
  474. return false;
  475. }
  476. const resolved = this.resolve(key, options);
  477. return resolved && resolved.res !== undefined;
  478. }
  479. extractFromKey(key, options) {
  480. let nsSeparator = options.nsSeparator !== undefined ? options.nsSeparator : this.options.nsSeparator;
  481. if (nsSeparator === undefined) nsSeparator = ':';
  482. const keySeparator = options.keySeparator !== undefined ? options.keySeparator : this.options.keySeparator;
  483. let namespaces = options.ns || this.options.defaultNS || [];
  484. const wouldCheckForNsInKey = nsSeparator && key.indexOf(nsSeparator) > -1;
  485. const seemsNaturalLanguage = !this.options.userDefinedKeySeparator && !options.keySeparator && !this.options.userDefinedNsSeparator && !options.nsSeparator && !looksLikeObjectPath(key, nsSeparator, keySeparator);
  486. if (wouldCheckForNsInKey && !seemsNaturalLanguage) {
  487. const m = key.match(this.interpolator.nestingRegexp);
  488. if (m && m.length > 0) {
  489. return {
  490. key,
  491. namespaces: isString(namespaces) ? [namespaces] : namespaces
  492. };
  493. }
  494. const parts = key.split(nsSeparator);
  495. if (nsSeparator !== keySeparator || nsSeparator === keySeparator && this.options.ns.indexOf(parts[0]) > -1) namespaces = parts.shift();
  496. key = parts.join(keySeparator);
  497. }
  498. return {
  499. key,
  500. namespaces: isString(namespaces) ? [namespaces] : namespaces
  501. };
  502. }
  503. translate(keys, options, lastKey) {
  504. if (typeof options !== 'object' && this.options.overloadTranslationOptionHandler) {
  505. options = this.options.overloadTranslationOptionHandler(arguments);
  506. }
  507. if (typeof options === 'object') options = {
  508. ...options
  509. };
  510. if (!options) options = {};
  511. if (keys === undefined || keys === null) return '';
  512. if (!Array.isArray(keys)) keys = [String(keys)];
  513. const returnDetails = options.returnDetails !== undefined ? options.returnDetails : this.options.returnDetails;
  514. const keySeparator = options.keySeparator !== undefined ? options.keySeparator : this.options.keySeparator;
  515. const {
  516. key,
  517. namespaces
  518. } = this.extractFromKey(keys[keys.length - 1], options);
  519. const namespace = namespaces[namespaces.length - 1];
  520. const lng = options.lng || this.language;
  521. const appendNamespaceToCIMode = options.appendNamespaceToCIMode || this.options.appendNamespaceToCIMode;
  522. if (lng && lng.toLowerCase() === 'cimode') {
  523. if (appendNamespaceToCIMode) {
  524. const nsSeparator = options.nsSeparator || this.options.nsSeparator;
  525. if (returnDetails) {
  526. return {
  527. res: `${namespace}${nsSeparator}${key}`,
  528. usedKey: key,
  529. exactUsedKey: key,
  530. usedLng: lng,
  531. usedNS: namespace,
  532. usedParams: this.getUsedParamsDetails(options)
  533. };
  534. }
  535. return `${namespace}${nsSeparator}${key}`;
  536. }
  537. if (returnDetails) {
  538. return {
  539. res: key,
  540. usedKey: key,
  541. exactUsedKey: key,
  542. usedLng: lng,
  543. usedNS: namespace,
  544. usedParams: this.getUsedParamsDetails(options)
  545. };
  546. }
  547. return key;
  548. }
  549. const resolved = this.resolve(keys, options);
  550. let res = resolved && resolved.res;
  551. const resUsedKey = resolved && resolved.usedKey || key;
  552. const resExactUsedKey = resolved && resolved.exactUsedKey || key;
  553. const resType = Object.prototype.toString.apply(res);
  554. const noObject = ['[object Number]', '[object Function]', '[object RegExp]'];
  555. const joinArrays = options.joinArrays !== undefined ? options.joinArrays : this.options.joinArrays;
  556. const handleAsObjectInI18nFormat = !this.i18nFormat || this.i18nFormat.handleAsObject;
  557. const handleAsObject = !isString(res) && typeof res !== 'boolean' && typeof res !== 'number';
  558. if (handleAsObjectInI18nFormat && res && handleAsObject && noObject.indexOf(resType) < 0 && !(isString(joinArrays) && Array.isArray(res))) {
  559. if (!options.returnObjects && !this.options.returnObjects) {
  560. if (!this.options.returnedObjectHandler) {
  561. this.logger.warn('accessing an object - but returnObjects options is not enabled!');
  562. }
  563. const r = this.options.returnedObjectHandler ? this.options.returnedObjectHandler(resUsedKey, res, {
  564. ...options,
  565. ns: namespaces
  566. }) : `key '${key} (${this.language})' returned an object instead of string.`;
  567. if (returnDetails) {
  568. resolved.res = r;
  569. resolved.usedParams = this.getUsedParamsDetails(options);
  570. return resolved;
  571. }
  572. return r;
  573. }
  574. if (keySeparator) {
  575. const resTypeIsArray = Array.isArray(res);
  576. const copy = resTypeIsArray ? [] : {};
  577. const newKeyToUse = resTypeIsArray ? resExactUsedKey : resUsedKey;
  578. for (const m in res) {
  579. if (Object.prototype.hasOwnProperty.call(res, m)) {
  580. const deepKey = `${newKeyToUse}${keySeparator}${m}`;
  581. copy[m] = this.translate(deepKey, {
  582. ...options,
  583. ...{
  584. joinArrays: false,
  585. ns: namespaces
  586. }
  587. });
  588. if (copy[m] === deepKey) copy[m] = res[m];
  589. }
  590. }
  591. res = copy;
  592. }
  593. } else if (handleAsObjectInI18nFormat && isString(joinArrays) && Array.isArray(res)) {
  594. res = res.join(joinArrays);
  595. if (res) res = this.extendTranslation(res, keys, options, lastKey);
  596. } else {
  597. let usedDefault = false;
  598. let usedKey = false;
  599. const needsPluralHandling = options.count !== undefined && !isString(options.count);
  600. const hasDefaultValue = Translator.hasDefaultValue(options);
  601. const defaultValueSuffix = needsPluralHandling ? this.pluralResolver.getSuffix(lng, options.count, options) : '';
  602. const defaultValueSuffixOrdinalFallback = options.ordinal && needsPluralHandling ? this.pluralResolver.getSuffix(lng, options.count, {
  603. ordinal: false
  604. }) : '';
  605. const needsZeroSuffixLookup = needsPluralHandling && !options.ordinal && options.count === 0 && this.pluralResolver.shouldUseIntlApi();
  606. const defaultValue = needsZeroSuffixLookup && options[`defaultValue${this.options.pluralSeparator}zero`] || options[`defaultValue${defaultValueSuffix}`] || options[`defaultValue${defaultValueSuffixOrdinalFallback}`] || options.defaultValue;
  607. if (!this.isValidLookup(res) && hasDefaultValue) {
  608. usedDefault = true;
  609. res = defaultValue;
  610. }
  611. if (!this.isValidLookup(res)) {
  612. usedKey = true;
  613. res = key;
  614. }
  615. const missingKeyNoValueFallbackToKey = options.missingKeyNoValueFallbackToKey || this.options.missingKeyNoValueFallbackToKey;
  616. const resForMissing = missingKeyNoValueFallbackToKey && usedKey ? undefined : res;
  617. const updateMissing = hasDefaultValue && defaultValue !== res && this.options.updateMissing;
  618. if (usedKey || usedDefault || updateMissing) {
  619. this.logger.log(updateMissing ? 'updateKey' : 'missingKey', lng, namespace, key, updateMissing ? defaultValue : res);
  620. if (keySeparator) {
  621. const fk = this.resolve(key, {
  622. ...options,
  623. keySeparator: false
  624. });
  625. if (fk && fk.res) this.logger.warn('Seems the loaded translations were in flat JSON format instead of nested. Either set keySeparator: false on init or make sure your translations are published in nested format.');
  626. }
  627. let lngs = [];
  628. const fallbackLngs = this.languageUtils.getFallbackCodes(this.options.fallbackLng, options.lng || this.language);
  629. if (this.options.saveMissingTo === 'fallback' && fallbackLngs && fallbackLngs[0]) {
  630. for (let i = 0; i < fallbackLngs.length; i++) {
  631. lngs.push(fallbackLngs[i]);
  632. }
  633. } else if (this.options.saveMissingTo === 'all') {
  634. lngs = this.languageUtils.toResolveHierarchy(options.lng || this.language);
  635. } else {
  636. lngs.push(options.lng || this.language);
  637. }
  638. const send = (l, k, specificDefaultValue) => {
  639. const defaultForMissing = hasDefaultValue && specificDefaultValue !== res ? specificDefaultValue : resForMissing;
  640. if (this.options.missingKeyHandler) {
  641. this.options.missingKeyHandler(l, namespace, k, defaultForMissing, updateMissing, options);
  642. } else if (this.backendConnector && this.backendConnector.saveMissing) {
  643. this.backendConnector.saveMissing(l, namespace, k, defaultForMissing, updateMissing, options);
  644. }
  645. this.emit('missingKey', l, namespace, k, res);
  646. };
  647. if (this.options.saveMissing) {
  648. if (this.options.saveMissingPlurals && needsPluralHandling) {
  649. lngs.forEach(language => {
  650. const suffixes = this.pluralResolver.getSuffixes(language, options);
  651. if (needsZeroSuffixLookup && options[`defaultValue${this.options.pluralSeparator}zero`] && suffixes.indexOf(`${this.options.pluralSeparator}zero`) < 0) {
  652. suffixes.push(`${this.options.pluralSeparator}zero`);
  653. }
  654. suffixes.forEach(suffix => {
  655. send([language], key + suffix, options[`defaultValue${suffix}`] || defaultValue);
  656. });
  657. });
  658. } else {
  659. send(lngs, key, defaultValue);
  660. }
  661. }
  662. }
  663. res = this.extendTranslation(res, keys, options, resolved, lastKey);
  664. if (usedKey && res === key && this.options.appendNamespaceToMissingKey) res = `${namespace}:${key}`;
  665. if ((usedKey || usedDefault) && this.options.parseMissingKeyHandler) {
  666. if (this.options.compatibilityAPI !== 'v1') {
  667. res = this.options.parseMissingKeyHandler(this.options.appendNamespaceToMissingKey ? `${namespace}:${key}` : key, usedDefault ? res : undefined);
  668. } else {
  669. res = this.options.parseMissingKeyHandler(res);
  670. }
  671. }
  672. }
  673. if (returnDetails) {
  674. resolved.res = res;
  675. resolved.usedParams = this.getUsedParamsDetails(options);
  676. return resolved;
  677. }
  678. return res;
  679. }
  680. extendTranslation(res, key, options, resolved, lastKey) {
  681. var _this = this;
  682. if (this.i18nFormat && this.i18nFormat.parse) {
  683. res = this.i18nFormat.parse(res, {
  684. ...this.options.interpolation.defaultVariables,
  685. ...options
  686. }, options.lng || this.language || resolved.usedLng, resolved.usedNS, resolved.usedKey, {
  687. resolved
  688. });
  689. } else if (!options.skipInterpolation) {
  690. if (options.interpolation) this.interpolator.init({
  691. ...options,
  692. ...{
  693. interpolation: {
  694. ...this.options.interpolation,
  695. ...options.interpolation
  696. }
  697. }
  698. });
  699. const skipOnVariables = isString(res) && (options && options.interpolation && options.interpolation.skipOnVariables !== undefined ? options.interpolation.skipOnVariables : this.options.interpolation.skipOnVariables);
  700. let nestBef;
  701. if (skipOnVariables) {
  702. const nb = res.match(this.interpolator.nestingRegexp);
  703. nestBef = nb && nb.length;
  704. }
  705. let data = options.replace && !isString(options.replace) ? options.replace : options;
  706. if (this.options.interpolation.defaultVariables) data = {
  707. ...this.options.interpolation.defaultVariables,
  708. ...data
  709. };
  710. res = this.interpolator.interpolate(res, data, options.lng || this.language || resolved.usedLng, options);
  711. if (skipOnVariables) {
  712. const na = res.match(this.interpolator.nestingRegexp);
  713. const nestAft = na && na.length;
  714. if (nestBef < nestAft) options.nest = false;
  715. }
  716. if (!options.lng && this.options.compatibilityAPI !== 'v1' && resolved && resolved.res) options.lng = this.language || resolved.usedLng;
  717. if (options.nest !== false) res = this.interpolator.nest(res, function () {
  718. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  719. args[_key] = arguments[_key];
  720. }
  721. if (lastKey && lastKey[0] === args[0] && !options.context) {
  722. _this.logger.warn(`It seems you are nesting recursively key: ${args[0]} in key: ${key[0]}`);
  723. return null;
  724. }
  725. return _this.translate(...args, key);
  726. }, options);
  727. if (options.interpolation) this.interpolator.reset();
  728. }
  729. const postProcess = options.postProcess || this.options.postProcess;
  730. const postProcessorNames = isString(postProcess) ? [postProcess] : postProcess;
  731. if (res !== undefined && res !== null && postProcessorNames && postProcessorNames.length && options.applyPostProcessor !== false) {
  732. res = postProcessor.handle(postProcessorNames, res, key, this.options && this.options.postProcessPassResolved ? {
  733. i18nResolved: {
  734. ...resolved,
  735. usedParams: this.getUsedParamsDetails(options)
  736. },
  737. ...options
  738. } : options, this);
  739. }
  740. return res;
  741. }
  742. resolve(keys) {
  743. let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  744. let found;
  745. let usedKey;
  746. let exactUsedKey;
  747. let usedLng;
  748. let usedNS;
  749. if (isString(keys)) keys = [keys];
  750. keys.forEach(k => {
  751. if (this.isValidLookup(found)) return;
  752. const extracted = this.extractFromKey(k, options);
  753. const key = extracted.key;
  754. usedKey = key;
  755. let namespaces = extracted.namespaces;
  756. if (this.options.fallbackNS) namespaces = namespaces.concat(this.options.fallbackNS);
  757. const needsPluralHandling = options.count !== undefined && !isString(options.count);
  758. const needsZeroSuffixLookup = needsPluralHandling && !options.ordinal && options.count === 0 && this.pluralResolver.shouldUseIntlApi();
  759. const needsContextHandling = options.context !== undefined && (isString(options.context) || typeof options.context === 'number') && options.context !== '';
  760. const codes = options.lngs ? options.lngs : this.languageUtils.toResolveHierarchy(options.lng || this.language, options.fallbackLng);
  761. namespaces.forEach(ns => {
  762. if (this.isValidLookup(found)) return;
  763. usedNS = ns;
  764. if (!checkedLoadedFor[`${codes[0]}-${ns}`] && this.utils && this.utils.hasLoadedNamespace && !this.utils.hasLoadedNamespace(usedNS)) {
  765. checkedLoadedFor[`${codes[0]}-${ns}`] = true;
  766. this.logger.warn(`key "${usedKey}" for languages "${codes.join(', ')}" won't get resolved as namespace "${usedNS}" was not yet loaded`, 'This means something IS WRONG in your setup. You access the t function before i18next.init / i18next.loadNamespace / i18next.changeLanguage was done. Wait for the callback or Promise to resolve before accessing it!!!');
  767. }
  768. codes.forEach(code => {
  769. if (this.isValidLookup(found)) return;
  770. usedLng = code;
  771. const finalKeys = [key];
  772. if (this.i18nFormat && this.i18nFormat.addLookupKeys) {
  773. this.i18nFormat.addLookupKeys(finalKeys, key, code, ns, options);
  774. } else {
  775. let pluralSuffix;
  776. if (needsPluralHandling) pluralSuffix = this.pluralResolver.getSuffix(code, options.count, options);
  777. const zeroSuffix = `${this.options.pluralSeparator}zero`;
  778. const ordinalPrefix = `${this.options.pluralSeparator}ordinal${this.options.pluralSeparator}`;
  779. if (needsPluralHandling) {
  780. finalKeys.push(key + pluralSuffix);
  781. if (options.ordinal && pluralSuffix.indexOf(ordinalPrefix) === 0) {
  782. finalKeys.push(key + pluralSuffix.replace(ordinalPrefix, this.options.pluralSeparator));
  783. }
  784. if (needsZeroSuffixLookup) {
  785. finalKeys.push(key + zeroSuffix);
  786. }
  787. }
  788. if (needsContextHandling) {
  789. const contextKey = `${key}${this.options.contextSeparator}${options.context}`;
  790. finalKeys.push(contextKey);
  791. if (needsPluralHandling) {
  792. finalKeys.push(contextKey + pluralSuffix);
  793. if (options.ordinal && pluralSuffix.indexOf(ordinalPrefix) === 0) {
  794. finalKeys.push(contextKey + pluralSuffix.replace(ordinalPrefix, this.options.pluralSeparator));
  795. }
  796. if (needsZeroSuffixLookup) {
  797. finalKeys.push(contextKey + zeroSuffix);
  798. }
  799. }
  800. }
  801. }
  802. let possibleKey;
  803. while (possibleKey = finalKeys.pop()) {
  804. if (!this.isValidLookup(found)) {
  805. exactUsedKey = possibleKey;
  806. found = this.getResource(code, ns, possibleKey, options);
  807. }
  808. }
  809. });
  810. });
  811. });
  812. return {
  813. res: found,
  814. usedKey,
  815. exactUsedKey,
  816. usedLng,
  817. usedNS
  818. };
  819. }
  820. isValidLookup(res) {
  821. return res !== undefined && !(!this.options.returnNull && res === null) && !(!this.options.returnEmptyString && res === '');
  822. }
  823. getResource(code, ns, key) {
  824. let options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
  825. if (this.i18nFormat && this.i18nFormat.getResource) return this.i18nFormat.getResource(code, ns, key, options);
  826. return this.resourceStore.getResource(code, ns, key, options);
  827. }
  828. getUsedParamsDetails() {
  829. let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  830. const optionsKeys = ['defaultValue', 'ordinal', 'context', 'replace', 'lng', 'lngs', 'fallbackLng', 'ns', 'keySeparator', 'nsSeparator', 'returnObjects', 'returnDetails', 'joinArrays', 'postProcess', 'interpolation'];
  831. const useOptionsReplaceForData = options.replace && !isString(options.replace);
  832. let data = useOptionsReplaceForData ? options.replace : options;
  833. if (useOptionsReplaceForData && typeof options.count !== 'undefined') {
  834. data.count = options.count;
  835. }
  836. if (this.options.interpolation.defaultVariables) {
  837. data = {
  838. ...this.options.interpolation.defaultVariables,
  839. ...data
  840. };
  841. }
  842. if (!useOptionsReplaceForData) {
  843. data = {
  844. ...data
  845. };
  846. for (const key of optionsKeys) {
  847. delete data[key];
  848. }
  849. }
  850. return data;
  851. }
  852. static hasDefaultValue(options) {
  853. const prefix = 'defaultValue';
  854. for (const option in options) {
  855. if (Object.prototype.hasOwnProperty.call(options, option) && prefix === option.substring(0, prefix.length) && undefined !== options[option]) {
  856. return true;
  857. }
  858. }
  859. return false;
  860. }
  861. }
  862. const capitalize = string => string.charAt(0).toUpperCase() + string.slice(1);
  863. class LanguageUtil {
  864. constructor(options) {
  865. this.options = options;
  866. this.supportedLngs = this.options.supportedLngs || false;
  867. this.logger = baseLogger.create('languageUtils');
  868. }
  869. getScriptPartFromCode(code) {
  870. code = getCleanedCode(code);
  871. if (!code || code.indexOf('-') < 0) return null;
  872. const p = code.split('-');
  873. if (p.length === 2) return null;
  874. p.pop();
  875. if (p[p.length - 1].toLowerCase() === 'x') return null;
  876. return this.formatLanguageCode(p.join('-'));
  877. }
  878. getLanguagePartFromCode(code) {
  879. code = getCleanedCode(code);
  880. if (!code || code.indexOf('-') < 0) return code;
  881. const p = code.split('-');
  882. return this.formatLanguageCode(p[0]);
  883. }
  884. formatLanguageCode(code) {
  885. if (isString(code) && code.indexOf('-') > -1) {
  886. if (typeof Intl !== 'undefined' && typeof Intl.getCanonicalLocales !== 'undefined') {
  887. try {
  888. let formattedCode = Intl.getCanonicalLocales(code)[0];
  889. if (formattedCode && this.options.lowerCaseLng) {
  890. formattedCode = formattedCode.toLowerCase();
  891. }
  892. if (formattedCode) return formattedCode;
  893. } catch (e) {}
  894. }
  895. const specialCases = ['hans', 'hant', 'latn', 'cyrl', 'cans', 'mong', 'arab'];
  896. let p = code.split('-');
  897. if (this.options.lowerCaseLng) {
  898. p = p.map(part => part.toLowerCase());
  899. } else if (p.length === 2) {
  900. p[0] = p[0].toLowerCase();
  901. p[1] = p[1].toUpperCase();
  902. if (specialCases.indexOf(p[1].toLowerCase()) > -1) p[1] = capitalize(p[1].toLowerCase());
  903. } else if (p.length === 3) {
  904. p[0] = p[0].toLowerCase();
  905. if (p[1].length === 2) p[1] = p[1].toUpperCase();
  906. if (p[0] !== 'sgn' && p[2].length === 2) p[2] = p[2].toUpperCase();
  907. if (specialCases.indexOf(p[1].toLowerCase()) > -1) p[1] = capitalize(p[1].toLowerCase());
  908. if (specialCases.indexOf(p[2].toLowerCase()) > -1) p[2] = capitalize(p[2].toLowerCase());
  909. }
  910. return p.join('-');
  911. }
  912. return this.options.cleanCode || this.options.lowerCaseLng ? code.toLowerCase() : code;
  913. }
  914. isSupportedCode(code) {
  915. if (this.options.load === 'languageOnly' || this.options.nonExplicitSupportedLngs) {
  916. code = this.getLanguagePartFromCode(code);
  917. }
  918. return !this.supportedLngs || !this.supportedLngs.length || this.supportedLngs.indexOf(code) > -1;
  919. }
  920. getBestMatchFromCodes(codes) {
  921. if (!codes) return null;
  922. let found;
  923. codes.forEach(code => {
  924. if (found) return;
  925. const cleanedLng = this.formatLanguageCode(code);
  926. if (!this.options.supportedLngs || this.isSupportedCode(cleanedLng)) found = cleanedLng;
  927. });
  928. if (!found && this.options.supportedLngs) {
  929. codes.forEach(code => {
  930. if (found) return;
  931. const lngOnly = this.getLanguagePartFromCode(code);
  932. if (this.isSupportedCode(lngOnly)) return found = lngOnly;
  933. found = this.options.supportedLngs.find(supportedLng => {
  934. if (supportedLng === lngOnly) return supportedLng;
  935. if (supportedLng.indexOf('-') < 0 && lngOnly.indexOf('-') < 0) return;
  936. if (supportedLng.indexOf('-') > 0 && lngOnly.indexOf('-') < 0 && supportedLng.substring(0, supportedLng.indexOf('-')) === lngOnly) return supportedLng;
  937. if (supportedLng.indexOf(lngOnly) === 0 && lngOnly.length > 1) return supportedLng;
  938. });
  939. });
  940. }
  941. if (!found) found = this.getFallbackCodes(this.options.fallbackLng)[0];
  942. return found;
  943. }
  944. getFallbackCodes(fallbacks, code) {
  945. if (!fallbacks) return [];
  946. if (typeof fallbacks === 'function') fallbacks = fallbacks(code);
  947. if (isString(fallbacks)) fallbacks = [fallbacks];
  948. if (Array.isArray(fallbacks)) return fallbacks;
  949. if (!code) return fallbacks.default || [];
  950. let found = fallbacks[code];
  951. if (!found) found = fallbacks[this.getScriptPartFromCode(code)];
  952. if (!found) found = fallbacks[this.formatLanguageCode(code)];
  953. if (!found) found = fallbacks[this.getLanguagePartFromCode(code)];
  954. if (!found) found = fallbacks.default;
  955. return found || [];
  956. }
  957. toResolveHierarchy(code, fallbackCode) {
  958. const fallbackCodes = this.getFallbackCodes(fallbackCode || this.options.fallbackLng || [], code);
  959. const codes = [];
  960. const addCode = c => {
  961. if (!c) return;
  962. if (this.isSupportedCode(c)) {
  963. codes.push(c);
  964. } else {
  965. this.logger.warn(`rejecting language code not found in supportedLngs: ${c}`);
  966. }
  967. };
  968. if (isString(code) && (code.indexOf('-') > -1 || code.indexOf('_') > -1)) {
  969. if (this.options.load !== 'languageOnly') addCode(this.formatLanguageCode(code));
  970. if (this.options.load !== 'languageOnly' && this.options.load !== 'currentOnly') addCode(this.getScriptPartFromCode(code));
  971. if (this.options.load !== 'currentOnly') addCode(this.getLanguagePartFromCode(code));
  972. } else if (isString(code)) {
  973. addCode(this.formatLanguageCode(code));
  974. }
  975. fallbackCodes.forEach(fc => {
  976. if (codes.indexOf(fc) < 0) addCode(this.formatLanguageCode(fc));
  977. });
  978. return codes;
  979. }
  980. }
  981. let sets = [{
  982. lngs: ['ach', 'ak', 'am', 'arn', 'br', 'fil', 'gun', 'ln', 'mfe', 'mg', 'mi', 'oc', 'pt', 'pt-BR', 'tg', 'tl', 'ti', 'tr', 'uz', 'wa'],
  983. nr: [1, 2],
  984. fc: 1
  985. }, {
  986. lngs: ['af', 'an', 'ast', 'az', 'bg', 'bn', 'ca', 'da', 'de', 'dev', 'el', 'en', 'eo', 'es', 'et', 'eu', 'fi', 'fo', 'fur', 'fy', 'gl', 'gu', 'ha', 'hi', 'hu', 'hy', 'ia', 'it', 'kk', 'kn', 'ku', 'lb', 'mai', 'ml', 'mn', 'mr', 'nah', 'nap', 'nb', 'ne', 'nl', 'nn', 'no', 'nso', 'pa', 'pap', 'pms', 'ps', 'pt-PT', 'rm', 'sco', 'se', 'si', 'so', 'son', 'sq', 'sv', 'sw', 'ta', 'te', 'tk', 'ur', 'yo'],
  987. nr: [1, 2],
  988. fc: 2
  989. }, {
  990. lngs: ['ay', 'bo', 'cgg', 'fa', 'ht', 'id', 'ja', 'jbo', 'ka', 'km', 'ko', 'ky', 'lo', 'ms', 'sah', 'su', 'th', 'tt', 'ug', 'vi', 'wo', 'zh'],
  991. nr: [1],
  992. fc: 3
  993. }, {
  994. lngs: ['be', 'bs', 'cnr', 'dz', 'hr', 'ru', 'sr', 'uk'],
  995. nr: [1, 2, 5],
  996. fc: 4
  997. }, {
  998. lngs: ['ar'],
  999. nr: [0, 1, 2, 3, 11, 100],
  1000. fc: 5
  1001. }, {
  1002. lngs: ['cs', 'sk'],
  1003. nr: [1, 2, 5],
  1004. fc: 6
  1005. }, {
  1006. lngs: ['csb', 'pl'],
  1007. nr: [1, 2, 5],
  1008. fc: 7
  1009. }, {
  1010. lngs: ['cy'],
  1011. nr: [1, 2, 3, 8],
  1012. fc: 8
  1013. }, {
  1014. lngs: ['fr'],
  1015. nr: [1, 2],
  1016. fc: 9
  1017. }, {
  1018. lngs: ['ga'],
  1019. nr: [1, 2, 3, 7, 11],
  1020. fc: 10
  1021. }, {
  1022. lngs: ['gd'],
  1023. nr: [1, 2, 3, 20],
  1024. fc: 11
  1025. }, {
  1026. lngs: ['is'],
  1027. nr: [1, 2],
  1028. fc: 12
  1029. }, {
  1030. lngs: ['jv'],
  1031. nr: [0, 1],
  1032. fc: 13
  1033. }, {
  1034. lngs: ['kw'],
  1035. nr: [1, 2, 3, 4],
  1036. fc: 14
  1037. }, {
  1038. lngs: ['lt'],
  1039. nr: [1, 2, 10],
  1040. fc: 15
  1041. }, {
  1042. lngs: ['lv'],
  1043. nr: [1, 2, 0],
  1044. fc: 16
  1045. }, {
  1046. lngs: ['mk'],
  1047. nr: [1, 2],
  1048. fc: 17
  1049. }, {
  1050. lngs: ['mnk'],
  1051. nr: [0, 1, 2],
  1052. fc: 18
  1053. }, {
  1054. lngs: ['mt'],
  1055. nr: [1, 2, 11, 20],
  1056. fc: 19
  1057. }, {
  1058. lngs: ['or'],
  1059. nr: [2, 1],
  1060. fc: 2
  1061. }, {
  1062. lngs: ['ro'],
  1063. nr: [1, 2, 20],
  1064. fc: 20
  1065. }, {
  1066. lngs: ['sl'],
  1067. nr: [5, 1, 2, 3],
  1068. fc: 21
  1069. }, {
  1070. lngs: ['he', 'iw'],
  1071. nr: [1, 2, 20, 21],
  1072. fc: 22
  1073. }];
  1074. let _rulesPluralsTypes = {
  1075. 1: n => Number(n > 1),
  1076. 2: n => Number(n != 1),
  1077. 3: n => 0,
  1078. 4: n => Number(n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2),
  1079. 5: n => Number(n == 0 ? 0 : n == 1 ? 1 : n == 2 ? 2 : n % 100 >= 3 && n % 100 <= 10 ? 3 : n % 100 >= 11 ? 4 : 5),
  1080. 6: n => Number(n == 1 ? 0 : n >= 2 && n <= 4 ? 1 : 2),
  1081. 7: n => Number(n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2),
  1082. 8: n => Number(n == 1 ? 0 : n == 2 ? 1 : n != 8 && n != 11 ? 2 : 3),
  1083. 9: n => Number(n >= 2),
  1084. 10: n => Number(n == 1 ? 0 : n == 2 ? 1 : n < 7 ? 2 : n < 11 ? 3 : 4),
  1085. 11: n => Number(n == 1 || n == 11 ? 0 : n == 2 || n == 12 ? 1 : n > 2 && n < 20 ? 2 : 3),
  1086. 12: n => Number(n % 10 != 1 || n % 100 == 11),
  1087. 13: n => Number(n !== 0),
  1088. 14: n => Number(n == 1 ? 0 : n == 2 ? 1 : n == 3 ? 2 : 3),
  1089. 15: n => Number(n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2),
  1090. 16: n => Number(n % 10 == 1 && n % 100 != 11 ? 0 : n !== 0 ? 1 : 2),
  1091. 17: n => Number(n == 1 || n % 10 == 1 && n % 100 != 11 ? 0 : 1),
  1092. 18: n => Number(n == 0 ? 0 : n == 1 ? 1 : 2),
  1093. 19: n => Number(n == 1 ? 0 : n == 0 || n % 100 > 1 && n % 100 < 11 ? 1 : n % 100 > 10 && n % 100 < 20 ? 2 : 3),
  1094. 20: n => Number(n == 1 ? 0 : n == 0 || n % 100 > 0 && n % 100 < 20 ? 1 : 2),
  1095. 21: n => Number(n % 100 == 1 ? 1 : n % 100 == 2 ? 2 : n % 100 == 3 || n % 100 == 4 ? 3 : 0),
  1096. 22: n => Number(n == 1 ? 0 : n == 2 ? 1 : (n < 0 || n > 10) && n % 10 == 0 ? 2 : 3)
  1097. };
  1098. const nonIntlVersions = ['v1', 'v2', 'v3'];
  1099. const intlVersions = ['v4'];
  1100. const suffixesOrder = {
  1101. zero: 0,
  1102. one: 1,
  1103. two: 2,
  1104. few: 3,
  1105. many: 4,
  1106. other: 5
  1107. };
  1108. const createRules = () => {
  1109. const rules = {};
  1110. sets.forEach(set => {
  1111. set.lngs.forEach(l => {
  1112. rules[l] = {
  1113. numbers: set.nr,
  1114. plurals: _rulesPluralsTypes[set.fc]
  1115. };
  1116. });
  1117. });
  1118. return rules;
  1119. };
  1120. class PluralResolver {
  1121. constructor(languageUtils) {
  1122. let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  1123. this.languageUtils = languageUtils;
  1124. this.options = options;
  1125. this.logger = baseLogger.create('pluralResolver');
  1126. if ((!this.options.compatibilityJSON || intlVersions.includes(this.options.compatibilityJSON)) && (typeof Intl === 'undefined' || !Intl.PluralRules)) {
  1127. this.options.compatibilityJSON = 'v3';
  1128. this.logger.error('Your environment seems not to be Intl API compatible, use an Intl.PluralRules polyfill. Will fallback to the compatibilityJSON v3 format handling.');
  1129. }
  1130. this.rules = createRules();
  1131. this.pluralRulesCache = {};
  1132. }
  1133. addRule(lng, obj) {
  1134. this.rules[lng] = obj;
  1135. }
  1136. clearCache() {
  1137. this.pluralRulesCache = {};
  1138. }
  1139. getRule(code) {
  1140. let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  1141. if (this.shouldUseIntlApi()) {
  1142. const cleanedCode = getCleanedCode(code === 'dev' ? 'en' : code);
  1143. const type = options.ordinal ? 'ordinal' : 'cardinal';
  1144. const cacheKey = JSON.stringify({
  1145. cleanedCode,
  1146. type
  1147. });
  1148. if (cacheKey in this.pluralRulesCache) {
  1149. return this.pluralRulesCache[cacheKey];
  1150. }
  1151. let rule;
  1152. try {
  1153. rule = new Intl.PluralRules(cleanedCode, {
  1154. type
  1155. });
  1156. } catch (err) {
  1157. if (!code.match(/-|_/)) return;
  1158. const lngPart = this.languageUtils.getLanguagePartFromCode(code);
  1159. rule = this.getRule(lngPart, options);
  1160. }
  1161. this.pluralRulesCache[cacheKey] = rule;
  1162. return rule;
  1163. }
  1164. return this.rules[code] || this.rules[this.languageUtils.getLanguagePartFromCode(code)];
  1165. }
  1166. needsPlural(code) {
  1167. let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  1168. const rule = this.getRule(code, options);
  1169. if (this.shouldUseIntlApi()) {
  1170. return rule && rule.resolvedOptions().pluralCategories.length > 1;
  1171. }
  1172. return rule && rule.numbers.length > 1;
  1173. }
  1174. getPluralFormsOfKey(code, key) {
  1175. let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
  1176. return this.getSuffixes(code, options).map(suffix => `${key}${suffix}`);
  1177. }
  1178. getSuffixes(code) {
  1179. let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  1180. const rule = this.getRule(code, options);
  1181. if (!rule) {
  1182. return [];
  1183. }
  1184. if (this.shouldUseIntlApi()) {
  1185. return rule.resolvedOptions().pluralCategories.sort((pluralCategory1, pluralCategory2) => suffixesOrder[pluralCategory1] - suffixesOrder[pluralCategory2]).map(pluralCategory => `${this.options.prepend}${options.ordinal ? `ordinal${this.options.prepend}` : ''}${pluralCategory}`);
  1186. }
  1187. return rule.numbers.map(number => this.getSuffix(code, number, options));
  1188. }
  1189. getSuffix(code, count) {
  1190. let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
  1191. const rule = this.getRule(code, options);
  1192. if (rule) {
  1193. if (this.shouldUseIntlApi()) {
  1194. return `${this.options.prepend}${options.ordinal ? `ordinal${this.options.prepend}` : ''}${rule.select(count)}`;
  1195. }
  1196. return this.getSuffixRetroCompatible(rule, count);
  1197. }
  1198. this.logger.warn(`no plural rule found for: ${code}`);
  1199. return '';
  1200. }
  1201. getSuffixRetroCompatible(rule, count) {
  1202. const idx = rule.noAbs ? rule.plurals(count) : rule.plurals(Math.abs(count));
  1203. let suffix = rule.numbers[idx];
  1204. if (this.options.simplifyPluralSuffix && rule.numbers.length === 2 && rule.numbers[0] === 1) {
  1205. if (suffix === 2) {
  1206. suffix = 'plural';
  1207. } else if (suffix === 1) {
  1208. suffix = '';
  1209. }
  1210. }
  1211. const returnSuffix = () => this.options.prepend && suffix.toString() ? this.options.prepend + suffix.toString() : suffix.toString();
  1212. if (this.options.compatibilityJSON === 'v1') {
  1213. if (suffix === 1) return '';
  1214. if (typeof suffix === 'number') return `_plural_${suffix.toString()}`;
  1215. return returnSuffix();
  1216. } else if (this.options.compatibilityJSON === 'v2') {
  1217. return returnSuffix();
  1218. } else if (this.options.simplifyPluralSuffix && rule.numbers.length === 2 && rule.numbers[0] === 1) {
  1219. return returnSuffix();
  1220. }
  1221. return this.options.prepend && idx.toString() ? this.options.prepend + idx.toString() : idx.toString();
  1222. }
  1223. shouldUseIntlApi() {
  1224. return !nonIntlVersions.includes(this.options.compatibilityJSON);
  1225. }
  1226. }
  1227. const deepFindWithDefaults = function (data, defaultData, key) {
  1228. let keySeparator = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : '.';
  1229. let ignoreJSONStructure = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true;
  1230. let path = getPathWithDefaults(data, defaultData, key);
  1231. if (!path && ignoreJSONStructure && isString(key)) {
  1232. path = deepFind(data, key, keySeparator);
  1233. if (path === undefined) path = deepFind(defaultData, key, keySeparator);
  1234. }
  1235. return path;
  1236. };
  1237. const regexSafe = val => val.replace(/\$/g, '$$$$');
  1238. class Interpolator {
  1239. constructor() {
  1240. let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  1241. this.logger = baseLogger.create('interpolator');
  1242. this.options = options;
  1243. this.format = options.interpolation && options.interpolation.format || (value => value);
  1244. this.init(options);
  1245. }
  1246. init() {
  1247. let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  1248. if (!options.interpolation) options.interpolation = {
  1249. escapeValue: true
  1250. };
  1251. const {
  1252. escape: escape$1,
  1253. escapeValue,
  1254. useRawValueToEscape,
  1255. prefix,
  1256. prefixEscaped,
  1257. suffix,
  1258. suffixEscaped,
  1259. formatSeparator,
  1260. unescapeSuffix,
  1261. unescapePrefix,
  1262. nestingPrefix,
  1263. nestingPrefixEscaped,
  1264. nestingSuffix,
  1265. nestingSuffixEscaped,
  1266. nestingOptionsSeparator,
  1267. maxReplaces,
  1268. alwaysFormat
  1269. } = options.interpolation;
  1270. this.escape = escape$1 !== undefined ? escape$1 : escape;
  1271. this.escapeValue = escapeValue !== undefined ? escapeValue : true;
  1272. this.useRawValueToEscape = useRawValueToEscape !== undefined ? useRawValueToEscape : false;
  1273. this.prefix = prefix ? regexEscape(prefix) : prefixEscaped || '{{';
  1274. this.suffix = suffix ? regexEscape(suffix) : suffixEscaped || '}}';
  1275. this.formatSeparator = formatSeparator || ',';
  1276. this.unescapePrefix = unescapeSuffix ? '' : unescapePrefix || '-';
  1277. this.unescapeSuffix = this.unescapePrefix ? '' : unescapeSuffix || '';
  1278. this.nestingPrefix = nestingPrefix ? regexEscape(nestingPrefix) : nestingPrefixEscaped || regexEscape('$t(');
  1279. this.nestingSuffix = nestingSuffix ? regexEscape(nestingSuffix) : nestingSuffixEscaped || regexEscape(')');
  1280. this.nestingOptionsSeparator = nestingOptionsSeparator || ',';
  1281. this.maxReplaces = maxReplaces || 1000;
  1282. this.alwaysFormat = alwaysFormat !== undefined ? alwaysFormat : false;
  1283. this.resetRegExp();
  1284. }
  1285. reset() {
  1286. if (this.options) this.init(this.options);
  1287. }
  1288. resetRegExp() {
  1289. const getOrResetRegExp = (existingRegExp, pattern) => {
  1290. if (existingRegExp && existingRegExp.source === pattern) {
  1291. existingRegExp.lastIndex = 0;
  1292. return existingRegExp;
  1293. }
  1294. return new RegExp(pattern, 'g');
  1295. };
  1296. this.regexp = getOrResetRegExp(this.regexp, `${this.prefix}(.+?)${this.suffix}`);
  1297. this.regexpUnescape = getOrResetRegExp(this.regexpUnescape, `${this.prefix}${this.unescapePrefix}(.+?)${this.unescapeSuffix}${this.suffix}`);
  1298. this.nestingRegexp = getOrResetRegExp(this.nestingRegexp, `${this.nestingPrefix}(.+?)${this.nestingSuffix}`);
  1299. }
  1300. interpolate(str, data, lng, options) {
  1301. let match;
  1302. let value;
  1303. let replaces;
  1304. const defaultData = this.options && this.options.interpolation && this.options.interpolation.defaultVariables || {};
  1305. const handleFormat = key => {
  1306. if (key.indexOf(this.formatSeparator) < 0) {
  1307. const path = deepFindWithDefaults(data, defaultData, key, this.options.keySeparator, this.options.ignoreJSONStructure);
  1308. return this.alwaysFormat ? this.format(path, undefined, lng, {
  1309. ...options,
  1310. ...data,
  1311. interpolationkey: key
  1312. }) : path;
  1313. }
  1314. const p = key.split(this.formatSeparator);
  1315. const k = p.shift().trim();
  1316. const f = p.join(this.formatSeparator).trim();
  1317. return this.format(deepFindWithDefaults(data, defaultData, k, this.options.keySeparator, this.options.ignoreJSONStructure), f, lng, {
  1318. ...options,
  1319. ...data,
  1320. interpolationkey: k
  1321. });
  1322. };
  1323. this.resetRegExp();
  1324. const missingInterpolationHandler = options && options.missingInterpolationHandler || this.options.missingInterpolationHandler;
  1325. const skipOnVariables = options && options.interpolation && options.interpolation.skipOnVariables !== undefined ? options.interpolation.skipOnVariables : this.options.interpolation.skipOnVariables;
  1326. const todos = [{
  1327. regex: this.regexpUnescape,
  1328. safeValue: val => regexSafe(val)
  1329. }, {
  1330. regex: this.regexp,
  1331. safeValue: val => this.escapeValue ? regexSafe(this.escape(val)) : regexSafe(val)
  1332. }];
  1333. todos.forEach(todo => {
  1334. replaces = 0;
  1335. while (match = todo.regex.exec(str)) {
  1336. const matchedVar = match[1].trim();
  1337. value = handleFormat(matchedVar);
  1338. if (value === undefined) {
  1339. if (typeof missingInterpolationHandler === 'function') {
  1340. const temp = missingInterpolationHandler(str, match, options);
  1341. value = isString(temp) ? temp : '';
  1342. } else if (options && Object.prototype.hasOwnProperty.call(options, matchedVar)) {
  1343. value = '';
  1344. } else if (skipOnVariables) {
  1345. value = match[0];
  1346. continue;
  1347. } else {
  1348. this.logger.warn(`missed to pass in variable ${matchedVar} for interpolating ${str}`);
  1349. value = '';
  1350. }
  1351. } else if (!isString(value) && !this.useRawValueToEscape) {
  1352. value = makeString(value);
  1353. }
  1354. const safeValue = todo.safeValue(value);
  1355. str = str.replace(match[0], safeValue);
  1356. if (skipOnVariables) {
  1357. todo.regex.lastIndex += value.length;
  1358. todo.regex.lastIndex -= match[0].length;
  1359. } else {
  1360. todo.regex.lastIndex = 0;
  1361. }
  1362. replaces++;
  1363. if (replaces >= this.maxReplaces) {
  1364. break;
  1365. }
  1366. }
  1367. });
  1368. return str;
  1369. }
  1370. nest(str, fc) {
  1371. let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
  1372. let match;
  1373. let value;
  1374. let clonedOptions;
  1375. const handleHasOptions = (key, inheritedOptions) => {
  1376. const sep = this.nestingOptionsSeparator;
  1377. if (key.indexOf(sep) < 0) return key;
  1378. const c = key.split(new RegExp(`${sep}[ ]*{`));
  1379. let optionsString = `{${c[1]}`;
  1380. key = c[0];
  1381. optionsString = this.interpolate(optionsString, clonedOptions);
  1382. const matchedSingleQuotes = optionsString.match(/'/g);
  1383. const matchedDoubleQuotes = optionsString.match(/"/g);
  1384. if (matchedSingleQuotes && matchedSingleQuotes.length % 2 === 0 && !matchedDoubleQuotes || matchedDoubleQuotes.length % 2 !== 0) {
  1385. optionsString = optionsString.replace(/'/g, '"');
  1386. }
  1387. try {
  1388. clonedOptions = JSON.parse(optionsString);
  1389. if (inheritedOptions) clonedOptions = {
  1390. ...inheritedOptions,
  1391. ...clonedOptions
  1392. };
  1393. } catch (e) {
  1394. this.logger.warn(`failed parsing options string in nesting for key ${key}`, e);
  1395. return `${key}${sep}${optionsString}`;
  1396. }
  1397. if (clonedOptions.defaultValue && clonedOptions.defaultValue.indexOf(this.prefix) > -1) delete clonedOptions.defaultValue;
  1398. return key;
  1399. };
  1400. while (match = this.nestingRegexp.exec(str)) {
  1401. let formatters = [];
  1402. clonedOptions = {
  1403. ...options
  1404. };
  1405. clonedOptions = clonedOptions.replace && !isString(clonedOptions.replace) ? clonedOptions.replace : clonedOptions;
  1406. clonedOptions.applyPostProcessor = false;
  1407. delete clonedOptions.defaultValue;
  1408. let doReduce = false;
  1409. if (match[0].indexOf(this.formatSeparator) !== -1 && !/{.*}/.test(match[1])) {
  1410. const r = match[1].split(this.formatSeparator).map(elem => elem.trim());
  1411. match[1] = r.shift();
  1412. formatters = r;
  1413. doReduce = true;
  1414. }
  1415. value = fc(handleHasOptions.call(this, match[1].trim(), clonedOptions), clonedOptions);
  1416. if (value && match[0] === str && !isString(value)) return value;
  1417. if (!isString(value)) value = makeString(value);
  1418. if (!value) {
  1419. this.logger.warn(`missed to resolve ${match[1]} for nesting ${str}`);
  1420. value = '';
  1421. }
  1422. if (doReduce) {
  1423. value = formatters.reduce((v, f) => this.format(v, f, options.lng, {
  1424. ...options,
  1425. interpolationkey: match[1].trim()
  1426. }), value.trim());
  1427. }
  1428. str = str.replace(match[0], value);
  1429. this.regexp.lastIndex = 0;
  1430. }
  1431. return str;
  1432. }
  1433. }
  1434. const parseFormatStr = formatStr => {
  1435. let formatName = formatStr.toLowerCase().trim();
  1436. const formatOptions = {};
  1437. if (formatStr.indexOf('(') > -1) {
  1438. const p = formatStr.split('(');
  1439. formatName = p[0].toLowerCase().trim();
  1440. const optStr = p[1].substring(0, p[1].length - 1);
  1441. if (formatName === 'currency' && optStr.indexOf(':') < 0) {
  1442. if (!formatOptions.currency) formatOptions.currency = optStr.trim();
  1443. } else if (formatName === 'relativetime' && optStr.indexOf(':') < 0) {
  1444. if (!formatOptions.range) formatOptions.range = optStr.trim();
  1445. } else {
  1446. const opts = optStr.split(';');
  1447. opts.forEach(opt => {
  1448. if (opt) {
  1449. const [key, ...rest] = opt.split(':');
  1450. const val = rest.join(':').trim().replace(/^'+|'+$/g, '');
  1451. const trimmedKey = key.trim();
  1452. if (!formatOptions[trimmedKey]) formatOptions[trimmedKey] = val;
  1453. if (val === 'false') formatOptions[trimmedKey] = false;
  1454. if (val === 'true') formatOptions[trimmedKey] = true;
  1455. if (!isNaN(val)) formatOptions[trimmedKey] = parseInt(val, 10);
  1456. }
  1457. });
  1458. }
  1459. }
  1460. return {
  1461. formatName,
  1462. formatOptions
  1463. };
  1464. };
  1465. const createCachedFormatter = fn => {
  1466. const cache = {};
  1467. return (val, lng, options) => {
  1468. let optForCache = options;
  1469. if (options && options.interpolationkey && options.formatParams && options.formatParams[options.interpolationkey] && options[options.interpolationkey]) {
  1470. optForCache = {
  1471. ...optForCache,
  1472. [options.interpolationkey]: undefined
  1473. };
  1474. }
  1475. const key = lng + JSON.stringify(optForCache);
  1476. let formatter = cache[key];
  1477. if (!formatter) {
  1478. formatter = fn(getCleanedCode(lng), options);
  1479. cache[key] = formatter;
  1480. }
  1481. return formatter(val);
  1482. };
  1483. };
  1484. class Formatter {
  1485. constructor() {
  1486. let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  1487. this.logger = baseLogger.create('formatter');
  1488. this.options = options;
  1489. this.formats = {
  1490. number: createCachedFormatter((lng, opt) => {
  1491. const formatter = new Intl.NumberFormat(lng, {
  1492. ...opt
  1493. });
  1494. return val => formatter.format(val);
  1495. }),
  1496. currency: createCachedFormatter((lng, opt) => {
  1497. const formatter = new Intl.NumberFormat(lng, {
  1498. ...opt,
  1499. style: 'currency'
  1500. });
  1501. return val => formatter.format(val);
  1502. }),
  1503. datetime: createCachedFormatter((lng, opt) => {
  1504. const formatter = new Intl.DateTimeFormat(lng, {
  1505. ...opt
  1506. });
  1507. return val => formatter.format(val);
  1508. }),
  1509. relativetime: createCachedFormatter((lng, opt) => {
  1510. const formatter = new Intl.RelativeTimeFormat(lng, {
  1511. ...opt
  1512. });
  1513. return val => formatter.format(val, opt.range || 'day');
  1514. }),
  1515. list: createCachedFormatter((lng, opt) => {
  1516. const formatter = new Intl.ListFormat(lng, {
  1517. ...opt
  1518. });
  1519. return val => formatter.format(val);
  1520. })
  1521. };
  1522. this.init(options);
  1523. }
  1524. init(services) {
  1525. let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
  1526. interpolation: {}
  1527. };
  1528. this.formatSeparator = options.interpolation.formatSeparator || ',';
  1529. }
  1530. add(name, fc) {
  1531. this.formats[name.toLowerCase().trim()] = fc;
  1532. }
  1533. addCached(name, fc) {
  1534. this.formats[name.toLowerCase().trim()] = createCachedFormatter(fc);
  1535. }
  1536. format(value, format, lng) {
  1537. let options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
  1538. const formats = format.split(this.formatSeparator);
  1539. if (formats.length > 1 && formats[0].indexOf('(') > 1 && formats[0].indexOf(')') < 0 && formats.find(f => f.indexOf(')') > -1)) {
  1540. const lastIndex = formats.findIndex(f => f.indexOf(')') > -1);
  1541. formats[0] = [formats[0], ...formats.splice(1, lastIndex)].join(this.formatSeparator);
  1542. }
  1543. const result = formats.reduce((mem, f) => {
  1544. const {
  1545. formatName,
  1546. formatOptions
  1547. } = parseFormatStr(f);
  1548. if (this.formats[formatName]) {
  1549. let formatted = mem;
  1550. try {
  1551. const valOptions = options && options.formatParams && options.formatParams[options.interpolationkey] || {};
  1552. const l = valOptions.locale || valOptions.lng || options.locale || options.lng || lng;
  1553. formatted = this.formats[formatName](mem, l, {
  1554. ...formatOptions,
  1555. ...options,
  1556. ...valOptions
  1557. });
  1558. } catch (error) {
  1559. this.logger.warn(error);
  1560. }
  1561. return formatted;
  1562. } else {
  1563. this.logger.warn(`there was no format function for ${formatName}`);
  1564. }
  1565. return mem;
  1566. }, value);
  1567. return result;
  1568. }
  1569. }
  1570. const removePending = (q, name) => {
  1571. if (q.pending[name] !== undefined) {
  1572. delete q.pending[name];
  1573. q.pendingCount--;
  1574. }
  1575. };
  1576. class Connector extends EventEmitter {
  1577. constructor(backend, store, services) {
  1578. let options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
  1579. super();
  1580. this.backend = backend;
  1581. this.store = store;
  1582. this.services = services;
  1583. this.languageUtils = services.languageUtils;
  1584. this.options = options;
  1585. this.logger = baseLogger.create('backendConnector');
  1586. this.waitingReads = [];
  1587. this.maxParallelReads = options.maxParallelReads || 10;
  1588. this.readingCalls = 0;
  1589. this.maxRetries = options.maxRetries >= 0 ? options.maxRetries : 5;
  1590. this.retryTimeout = options.retryTimeout >= 1 ? options.retryTimeout : 350;
  1591. this.state = {};
  1592. this.queue = [];
  1593. if (this.backend && this.backend.init) {
  1594. this.backend.init(services, options.backend, options);
  1595. }
  1596. }
  1597. queueLoad(languages, namespaces, options, callback) {
  1598. const toLoad = {};
  1599. const pending = {};
  1600. const toLoadLanguages = {};
  1601. const toLoadNamespaces = {};
  1602. languages.forEach(lng => {
  1603. let hasAllNamespaces = true;
  1604. namespaces.forEach(ns => {
  1605. const name = `${lng}|${ns}`;
  1606. if (!options.reload && this.store.hasResourceBundle(lng, ns)) {
  1607. this.state[name] = 2;
  1608. } else if (this.state[name] < 0) ; else if (this.state[name] === 1) {
  1609. if (pending[name] === undefined) pending[name] = true;
  1610. } else {
  1611. this.state[name] = 1;
  1612. hasAllNamespaces = false;
  1613. if (pending[name] === undefined) pending[name] = true;
  1614. if (toLoad[name] === undefined) toLoad[name] = true;
  1615. if (toLoadNamespaces[ns] === undefined) toLoadNamespaces[ns] = true;
  1616. }
  1617. });
  1618. if (!hasAllNamespaces) toLoadLanguages[lng] = true;
  1619. });
  1620. if (Object.keys(toLoad).length || Object.keys(pending).length) {
  1621. this.queue.push({
  1622. pending,
  1623. pendingCount: Object.keys(pending).length,
  1624. loaded: {},
  1625. errors: [],
  1626. callback
  1627. });
  1628. }
  1629. return {
  1630. toLoad: Object.keys(toLoad),
  1631. pending: Object.keys(pending),
  1632. toLoadLanguages: Object.keys(toLoadLanguages),
  1633. toLoadNamespaces: Object.keys(toLoadNamespaces)
  1634. };
  1635. }
  1636. loaded(name, err, data) {
  1637. const s = name.split('|');
  1638. const lng = s[0];
  1639. const ns = s[1];
  1640. if (err) this.emit('failedLoading', lng, ns, err);
  1641. if (!err && data) {
  1642. this.store.addResourceBundle(lng, ns, data, undefined, undefined, {
  1643. skipCopy: true
  1644. });
  1645. }
  1646. this.state[name] = err ? -1 : 2;
  1647. if (err && data) this.state[name] = 0;
  1648. const loaded = {};
  1649. this.queue.forEach(q => {
  1650. pushPath(q.loaded, [lng], ns);
  1651. removePending(q, name);
  1652. if (err) q.errors.push(err);
  1653. if (q.pendingCount === 0 && !q.done) {
  1654. Object.keys(q.loaded).forEach(l => {
  1655. if (!loaded[l]) loaded[l] = {};
  1656. const loadedKeys = q.loaded[l];
  1657. if (loadedKeys.length) {
  1658. loadedKeys.forEach(n => {
  1659. if (loaded[l][n] === undefined) loaded[l][n] = true;
  1660. });
  1661. }
  1662. });
  1663. q.done = true;
  1664. if (q.errors.length) {
  1665. q.callback(q.errors);
  1666. } else {
  1667. q.callback();
  1668. }
  1669. }
  1670. });
  1671. this.emit('loaded', loaded);
  1672. this.queue = this.queue.filter(q => !q.done);
  1673. }
  1674. read(lng, ns, fcName) {
  1675. let tried = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
  1676. let wait = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : this.retryTimeout;
  1677. let callback = arguments.length > 5 ? arguments[5] : undefined;
  1678. if (!lng.length) return callback(null, {});
  1679. if (this.readingCalls >= this.maxParallelReads) {
  1680. this.waitingReads.push({
  1681. lng,
  1682. ns,
  1683. fcName,
  1684. tried,
  1685. wait,
  1686. callback
  1687. });
  1688. return;
  1689. }
  1690. this.readingCalls++;
  1691. const resolver = (err, data) => {
  1692. this.readingCalls--;
  1693. if (this.waitingReads.length > 0) {
  1694. const next = this.waitingReads.shift();
  1695. this.read(next.lng, next.ns, next.fcName, next.tried, next.wait, next.callback);
  1696. }
  1697. if (err && data && tried < this.maxRetries) {
  1698. setTimeout(() => {
  1699. this.read.call(this, lng, ns, fcName, tried + 1, wait * 2, callback);
  1700. }, wait);
  1701. return;
  1702. }
  1703. callback(err, data);
  1704. };
  1705. const fc = this.backend[fcName].bind(this.backend);
  1706. if (fc.length === 2) {
  1707. try {
  1708. const r = fc(lng, ns);
  1709. if (r && typeof r.then === 'function') {
  1710. r.then(data => resolver(null, data)).catch(resolver);
  1711. } else {
  1712. resolver(null, r);
  1713. }
  1714. } catch (err) {
  1715. resolver(err);
  1716. }
  1717. return;
  1718. }
  1719. return fc(lng, ns, resolver);
  1720. }
  1721. prepareLoading(languages, namespaces) {
  1722. let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
  1723. let callback = arguments.length > 3 ? arguments[3] : undefined;
  1724. if (!this.backend) {
  1725. this.logger.warn('No backend was added via i18next.use. Will not load resources.');
  1726. return callback && callback();
  1727. }
  1728. if (isString(languages)) languages = this.languageUtils.toResolveHierarchy(languages);
  1729. if (isString(namespaces)) namespaces = [namespaces];
  1730. const toLoad = this.queueLoad(languages, namespaces, options, callback);
  1731. if (!toLoad.toLoad.length) {
  1732. if (!toLoad.pending.length) callback();
  1733. return null;
  1734. }
  1735. toLoad.toLoad.forEach(name => {
  1736. this.loadOne(name);
  1737. });
  1738. }
  1739. load(languages, namespaces, callback) {
  1740. this.prepareLoading(languages, namespaces, {}, callback);
  1741. }
  1742. reload(languages, namespaces, callback) {
  1743. this.prepareLoading(languages, namespaces, {
  1744. reload: true
  1745. }, callback);
  1746. }
  1747. loadOne(name) {
  1748. let prefix = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
  1749. const s = name.split('|');
  1750. const lng = s[0];
  1751. const ns = s[1];
  1752. this.read(lng, ns, 'read', undefined, undefined, (err, data) => {
  1753. if (err) this.logger.warn(`${prefix}loading namespace ${ns} for language ${lng} failed`, err);
  1754. if (!err && data) this.logger.log(`${prefix}loaded namespace ${ns} for language ${lng}`, data);
  1755. this.loaded(name, err, data);
  1756. });
  1757. }
  1758. saveMissing(languages, namespace, key, fallbackValue, isUpdate) {
  1759. let options = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : {};
  1760. let clb = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : () => {};
  1761. if (this.services.utils && this.services.utils.hasLoadedNamespace && !this.services.utils.hasLoadedNamespace(namespace)) {
  1762. this.logger.warn(`did not save key "${key}" as the namespace "${namespace}" was not yet loaded`, 'This means something IS WRONG in your setup. You access the t function before i18next.init / i18next.loadNamespace / i18next.changeLanguage was done. Wait for the callback or Promise to resolve before accessing it!!!');
  1763. return;
  1764. }
  1765. if (key === undefined || key === null || key === '') return;
  1766. if (this.backend && this.backend.create) {
  1767. const opts = {
  1768. ...options,
  1769. isUpdate
  1770. };
  1771. const fc = this.backend.create.bind(this.backend);
  1772. if (fc.length < 6) {
  1773. try {
  1774. let r;
  1775. if (fc.length === 5) {
  1776. r = fc(languages, namespace, key, fallbackValue, opts);
  1777. } else {
  1778. r = fc(languages, namespace, key, fallbackValue);
  1779. }
  1780. if (r && typeof r.then === 'function') {
  1781. r.then(data => clb(null, data)).catch(clb);
  1782. } else {
  1783. clb(null, r);
  1784. }
  1785. } catch (err) {
  1786. clb(err);
  1787. }
  1788. } else {
  1789. fc(languages, namespace, key, fallbackValue, clb, opts);
  1790. }
  1791. }
  1792. if (!languages || !languages[0]) return;
  1793. this.store.addResource(languages[0], namespace, key, fallbackValue);
  1794. }
  1795. }
  1796. const get = () => ({
  1797. debug: false,
  1798. initImmediate: true,
  1799. ns: ['translation'],
  1800. defaultNS: ['translation'],
  1801. fallbackLng: ['dev'],
  1802. fallbackNS: false,
  1803. supportedLngs: false,
  1804. nonExplicitSupportedLngs: false,
  1805. load: 'all',
  1806. preload: false,
  1807. simplifyPluralSuffix: true,
  1808. keySeparator: '.',
  1809. nsSeparator: ':',
  1810. pluralSeparator: '_',
  1811. contextSeparator: '_',
  1812. partialBundledLanguages: false,
  1813. saveMissing: false,
  1814. updateMissing: false,
  1815. saveMissingTo: 'fallback',
  1816. saveMissingPlurals: true,
  1817. missingKeyHandler: false,
  1818. missingInterpolationHandler: false,
  1819. postProcess: false,
  1820. postProcessPassResolved: false,
  1821. returnNull: false,
  1822. returnEmptyString: true,
  1823. returnObjects: false,
  1824. joinArrays: false,
  1825. returnedObjectHandler: false,
  1826. parseMissingKeyHandler: false,
  1827. appendNamespaceToMissingKey: false,
  1828. appendNamespaceToCIMode: false,
  1829. overloadTranslationOptionHandler: args => {
  1830. let ret = {};
  1831. if (typeof args[1] === 'object') ret = args[1];
  1832. if (isString(args[1])) ret.defaultValue = args[1];
  1833. if (isString(args[2])) ret.tDescription = args[2];
  1834. if (typeof args[2] === 'object' || typeof args[3] === 'object') {
  1835. const options = args[3] || args[2];
  1836. Object.keys(options).forEach(key => {
  1837. ret[key] = options[key];
  1838. });
  1839. }
  1840. return ret;
  1841. },
  1842. interpolation: {
  1843. escapeValue: true,
  1844. format: value => value,
  1845. prefix: '{{',
  1846. suffix: '}}',
  1847. formatSeparator: ',',
  1848. unescapePrefix: '-',
  1849. nestingPrefix: '$t(',
  1850. nestingSuffix: ')',
  1851. nestingOptionsSeparator: ',',
  1852. maxReplaces: 1000,
  1853. skipOnVariables: true
  1854. }
  1855. });
  1856. const transformOptions = options => {
  1857. if (isString(options.ns)) options.ns = [options.ns];
  1858. if (isString(options.fallbackLng)) options.fallbackLng = [options.fallbackLng];
  1859. if (isString(options.fallbackNS)) options.fallbackNS = [options.fallbackNS];
  1860. if (options.supportedLngs && options.supportedLngs.indexOf('cimode') < 0) {
  1861. options.supportedLngs = options.supportedLngs.concat(['cimode']);
  1862. }
  1863. return options;
  1864. };
  1865. const noop = () => {};
  1866. const bindMemberFunctions = inst => {
  1867. const mems = Object.getOwnPropertyNames(Object.getPrototypeOf(inst));
  1868. mems.forEach(mem => {
  1869. if (typeof inst[mem] === 'function') {
  1870. inst[mem] = inst[mem].bind(inst);
  1871. }
  1872. });
  1873. };
  1874. class I18n extends EventEmitter {
  1875. constructor() {
  1876. let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  1877. let callback = arguments.length > 1 ? arguments[1] : undefined;
  1878. super();
  1879. this.options = transformOptions(options);
  1880. this.services = {};
  1881. this.logger = baseLogger;
  1882. this.modules = {
  1883. external: []
  1884. };
  1885. bindMemberFunctions(this);
  1886. if (callback && !this.isInitialized && !options.isClone) {
  1887. if (!this.options.initImmediate) {
  1888. this.init(options, callback);
  1889. return this;
  1890. }
  1891. setTimeout(() => {
  1892. this.init(options, callback);
  1893. }, 0);
  1894. }
  1895. }
  1896. init() {
  1897. var _this = this;
  1898. let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  1899. let callback = arguments.length > 1 ? arguments[1] : undefined;
  1900. this.isInitializing = true;
  1901. if (typeof options === 'function') {
  1902. callback = options;
  1903. options = {};
  1904. }
  1905. if (!options.defaultNS && options.defaultNS !== false && options.ns) {
  1906. if (isString(options.ns)) {
  1907. options.defaultNS = options.ns;
  1908. } else if (options.ns.indexOf('translation') < 0) {
  1909. options.defaultNS = options.ns[0];
  1910. }
  1911. }
  1912. const defOpts = get();
  1913. this.options = {
  1914. ...defOpts,
  1915. ...this.options,
  1916. ...transformOptions(options)
  1917. };
  1918. if (this.options.compatibilityAPI !== 'v1') {
  1919. this.options.interpolation = {
  1920. ...defOpts.interpolation,
  1921. ...this.options.interpolation
  1922. };
  1923. }
  1924. if (options.keySeparator !== undefined) {
  1925. this.options.userDefinedKeySeparator = options.keySeparator;
  1926. }
  1927. if (options.nsSeparator !== undefined) {
  1928. this.options.userDefinedNsSeparator = options.nsSeparator;
  1929. }
  1930. const createClassOnDemand = ClassOrObject => {
  1931. if (!ClassOrObject) return null;
  1932. if (typeof ClassOrObject === 'function') return new ClassOrObject();
  1933. return ClassOrObject;
  1934. };
  1935. if (!this.options.isClone) {
  1936. if (this.modules.logger) {
  1937. baseLogger.init(createClassOnDemand(this.modules.logger), this.options);
  1938. } else {
  1939. baseLogger.init(null, this.options);
  1940. }
  1941. let formatter;
  1942. if (this.modules.formatter) {
  1943. formatter = this.modules.formatter;
  1944. } else if (typeof Intl !== 'undefined') {
  1945. formatter = Formatter;
  1946. }
  1947. const lu = new LanguageUtil(this.options);
  1948. this.store = new ResourceStore(this.options.resources, this.options);
  1949. const s = this.services;
  1950. s.logger = baseLogger;
  1951. s.resourceStore = this.store;
  1952. s.languageUtils = lu;
  1953. s.pluralResolver = new PluralResolver(lu, {
  1954. prepend: this.options.pluralSeparator,
  1955. compatibilityJSON: this.options.compatibilityJSON,
  1956. simplifyPluralSuffix: this.options.simplifyPluralSuffix
  1957. });
  1958. if (formatter && (!this.options.interpolation.format || this.options.interpolation.format === defOpts.interpolation.format)) {
  1959. s.formatter = createClassOnDemand(formatter);
  1960. s.formatter.init(s, this.options);
  1961. this.options.interpolation.format = s.formatter.format.bind(s.formatter);
  1962. }
  1963. s.interpolator = new Interpolator(this.options);
  1964. s.utils = {
  1965. hasLoadedNamespace: this.hasLoadedNamespace.bind(this)
  1966. };
  1967. s.backendConnector = new Connector(createClassOnDemand(this.modules.backend), s.resourceStore, s, this.options);
  1968. s.backendConnector.on('*', function (event) {
  1969. for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
  1970. args[_key - 1] = arguments[_key];
  1971. }
  1972. _this.emit(event, ...args);
  1973. });
  1974. if (this.modules.languageDetector) {
  1975. s.languageDetector = createClassOnDemand(this.modules.languageDetector);
  1976. if (s.languageDetector.init) s.languageDetector.init(s, this.options.detection, this.options);
  1977. }
  1978. if (this.modules.i18nFormat) {
  1979. s.i18nFormat = createClassOnDemand(this.modules.i18nFormat);
  1980. if (s.i18nFormat.init) s.i18nFormat.init(this);
  1981. }
  1982. this.translator = new Translator(this.services, this.options);
  1983. this.translator.on('*', function (event) {
  1984. for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
  1985. args[_key2 - 1] = arguments[_key2];
  1986. }
  1987. _this.emit(event, ...args);
  1988. });
  1989. this.modules.external.forEach(m => {
  1990. if (m.init) m.init(this);
  1991. });
  1992. }
  1993. this.format = this.options.interpolation.format;
  1994. if (!callback) callback = noop;
  1995. if (this.options.fallbackLng && !this.services.languageDetector && !this.options.lng) {
  1996. const codes = this.services.languageUtils.getFallbackCodes(this.options.fallbackLng);
  1997. if (codes.length > 0 && codes[0] !== 'dev') this.options.lng = codes[0];
  1998. }
  1999. if (!this.services.languageDetector && !this.options.lng) {
  2000. this.logger.warn('init: no languageDetector is used and no lng is defined');
  2001. }
  2002. const storeApi = ['getResource', 'hasResourceBundle', 'getResourceBundle', 'getDataByLanguage'];
  2003. storeApi.forEach(fcName => {
  2004. this[fcName] = function () {
  2005. return _this.store[fcName](...arguments);
  2006. };
  2007. });
  2008. const storeApiChained = ['addResource', 'addResources', 'addResourceBundle', 'removeResourceBundle'];
  2009. storeApiChained.forEach(fcName => {
  2010. this[fcName] = function () {
  2011. _this.store[fcName](...arguments);
  2012. return _this;
  2013. };
  2014. });
  2015. const deferred = defer();
  2016. const load = () => {
  2017. const finish = (err, t) => {
  2018. this.isInitializing = false;
  2019. if (this.isInitialized && !this.initializedStoreOnce) this.logger.warn('init: i18next is already initialized. You should call init just once!');
  2020. this.isInitialized = true;
  2021. if (!this.options.isClone) this.logger.log('initialized', this.options);
  2022. this.emit('initialized', this.options);
  2023. deferred.resolve(t);
  2024. callback(err, t);
  2025. };
  2026. if (this.languages && this.options.compatibilityAPI !== 'v1' && !this.isInitialized) return finish(null, this.t.bind(this));
  2027. this.changeLanguage(this.options.lng, finish);
  2028. };
  2029. if (this.options.resources || !this.options.initImmediate) {
  2030. load();
  2031. } else {
  2032. setTimeout(load, 0);
  2033. }
  2034. return deferred;
  2035. }
  2036. loadResources(language) {
  2037. let callback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : noop;
  2038. let usedCallback = callback;
  2039. const usedLng = isString(language) ? language : this.language;
  2040. if (typeof language === 'function') usedCallback = language;
  2041. if (!this.options.resources || this.options.partialBundledLanguages) {
  2042. if (usedLng && usedLng.toLowerCase() === 'cimode' && (!this.options.preload || this.options.preload.length === 0)) return usedCallback();
  2043. const toLoad = [];
  2044. const append = lng => {
  2045. if (!lng) return;
  2046. if (lng === 'cimode') return;
  2047. const lngs = this.services.languageUtils.toResolveHierarchy(lng);
  2048. lngs.forEach(l => {
  2049. if (l === 'cimode') return;
  2050. if (toLoad.indexOf(l) < 0) toLoad.push(l);
  2051. });
  2052. };
  2053. if (!usedLng) {
  2054. const fallbacks = this.services.languageUtils.getFallbackCodes(this.options.fallbackLng);
  2055. fallbacks.forEach(l => append(l));
  2056. } else {
  2057. append(usedLng);
  2058. }
  2059. if (this.options.preload) {
  2060. this.options.preload.forEach(l => append(l));
  2061. }
  2062. this.services.backendConnector.load(toLoad, this.options.ns, e => {
  2063. if (!e && !this.resolvedLanguage && this.language) this.setResolvedLanguage(this.language);
  2064. usedCallback(e);
  2065. });
  2066. } else {
  2067. usedCallback(null);
  2068. }
  2069. }
  2070. reloadResources(lngs, ns, callback) {
  2071. const deferred = defer();
  2072. if (typeof lngs === 'function') {
  2073. callback = lngs;
  2074. lngs = undefined;
  2075. }
  2076. if (typeof ns === 'function') {
  2077. callback = ns;
  2078. ns = undefined;
  2079. }
  2080. if (!lngs) lngs = this.languages;
  2081. if (!ns) ns = this.options.ns;
  2082. if (!callback) callback = noop;
  2083. this.services.backendConnector.reload(lngs, ns, err => {
  2084. deferred.resolve();
  2085. callback(err);
  2086. });
  2087. return deferred;
  2088. }
  2089. use(module) {
  2090. if (!module) throw new Error('You are passing an undefined module! Please check the object you are passing to i18next.use()');
  2091. if (!module.type) throw new Error('You are passing a wrong module! Please check the object you are passing to i18next.use()');
  2092. if (module.type === 'backend') {
  2093. this.modules.backend = module;
  2094. }
  2095. if (module.type === 'logger' || module.log && module.warn && module.error) {
  2096. this.modules.logger = module;
  2097. }
  2098. if (module.type === 'languageDetector') {
  2099. this.modules.languageDetector = module;
  2100. }
  2101. if (module.type === 'i18nFormat') {
  2102. this.modules.i18nFormat = module;
  2103. }
  2104. if (module.type === 'postProcessor') {
  2105. postProcessor.addPostProcessor(module);
  2106. }
  2107. if (module.type === 'formatter') {
  2108. this.modules.formatter = module;
  2109. }
  2110. if (module.type === '3rdParty') {
  2111. this.modules.external.push(module);
  2112. }
  2113. return this;
  2114. }
  2115. setResolvedLanguage(l) {
  2116. if (!l || !this.languages) return;
  2117. if (['cimode', 'dev'].indexOf(l) > -1) return;
  2118. for (let li = 0; li < this.languages.length; li++) {
  2119. const lngInLngs = this.languages[li];
  2120. if (['cimode', 'dev'].indexOf(lngInLngs) > -1) continue;
  2121. if (this.store.hasLanguageSomeTranslations(lngInLngs)) {
  2122. this.resolvedLanguage = lngInLngs;
  2123. break;
  2124. }
  2125. }
  2126. }
  2127. changeLanguage(lng, callback) {
  2128. var _this2 = this;
  2129. this.isLanguageChangingTo = lng;
  2130. const deferred = defer();
  2131. this.emit('languageChanging', lng);
  2132. const setLngProps = l => {
  2133. this.language = l;
  2134. this.languages = this.services.languageUtils.toResolveHierarchy(l);
  2135. this.resolvedLanguage = undefined;
  2136. this.setResolvedLanguage(l);
  2137. };
  2138. const done = (err, l) => {
  2139. if (l) {
  2140. setLngProps(l);
  2141. this.translator.changeLanguage(l);
  2142. this.isLanguageChangingTo = undefined;
  2143. this.emit('languageChanged', l);
  2144. this.logger.log('languageChanged', l);
  2145. } else {
  2146. this.isLanguageChangingTo = undefined;
  2147. }
  2148. deferred.resolve(function () {
  2149. return _this2.t(...arguments);
  2150. });
  2151. if (callback) callback(err, function () {
  2152. return _this2.t(...arguments);
  2153. });
  2154. };
  2155. const setLng = lngs => {
  2156. if (!lng && !lngs && this.services.languageDetector) lngs = [];
  2157. const l = isString(lngs) ? lngs : this.services.languageUtils.getBestMatchFromCodes(lngs);
  2158. if (l) {
  2159. if (!this.language) {
  2160. setLngProps(l);
  2161. }
  2162. if (!this.translator.language) this.translator.changeLanguage(l);
  2163. if (this.services.languageDetector && this.services.languageDetector.cacheUserLanguage) this.services.languageDetector.cacheUserLanguage(l);
  2164. }
  2165. this.loadResources(l, err => {
  2166. done(err, l);
  2167. });
  2168. };
  2169. if (!lng && this.services.languageDetector && !this.services.languageDetector.async) {
  2170. setLng(this.services.languageDetector.detect());
  2171. } else if (!lng && this.services.languageDetector && this.services.languageDetector.async) {
  2172. if (this.services.languageDetector.detect.length === 0) {
  2173. this.services.languageDetector.detect().then(setLng);
  2174. } else {
  2175. this.services.languageDetector.detect(setLng);
  2176. }
  2177. } else {
  2178. setLng(lng);
  2179. }
  2180. return deferred;
  2181. }
  2182. getFixedT(lng, ns, keyPrefix) {
  2183. var _this3 = this;
  2184. const fixedT = function (key, opts) {
  2185. let options;
  2186. if (typeof opts !== 'object') {
  2187. for (var _len3 = arguments.length, rest = new Array(_len3 > 2 ? _len3 - 2 : 0), _key3 = 2; _key3 < _len3; _key3++) {
  2188. rest[_key3 - 2] = arguments[_key3];
  2189. }
  2190. options = _this3.options.overloadTranslationOptionHandler([key, opts].concat(rest));
  2191. } else {
  2192. options = {
  2193. ...opts
  2194. };
  2195. }
  2196. options.lng = options.lng || fixedT.lng;
  2197. options.lngs = options.lngs || fixedT.lngs;
  2198. options.ns = options.ns || fixedT.ns;
  2199. if (options.keyPrefix !== '') options.keyPrefix = options.keyPrefix || keyPrefix || fixedT.keyPrefix;
  2200. const keySeparator = _this3.options.keySeparator || '.';
  2201. let resultKey;
  2202. if (options.keyPrefix && Array.isArray(key)) {
  2203. resultKey = key.map(k => `${options.keyPrefix}${keySeparator}${k}`);
  2204. } else {
  2205. resultKey = options.keyPrefix ? `${options.keyPrefix}${keySeparator}${key}` : key;
  2206. }
  2207. return _this3.t(resultKey, options);
  2208. };
  2209. if (isString(lng)) {
  2210. fixedT.lng = lng;
  2211. } else {
  2212. fixedT.lngs = lng;
  2213. }
  2214. fixedT.ns = ns;
  2215. fixedT.keyPrefix = keyPrefix;
  2216. return fixedT;
  2217. }
  2218. t() {
  2219. return this.translator && this.translator.translate(...arguments);
  2220. }
  2221. exists() {
  2222. return this.translator && this.translator.exists(...arguments);
  2223. }
  2224. setDefaultNamespace(ns) {
  2225. this.options.defaultNS = ns;
  2226. }
  2227. hasLoadedNamespace(ns) {
  2228. let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  2229. if (!this.isInitialized) {
  2230. this.logger.warn('hasLoadedNamespace: i18next was not initialized', this.languages);
  2231. return false;
  2232. }
  2233. if (!this.languages || !this.languages.length) {
  2234. this.logger.warn('hasLoadedNamespace: i18n.languages were undefined or empty', this.languages);
  2235. return false;
  2236. }
  2237. const lng = options.lng || this.resolvedLanguage || this.languages[0];
  2238. const fallbackLng = this.options ? this.options.fallbackLng : false;
  2239. const lastLng = this.languages[this.languages.length - 1];
  2240. if (lng.toLowerCase() === 'cimode') return true;
  2241. const loadNotPending = (l, n) => {
  2242. const loadState = this.services.backendConnector.state[`${l}|${n}`];
  2243. return loadState === -1 || loadState === 0 || loadState === 2;
  2244. };
  2245. if (options.precheck) {
  2246. const preResult = options.precheck(this, loadNotPending);
  2247. if (preResult !== undefined) return preResult;
  2248. }
  2249. if (this.hasResourceBundle(lng, ns)) return true;
  2250. if (!this.services.backendConnector.backend || this.options.resources && !this.options.partialBundledLanguages) return true;
  2251. if (loadNotPending(lng, ns) && (!fallbackLng || loadNotPending(lastLng, ns))) return true;
  2252. return false;
  2253. }
  2254. loadNamespaces(ns, callback) {
  2255. const deferred = defer();
  2256. if (!this.options.ns) {
  2257. if (callback) callback();
  2258. return Promise.resolve();
  2259. }
  2260. if (isString(ns)) ns = [ns];
  2261. ns.forEach(n => {
  2262. if (this.options.ns.indexOf(n) < 0) this.options.ns.push(n);
  2263. });
  2264. this.loadResources(err => {
  2265. deferred.resolve();
  2266. if (callback) callback(err);
  2267. });
  2268. return deferred;
  2269. }
  2270. loadLanguages(lngs, callback) {
  2271. const deferred = defer();
  2272. if (isString(lngs)) lngs = [lngs];
  2273. const preloaded = this.options.preload || [];
  2274. const newLngs = lngs.filter(lng => preloaded.indexOf(lng) < 0 && this.services.languageUtils.isSupportedCode(lng));
  2275. if (!newLngs.length) {
  2276. if (callback) callback();
  2277. return Promise.resolve();
  2278. }
  2279. this.options.preload = preloaded.concat(newLngs);
  2280. this.loadResources(err => {
  2281. deferred.resolve();
  2282. if (callback) callback(err);
  2283. });
  2284. return deferred;
  2285. }
  2286. dir(lng) {
  2287. if (!lng) lng = this.resolvedLanguage || (this.languages && this.languages.length > 0 ? this.languages[0] : this.language);
  2288. if (!lng) return 'rtl';
  2289. const rtlLngs = ['ar', 'shu', 'sqr', 'ssh', 'xaa', 'yhd', 'yud', 'aao', 'abh', 'abv', 'acm', 'acq', 'acw', 'acx', 'acy', 'adf', 'ads', 'aeb', 'aec', 'afb', 'ajp', 'apc', 'apd', 'arb', 'arq', 'ars', 'ary', 'arz', 'auz', 'avl', 'ayh', 'ayl', 'ayn', 'ayp', 'bbz', 'pga', 'he', 'iw', 'ps', 'pbt', 'pbu', 'pst', 'prp', 'prd', 'ug', 'ur', 'ydd', 'yds', 'yih', 'ji', 'yi', 'hbo', 'men', 'xmn', 'fa', 'jpr', 'peo', 'pes', 'prs', 'dv', 'sam', 'ckb'];
  2290. const languageUtils = this.services && this.services.languageUtils || new LanguageUtil(get());
  2291. return rtlLngs.indexOf(languageUtils.getLanguagePartFromCode(lng)) > -1 || lng.toLowerCase().indexOf('-arab') > 1 ? 'rtl' : 'ltr';
  2292. }
  2293. static createInstance() {
  2294. let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  2295. let callback = arguments.length > 1 ? arguments[1] : undefined;
  2296. return new I18n(options, callback);
  2297. }
  2298. cloneInstance() {
  2299. let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  2300. let callback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : noop;
  2301. const forkResourceStore = options.forkResourceStore;
  2302. if (forkResourceStore) delete options.forkResourceStore;
  2303. const mergedOptions = {
  2304. ...this.options,
  2305. ...options,
  2306. ...{
  2307. isClone: true
  2308. }
  2309. };
  2310. const clone = new I18n(mergedOptions);
  2311. if (options.debug !== undefined || options.prefix !== undefined) {
  2312. clone.logger = clone.logger.clone(options);
  2313. }
  2314. const membersToCopy = ['store', 'services', 'language'];
  2315. membersToCopy.forEach(m => {
  2316. clone[m] = this[m];
  2317. });
  2318. clone.services = {
  2319. ...this.services
  2320. };
  2321. clone.services.utils = {
  2322. hasLoadedNamespace: clone.hasLoadedNamespace.bind(clone)
  2323. };
  2324. if (forkResourceStore) {
  2325. clone.store = new ResourceStore(this.store.data, mergedOptions);
  2326. clone.services.resourceStore = clone.store;
  2327. }
  2328. clone.translator = new Translator(clone.services, mergedOptions);
  2329. clone.translator.on('*', function (event) {
  2330. for (var _len4 = arguments.length, args = new Array(_len4 > 1 ? _len4 - 1 : 0), _key4 = 1; _key4 < _len4; _key4++) {
  2331. args[_key4 - 1] = arguments[_key4];
  2332. }
  2333. clone.emit(event, ...args);
  2334. });
  2335. clone.init(mergedOptions, callback);
  2336. clone.translator.options = mergedOptions;
  2337. clone.translator.backendConnector.services.utils = {
  2338. hasLoadedNamespace: clone.hasLoadedNamespace.bind(clone)
  2339. };
  2340. return clone;
  2341. }
  2342. toJSON() {
  2343. return {
  2344. options: this.options,
  2345. store: this.store,
  2346. language: this.language,
  2347. languages: this.languages,
  2348. resolvedLanguage: this.resolvedLanguage
  2349. };
  2350. }
  2351. }
  2352. const instance = I18n.createInstance();
  2353. instance.createInstance = I18n.createInstance;
  2354. const createInstance = instance.createInstance;
  2355. const dir = instance.dir;
  2356. const init = instance.init;
  2357. const loadResources = instance.loadResources;
  2358. const reloadResources = instance.reloadResources;
  2359. const use = instance.use;
  2360. const changeLanguage = instance.changeLanguage;
  2361. const getFixedT = instance.getFixedT;
  2362. const t = instance.t;
  2363. const exists = instance.exists;
  2364. const setDefaultNamespace = instance.setDefaultNamespace;
  2365. const hasLoadedNamespace = instance.hasLoadedNamespace;
  2366. const loadNamespaces = instance.loadNamespaces;
  2367. const loadLanguages = instance.loadLanguages;
  2368. export { changeLanguage, createInstance, instance as default, dir, exists, getFixedT, hasLoadedNamespace, init, loadLanguages, loadNamespaces, loadResources, reloadResources, setDefaultNamespace, t, use };