i18next.js 87 KB

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