@depup/i18next 26.0.4-depup.0 → 26.3.6-depup.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.
@@ -0,0 +1,29 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(gh pr *)",
5
+ "Read(//Users/adrai/Projects/i18next/react-i18next/**)",
6
+ "Bash(git add *)",
7
+ "Bash(git commit *)",
8
+ "Read(//Users/adrai/Projects/i18next/**)",
9
+ "Bash(gh issue *)",
10
+ "Read(//tmp/**)",
11
+ "WebFetch(domain:github.com)",
12
+ "Bash(grep -n '$PreservedValue\\\\b' /Users/adrai/Projects/i18next/i18next/typescript/helpers.d.ts)",
13
+ "Bash(npm run *)",
14
+ "Bash(cat > *)",
15
+ "Bash(python3 -c \"import json,sys; print\\(json.load\\(sys.stdin\\)['version']\\)\")",
16
+ "Bash(npm test *)",
17
+ "Bash(git pull *)",
18
+ "Bash(git push *)",
19
+ "Bash(gh api *)",
20
+ "Bash(curl -s \"https://raw.githubusercontent.com/GabenGar/todos/d6d77cef716fcdc5a3991f84605176f85a69cdc8/apps/frontend/src/lib/internationalization/augs.d.ts\")"
21
+ ],
22
+ "additionalDirectories": [
23
+ "/Users/adrai/Projects/i18next/react-i18next",
24
+ "/tmp",
25
+ "/Users/adrai/Projects/i18next/i18next-gitbook/overview",
26
+ "/Users/adrai/Projects/i18next/i18next-icu"
27
+ ]
28
+ }
29
+ }
package/README.md CHANGED
@@ -13,8 +13,8 @@ npm install @depup/i18next
13
13
 
14
14
  | Field | Value |
15
15
  |-------|-------|
16
- | Original | [i18next](https://www.npmjs.com/package/i18next) @ 26.0.4 |
17
- | Processed | 2026-04-08 |
16
+ | Original | [i18next](https://www.npmjs.com/package/i18next) @ 26.3.6 |
17
+ | Processed | 2026-07-21 |
18
18
  | Smoke test | passed |
19
19
  | Deps updated | 0 |
20
20
 
package/changes.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "bumped": {},
3
- "timestamp": "2026-04-08T12:31:15.497Z",
3
+ "timestamp": "2026-07-21T17:26:23.760Z",
4
4
  "totalUpdated": 0
5
5
  }
@@ -93,7 +93,7 @@ const getPathWithDefaults = (data, defaultData, key) => {
93
93
  const deepExtend = (target, source, overwrite) => {
94
94
  for (const prop in source) {
95
95
  if (prop !== '__proto__' && prop !== 'constructor') {
96
- if (prop in target) {
96
+ if (Object.prototype.hasOwnProperty.call(target, prop)) {
97
97
  if (isString(target[prop]) || target[prop] instanceof String || isString(source[prop]) || source[prop] instanceof String) {
98
98
  if (overwrite) target[prop] = source[prop];
99
99
  } else {
@@ -231,6 +231,7 @@ class Logger {
231
231
  }
232
232
  forward(args, lvl, prefix, debugOnly) {
233
233
  if (debugOnly && !this.debug) return null;
234
+ args = args.map(a => isString(a) ? a.replace(/[\r\n\x00-\x1F\x7F]/g, ' ') : a);
234
235
  if (isString(args[0])) args[0] = `${prefix}${this.prefix} ${args[0]}`;
235
236
  return this.logger[lvl](args);
236
237
  }
@@ -461,11 +462,15 @@ function keysFromSelector(selector, opts) {
461
462
  } = selector(createProxy());
462
463
  const keySeparator = opts?.keySeparator ?? '.';
463
464
  const nsSeparator = opts?.nsSeparator ?? ':';
465
+ const strict = opts?.enableSelector === 'strict';
464
466
  if (path.length > 1 && nsSeparator) {
465
467
  const ns = opts?.ns;
466
- const nsArray = Array.isArray(ns) ? ns : null;
467
- if (nsArray && nsArray.length > 1 && nsArray.slice(1).includes(path[0])) {
468
- return `${path[0]}${nsSeparator}${path.slice(1).join(keySeparator)}`;
468
+ const nsList = strict ? Array.isArray(ns) ? ns : ns ? [ns] : null : Array.isArray(ns) ? ns : null;
469
+ if (nsList) {
470
+ const candidates = strict ? nsList : nsList.length > 1 ? nsList.slice(1) : [];
471
+ if (candidates.includes(path[0])) {
472
+ return `${path[0]}${nsSeparator}${path.slice(1).join(keySeparator)}`;
473
+ }
469
474
  }
470
475
  }
471
476
  return path.join(keySeparator);
@@ -668,7 +673,7 @@ class Translator extends EventEmitter {
668
673
  const resForMissing = missingKeyNoValueFallbackToKey && usedKey ? undefined : res;
669
674
  const updateMissing = hasDefaultValue && defaultValue !== res && this.options.updateMissing;
670
675
  if (usedKey || usedDefault || updateMissing) {
671
- this.logger.log(updateMissing ? 'updateKey' : 'missingKey', lng, namespace, key, updateMissing ? defaultValue : res);
676
+ this.logger.log(updateMissing ? 'updateKey' : 'missingKey', lng, namespace, needsPluralHandling && !updateMissing ? `${key}${this.pluralResolver.getSuffix(lng, opt.count, opt)}` : key, updateMissing ? defaultValue : res);
672
677
  if (keySeparator) {
673
678
  const fk = this.resolve(key, {
674
679
  ...opt,
@@ -878,7 +883,10 @@ class Translator extends EventEmitter {
878
883
  const useOptionsReplaceForData = options.replace && !isString(options.replace);
879
884
  let data = useOptionsReplaceForData ? options.replace : options;
880
885
  if (useOptionsReplaceForData && typeof options.count !== 'undefined') {
881
- data.count = options.count;
886
+ data = {
887
+ ...data,
888
+ count: options.count
889
+ };
882
890
  }
883
891
  if (this.options.interpolation.defaultVariables) {
884
892
  data = {
@@ -1136,8 +1144,8 @@ class Interpolator {
1136
1144
  this.prefix = prefix ? regexEscape(prefix) : prefixEscaped || '{{';
1137
1145
  this.suffix = suffix ? regexEscape(suffix) : suffixEscaped || '}}';
1138
1146
  this.formatSeparator = formatSeparator || ',';
1139
- this.unescapePrefix = unescapeSuffix ? '' : unescapePrefix || '-';
1140
- this.unescapeSuffix = this.unescapePrefix ? '' : unescapeSuffix || '';
1147
+ this.unescapePrefix = unescapeSuffix ? '' : unescapePrefix ? regexEscape(unescapePrefix) : '-';
1148
+ this.unescapeSuffix = this.unescapePrefix ? '' : unescapeSuffix ? regexEscape(unescapeSuffix) : '';
1141
1149
  this.nestingPrefix = nestingPrefix ? regexEscape(nestingPrefix) : nestingPrefixEscaped || regexEscape('$t(');
1142
1150
  this.nestingSuffix = nestingSuffix ? regexEscape(nestingSuffix) : nestingSuffixEscaped || regexEscape(')');
1143
1151
  this.nestingOptionsSeparator = nestingOptionsSeparator || ',';
@@ -1184,14 +1192,17 @@ class Interpolator {
1184
1192
  });
1185
1193
  };
1186
1194
  this.resetRegExp();
1195
+ if (!this.escapeValue && typeof str === 'string' && /\$t\([^)]*\{[^}]*\{\{/.test(str)) {
1196
+ this.logger.warn('nesting options string contains interpolated variables with escapeValue: false — ' + 'if any of those values are attacker-controlled they can inject additional ' + 'nesting options (e.g. redirect lng/ns). Sanitise untrusted input before passing ' + 'it to t(), or keep escapeValue: true.');
1197
+ }
1187
1198
  const missingInterpolationHandler = options?.missingInterpolationHandler || this.options.missingInterpolationHandler;
1188
1199
  const skipOnVariables = options?.interpolation?.skipOnVariables !== undefined ? options.interpolation.skipOnVariables : this.options.interpolation.skipOnVariables;
1189
1200
  const todos = [{
1190
1201
  regex: this.regexpUnescape,
1191
- safeValue: val => regexSafe(val)
1202
+ safeValue: val => val
1192
1203
  }, {
1193
1204
  regex: this.regexp,
1194
- safeValue: val => this.escapeValue ? regexSafe(this.escape(val)) : regexSafe(val)
1205
+ safeValue: val => this.escapeValue ? this.escape(val) : val
1195
1206
  }];
1196
1207
  todos.forEach(todo => {
1197
1208
  replaces = 0;
@@ -1215,9 +1226,9 @@ class Interpolator {
1215
1226
  value = makeString(value);
1216
1227
  }
1217
1228
  const safeValue = todo.safeValue(value);
1218
- str = str.replace(match[0], safeValue);
1229
+ str = str.replace(match[0], regexSafe(safeValue));
1219
1230
  if (skipOnVariables) {
1220
- todo.regex.lastIndex += value.length;
1231
+ todo.regex.lastIndex += safeValue.length;
1221
1232
  todo.regex.lastIndex -= match[0].length;
1222
1233
  } else {
1223
1234
  todo.regex.lastIndex = 0;
@@ -1267,7 +1278,7 @@ class Interpolator {
1267
1278
  clonedOptions = clonedOptions.replace && !isString(clonedOptions.replace) ? clonedOptions.replace : clonedOptions;
1268
1279
  clonedOptions.applyPostProcessor = false;
1269
1280
  delete clonedOptions.defaultValue;
1270
- const keyEndIndex = /{.*}/.test(match[1]) ? match[1].lastIndexOf('}') + 1 : match[1].indexOf(this.formatSeparator);
1281
+ const keyEndIndex = /{.*}/s.test(match[1]) ? match[1].lastIndexOf('}') + 1 : match[1].indexOf(this.formatSeparator);
1271
1282
  if (keyEndIndex !== -1) {
1272
1283
  formatters = match[1].slice(keyEndIndex).split(this.formatSeparator).map(elem => elem.trim()).filter(Boolean);
1273
1284
  match[1] = match[1].slice(0, keyEndIndex);
@@ -1397,10 +1408,14 @@ class Formatter {
1397
1408
  format(value, format, lng, options = {}) {
1398
1409
  if (!format) return value;
1399
1410
  if (value == null) return value;
1400
- const formats = format.split(this.formatSeparator);
1401
- if (formats.length > 1 && formats[0].indexOf('(') > 1 && !formats[0].includes(')') && formats.find(f => f.includes(')'))) {
1402
- const lastIndex = formats.findIndex(f => f.includes(')'));
1403
- formats[0] = [formats[0], ...formats.splice(1, lastIndex)].join(this.formatSeparator);
1411
+ const rawFormats = format.split(this.formatSeparator);
1412
+ const formats = [];
1413
+ for (let i = 0; i < rawFormats.length; i++) {
1414
+ let f = rawFormats[i];
1415
+ while (f.indexOf('(') > -1 && !f.includes(')') && i + 1 < rawFormats.length) {
1416
+ f = `${f}${this.formatSeparator}${rawFormats[++i]}`;
1417
+ }
1418
+ formats.push(f);
1404
1419
  }
1405
1420
  const result = formats.reduce((mem, f) => {
1406
1421
  const {
@@ -1661,6 +1676,7 @@ const get = () => ({
1661
1676
  nsSeparator: ':',
1662
1677
  pluralSeparator: '_',
1663
1678
  contextSeparator: '_',
1679
+ enableSelector: false,
1664
1680
  partialBundledLanguages: false,
1665
1681
  saveMissing: false,
1666
1682
  updateMissing: false,
@@ -1862,7 +1878,7 @@ class I18n extends EventEmitter {
1862
1878
  deferred.resolve(t);
1863
1879
  callback(err, t);
1864
1880
  };
1865
- if (this.languages && !this.isInitialized) return finish(null, this.t.bind(this));
1881
+ if ((this.languages || this.isLanguageChangingTo) && !this.isInitialized) return finish(null, this.t.bind(this));
1866
1882
  this.changeLanguage(this.options.lng, finish);
1867
1883
  };
1868
1884
  if (this.options.resources || !this.options.initAsync) {
@@ -2017,7 +2033,8 @@ class I18n extends EventEmitter {
2017
2033
  }
2018
2034
  return deferred;
2019
2035
  }
2020
- getFixedT(lng, ns, keyPrefix) {
2036
+ getFixedT(lng, ns, keyPrefix, fixedOpts) {
2037
+ const scopeNs = fixedOpts?.scopeNs;
2021
2038
  const fixedT = (key, opts, ...rest) => {
2022
2039
  let o;
2023
2040
  if (typeof opts !== 'object') {
@@ -2029,12 +2046,14 @@ class I18n extends EventEmitter {
2029
2046
  }
2030
2047
  o.lng = o.lng || fixedT.lng;
2031
2048
  o.lngs = o.lngs || fixedT.lngs;
2049
+ const explicitCallNs = o.ns !== undefined && o.ns !== null;
2032
2050
  o.ns = o.ns || fixedT.ns;
2033
2051
  if (o.keyPrefix !== '') o.keyPrefix = o.keyPrefix || keyPrefix || fixedT.keyPrefix;
2034
2052
  const selectorOpts = {
2035
2053
  ...this.options,
2036
2054
  ...o
2037
2055
  };
2056
+ if (Array.isArray(scopeNs) && !explicitCallNs) selectorOpts.ns = scopeNs;
2038
2057
  if (typeof o.keyPrefix === 'function') o.keyPrefix = keysFromSelector(o.keyPrefix, selectorOpts);
2039
2058
  const keySeparator = this.options.keySeparator || '.';
2040
2059
  let resultKey;
@@ -91,7 +91,7 @@ const getPathWithDefaults = (data, defaultData, key) => {
91
91
  const deepExtend = (target, source, overwrite) => {
92
92
  for (const prop in source) {
93
93
  if (prop !== '__proto__' && prop !== 'constructor') {
94
- if (prop in target) {
94
+ if (Object.prototype.hasOwnProperty.call(target, prop)) {
95
95
  if (isString(target[prop]) || target[prop] instanceof String || isString(source[prop]) || source[prop] instanceof String) {
96
96
  if (overwrite) target[prop] = source[prop];
97
97
  } else {
@@ -229,6 +229,7 @@ class Logger {
229
229
  }
230
230
  forward(args, lvl, prefix, debugOnly) {
231
231
  if (debugOnly && !this.debug) return null;
232
+ args = args.map(a => isString(a) ? a.replace(/[\r\n\x00-\x1F\x7F]/g, ' ') : a);
232
233
  if (isString(args[0])) args[0] = `${prefix}${this.prefix} ${args[0]}`;
233
234
  return this.logger[lvl](args);
234
235
  }
@@ -459,11 +460,15 @@ function keysFromSelector(selector, opts) {
459
460
  } = selector(createProxy());
460
461
  const keySeparator = opts?.keySeparator ?? '.';
461
462
  const nsSeparator = opts?.nsSeparator ?? ':';
463
+ const strict = opts?.enableSelector === 'strict';
462
464
  if (path.length > 1 && nsSeparator) {
463
465
  const ns = opts?.ns;
464
- const nsArray = Array.isArray(ns) ? ns : null;
465
- if (nsArray && nsArray.length > 1 && nsArray.slice(1).includes(path[0])) {
466
- return `${path[0]}${nsSeparator}${path.slice(1).join(keySeparator)}`;
466
+ const nsList = strict ? Array.isArray(ns) ? ns : ns ? [ns] : null : Array.isArray(ns) ? ns : null;
467
+ if (nsList) {
468
+ const candidates = strict ? nsList : nsList.length > 1 ? nsList.slice(1) : [];
469
+ if (candidates.includes(path[0])) {
470
+ return `${path[0]}${nsSeparator}${path.slice(1).join(keySeparator)}`;
471
+ }
467
472
  }
468
473
  }
469
474
  return path.join(keySeparator);
@@ -666,7 +671,7 @@ class Translator extends EventEmitter {
666
671
  const resForMissing = missingKeyNoValueFallbackToKey && usedKey ? undefined : res;
667
672
  const updateMissing = hasDefaultValue && defaultValue !== res && this.options.updateMissing;
668
673
  if (usedKey || usedDefault || updateMissing) {
669
- this.logger.log(updateMissing ? 'updateKey' : 'missingKey', lng, namespace, key, updateMissing ? defaultValue : res);
674
+ this.logger.log(updateMissing ? 'updateKey' : 'missingKey', lng, namespace, needsPluralHandling && !updateMissing ? `${key}${this.pluralResolver.getSuffix(lng, opt.count, opt)}` : key, updateMissing ? defaultValue : res);
670
675
  if (keySeparator) {
671
676
  const fk = this.resolve(key, {
672
677
  ...opt,
@@ -876,7 +881,10 @@ class Translator extends EventEmitter {
876
881
  const useOptionsReplaceForData = options.replace && !isString(options.replace);
877
882
  let data = useOptionsReplaceForData ? options.replace : options;
878
883
  if (useOptionsReplaceForData && typeof options.count !== 'undefined') {
879
- data.count = options.count;
884
+ data = {
885
+ ...data,
886
+ count: options.count
887
+ };
880
888
  }
881
889
  if (this.options.interpolation.defaultVariables) {
882
890
  data = {
@@ -1134,8 +1142,8 @@ class Interpolator {
1134
1142
  this.prefix = prefix ? regexEscape(prefix) : prefixEscaped || '{{';
1135
1143
  this.suffix = suffix ? regexEscape(suffix) : suffixEscaped || '}}';
1136
1144
  this.formatSeparator = formatSeparator || ',';
1137
- this.unescapePrefix = unescapeSuffix ? '' : unescapePrefix || '-';
1138
- this.unescapeSuffix = this.unescapePrefix ? '' : unescapeSuffix || '';
1145
+ this.unescapePrefix = unescapeSuffix ? '' : unescapePrefix ? regexEscape(unescapePrefix) : '-';
1146
+ this.unescapeSuffix = this.unescapePrefix ? '' : unescapeSuffix ? regexEscape(unescapeSuffix) : '';
1139
1147
  this.nestingPrefix = nestingPrefix ? regexEscape(nestingPrefix) : nestingPrefixEscaped || regexEscape('$t(');
1140
1148
  this.nestingSuffix = nestingSuffix ? regexEscape(nestingSuffix) : nestingSuffixEscaped || regexEscape(')');
1141
1149
  this.nestingOptionsSeparator = nestingOptionsSeparator || ',';
@@ -1182,14 +1190,17 @@ class Interpolator {
1182
1190
  });
1183
1191
  };
1184
1192
  this.resetRegExp();
1193
+ if (!this.escapeValue && typeof str === 'string' && /\$t\([^)]*\{[^}]*\{\{/.test(str)) {
1194
+ this.logger.warn('nesting options string contains interpolated variables with escapeValue: false — ' + 'if any of those values are attacker-controlled they can inject additional ' + 'nesting options (e.g. redirect lng/ns). Sanitise untrusted input before passing ' + 'it to t(), or keep escapeValue: true.');
1195
+ }
1185
1196
  const missingInterpolationHandler = options?.missingInterpolationHandler || this.options.missingInterpolationHandler;
1186
1197
  const skipOnVariables = options?.interpolation?.skipOnVariables !== undefined ? options.interpolation.skipOnVariables : this.options.interpolation.skipOnVariables;
1187
1198
  const todos = [{
1188
1199
  regex: this.regexpUnescape,
1189
- safeValue: val => regexSafe(val)
1200
+ safeValue: val => val
1190
1201
  }, {
1191
1202
  regex: this.regexp,
1192
- safeValue: val => this.escapeValue ? regexSafe(this.escape(val)) : regexSafe(val)
1203
+ safeValue: val => this.escapeValue ? this.escape(val) : val
1193
1204
  }];
1194
1205
  todos.forEach(todo => {
1195
1206
  replaces = 0;
@@ -1213,9 +1224,9 @@ class Interpolator {
1213
1224
  value = makeString(value);
1214
1225
  }
1215
1226
  const safeValue = todo.safeValue(value);
1216
- str = str.replace(match[0], safeValue);
1227
+ str = str.replace(match[0], regexSafe(safeValue));
1217
1228
  if (skipOnVariables) {
1218
- todo.regex.lastIndex += value.length;
1229
+ todo.regex.lastIndex += safeValue.length;
1219
1230
  todo.regex.lastIndex -= match[0].length;
1220
1231
  } else {
1221
1232
  todo.regex.lastIndex = 0;
@@ -1265,7 +1276,7 @@ class Interpolator {
1265
1276
  clonedOptions = clonedOptions.replace && !isString(clonedOptions.replace) ? clonedOptions.replace : clonedOptions;
1266
1277
  clonedOptions.applyPostProcessor = false;
1267
1278
  delete clonedOptions.defaultValue;
1268
- const keyEndIndex = /{.*}/.test(match[1]) ? match[1].lastIndexOf('}') + 1 : match[1].indexOf(this.formatSeparator);
1279
+ const keyEndIndex = /{.*}/s.test(match[1]) ? match[1].lastIndexOf('}') + 1 : match[1].indexOf(this.formatSeparator);
1269
1280
  if (keyEndIndex !== -1) {
1270
1281
  formatters = match[1].slice(keyEndIndex).split(this.formatSeparator).map(elem => elem.trim()).filter(Boolean);
1271
1282
  match[1] = match[1].slice(0, keyEndIndex);
@@ -1395,10 +1406,14 @@ class Formatter {
1395
1406
  format(value, format, lng, options = {}) {
1396
1407
  if (!format) return value;
1397
1408
  if (value == null) return value;
1398
- const formats = format.split(this.formatSeparator);
1399
- if (formats.length > 1 && formats[0].indexOf('(') > 1 && !formats[0].includes(')') && formats.find(f => f.includes(')'))) {
1400
- const lastIndex = formats.findIndex(f => f.includes(')'));
1401
- formats[0] = [formats[0], ...formats.splice(1, lastIndex)].join(this.formatSeparator);
1409
+ const rawFormats = format.split(this.formatSeparator);
1410
+ const formats = [];
1411
+ for (let i = 0; i < rawFormats.length; i++) {
1412
+ let f = rawFormats[i];
1413
+ while (f.indexOf('(') > -1 && !f.includes(')') && i + 1 < rawFormats.length) {
1414
+ f = `${f}${this.formatSeparator}${rawFormats[++i]}`;
1415
+ }
1416
+ formats.push(f);
1402
1417
  }
1403
1418
  const result = formats.reduce((mem, f) => {
1404
1419
  const {
@@ -1659,6 +1674,7 @@ const get = () => ({
1659
1674
  nsSeparator: ':',
1660
1675
  pluralSeparator: '_',
1661
1676
  contextSeparator: '_',
1677
+ enableSelector: false,
1662
1678
  partialBundledLanguages: false,
1663
1679
  saveMissing: false,
1664
1680
  updateMissing: false,
@@ -1860,7 +1876,7 @@ class I18n extends EventEmitter {
1860
1876
  deferred.resolve(t);
1861
1877
  callback(err, t);
1862
1878
  };
1863
- if (this.languages && !this.isInitialized) return finish(null, this.t.bind(this));
1879
+ if ((this.languages || this.isLanguageChangingTo) && !this.isInitialized) return finish(null, this.t.bind(this));
1864
1880
  this.changeLanguage(this.options.lng, finish);
1865
1881
  };
1866
1882
  if (this.options.resources || !this.options.initAsync) {
@@ -2015,7 +2031,8 @@ class I18n extends EventEmitter {
2015
2031
  }
2016
2032
  return deferred;
2017
2033
  }
2018
- getFixedT(lng, ns, keyPrefix) {
2034
+ getFixedT(lng, ns, keyPrefix, fixedOpts) {
2035
+ const scopeNs = fixedOpts?.scopeNs;
2019
2036
  const fixedT = (key, opts, ...rest) => {
2020
2037
  let o;
2021
2038
  if (typeof opts !== 'object') {
@@ -2027,12 +2044,14 @@ class I18n extends EventEmitter {
2027
2044
  }
2028
2045
  o.lng = o.lng || fixedT.lng;
2029
2046
  o.lngs = o.lngs || fixedT.lngs;
2047
+ const explicitCallNs = o.ns !== undefined && o.ns !== null;
2030
2048
  o.ns = o.ns || fixedT.ns;
2031
2049
  if (o.keyPrefix !== '') o.keyPrefix = o.keyPrefix || keyPrefix || fixedT.keyPrefix;
2032
2050
  const selectorOpts = {
2033
2051
  ...this.options,
2034
2052
  ...o
2035
2053
  };
2054
+ if (Array.isArray(scopeNs) && !explicitCallNs) selectorOpts.ns = scopeNs;
2036
2055
  if (typeof o.keyPrefix === 'function') o.keyPrefix = keysFromSelector(o.keyPrefix, selectorOpts);
2037
2056
  const keySeparator = this.options.keySeparator || '.';
2038
2057
  let resultKey;
@@ -1 +1 @@
1
- {"type":"module","version":"26.0.4"}
1
+ {"type":"module","version":"26.3.6"}
@@ -97,7 +97,7 @@
97
97
  const deepExtend = (target, source, overwrite) => {
98
98
  for (const prop in source) {
99
99
  if (prop !== '__proto__' && prop !== 'constructor') {
100
- if (prop in target) {
100
+ if (Object.prototype.hasOwnProperty.call(target, prop)) {
101
101
  if (isString(target[prop]) || target[prop] instanceof String || isString(source[prop]) || source[prop] instanceof String) {
102
102
  if (overwrite) target[prop] = source[prop];
103
103
  } else {
@@ -235,6 +235,7 @@
235
235
  }
236
236
  forward(args, lvl, prefix, debugOnly) {
237
237
  if (debugOnly && !this.debug) return null;
238
+ args = args.map(a => isString(a) ? a.replace(/[\r\n\x00-\x1F\x7F]/g, ' ') : a);
238
239
  if (isString(args[0])) args[0] = `${prefix}${this.prefix} ${args[0]}`;
239
240
  return this.logger[lvl](args);
240
241
  }
@@ -465,11 +466,15 @@
465
466
  } = selector(createProxy());
466
467
  const keySeparator = opts?.keySeparator ?? '.';
467
468
  const nsSeparator = opts?.nsSeparator ?? ':';
469
+ const strict = opts?.enableSelector === 'strict';
468
470
  if (path.length > 1 && nsSeparator) {
469
471
  const ns = opts?.ns;
470
- const nsArray = Array.isArray(ns) ? ns : null;
471
- if (nsArray && nsArray.length > 1 && nsArray.slice(1).includes(path[0])) {
472
- return `${path[0]}${nsSeparator}${path.slice(1).join(keySeparator)}`;
472
+ const nsList = strict ? Array.isArray(ns) ? ns : ns ? [ns] : null : Array.isArray(ns) ? ns : null;
473
+ if (nsList) {
474
+ const candidates = strict ? nsList : nsList.length > 1 ? nsList.slice(1) : [];
475
+ if (candidates.includes(path[0])) {
476
+ return `${path[0]}${nsSeparator}${path.slice(1).join(keySeparator)}`;
477
+ }
473
478
  }
474
479
  }
475
480
  return path.join(keySeparator);
@@ -672,7 +677,7 @@
672
677
  const resForMissing = missingKeyNoValueFallbackToKey && usedKey ? undefined : res;
673
678
  const updateMissing = hasDefaultValue && defaultValue !== res && this.options.updateMissing;
674
679
  if (usedKey || usedDefault || updateMissing) {
675
- this.logger.log(updateMissing ? 'updateKey' : 'missingKey', lng, namespace, key, updateMissing ? defaultValue : res);
680
+ this.logger.log(updateMissing ? 'updateKey' : 'missingKey', lng, namespace, needsPluralHandling && !updateMissing ? `${key}${this.pluralResolver.getSuffix(lng, opt.count, opt)}` : key, updateMissing ? defaultValue : res);
676
681
  if (keySeparator) {
677
682
  const fk = this.resolve(key, {
678
683
  ...opt,
@@ -882,7 +887,10 @@
882
887
  const useOptionsReplaceForData = options.replace && !isString(options.replace);
883
888
  let data = useOptionsReplaceForData ? options.replace : options;
884
889
  if (useOptionsReplaceForData && typeof options.count !== 'undefined') {
885
- data.count = options.count;
890
+ data = {
891
+ ...data,
892
+ count: options.count
893
+ };
886
894
  }
887
895
  if (this.options.interpolation.defaultVariables) {
888
896
  data = {
@@ -1140,8 +1148,8 @@
1140
1148
  this.prefix = prefix ? regexEscape(prefix) : prefixEscaped || '{{';
1141
1149
  this.suffix = suffix ? regexEscape(suffix) : suffixEscaped || '}}';
1142
1150
  this.formatSeparator = formatSeparator || ',';
1143
- this.unescapePrefix = unescapeSuffix ? '' : unescapePrefix || '-';
1144
- this.unescapeSuffix = this.unescapePrefix ? '' : unescapeSuffix || '';
1151
+ this.unescapePrefix = unescapeSuffix ? '' : unescapePrefix ? regexEscape(unescapePrefix) : '-';
1152
+ this.unescapeSuffix = this.unescapePrefix ? '' : unescapeSuffix ? regexEscape(unescapeSuffix) : '';
1145
1153
  this.nestingPrefix = nestingPrefix ? regexEscape(nestingPrefix) : nestingPrefixEscaped || regexEscape('$t(');
1146
1154
  this.nestingSuffix = nestingSuffix ? regexEscape(nestingSuffix) : nestingSuffixEscaped || regexEscape(')');
1147
1155
  this.nestingOptionsSeparator = nestingOptionsSeparator || ',';
@@ -1188,14 +1196,17 @@
1188
1196
  });
1189
1197
  };
1190
1198
  this.resetRegExp();
1199
+ if (!this.escapeValue && typeof str === 'string' && /\$t\([^)]*\{[^}]*\{\{/.test(str)) {
1200
+ this.logger.warn('nesting options string contains interpolated variables with escapeValue: false — ' + 'if any of those values are attacker-controlled they can inject additional ' + 'nesting options (e.g. redirect lng/ns). Sanitise untrusted input before passing ' + 'it to t(), or keep escapeValue: true.');
1201
+ }
1191
1202
  const missingInterpolationHandler = options?.missingInterpolationHandler || this.options.missingInterpolationHandler;
1192
1203
  const skipOnVariables = options?.interpolation?.skipOnVariables !== undefined ? options.interpolation.skipOnVariables : this.options.interpolation.skipOnVariables;
1193
1204
  const todos = [{
1194
1205
  regex: this.regexpUnescape,
1195
- safeValue: val => regexSafe(val)
1206
+ safeValue: val => val
1196
1207
  }, {
1197
1208
  regex: this.regexp,
1198
- safeValue: val => this.escapeValue ? regexSafe(this.escape(val)) : regexSafe(val)
1209
+ safeValue: val => this.escapeValue ? this.escape(val) : val
1199
1210
  }];
1200
1211
  todos.forEach(todo => {
1201
1212
  replaces = 0;
@@ -1219,9 +1230,9 @@
1219
1230
  value = makeString(value);
1220
1231
  }
1221
1232
  const safeValue = todo.safeValue(value);
1222
- str = str.replace(match[0], safeValue);
1233
+ str = str.replace(match[0], regexSafe(safeValue));
1223
1234
  if (skipOnVariables) {
1224
- todo.regex.lastIndex += value.length;
1235
+ todo.regex.lastIndex += safeValue.length;
1225
1236
  todo.regex.lastIndex -= match[0].length;
1226
1237
  } else {
1227
1238
  todo.regex.lastIndex = 0;
@@ -1271,7 +1282,7 @@
1271
1282
  clonedOptions = clonedOptions.replace && !isString(clonedOptions.replace) ? clonedOptions.replace : clonedOptions;
1272
1283
  clonedOptions.applyPostProcessor = false;
1273
1284
  delete clonedOptions.defaultValue;
1274
- const keyEndIndex = /{.*}/.test(match[1]) ? match[1].lastIndexOf('}') + 1 : match[1].indexOf(this.formatSeparator);
1285
+ const keyEndIndex = /{.*}/s.test(match[1]) ? match[1].lastIndexOf('}') + 1 : match[1].indexOf(this.formatSeparator);
1275
1286
  if (keyEndIndex !== -1) {
1276
1287
  formatters = match[1].slice(keyEndIndex).split(this.formatSeparator).map(elem => elem.trim()).filter(Boolean);
1277
1288
  match[1] = match[1].slice(0, keyEndIndex);
@@ -1401,10 +1412,14 @@
1401
1412
  format(value, format, lng, options = {}) {
1402
1413
  if (!format) return value;
1403
1414
  if (value == null) return value;
1404
- const formats = format.split(this.formatSeparator);
1405
- if (formats.length > 1 && formats[0].indexOf('(') > 1 && !formats[0].includes(')') && formats.find(f => f.includes(')'))) {
1406
- const lastIndex = formats.findIndex(f => f.includes(')'));
1407
- formats[0] = [formats[0], ...formats.splice(1, lastIndex)].join(this.formatSeparator);
1415
+ const rawFormats = format.split(this.formatSeparator);
1416
+ const formats = [];
1417
+ for (let i = 0; i < rawFormats.length; i++) {
1418
+ let f = rawFormats[i];
1419
+ while (f.indexOf('(') > -1 && !f.includes(')') && i + 1 < rawFormats.length) {
1420
+ f = `${f}${this.formatSeparator}${rawFormats[++i]}`;
1421
+ }
1422
+ formats.push(f);
1408
1423
  }
1409
1424
  const result = formats.reduce((mem, f) => {
1410
1425
  const {
@@ -1665,6 +1680,7 @@
1665
1680
  nsSeparator: ':',
1666
1681
  pluralSeparator: '_',
1667
1682
  contextSeparator: '_',
1683
+ enableSelector: false,
1668
1684
  partialBundledLanguages: false,
1669
1685
  saveMissing: false,
1670
1686
  updateMissing: false,
@@ -1866,7 +1882,7 @@
1866
1882
  deferred.resolve(t);
1867
1883
  callback(err, t);
1868
1884
  };
1869
- if (this.languages && !this.isInitialized) return finish(null, this.t.bind(this));
1885
+ if ((this.languages || this.isLanguageChangingTo) && !this.isInitialized) return finish(null, this.t.bind(this));
1870
1886
  this.changeLanguage(this.options.lng, finish);
1871
1887
  };
1872
1888
  if (this.options.resources || !this.options.initAsync) {
@@ -2021,7 +2037,8 @@
2021
2037
  }
2022
2038
  return deferred;
2023
2039
  }
2024
- getFixedT(lng, ns, keyPrefix) {
2040
+ getFixedT(lng, ns, keyPrefix, fixedOpts) {
2041
+ const scopeNs = fixedOpts?.scopeNs;
2025
2042
  const fixedT = (key, opts, ...rest) => {
2026
2043
  let o;
2027
2044
  if (typeof opts !== 'object') {
@@ -2033,12 +2050,14 @@
2033
2050
  }
2034
2051
  o.lng = o.lng || fixedT.lng;
2035
2052
  o.lngs = o.lngs || fixedT.lngs;
2053
+ const explicitCallNs = o.ns !== undefined && o.ns !== null;
2036
2054
  o.ns = o.ns || fixedT.ns;
2037
2055
  if (o.keyPrefix !== '') o.keyPrefix = o.keyPrefix || keyPrefix || fixedT.keyPrefix;
2038
2056
  const selectorOpts = {
2039
2057
  ...this.options,
2040
2058
  ...o
2041
2059
  };
2060
+ if (Array.isArray(scopeNs) && !explicitCallNs) selectorOpts.ns = scopeNs;
2042
2061
  if (typeof o.keyPrefix === 'function') o.keyPrefix = keysFromSelector(o.keyPrefix, selectorOpts);
2043
2062
  const keySeparator = this.options.keySeparator || '.';
2044
2063
  let resultKey;