• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

i18next / react-i18next / 18932102671

30 Oct 2025 06:35AM UTC coverage: 97.439% (-0.3%) from 97.735%
18932102671

push

github

web-flow
fix trans component break with less than sign (#1880)

* fix trans component break with less than sign

* fix failed test cases

628 of 683 branches covered (91.95%)

80 of 80 new or added lines in 1 file covered. (100.0%)

10 existing lines in 1 file now uncovered.

2701 of 2772 relevant lines covered (97.44%)

60.42 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.72
/src/TransWithoutContext.js
1
import { Fragment, isValidElement, cloneElement, createElement, Children } from 'react';
1✔
2
import { keyFromSelector } from 'i18next';
1✔
3
import HTML from 'html-parse-stringify';
1✔
4
import { isObject, isString, warn, warnOnce } from './utils.js';
1✔
5
import { getDefaults } from './defaults.js';
1✔
6
import { getI18n } from './i18nInstance.js';
1✔
7

1✔
8
const hasChildren = (node, checkLength) => {
1✔
9
  if (!node) return false;
297✔
10
  const base = node.props?.children ?? node.children;
297✔
11
  if (checkLength) return base.length > 0;
297✔
12
  return !!base;
207✔
13
};
207✔
14

1✔
15
const getChildren = (node) => {
1✔
16
  if (!node) return [];
229!
17
  const children = node.props?.children ?? node.children;
229✔
18
  return node.props?.i18nIsDynamicList ? getAsArray(children) : children;
229✔
19
};
229✔
20

1✔
21
const hasValidReactChildren = (children) =>
1✔
22
  Array.isArray(children) && children.every(isValidElement);
1✔
23

1✔
24
const getAsArray = (data) => (Array.isArray(data) ? data : [data]);
1✔
25

1✔
26
const mergeProps = (source, target) => {
1✔
27
  const newTarget = { ...target };
3✔
28
  // overwrite source.props when target.props already set
3✔
29
  newTarget.props = Object.assign(source.props, target.props);
3✔
30
  return newTarget;
3✔
31
};
3✔
32

1✔
33
export const nodesToString = (children, i18nOptions, i18n, i18nKey) => {
1✔
34
  if (!children) return '';
113✔
35
  let stringNode = '';
64✔
36

64✔
37
  // do not use `React.Children.toArray`, will fail at object children
64✔
38
  const childrenArray = getAsArray(children);
64✔
39
  const keepArray = i18nOptions?.transSupportBasicHtmlNodes
64✔
40
    ? (i18nOptions.transKeepBasicHtmlNodesFor ?? [])
113!
41
    : [];
113✔
42

113✔
43
  // e.g. lorem <br/> ipsum {{ messageCount, format }} dolor <strong>bold</strong> amet
113✔
44
  childrenArray.forEach((child, childIndex) => {
113✔
45
    if (isString(child)) {
142✔
46
      // actual e.g. lorem
90✔
47
      // expected e.g. lorem
90✔
48
      stringNode += `${child}`;
90✔
49
      return;
90✔
50
    }
90✔
51
    if (isValidElement(child)) {
115✔
52
      const { props, type } = child;
37✔
53
      const childPropsCount = Object.keys(props).length;
37✔
54
      const shouldKeepChild = keepArray.indexOf(type) > -1;
37✔
55
      const childChildren = props.children;
37✔
56

37✔
57
      if (!childChildren && shouldKeepChild && !childPropsCount) {
37✔
58
        // actual e.g. lorem <br/> ipsum
2✔
59
        // expected e.g. lorem <br/> ipsum
2✔
60
        stringNode += `<${type}/>`;
2✔
61
        return;
2✔
62
      }
2✔
63
      if ((!childChildren && (!shouldKeepChild || childPropsCount)) || props.i18nIsDynamicList) {
37✔
64
        // actual e.g. lorem <hr className="test" /> ipsum
8✔
65
        // expected e.g. lorem <0></0> ipsum
8✔
66
        // or
8✔
67
        // we got a dynamic list like
8✔
68
        // e.g. <ul i18nIsDynamicList>{['a', 'b'].map(item => ( <li key={item}>{item}</li> ))}</ul>
8✔
69
        // expected e.g. "<0></0>", not e.g. "<0><0>a</0><1>b</1></0>"
8✔
70
        stringNode += `<${childIndex}></${childIndex}>`;
8✔
71
        return;
8✔
72
      }
8✔
73
      if (shouldKeepChild && childPropsCount === 1 && isString(childChildren)) {
37✔
74
        // actual e.g. dolor <strong>bold</strong> amet
2✔
75
        // expected e.g. dolor <strong>bold</strong> amet
2✔
76
        stringNode += `<${type}>${childChildren}</${type}>`;
2✔
77
        return;
2✔
78
      }
2✔
79
      // regular case mapping the inner children
25✔
80
      const content = nodesToString(childChildren, i18nOptions, i18n, i18nKey);
25✔
81
      stringNode += `<${childIndex}>${content}</${childIndex}>`;
25✔
82
      return;
25✔
83
    }
25✔
84
    if (child === null) {
101✔
85
      warn(i18n, 'TRANS_NULL_VALUE', `Passed in a null value as child`, { i18nKey });
1✔
86
      return;
1✔
87
    }
1✔
88
    if (isObject(child)) {
14✔
89
      // e.g. lorem {{ value, format }} ipsum
14✔
90
      const { format, ...clone } = child;
14✔
91
      const keys = Object.keys(clone);
14✔
92

14✔
93
      if (keys.length === 1) {
14✔
94
        const value = format ? `${keys[0]}, ${format}` : keys[0];
14!
95
        stringNode += `{{${value}}}`;
14✔
96
        return;
14✔
97
      }
14✔
98
      warn(
×
99
        i18n,
×
100
        'TRANS_INVALID_OBJ',
×
101
        `Invalid child - Object should only have keys {{ value, format }} (format is optional).`,
×
102
        { i18nKey, child },
×
103
      );
×
104
      return;
×
105
    }
×
106
    warn(
×
107
      i18n,
×
108
      'TRANS_INVALID_VAR',
×
109
      `Passed in a variable like {number} - pass variables for interpolation as full objects like {{number}}.`,
×
110
      { i18nKey, child },
×
111
    );
×
112
  });
113✔
113

113✔
114
  return stringNode;
113✔
115
};
113✔
116

1✔
117
/**
1✔
118
 * Escape literal < characters that are not part of valid tags
1✔
119
 * Valid tags are: numbered tags like <0>, </0> or named tags from keepArray/knownComponents
1✔
120
 * @param {string} str - The string to escape
1✔
121
 * @param {Array<string>} keepArray - Array of HTML tag names to keep
1✔
122
 * @param {Object} knownComponentsMap - Map of known component names
1✔
123
 * @returns {string} String with literal < characters escaped
1✔
124
 */
1✔
125
const escapeLiteralLessThan = (str, keepArray = [], knownComponentsMap = {}) => {
1✔
126
  if (!str) return str;
64!
127

64✔
128
  // Build a list of valid tag names (numbered indices and known component names)
64✔
129
  const knownNames = Object.keys(knownComponentsMap);
64✔
130
  const allValidNames = [...keepArray, ...knownNames];
64✔
131

64✔
132
  // Pattern to match:
64✔
133
  // 1. Opening tags: <number> or <name> where name is in allValidNames
64✔
134
  // 2. Closing tags: </number> or </name> where name is in allValidNames
64✔
135
  // 3. Self-closing tags: <name/> or <name /> where name is in keepArray
64✔
136
  // Everything else starting with < should be escaped
64✔
137

64✔
138
  let result = '';
64✔
139
  let i = 0;
64✔
140

64✔
141
  while (i < str.length) {
64✔
142
    if (str[i] === '<') {
1,684✔
143
      // Check if this is a valid tag
165✔
144
      let isValidTag = false;
165✔
145

165✔
146
      // Check for closing tag: </number> or </name>
165✔
147
      const closingMatch = str.slice(i).match(/^<\/(\d+|[a-zA-Z][a-zA-Z0-9]*)>/);
165✔
148
      if (closingMatch) {
165✔
149
        const tagName = closingMatch[1];
74✔
150
        // Valid if it's a number or in our valid names list
74✔
151
        if (/^\d+$/.test(tagName) || allValidNames.includes(tagName)) {
74✔
152
          isValidTag = true;
73✔
153
          result += closingMatch[0];
73✔
154
          i += closingMatch[0].length;
73✔
155
        }
73✔
156
      }
74✔
157

165✔
158
      // Check for opening tag: <number> or <name> or <name/> or <name />
165✔
159
      // Also handle tags with attributes: <0 href="..."> or <name class="...">
165✔
160
      if (!isValidTag) {
165✔
161
        // Match: <tagName [attributes] [/]>
92✔
162
        // Attributes pattern: name="value" or name='value' or name (boolean)
92✔
163
        const openingMatch = str
92✔
164
          .slice(i)
92✔
165
          .match(
92✔
166
            /^<(\d+|[a-zA-Z][a-zA-Z0-9]*)(\s+[\w\-]+(?:=(?:"[^"]*"|'[^']*'|[^\s>]+))?)*\s*(\/)?>/,
92✔
167
          );
92✔
168
        if (openingMatch) {
92✔
169
          const tagName = openingMatch[1];
87✔
170
          // Valid if it's a number or in our valid names list
87✔
171
          if (/^\d+$/.test(tagName) || allValidNames.includes(tagName)) {
87✔
172
            isValidTag = true;
85✔
173
            result += openingMatch[0];
85✔
174
            i += openingMatch[0].length;
85✔
175
          }
85✔
176
        }
87✔
177
      }
92✔
178

165✔
179
      // If not a valid tag, escape the <
165✔
180
      if (!isValidTag) {
165✔
181
        result += '&lt;';
7✔
182
        i++;
7✔
183
      }
7✔
184
    } else {
1,684✔
185
      result += str[i];
1,519✔
186
      i++;
1,519✔
187
    }
1,519✔
188
  }
1,684✔
189

64✔
190
  return result;
64✔
191
};
64✔
192

1✔
193
const renderNodes = (
1✔
194
  children,
80✔
195
  knownComponentsMap,
80✔
196
  targetString,
80✔
197
  i18n,
80✔
198
  i18nOptions,
80✔
199
  combinedTOpts,
80✔
200
  shouldUnescape,
80✔
201
) => {
80✔
202
  if (targetString === '') return [];
80!
203

80✔
204
  // check if contains tags we need to replace from html string to react nodes
80✔
205
  const keepArray = i18nOptions.transKeepBasicHtmlNodesFor || [];
80!
206
  const emptyChildrenButNeedsHandling =
80✔
207
    targetString && new RegExp(keepArray.map((keep) => `<${keep}`).join('|')).test(targetString);
80✔
208

80✔
209
  // no need to replace tags in the targetstring
80✔
210
  if (!children && !knownComponentsMap && !emptyChildrenButNeedsHandling && !shouldUnescape)
80✔
211
    return [targetString];
80✔
212

64✔
213
  // v2 -> interpolates upfront no need for "some <0>{{var}}</0>"" -> will be just "some {{var}}" in translation file
64✔
214
  const data = knownComponentsMap ?? {};
80✔
215

80✔
216
  const getData = (childs) => {
80✔
217
    const childrenArray = getAsArray(childs);
100✔
218

100✔
219
    childrenArray.forEach((child) => {
100✔
220
      if (isString(child)) return;
165✔
221
      if (hasChildren(child)) getData(getChildren(child));
156✔
222
      else if (isObject(child) && !isValidElement(child)) Object.assign(data, child);
46✔
223
    });
100✔
224
  };
80✔
225

80✔
226
  getData(children);
80✔
227

80✔
228
  // Escape literal < characters that are not part of valid tags before parsing
80✔
229
  const escapedString = escapeLiteralLessThan(targetString, keepArray, data);
80✔
230

80✔
231
  // parse ast from string with additional wrapper tag
80✔
232
  // -> avoids issues in parser removing prepending text nodes
80✔
233
  const ast = HTML.parse(`<0>${escapedString}</0>`);
80✔
234
  const opts = { ...data, ...combinedTOpts };
80✔
235

80✔
236
  const renderInner = (child, node, rootReactNode) => {
80✔
237
    const childs = getChildren(child);
129✔
238
    const mappedChildren = mapAST(childs, node.children, rootReactNode);
129✔
239
    // `mappedChildren` will always be empty if using the `i18nIsDynamicList` prop,
129✔
240
    // but the children might not necessarily be react components
129✔
241
    return (hasValidReactChildren(childs) && mappedChildren.length === 0) ||
129✔
242
      child.props?.i18nIsDynamicList
126✔
243
      ? childs
129✔
244
      : mappedChildren;
129✔
245
  };
80✔
246

80✔
247
  const pushTranslatedJSX = (child, inner, mem, i, isVoid) => {
80✔
248
    if (child.dummy) {
131✔
249
      child.children = inner; // needed on preact!
64✔
250
      mem.push(cloneElement(child, { key: i }, isVoid ? undefined : inner));
64!
251
    } else {
131✔
252
      mem.push(
67✔
253
        ...Children.map([child], (c) => {
67✔
254
          const props = { ...c.props };
67✔
255
          delete props.i18nIsDynamicList;
67✔
256
          // <c.type {...props} key={i} ref={c.ref} {...(isVoid ? {} : { children: inner })} />;
67✔
257
          return createElement(
67✔
258
            c.type,
67✔
259
            {
67✔
260
              ...props,
67✔
261
              key: i,
67✔
262
              ref: c.props.ref ?? c.ref, // ref is a prop in react >= v19
67✔
263
            },
67✔
264
            isVoid ? null : inner,
67✔
265
          );
67✔
266
        }),
67✔
267
      );
67✔
268
    }
67✔
269
  };
80✔
270

80✔
271
  // reactNode (the jsx root element or child)
80✔
272
  // astNode (the translation string as html ast)
80✔
273
  // rootReactNode (the most outer jsx children array or trans components prop)
80✔
274
  const mapAST = (reactNode, astNode, rootReactNode) => {
80✔
275
    const reactNodes = getAsArray(reactNode);
198✔
276
    const astNodes = getAsArray(astNode);
198✔
277

198✔
278
    return astNodes.reduce((mem, node, i) => {
198✔
279
      const translationContent =
305✔
280
        node.children?.[0]?.content &&
305✔
281
        i18n.services.interpolator.interpolate(node.children[0].content, opts, i18n.language);
305✔
282

305✔
283
      if (node.type === 'tag') {
305✔
284
        // regular array (components or children)
149✔
285
        let tmp = reactNodes[parseInt(node.name, 10)];
149✔
286
        if (!tmp && knownComponentsMap) tmp = knownComponentsMap[node.name];
149✔
287

149✔
288
        // trans components is an object
149✔
289
        if (rootReactNode.length === 1 && !tmp) tmp = rootReactNode[0][node.name];
149✔
290

149✔
291
        // neither
149✔
292
        if (!tmp) tmp = {};
149✔
293

149✔
294
        const child =
149✔
295
          Object.keys(node.attrs).length !== 0 ? mergeProps({ props: node.attrs }, tmp) : tmp;
149✔
296

149✔
297
        const isElement = isValidElement(child);
149✔
298

149✔
299
        const isValidTranslationWithChildren =
149✔
300
          isElement && hasChildren(node, true) && !node.voidElement;
149✔
301

149✔
302
        const isEmptyTransWithHTML =
149✔
303
          emptyChildrenButNeedsHandling && isObject(child) && child.dummy && !isElement;
149✔
304

149✔
305
        const isKnownComponent =
149✔
306
          isObject(knownComponentsMap) && Object.hasOwnProperty.call(knownComponentsMap, node.name);
149✔
307

149✔
308
        if (isString(child)) {
149✔
309
          const value = i18n.services.interpolator.interpolate(child, opts, i18n.language);
1✔
310
          mem.push(value);
1✔
311
        } else if (
149✔
312
          hasChildren(child) || // the jsx element has children -> loop
148✔
313
          isValidTranslationWithChildren // valid jsx element with no children but the translation has -> loop
148✔
314
        ) {
148✔
315
          const inner = renderInner(child, node, rootReactNode);
121✔
316
          pushTranslatedJSX(child, inner, mem, i);
121✔
317
        } else if (isEmptyTransWithHTML) {
148!
318
          // we have a empty Trans node (the dummy element) with a targetstring that contains html tags needing
×
319
          // conversion to react nodes
×
320
          // so we just need to map the inner stuff
×
321
          const inner = mapAST(
×
322
            reactNodes /* wrong but we need something */,
×
323
            node.children,
×
324
            rootReactNode,
×
325
          );
×
326
          pushTranslatedJSX(child, inner, mem, i);
×
327
        } else if (Number.isNaN(parseFloat(node.name))) {
27✔
328
          if (isKnownComponent) {
17✔
329
            const inner = renderInner(child, node, rootReactNode);
8✔
330
            pushTranslatedJSX(child, inner, mem, i, node.voidElement);
8✔
331
          } else if (i18nOptions.transSupportBasicHtmlNodes && keepArray.indexOf(node.name) > -1) {
17✔
332
            if (node.voidElement) {
9✔
333
              mem.push(createElement(node.name, { key: `${node.name}-${i}` }));
4✔
334
            } else {
9✔
335
              const inner = mapAST(
5✔
336
                reactNodes /* wrong but we need something */,
5✔
337
                node.children,
5✔
338
                rootReactNode,
5✔
339
              );
5✔
340

5✔
341
              mem.push(createElement(node.name, { key: `${node.name}-${i}` }, inner));
5✔
342
            }
5✔
343
          } else if (node.voidElement) {
9!
UNCOV
344
            mem.push(`<${node.name} />`);
×
UNCOV
345
          } else {
×
UNCOV
346
            const inner = mapAST(
×
UNCOV
347
              reactNodes /* wrong but we need something */,
×
UNCOV
348
              node.children,
×
UNCOV
349
              rootReactNode,
×
UNCOV
350
            );
×
UNCOV
351

×
UNCOV
352
            mem.push(`<${node.name}>${inner}</${node.name}>`);
×
UNCOV
353
          }
×
354
        } else if (isObject(child) && !isElement) {
27✔
355
          const content = node.children[0] ? translationContent : null;
8!
356

8✔
357
          // v1
8✔
358
          // as interpolation was done already we just have a regular content node
8✔
359
          // in the translation AST while having an object in reactNodes
8✔
360
          // -> push the content no need to interpolate again
8✔
361
          if (content) mem.push(content);
8✔
362
        } else {
10✔
363
          // If component does not have children, but translation - has
2✔
364
          // with this in component could be components={[<span class='make-beautiful'/>]} and in translation - 'some text <0>some highlighted message</0>'
2✔
365
          pushTranslatedJSX(
2✔
366
            child,
2✔
367
            translationContent,
2✔
368
            mem,
2✔
369
            i,
2✔
370
            node.children.length !== 1 || !translationContent,
2!
371
          );
2✔
372
        }
2✔
373
      } else if (node.type === 'text') {
305✔
374
        const wrapTextNodes = i18nOptions.transWrapTextNodes;
156✔
375
        const content = shouldUnescape
156✔
376
          ? i18nOptions.unescape(
156✔
377
              i18n.services.interpolator.interpolate(node.content, opts, i18n.language),
6✔
378
            )
6✔
379
          : i18n.services.interpolator.interpolate(node.content, opts, i18n.language);
156✔
380
        if (wrapTextNodes) {
156✔
381
          mem.push(createElement(wrapTextNodes, { key: `${node.name}-${i}` }, content));
3✔
382
        } else {
156✔
383
          mem.push(content);
153✔
384
        }
153✔
385
      }
156✔
386
      return mem;
305✔
387
    }, []);
198✔
388
  };
80✔
389

80✔
390
  // call mapAST with having react nodes nested into additional node like
80✔
391
  // we did for the string ast from translation
80✔
392
  // return the children of that extra node to get expected result
80✔
393
  const result = mapAST(
80✔
394
    [{ dummy: true, children: children || [] }],
80✔
395
    ast,
80✔
396
    getAsArray(children || []),
80✔
397
  );
80✔
398
  return getChildren(result[0]);
80✔
399
};
80✔
400

1✔
401
const fixComponentProps = (component, index, translation) => {
1✔
402
  const componentKey = component.key || index;
40✔
403
  const comp = cloneElement(component, { key: componentKey });
40✔
404
  if (
40✔
405
    !comp.props ||
40✔
406
    !comp.props.children ||
40✔
407
    (translation.indexOf(`${index}/>`) < 0 && translation.indexOf(`${index} />`) < 0)
27✔
408
  ) {
40✔
409
    return comp;
36✔
410
  }
36✔
411

4✔
412
  function Componentized() {
4✔
413
    // <>{comp}</>
4✔
414
    return createElement(Fragment, null, comp);
4✔
415
  }
4✔
416
  // <Componentized />
4✔
417
  return createElement(Componentized, { key: componentKey });
4✔
418
};
4✔
419

1✔
420
const generateArrayComponents = (components, translation) =>
1✔
421
  components.map((c, index) => fixComponentProps(c, index, translation));
1✔
422

1✔
423
const generateObjectComponents = (components, translation) => {
1✔
424
  const componentMap = {};
21✔
425

21✔
426
  Object.keys(components).forEach((c) => {
21✔
427
    Object.assign(componentMap, {
29✔
428
      [c]: fixComponentProps(components[c], c, translation),
29✔
429
    });
29✔
430
  });
21✔
431

21✔
432
  return componentMap;
21✔
433
};
21✔
434

1✔
435
const generateComponents = (components, translation, i18n, i18nKey) => {
1✔
436
  if (!components) return null;
80✔
437

32✔
438
  // components could be either an array or an object
32✔
439

32✔
440
  if (Array.isArray(components)) {
77✔
441
    return generateArrayComponents(components, translation);
10✔
442
  }
10✔
443

22✔
444
  if (isObject(components)) {
69✔
445
    return generateObjectComponents(components, translation);
21✔
446
  }
21✔
447

1✔
448
  // if components is not an array or an object, warn the user
1✔
449
  // and return null
1✔
450
  warnOnce(
1✔
451
    i18n,
1✔
452
    'TRANS_INVALID_COMPONENTS',
1✔
453
    `<Trans /> "components" prop expects an object or array`,
1✔
454
    { i18nKey },
1✔
455
  );
1✔
456
  return null;
1✔
457
};
1✔
458

1✔
459
// A component map is an object like: { Button: <button> }, but not an object like { 1: <button> }
1✔
460
const isComponentsMap = (object) => {
1✔
461
  if (!isObject(object)) return false;
80✔
462
  if (Array.isArray(object)) return false;
77✔
463
  return Object.keys(object).reduce(
21✔
464
    (acc, key) => acc && Number.isNaN(Number.parseFloat(key)),
21✔
465
    true,
21✔
466
  );
21✔
467
};
21✔
468

1✔
469
export function Trans({
1✔
470
  children,
80✔
471
  count,
80✔
472
  parent,
80✔
473
  i18nKey,
80✔
474
  context,
80✔
475
  tOptions = {},
80✔
476
  values,
80✔
477
  defaults,
80✔
478
  components,
80✔
479
  ns,
80✔
480
  i18n: i18nFromProps,
80✔
481
  t: tFromProps,
80✔
482
  shouldUnescape,
80✔
483
  ...additionalProps
80✔
484
}) {
80✔
485
  const i18n = i18nFromProps || getI18n();
80✔
486

80✔
487
  if (!i18n) {
80!
488
    warnOnce(
×
489
      i18n,
×
490
      'NO_I18NEXT_INSTANCE',
×
491
      `Trans: You need to pass in an i18next instance using i18nextReactModule`,
×
492
      { i18nKey },
×
493
    );
×
494
    return children;
×
495
  }
×
496

80✔
497
  const t = tFromProps || i18n.t.bind(i18n) || ((k) => k);
80!
498

80✔
499
  const reactI18nextOptions = { ...getDefaults(), ...i18n.options?.react };
80✔
500

80✔
501
  // prepare having a namespace
80✔
502
  let namespaces = ns || t.ns || i18n.options?.defaultNS;
80✔
503
  namespaces = isString(namespaces) ? [namespaces] : namespaces || ['translation'];
80!
504

80✔
505
  const nodeAsString = nodesToString(children, reactI18nextOptions, i18n, i18nKey);
80✔
506
  const defaultValue =
80✔
507
    defaults ||
80✔
508
    tOptions?.defaultValue ||
80✔
509
    nodeAsString ||
80✔
510
    reactI18nextOptions.transEmptyNodeValue ||
80✔
511
    (typeof i18nKey === 'function' ? keyFromSelector(i18nKey) : i18nKey);
80✔
512
  const { hashTransKey } = reactI18nextOptions;
80✔
513
  const key =
80✔
514
    i18nKey ||
80✔
515
    (hashTransKey ? hashTransKey(nodeAsString || defaultValue) : nodeAsString || defaultValue);
80!
516
  if (i18n.options?.interpolation?.defaultVariables) {
80✔
517
    // eslint-disable-next-line no-param-reassign
78✔
518
    values =
78✔
519
      values && Object.keys(values).length > 0
78✔
520
        ? { ...values, ...i18n.options.interpolation.defaultVariables }
78✔
521
        : { ...i18n.options.interpolation.defaultVariables };
78✔
522
  }
78✔
523
  const interpolationOverride =
80✔
524
    values ||
80✔
525
    (count !== undefined && !i18n.options?.interpolation?.alwaysFormat) || // https://github.com/i18next/react-i18next/issues/1719 + https://github.com/i18next/react-i18next/issues/1801
80✔
526
    !children // if !children gets problems in future, undo that fix: https://github.com/i18next/react-i18next/issues/1729 by removing !children from this condition
80✔
527
      ? tOptions.interpolation
80✔
528
      : { interpolation: { ...tOptions.interpolation, prefix: '#$?', suffix: '?$#' } };
80✔
529
  const combinedTOpts = {
80✔
530
    ...tOptions,
80✔
531
    context: context || tOptions.context, // Add `context` from the props or fallback to the value from `tOptions`
80✔
532
    count,
80✔
533
    ...values,
80✔
534
    ...interpolationOverride,
80✔
535
    defaultValue: defaults || tOptions?.defaultValue,
80✔
536
    ns: namespaces,
80✔
537
  };
80✔
538
  const translation = key ? t(key, combinedTOpts) : defaultValue;
80✔
539

80✔
540
  const generatedComponents = generateComponents(components, translation, i18n, i18nKey);
80✔
541
  let indexedChildren = generatedComponents || children;
80✔
542
  let componentsMap = null;
80✔
543
  if (isComponentsMap(generatedComponents)) {
80✔
544
    componentsMap = generatedComponents;
20✔
545
    indexedChildren = children;
20✔
546
  }
20✔
547

80✔
548
  const content = renderNodes(
80✔
549
    indexedChildren,
80✔
550
    componentsMap,
80✔
551
    translation,
80✔
552
    i18n,
80✔
553
    reactI18nextOptions,
80✔
554
    combinedTOpts,
80✔
555
    shouldUnescape,
80✔
556
  );
80✔
557

80✔
558
  // allows user to pass `null` to `parent`
80✔
559
  // and override `defaultTransParent` if is present
80✔
560
  const useAsParent = parent ?? reactI18nextOptions.defaultTransParent;
80✔
561

80✔
562
  return useAsParent ? createElement(useAsParent, additionalProps, content) : content;
80✔
563
}
80✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc