i18next 23.7.19 → 23.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -78,8 +78,9 @@ class EventEmitter {
78
78
  }
79
79
  on(events, listener) {
80
80
  events.split(' ').forEach(event => {
81
- this.observers[event] = this.observers[event] || [];
82
- this.observers[event].push(listener);
81
+ if (!this.observers[event]) this.observers[event] = new Map();
82
+ const numListeners = this.observers[event].get(listener) || 0;
83
+ this.observers[event].set(listener, numListeners + 1);
83
84
  });
84
85
  return this;
85
86
  }
@@ -89,22 +90,28 @@ class EventEmitter {
89
90
  delete this.observers[event];
90
91
  return;
91
92
  }
92
- this.observers[event] = this.observers[event].filter(l => l !== listener);
93
+ this.observers[event].delete(listener);
93
94
  }
94
95
  emit(event) {
95
96
  for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
96
97
  args[_key - 1] = arguments[_key];
97
98
  }
98
99
  if (this.observers[event]) {
99
- const cloned = [].concat(this.observers[event]);
100
- cloned.forEach(observer => {
101
- observer(...args);
100
+ const cloned = Array.from(this.observers[event].entries());
101
+ cloned.forEach(_ref => {
102
+ let [observer, numTimesAdded] = _ref;
103
+ for (let i = 0; i < numTimesAdded; i++) {
104
+ observer(...args);
105
+ }
102
106
  });
103
107
  }
104
108
  if (this.observers['*']) {
105
- const cloned = [].concat(this.observers['*']);
106
- cloned.forEach(observer => {
107
- observer.apply(observer, [event, ...args]);
109
+ const cloned = Array.from(this.observers['*'].entries());
110
+ cloned.forEach(_ref2 => {
111
+ let [observer, numTimesAdded] = _ref2;
112
+ for (let i = 0; i < numTimesAdded; i++) {
113
+ observer.apply(observer, [event, ...args]);
114
+ }
108
115
  });
109
116
  }
110
117
  }
@@ -130,28 +137,31 @@ function copy(a, s, t) {
130
137
  if (s[m]) t[m] = s[m];
131
138
  });
132
139
  }
140
+ const lastOfPathSeparatorRegExp = /###/g;
133
141
  function getLastOfPath(object, path, Empty) {
134
142
  function cleanKey(key) {
135
- return key && key.indexOf('###') > -1 ? key.replace(/###/g, '.') : key;
143
+ return key && key.indexOf('###') > -1 ? key.replace(lastOfPathSeparatorRegExp, '.') : key;
136
144
  }
137
145
  function canNotTraverseDeeper() {
138
146
  return !object || typeof object === 'string';
139
147
  }
140
- const stack = typeof path !== 'string' ? [].concat(path) : path.split('.');
141
- while (stack.length > 1) {
148
+ const stack = typeof path !== 'string' ? path : path.split('.');
149
+ let stackIndex = 0;
150
+ while (stackIndex < stack.length - 1) {
142
151
  if (canNotTraverseDeeper()) return {};
143
- const key = cleanKey(stack.shift());
152
+ const key = cleanKey(stack[stackIndex]);
144
153
  if (!object[key] && Empty) object[key] = new Empty();
145
154
  if (Object.prototype.hasOwnProperty.call(object, key)) {
146
155
  object = object[key];
147
156
  } else {
148
157
  object = {};
149
158
  }
159
+ ++stackIndex;
150
160
  }
151
161
  if (canNotTraverseDeeper()) return {};
152
162
  return {
153
163
  obj: object,
154
- k: cleanKey(stack.shift())
164
+ k: cleanKey(stack[stackIndex])
155
165
  };
156
166
  }
157
167
  function setPath(object, path, newValue) {
@@ -218,13 +228,34 @@ function escape(data) {
218
228
  }
219
229
  return data;
220
230
  }
231
+ class RegExpCache {
232
+ constructor(capacity) {
233
+ this.capacity = capacity;
234
+ this.regExpMap = new Map();
235
+ this.regExpQueue = [];
236
+ }
237
+ getRegExp(pattern) {
238
+ const regExpFromCache = this.regExpMap.get(pattern);
239
+ if (regExpFromCache !== undefined) {
240
+ return regExpFromCache;
241
+ }
242
+ const regExpNew = new RegExp(pattern);
243
+ if (this.regExpQueue.length === this.capacity) {
244
+ this.regExpMap.delete(this.regExpQueue.shift());
245
+ }
246
+ this.regExpMap.set(pattern, regExpNew);
247
+ this.regExpQueue.push(pattern);
248
+ return regExpNew;
249
+ }
250
+ }
221
251
  const chars = [' ', ',', '?', '!', ';'];
252
+ const looksLikeObjectPathRegExpCache = new RegExpCache(20);
222
253
  function looksLikeObjectPath(key, nsSeparator, keySeparator) {
223
254
  nsSeparator = nsSeparator || '';
224
255
  keySeparator = keySeparator || '';
225
256
  const possibleChars = chars.filter(c => nsSeparator.indexOf(c) < 0 && keySeparator.indexOf(c) < 0);
226
257
  if (possibleChars.length === 0) return true;
227
- const r = new RegExp(`(${possibleChars.map(c => c === '?' ? '\\?' : c).join('|')})`);
258
+ const r = looksLikeObjectPathRegExpCache.getRegExp(`(${possibleChars.map(c => c === '?' ? '\\?' : c).join('|')})`);
228
259
  let matched = !r.test(key);
229
260
  if (!matched) {
230
261
  const ki = key.indexOf(keySeparator);
@@ -238,33 +269,26 @@ function deepFind(obj, path) {
238
269
  let keySeparator = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '.';
239
270
  if (!obj) return undefined;
240
271
  if (obj[path]) return obj[path];
241
- const paths = path.split(keySeparator);
272
+ const tokens = path.split(keySeparator);
242
273
  let current = obj;
243
- for (let i = 0; i < paths.length; ++i) {
244
- if (!current) return undefined;
245
- if (typeof current[paths[i]] === 'string' && i + 1 < paths.length) {
274
+ for (let i = 0; i < tokens.length;) {
275
+ if (!current || typeof current !== 'object') {
246
276
  return undefined;
247
277
  }
248
- if (current[paths[i]] === undefined) {
249
- let j = 2;
250
- let p = paths.slice(i, i + j).join(keySeparator);
251
- let mix = current[p];
252
- while (mix === undefined && paths.length > i + j) {
253
- j++;
254
- p = paths.slice(i, i + j).join(keySeparator);
255
- mix = current[p];
278
+ let next;
279
+ let nextPath = '';
280
+ for (let j = i; j < tokens.length; ++j) {
281
+ if (j !== i) {
282
+ nextPath += keySeparator;
256
283
  }
257
- if (mix === undefined) return undefined;
258
- if (mix === null) return null;
259
- if (path.endsWith(p)) {
260
- if (typeof mix === 'string') return mix;
261
- if (p && typeof mix[p] === 'string') return mix[p];
284
+ nextPath += tokens[j];
285
+ next = current[nextPath];
286
+ if (next !== undefined) {
287
+ i += j - i + 1;
288
+ break;
262
289
  }
263
- const joinedPath = paths.slice(i + j).join(keySeparator);
264
- if (joinedPath) return deepFind(mix, joinedPath, keySeparator);
265
- return undefined;
266
290
  }
267
- current = current[paths[i]];
291
+ current = next;
268
292
  }
269
293
  return current;
270
294
  }
@@ -304,11 +328,20 @@ class ResourceStore extends EventEmitter {
304
328
  let options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
305
329
  const keySeparator = options.keySeparator !== undefined ? options.keySeparator : this.options.keySeparator;
306
330
  const ignoreJSONStructure = options.ignoreJSONStructure !== undefined ? options.ignoreJSONStructure : this.options.ignoreJSONStructure;
307
- let path = [lng, ns];
308
- if (key && typeof key !== 'string') path = path.concat(key);
309
- if (key && typeof key === 'string') path = path.concat(keySeparator ? key.split(keySeparator) : key);
331
+ let path;
310
332
  if (lng.indexOf('.') > -1) {
311
333
  path = lng.split('.');
334
+ } else {
335
+ path = [lng, ns];
336
+ if (key) {
337
+ if (Array.isArray(key)) {
338
+ path.push(...key);
339
+ } else if (typeof key === 'string' && keySeparator) {
340
+ path.push(...key.split(keySeparator));
341
+ } else {
342
+ path.push(key);
343
+ }
344
+ }
312
345
  }
313
346
  const result = getPath(this.data, path);
314
347
  if (result || !ignoreJSONStructure || typeof key !== 'string') return result;
@@ -606,7 +639,11 @@ class Translator extends EventEmitter {
606
639
  if (this.options.saveMissing) {
607
640
  if (this.options.saveMissingPlurals && needsPluralHandling) {
608
641
  lngs.forEach(language => {
609
- this.pluralResolver.getSuffixes(language, options).forEach(suffix => {
642
+ const suffixes = this.pluralResolver.getSuffixes(language, options);
643
+ if (needsZeroSuffixLookup && options[`defaultValue${this.options.pluralSeparator}zero`] && suffixes.indexOf(`${this.options.pluralSeparator}zero`) < 0) {
644
+ suffixes.push(`${this.options.pluralSeparator}zero`);
645
+ }
646
+ suffixes.forEach(suffix => {
610
647
  send([language], key + suffix, options[`defaultValue${suffix}`] || defaultValue);
611
648
  });
612
649
  });
@@ -1129,7 +1166,7 @@ class PluralResolver {
1129
1166
  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
1130
1167
  if (this.shouldUseIntlApi()) {
1131
1168
  try {
1132
- return new Intl.PluralRules(getCleanedCode(code), {
1169
+ return new Intl.PluralRules(getCleanedCode(code === 'dev' ? 'en' : code), {
1133
1170
  type: options.ordinal ? 'ordinal' : 'cardinal'
1134
1171
  });
1135
1172
  } catch (err) {
@@ -1243,12 +1280,16 @@ class Interpolator {
1243
1280
  if (this.options) this.init(this.options);
1244
1281
  }
1245
1282
  resetRegExp() {
1246
- const regexpStr = `${this.prefix}(.+?)${this.suffix}`;
1247
- this.regexp = new RegExp(regexpStr, 'g');
1248
- const regexpUnescapeStr = `${this.prefix}${this.unescapePrefix}(.+?)${this.unescapeSuffix}${this.suffix}`;
1249
- this.regexpUnescape = new RegExp(regexpUnescapeStr, 'g');
1250
- const nestingRegexpStr = `${this.nestingPrefix}(.+?)${this.nestingSuffix}`;
1251
- this.nestingRegexp = new RegExp(nestingRegexpStr, 'g');
1283
+ const getOrResetRegExp = (existingRegExp, pattern) => {
1284
+ if (existingRegExp && existingRegExp.source === pattern) {
1285
+ existingRegExp.lastIndex = 0;
1286
+ return existingRegExp;
1287
+ }
1288
+ return new RegExp(pattern, 'g');
1289
+ };
1290
+ this.regexp = getOrResetRegExp(this.regexp, `${this.prefix}(.+?)${this.suffix}`);
1291
+ this.regexpUnescape = getOrResetRegExp(this.regexpUnescape, `${this.prefix}${this.unescapePrefix}(.+?)${this.unescapeSuffix}${this.suffix}`);
1292
+ this.nestingRegexp = getOrResetRegExp(this.nestingRegexp, `${this.nestingPrefix}(.+?)${this.nestingSuffix}`);
1252
1293
  }
1253
1294
  interpolate(str, data, lng, options) {
1254
1295
  let match;
@@ -76,8 +76,9 @@ class EventEmitter {
76
76
  }
77
77
  on(events, listener) {
78
78
  events.split(' ').forEach(event => {
79
- this.observers[event] = this.observers[event] || [];
80
- this.observers[event].push(listener);
79
+ if (!this.observers[event]) this.observers[event] = new Map();
80
+ const numListeners = this.observers[event].get(listener) || 0;
81
+ this.observers[event].set(listener, numListeners + 1);
81
82
  });
82
83
  return this;
83
84
  }
@@ -87,22 +88,28 @@ class EventEmitter {
87
88
  delete this.observers[event];
88
89
  return;
89
90
  }
90
- this.observers[event] = this.observers[event].filter(l => l !== listener);
91
+ this.observers[event].delete(listener);
91
92
  }
92
93
  emit(event) {
93
94
  for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
94
95
  args[_key - 1] = arguments[_key];
95
96
  }
96
97
  if (this.observers[event]) {
97
- const cloned = [].concat(this.observers[event]);
98
- cloned.forEach(observer => {
99
- observer(...args);
98
+ const cloned = Array.from(this.observers[event].entries());
99
+ cloned.forEach(_ref => {
100
+ let [observer, numTimesAdded] = _ref;
101
+ for (let i = 0; i < numTimesAdded; i++) {
102
+ observer(...args);
103
+ }
100
104
  });
101
105
  }
102
106
  if (this.observers['*']) {
103
- const cloned = [].concat(this.observers['*']);
104
- cloned.forEach(observer => {
105
- observer.apply(observer, [event, ...args]);
107
+ const cloned = Array.from(this.observers['*'].entries());
108
+ cloned.forEach(_ref2 => {
109
+ let [observer, numTimesAdded] = _ref2;
110
+ for (let i = 0; i < numTimesAdded; i++) {
111
+ observer.apply(observer, [event, ...args]);
112
+ }
106
113
  });
107
114
  }
108
115
  }
@@ -128,28 +135,31 @@ function copy(a, s, t) {
128
135
  if (s[m]) t[m] = s[m];
129
136
  });
130
137
  }
138
+ const lastOfPathSeparatorRegExp = /###/g;
131
139
  function getLastOfPath(object, path, Empty) {
132
140
  function cleanKey(key) {
133
- return key && key.indexOf('###') > -1 ? key.replace(/###/g, '.') : key;
141
+ return key && key.indexOf('###') > -1 ? key.replace(lastOfPathSeparatorRegExp, '.') : key;
134
142
  }
135
143
  function canNotTraverseDeeper() {
136
144
  return !object || typeof object === 'string';
137
145
  }
138
- const stack = typeof path !== 'string' ? [].concat(path) : path.split('.');
139
- while (stack.length > 1) {
146
+ const stack = typeof path !== 'string' ? path : path.split('.');
147
+ let stackIndex = 0;
148
+ while (stackIndex < stack.length - 1) {
140
149
  if (canNotTraverseDeeper()) return {};
141
- const key = cleanKey(stack.shift());
150
+ const key = cleanKey(stack[stackIndex]);
142
151
  if (!object[key] && Empty) object[key] = new Empty();
143
152
  if (Object.prototype.hasOwnProperty.call(object, key)) {
144
153
  object = object[key];
145
154
  } else {
146
155
  object = {};
147
156
  }
157
+ ++stackIndex;
148
158
  }
149
159
  if (canNotTraverseDeeper()) return {};
150
160
  return {
151
161
  obj: object,
152
- k: cleanKey(stack.shift())
162
+ k: cleanKey(stack[stackIndex])
153
163
  };
154
164
  }
155
165
  function setPath(object, path, newValue) {
@@ -216,13 +226,34 @@ function escape(data) {
216
226
  }
217
227
  return data;
218
228
  }
229
+ class RegExpCache {
230
+ constructor(capacity) {
231
+ this.capacity = capacity;
232
+ this.regExpMap = new Map();
233
+ this.regExpQueue = [];
234
+ }
235
+ getRegExp(pattern) {
236
+ const regExpFromCache = this.regExpMap.get(pattern);
237
+ if (regExpFromCache !== undefined) {
238
+ return regExpFromCache;
239
+ }
240
+ const regExpNew = new RegExp(pattern);
241
+ if (this.regExpQueue.length === this.capacity) {
242
+ this.regExpMap.delete(this.regExpQueue.shift());
243
+ }
244
+ this.regExpMap.set(pattern, regExpNew);
245
+ this.regExpQueue.push(pattern);
246
+ return regExpNew;
247
+ }
248
+ }
219
249
  const chars = [' ', ',', '?', '!', ';'];
250
+ const looksLikeObjectPathRegExpCache = new RegExpCache(20);
220
251
  function looksLikeObjectPath(key, nsSeparator, keySeparator) {
221
252
  nsSeparator = nsSeparator || '';
222
253
  keySeparator = keySeparator || '';
223
254
  const possibleChars = chars.filter(c => nsSeparator.indexOf(c) < 0 && keySeparator.indexOf(c) < 0);
224
255
  if (possibleChars.length === 0) return true;
225
- const r = new RegExp(`(${possibleChars.map(c => c === '?' ? '\\?' : c).join('|')})`);
256
+ const r = looksLikeObjectPathRegExpCache.getRegExp(`(${possibleChars.map(c => c === '?' ? '\\?' : c).join('|')})`);
226
257
  let matched = !r.test(key);
227
258
  if (!matched) {
228
259
  const ki = key.indexOf(keySeparator);
@@ -236,33 +267,26 @@ function deepFind(obj, path) {
236
267
  let keySeparator = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '.';
237
268
  if (!obj) return undefined;
238
269
  if (obj[path]) return obj[path];
239
- const paths = path.split(keySeparator);
270
+ const tokens = path.split(keySeparator);
240
271
  let current = obj;
241
- for (let i = 0; i < paths.length; ++i) {
242
- if (!current) return undefined;
243
- if (typeof current[paths[i]] === 'string' && i + 1 < paths.length) {
272
+ for (let i = 0; i < tokens.length;) {
273
+ if (!current || typeof current !== 'object') {
244
274
  return undefined;
245
275
  }
246
- if (current[paths[i]] === undefined) {
247
- let j = 2;
248
- let p = paths.slice(i, i + j).join(keySeparator);
249
- let mix = current[p];
250
- while (mix === undefined && paths.length > i + j) {
251
- j++;
252
- p = paths.slice(i, i + j).join(keySeparator);
253
- mix = current[p];
276
+ let next;
277
+ let nextPath = '';
278
+ for (let j = i; j < tokens.length; ++j) {
279
+ if (j !== i) {
280
+ nextPath += keySeparator;
254
281
  }
255
- if (mix === undefined) return undefined;
256
- if (mix === null) return null;
257
- if (path.endsWith(p)) {
258
- if (typeof mix === 'string') return mix;
259
- if (p && typeof mix[p] === 'string') return mix[p];
282
+ nextPath += tokens[j];
283
+ next = current[nextPath];
284
+ if (next !== undefined) {
285
+ i += j - i + 1;
286
+ break;
260
287
  }
261
- const joinedPath = paths.slice(i + j).join(keySeparator);
262
- if (joinedPath) return deepFind(mix, joinedPath, keySeparator);
263
- return undefined;
264
288
  }
265
- current = current[paths[i]];
289
+ current = next;
266
290
  }
267
291
  return current;
268
292
  }
@@ -302,11 +326,20 @@ class ResourceStore extends EventEmitter {
302
326
  let options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
303
327
  const keySeparator = options.keySeparator !== undefined ? options.keySeparator : this.options.keySeparator;
304
328
  const ignoreJSONStructure = options.ignoreJSONStructure !== undefined ? options.ignoreJSONStructure : this.options.ignoreJSONStructure;
305
- let path = [lng, ns];
306
- if (key && typeof key !== 'string') path = path.concat(key);
307
- if (key && typeof key === 'string') path = path.concat(keySeparator ? key.split(keySeparator) : key);
329
+ let path;
308
330
  if (lng.indexOf('.') > -1) {
309
331
  path = lng.split('.');
332
+ } else {
333
+ path = [lng, ns];
334
+ if (key) {
335
+ if (Array.isArray(key)) {
336
+ path.push(...key);
337
+ } else if (typeof key === 'string' && keySeparator) {
338
+ path.push(...key.split(keySeparator));
339
+ } else {
340
+ path.push(key);
341
+ }
342
+ }
310
343
  }
311
344
  const result = getPath(this.data, path);
312
345
  if (result || !ignoreJSONStructure || typeof key !== 'string') return result;
@@ -604,7 +637,11 @@ class Translator extends EventEmitter {
604
637
  if (this.options.saveMissing) {
605
638
  if (this.options.saveMissingPlurals && needsPluralHandling) {
606
639
  lngs.forEach(language => {
607
- this.pluralResolver.getSuffixes(language, options).forEach(suffix => {
640
+ const suffixes = this.pluralResolver.getSuffixes(language, options);
641
+ if (needsZeroSuffixLookup && options[`defaultValue${this.options.pluralSeparator}zero`] && suffixes.indexOf(`${this.options.pluralSeparator}zero`) < 0) {
642
+ suffixes.push(`${this.options.pluralSeparator}zero`);
643
+ }
644
+ suffixes.forEach(suffix => {
608
645
  send([language], key + suffix, options[`defaultValue${suffix}`] || defaultValue);
609
646
  });
610
647
  });
@@ -1127,7 +1164,7 @@ class PluralResolver {
1127
1164
  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
1128
1165
  if (this.shouldUseIntlApi()) {
1129
1166
  try {
1130
- return new Intl.PluralRules(getCleanedCode(code), {
1167
+ return new Intl.PluralRules(getCleanedCode(code === 'dev' ? 'en' : code), {
1131
1168
  type: options.ordinal ? 'ordinal' : 'cardinal'
1132
1169
  });
1133
1170
  } catch (err) {
@@ -1241,12 +1278,16 @@ class Interpolator {
1241
1278
  if (this.options) this.init(this.options);
1242
1279
  }
1243
1280
  resetRegExp() {
1244
- const regexpStr = `${this.prefix}(.+?)${this.suffix}`;
1245
- this.regexp = new RegExp(regexpStr, 'g');
1246
- const regexpUnescapeStr = `${this.prefix}${this.unescapePrefix}(.+?)${this.unescapeSuffix}${this.suffix}`;
1247
- this.regexpUnescape = new RegExp(regexpUnescapeStr, 'g');
1248
- const nestingRegexpStr = `${this.nestingPrefix}(.+?)${this.nestingSuffix}`;
1249
- this.nestingRegexp = new RegExp(nestingRegexpStr, 'g');
1281
+ const getOrResetRegExp = (existingRegExp, pattern) => {
1282
+ if (existingRegExp && existingRegExp.source === pattern) {
1283
+ existingRegExp.lastIndex = 0;
1284
+ return existingRegExp;
1285
+ }
1286
+ return new RegExp(pattern, 'g');
1287
+ };
1288
+ this.regexp = getOrResetRegExp(this.regexp, `${this.prefix}(.+?)${this.suffix}`);
1289
+ this.regexpUnescape = getOrResetRegExp(this.regexpUnescape, `${this.prefix}${this.unescapePrefix}(.+?)${this.unescapeSuffix}${this.suffix}`);
1290
+ this.nestingRegexp = getOrResetRegExp(this.nestingRegexp, `${this.nestingPrefix}(.+?)${this.nestingSuffix}`);
1250
1291
  }
1251
1292
  interpolate(str, data, lng, options) {
1252
1293
  let match;