react-i18next 17.0.5 → 17.0.7

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.
@@ -26,17 +26,6 @@ const getChildren = node => {
26
26
  };
27
27
  const hasValidReactChildren = children => Array.isArray(children) && children.every(_react.isValidElement);
28
28
  const getAsArray = data => Array.isArray(data) ? data : [data];
29
- const hasNonKeepReactDescendant = (children, keepArray) => {
30
- if (children == null) return false;
31
- return getAsArray(children).some(child => {
32
- if (!(0, _react.isValidElement)(child)) return false;
33
- const props = child.props || {};
34
- const propCount = Object.keys(props).length;
35
- const isKeepEligible = keepArray.indexOf(child.type) > -1 && propCount <= 1 && !props.i18nIsDynamicList;
36
- if (!isKeepEligible) return true;
37
- return hasNonKeepReactDescendant(props.children, keepArray);
38
- });
39
- };
40
29
  const mergeProps = (source, target) => {
41
30
  const newTarget = {
42
31
  ...target
@@ -86,7 +75,7 @@ const nodesToString = (children, i18nOptions, i18n, i18nKey) => {
86
75
  stringNode += `<${childIndex}></${childIndex}>`;
87
76
  return;
88
77
  }
89
- if (shouldKeepChild && childPropsCount <= 1 && !hasNonKeepReactDescendant(childChildren, keepArray)) {
78
+ if (shouldKeepChild && childPropsCount <= 1) {
90
79
  const cnt = (0, _utils.isString)(childChildren) ? childChildren : nodesToString(childChildren, i18nOptions, i18n, i18nKey);
91
80
  stringNode += `<${type}>${cnt}</${type}>`;
92
81
  return;
@@ -227,6 +216,7 @@ const renderNodes = (children, knownComponentsMap, targetString, i18n, i18nOptio
227
216
  const mapAST = (reactNode, astNode, rootReactNode) => {
228
217
  const reactNodes = getAsArray(reactNode);
229
218
  const astNodes = getAsArray(astNode);
219
+ const keepTagOccurrence = {};
230
220
  return astNodes.reduce((mem, node, i) => {
231
221
  const translationContent = node.children?.[0]?.content && i18n.services.interpolator.interpolate(node.children[0].content, opts, i18n.language);
232
222
  if (node.type === 'tag') {
@@ -271,7 +261,22 @@ const renderNodes = (children, knownComponentsMap, targetString, i18n, i18nOptio
271
261
  key: `${node.name}-${i}`
272
262
  }));
273
263
  } else {
274
- const inner = mapAST(reactNodes, node.children, rootReactNode);
264
+ const occurrence = keepTagOccurrence[node.name] || 0;
265
+ keepTagOccurrence[node.name] = occurrence + 1;
266
+ let matched;
267
+ let seen = 0;
268
+ for (let r = 0; r < reactNodes.length; r += 1) {
269
+ const rn = reactNodes[r];
270
+ if ((0, _react.isValidElement)(rn) && rn.type === node.name) {
271
+ if (seen === occurrence) {
272
+ matched = rn;
273
+ break;
274
+ }
275
+ seen += 1;
276
+ }
277
+ }
278
+ const innerScope = matched ? getAsArray(getChildren(matched)) : reactNodes;
279
+ const inner = mapAST(innerScope, node.children, rootReactNode);
275
280
  mem.push((0, _react.createElement)(node.name, {
276
281
  key: `${node.name}-${i}`
277
282
  }, inner));
@@ -79,7 +79,9 @@ const useTranslation = (ns, props = {}) => {
79
79
  if (lastSnapshot && lastSnapshot.ready === calculatedReady && lastSnapshot.lng === currentLng && lastSnapshot.keyPrefix === keyPrefix && lastSnapshot.revision === currentRevision) {
80
80
  return lastSnapshot;
81
81
  }
82
- const calculatedT = i18n.getFixedT(currentLng, i18nOptions.nsMode === 'fallback' ? namespaces : namespaces[0], keyPrefix);
82
+ const calculatedT = i18n.getFixedT(currentLng, i18nOptions.nsMode === 'fallback' ? namespaces : namespaces[0], keyPrefix, {
83
+ scopeNs: namespaces
84
+ });
83
85
  const newSnapshot = {
84
86
  t: calculatedT,
85
87
  ready: calculatedReady,
@@ -18,17 +18,6 @@ const getChildren = node => {
18
18
  };
19
19
  const hasValidReactChildren = children => Array.isArray(children) && children.every(isValidElement);
20
20
  const getAsArray = data => Array.isArray(data) ? data : [data];
21
- const hasNonKeepReactDescendant = (children, keepArray) => {
22
- if (children == null) return false;
23
- return getAsArray(children).some(child => {
24
- if (!isValidElement(child)) return false;
25
- const props = child.props || {};
26
- const propCount = Object.keys(props).length;
27
- const isKeepEligible = keepArray.indexOf(child.type) > -1 && propCount <= 1 && !props.i18nIsDynamicList;
28
- if (!isKeepEligible) return true;
29
- return hasNonKeepReactDescendant(props.children, keepArray);
30
- });
31
- };
32
21
  const mergeProps = (source, target) => {
33
22
  const newTarget = {
34
23
  ...target
@@ -78,7 +67,7 @@ export const nodesToString = (children, i18nOptions, i18n, i18nKey) => {
78
67
  stringNode += `<${childIndex}></${childIndex}>`;
79
68
  return;
80
69
  }
81
- if (shouldKeepChild && childPropsCount <= 1 && !hasNonKeepReactDescendant(childChildren, keepArray)) {
70
+ if (shouldKeepChild && childPropsCount <= 1) {
82
71
  const cnt = isString(childChildren) ? childChildren : nodesToString(childChildren, i18nOptions, i18n, i18nKey);
83
72
  stringNode += `<${type}>${cnt}</${type}>`;
84
73
  return;
@@ -218,6 +207,7 @@ const renderNodes = (children, knownComponentsMap, targetString, i18n, i18nOptio
218
207
  const mapAST = (reactNode, astNode, rootReactNode) => {
219
208
  const reactNodes = getAsArray(reactNode);
220
209
  const astNodes = getAsArray(astNode);
210
+ const keepTagOccurrence = {};
221
211
  return astNodes.reduce((mem, node, i) => {
222
212
  const translationContent = node.children?.[0]?.content && i18n.services.interpolator.interpolate(node.children[0].content, opts, i18n.language);
223
213
  if (node.type === 'tag') {
@@ -262,7 +252,22 @@ const renderNodes = (children, knownComponentsMap, targetString, i18n, i18nOptio
262
252
  key: `${node.name}-${i}`
263
253
  }));
264
254
  } else {
265
- const inner = mapAST(reactNodes, node.children, rootReactNode);
255
+ const occurrence = keepTagOccurrence[node.name] || 0;
256
+ keepTagOccurrence[node.name] = occurrence + 1;
257
+ let matched;
258
+ let seen = 0;
259
+ for (let r = 0; r < reactNodes.length; r += 1) {
260
+ const rn = reactNodes[r];
261
+ if (isValidElement(rn) && rn.type === node.name) {
262
+ if (seen === occurrence) {
263
+ matched = rn;
264
+ break;
265
+ }
266
+ seen += 1;
267
+ }
268
+ }
269
+ const innerScope = matched ? getAsArray(getChildren(matched)) : reactNodes;
270
+ const inner = mapAST(innerScope, node.children, rootReactNode);
266
271
  mem.push(createElement(node.name, {
267
272
  key: `${node.name}-${i}`
268
273
  }, inner));
@@ -1 +1 @@
1
- {"type":"module","version":"17.0.5"}
1
+ {"type":"module","version":"17.0.7"}
@@ -73,7 +73,9 @@ export const useTranslation = (ns, props = {}) => {
73
73
  if (lastSnapshot && lastSnapshot.ready === calculatedReady && lastSnapshot.lng === currentLng && lastSnapshot.keyPrefix === keyPrefix && lastSnapshot.revision === currentRevision) {
74
74
  return lastSnapshot;
75
75
  }
76
- const calculatedT = i18n.getFixedT(currentLng, i18nOptions.nsMode === 'fallback' ? namespaces : namespaces[0], keyPrefix);
76
+ const calculatedT = i18n.getFixedT(currentLng, i18nOptions.nsMode === 'fallback' ? namespaces : namespaces[0], keyPrefix, {
77
+ scopeNs: namespaces
78
+ });
77
79
  const newSnapshot = {
78
80
  t: calculatedT,
79
81
  ready: calculatedReady,
@@ -234,6 +234,7 @@
234
234
  }
235
235
  forward(args, lvl, prefix, debugOnly) {
236
236
  if (debugOnly && !this.debug) return null;
237
+ args = args.map(a => isString$1(a) ? a.replace(/[\r\n\x00-\x1F\x7F]/g, ' ') : a);
237
238
  if (isString$1(args[0])) args[0] = `${prefix}${this.prefix} ${args[0]}`;
238
239
  return this.logger[lvl](args);
239
240
  }
@@ -666,7 +667,7 @@
666
667
  const resForMissing = missingKeyNoValueFallbackToKey && usedKey ? undefined : res;
667
668
  const updateMissing = hasDefaultValue && defaultValue !== res && this.options.updateMissing;
668
669
  if (usedKey || usedDefault || updateMissing) {
669
- this.logger.log(updateMissing ? 'updateKey' : 'missingKey', lng, namespace, key, updateMissing ? defaultValue : res);
670
+ this.logger.log(updateMissing ? 'updateKey' : 'missingKey', lng, namespace, needsPluralHandling && !updateMissing ? `${key}${this.pluralResolver.getSuffix(lng, opt.count, opt)}` : key, updateMissing ? defaultValue : res);
670
671
  if (keySeparator) {
671
672
  const fk = this.resolve(key, {
672
673
  ...opt,
@@ -1131,8 +1132,8 @@
1131
1132
  this.prefix = prefix ? regexEscape(prefix) : prefixEscaped || '{{';
1132
1133
  this.suffix = suffix ? regexEscape(suffix) : suffixEscaped || '}}';
1133
1134
  this.formatSeparator = formatSeparator || ',';
1134
- this.unescapePrefix = unescapeSuffix ? '' : unescapePrefix || '-';
1135
- this.unescapeSuffix = this.unescapePrefix ? '' : unescapeSuffix || '';
1135
+ this.unescapePrefix = unescapeSuffix ? '' : unescapePrefix ? regexEscape(unescapePrefix) : '-';
1136
+ this.unescapeSuffix = this.unescapePrefix ? '' : unescapeSuffix ? regexEscape(unescapeSuffix) : '';
1136
1137
  this.nestingPrefix = nestingPrefix ? regexEscape(nestingPrefix) : nestingPrefixEscaped || regexEscape('$t(');
1137
1138
  this.nestingSuffix = nestingSuffix ? regexEscape(nestingSuffix) : nestingSuffixEscaped || regexEscape(')');
1138
1139
  this.nestingOptionsSeparator = nestingOptionsSeparator || ',';
@@ -1179,6 +1180,9 @@
1179
1180
  });
1180
1181
  };
1181
1182
  this.resetRegExp();
1183
+ if (!this.escapeValue && typeof str === 'string' && /\$t\([^)]*\{[^}]*\{\{/.test(str)) {
1184
+ 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.');
1185
+ }
1182
1186
  const missingInterpolationHandler = options?.missingInterpolationHandler || this.options.missingInterpolationHandler;
1183
1187
  const skipOnVariables = options?.interpolation?.skipOnVariables !== undefined ? options.interpolation.skipOnVariables : this.options.interpolation.skipOnVariables;
1184
1188
  const todos = [{
@@ -1853,7 +1857,7 @@
1853
1857
  deferred.resolve(t);
1854
1858
  callback(err, t);
1855
1859
  };
1856
- if (this.languages && !this.isInitialized) return finish(null, this.t.bind(this));
1860
+ if ((this.languages || this.isLanguageChangingTo) && !this.isInitialized) return finish(null, this.t.bind(this));
1857
1861
  this.changeLanguage(this.options.lng, finish);
1858
1862
  };
1859
1863
  if (this.options.resources || !this.options.initAsync) {
@@ -2008,7 +2012,8 @@
2008
2012
  }
2009
2013
  return deferred;
2010
2014
  }
2011
- getFixedT(lng, ns, keyPrefix) {
2015
+ getFixedT(lng, ns, keyPrefix, fixedOpts) {
2016
+ const scopeNs = fixedOpts?.scopeNs;
2012
2017
  const fixedT = (key, opts, ...rest) => {
2013
2018
  let o;
2014
2019
  if (typeof opts !== 'object') {
@@ -2020,12 +2025,14 @@
2020
2025
  }
2021
2026
  o.lng = o.lng || fixedT.lng;
2022
2027
  o.lngs = o.lngs || fixedT.lngs;
2028
+ const explicitCallNs = o.ns !== undefined && o.ns !== null;
2023
2029
  o.ns = o.ns || fixedT.ns;
2024
2030
  if (o.keyPrefix !== '') o.keyPrefix = o.keyPrefix || keyPrefix || fixedT.keyPrefix;
2025
2031
  const selectorOpts = {
2026
2032
  ...this.options,
2027
2033
  ...o
2028
2034
  };
2035
+ if (Array.isArray(scopeNs) && !explicitCallNs) selectorOpts.ns = scopeNs;
2029
2036
  if (typeof o.keyPrefix === 'function') o.keyPrefix = keysFromSelector(o.keyPrefix, selectorOpts);
2030
2037
  const keySeparator = this.options.keySeparator || '.';
2031
2038
  let resultKey;
@@ -2476,17 +2483,6 @@
2476
2483
  };
2477
2484
  const hasValidReactChildren = children => Array.isArray(children) && children.every(React.isValidElement);
2478
2485
  const getAsArray = data => Array.isArray(data) ? data : [data];
2479
- const hasNonKeepReactDescendant = (children, keepArray) => {
2480
- if (children == null) return false;
2481
- return getAsArray(children).some(child => {
2482
- if (!React.isValidElement(child)) return false;
2483
- const props = child.props || {};
2484
- const propCount = Object.keys(props).length;
2485
- const isKeepEligible = keepArray.indexOf(child.type) > -1 && propCount <= 1 && !props.i18nIsDynamicList;
2486
- if (!isKeepEligible) return true;
2487
- return hasNonKeepReactDescendant(props.children, keepArray);
2488
- });
2489
- };
2490
2486
  const mergeProps = (source, target) => {
2491
2487
  const newTarget = {
2492
2488
  ...target
@@ -2536,7 +2532,7 @@
2536
2532
  stringNode += `<${childIndex}></${childIndex}>`;
2537
2533
  return;
2538
2534
  }
2539
- if (shouldKeepChild && childPropsCount <= 1 && !hasNonKeepReactDescendant(childChildren, keepArray)) {
2535
+ if (shouldKeepChild && childPropsCount <= 1) {
2540
2536
  const cnt = isString(childChildren) ? childChildren : nodesToString(childChildren, i18nOptions, i18n, i18nKey);
2541
2537
  stringNode += `<${type}>${cnt}</${type}>`;
2542
2538
  return;
@@ -2676,6 +2672,7 @@
2676
2672
  const mapAST = (reactNode, astNode, rootReactNode) => {
2677
2673
  const reactNodes = getAsArray(reactNode);
2678
2674
  const astNodes = getAsArray(astNode);
2675
+ const keepTagOccurrence = {};
2679
2676
  return astNodes.reduce((mem, node, i) => {
2680
2677
  const translationContent = node.children?.[0]?.content && i18n.services.interpolator.interpolate(node.children[0].content, opts, i18n.language);
2681
2678
  if (node.type === 'tag') {
@@ -2720,7 +2717,22 @@
2720
2717
  key: `${node.name}-${i}`
2721
2718
  }));
2722
2719
  } else {
2723
- const inner = mapAST(reactNodes, node.children, rootReactNode);
2720
+ const occurrence = keepTagOccurrence[node.name] || 0;
2721
+ keepTagOccurrence[node.name] = occurrence + 1;
2722
+ let matched;
2723
+ let seen = 0;
2724
+ for (let r = 0; r < reactNodes.length; r += 1) {
2725
+ const rn = reactNodes[r];
2726
+ if (React.isValidElement(rn) && rn.type === node.name) {
2727
+ if (seen === occurrence) {
2728
+ matched = rn;
2729
+ break;
2730
+ }
2731
+ seen += 1;
2732
+ }
2733
+ }
2734
+ const innerScope = matched ? getAsArray(getChildren(matched)) : reactNodes;
2735
+ const inner = mapAST(innerScope, node.children, rootReactNode);
2724
2736
  mem.push(React.createElement(node.name, {
2725
2737
  key: `${node.name}-${i}`
2726
2738
  }, inner));
@@ -3612,7 +3624,9 @@
3612
3624
  if (lastSnapshot && lastSnapshot.ready === calculatedReady && lastSnapshot.lng === currentLng && lastSnapshot.keyPrefix === keyPrefix && lastSnapshot.revision === currentRevision) {
3613
3625
  return lastSnapshot;
3614
3626
  }
3615
- const calculatedT = i18n.getFixedT(currentLng, i18nOptions.nsMode === 'fallback' ? namespaces : namespaces[0], keyPrefix);
3627
+ const calculatedT = i18n.getFixedT(currentLng, i18nOptions.nsMode === 'fallback' ? namespaces : namespaces[0], keyPrefix, {
3628
+ scopeNs: namespaces
3629
+ });
3616
3630
  const newSnapshot = {
3617
3631
  t: calculatedT,
3618
3632
  ready: calculatedReady,