react-i18next 11.8.13 → 11.10.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.
package/icu.macro.js CHANGED
@@ -5,14 +5,24 @@ const { createMacro } = require('babel-plugin-macros');
5
5
  module.exports = createMacro(ICUMacro);
6
6
 
7
7
  function ICUMacro({ references, state, babel }) {
8
- const t = babel.types;
9
- const { Trans = [], Plural = [], Select = [], SelectOrdinal = [] } = references;
8
+ const {
9
+ Trans = [],
10
+ Plural = [],
11
+ Select = [],
12
+ SelectOrdinal = [],
13
+ number = [],
14
+ date = [],
15
+ select = [],
16
+ selectOrdinal = [],
17
+ plural = [],
18
+ time = [],
19
+ } = references;
10
20
 
11
21
  // assert we have the react-i18next Trans component imported
12
- addNeededImports(state, babel);
22
+ addNeededImports(state, babel, references);
13
23
 
14
24
  // transform Plural and SelectOrdinal
15
- [...Plural, ...SelectOrdinal].forEach(referencePath => {
25
+ [...Plural, ...SelectOrdinal].forEach((referencePath) => {
16
26
  if (referencePath.parentPath.type === 'JSXOpeningElement') {
17
27
  pluralAsJSX(
18
28
  referencePath.parentPath,
@@ -28,7 +38,7 @@ function ICUMacro({ references, state, babel }) {
28
38
  });
29
39
 
30
40
  // transform Select
31
- Select.forEach(referencePath => {
41
+ Select.forEach((referencePath) => {
32
42
  if (referencePath.parentPath.type === 'JSXOpeningElement') {
33
43
  selectAsJSX(
34
44
  referencePath.parentPath,
@@ -44,7 +54,7 @@ function ICUMacro({ references, state, babel }) {
44
54
  });
45
55
 
46
56
  // transform Trans
47
- Trans.forEach(referencePath => {
57
+ Trans.forEach((referencePath) => {
48
58
  if (referencePath.parentPath.type === 'JSXOpeningElement') {
49
59
  transAsJSX(
50
60
  referencePath.parentPath,
@@ -59,6 +69,32 @@ function ICUMacro({ references, state, babel }) {
59
69
  // throw a helpful error message or something :)
60
70
  }
61
71
  });
72
+
73
+ // check for number`` and others outside of <Trans>
74
+ Object.entries({
75
+ number,
76
+ date,
77
+ time,
78
+ select,
79
+ plural,
80
+ selectOrdinal,
81
+ }).forEach(([name, node]) => {
82
+ node.forEach((item) => {
83
+ let f = item.parentPath;
84
+ while (f) {
85
+ if (babel.types.isJSXElement(f)) {
86
+ if (f.node.openingElement.name.name === 'Trans') {
87
+ // this is a valid use of number/date/time/etc.
88
+ return;
89
+ }
90
+ }
91
+ f = f.parentPath;
92
+ }
93
+ throw new Error(
94
+ `"${name}\`\`" can only be used inside <Trans> in "${item.node.loc.filename}" on line ${item.node.loc.start.line}`,
95
+ );
96
+ });
97
+ });
62
98
  }
63
99
 
64
100
  function pluralAsJSX(parentPath, { attributes }, babel) {
@@ -198,7 +234,7 @@ function transAsJSX(parentPath, { attributes, children }, babel, { filename }) {
198
234
  let clonedAttributes = cloneExistingAttributes(attributes);
199
235
  if (parseDefaults) {
200
236
  // remove existing defaults so it can be replaced later with the new parsed defaults
201
- clonedAttributes = clonedAttributes.filter(node => node.name.name !== 'defaults');
237
+ clonedAttributes = clonedAttributes.filter((node) => node.name.name !== 'defaults');
202
238
  }
203
239
 
204
240
  // replace the node with the new Trans
@@ -253,7 +289,7 @@ function cloneExistingAttributes(attributes) {
253
289
  }
254
290
 
255
291
  function findAttribute(name, attributes) {
256
- return attributes.find(child => {
292
+ return attributes.find((child) => {
257
293
  const ele = child.node ? child.node : child;
258
294
  return ele.name.name === name;
259
295
  });
@@ -284,41 +320,88 @@ function trimIndent(text) {
284
320
  return newText;
285
321
  }
286
322
 
323
+ /**
324
+ * add comma-delimited expressions like `{ val, number }`
325
+ */
326
+ function mergeCommaExpressions(ele) {
327
+ if (ele.expression && ele.expression.expressions) {
328
+ return `{${ele.expression.expressions
329
+ .reduce((m, i) => {
330
+ m.push(i.name || i.value);
331
+ return m;
332
+ }, [])
333
+ .join(', ')}}`;
334
+ }
335
+ return '';
336
+ }
337
+
338
+ /**
339
+ * this is for supporting complex icu type interpolations
340
+ * date`${variable}` and number`{${varName}, ::percent}`
341
+ * also, plural`{${count}, one { ... } other { ... }}
342
+ */
343
+ function mergeTaggedTemplateExpressions(ele, componentFoundIndex, t, babel) {
344
+ if (t.isTaggedTemplateExpression(ele.expression)) {
345
+ const [, text, index] = getTextAndInterpolatedVariables(
346
+ ele.expression.tag.name,
347
+ ele.expression,
348
+ componentFoundIndex,
349
+ babel,
350
+ );
351
+ return [text, index];
352
+ }
353
+ return ['', componentFoundIndex];
354
+ }
355
+
287
356
  function mergeChildren(children, babel, componentStartIndex = 0) {
288
357
  const t = babel.types;
289
358
  let componentFoundIndex = componentStartIndex;
290
359
 
291
360
  return children.reduce((mem, child) => {
292
361
  const ele = child.node ? child.node : child;
362
+ let result = mem;
293
363
 
294
364
  // add text, but trim indentation whitespace
295
- if (t.isJSXText(ele) && ele.value) mem += trimIndent(ele.value);
365
+ if (t.isJSXText(ele) && ele.value) result += trimIndent(ele.value);
296
366
  // add ?!? forgot
297
- if (ele.expression && ele.expression.value) mem += ele.expression.value;
367
+ if (ele.expression && ele.expression.value) result += ele.expression.value;
298
368
  // add `{ val }`
299
- if (ele.expression && ele.expression.name) mem += `{${ele.expression.name}}`;
369
+ if (ele.expression && ele.expression.name) result += `{${ele.expression.name}}`;
300
370
  // add `{ val, number }`
301
- if (ele.expression && ele.expression.expressions) {
302
- mem += `{${ele.expression.expressions
303
- .reduce((m, i) => {
304
- m.push(i.name || i.value);
305
- return m;
306
- }, [])
307
- .join(', ')}}`;
308
- }
371
+ result += mergeCommaExpressions(ele);
372
+ const [nextText, newIndex] = mergeTaggedTemplateExpressions(ele, componentFoundIndex, t, babel);
373
+ result += nextText;
374
+ componentFoundIndex = newIndex;
309
375
  // add <strong>...</strong> with replace to <0>inner string</0>
310
376
  if (t.isJSXElement(ele)) {
311
- mem += `<${componentFoundIndex}>${mergeChildren(
377
+ result += `<${componentFoundIndex}>${mergeChildren(
312
378
  ele.children,
313
379
  babel,
314
380
  )}</${componentFoundIndex}>`;
315
- componentFoundIndex++;
381
+ componentFoundIndex += 1;
316
382
  }
317
383
 
318
- return mem;
384
+ return result;
319
385
  }, '');
320
386
  }
321
387
 
388
+ const extractTaggedTemplateValues = (ele, babel, toObjectProperty) => {
389
+ // date`${variable}` and so on
390
+ if (ele.expression && ele.expression.type === 'TaggedTemplateExpression') {
391
+ const [variables] = getTextAndInterpolatedVariables(
392
+ ele.expression.tag.name,
393
+ ele.expression,
394
+ 0,
395
+ babel,
396
+ );
397
+ return variables.map((vari) => toObjectProperty(vari));
398
+ }
399
+ return [];
400
+ };
401
+
402
+ /**
403
+ * Extract the names of interpolated value as object properties to pass to Trans
404
+ */
322
405
  function getValues(children, babel) {
323
406
  const t = babel.types;
324
407
  const toObjectProperty = (name, value) =>
@@ -326,66 +409,100 @@ function getValues(children, babel) {
326
409
 
327
410
  return children.reduce((mem, child) => {
328
411
  const ele = child.node ? child.node : child;
412
+ let result = mem;
329
413
 
330
414
  // add `{ var }` to values
331
415
  if (ele.expression && ele.expression.name) mem.push(toObjectProperty(ele.expression.name));
332
416
  // add `{ var, number }` to values
333
417
  if (ele.expression && ele.expression.expressions)
334
- mem.push(
418
+ result.push(
335
419
  toObjectProperty(ele.expression.expressions[0].name || ele.expression.expressions[0].value),
336
420
  );
337
421
  // add `{ var: 'bar' }` to values
338
- if (ele.expression && ele.expression.properties) mem = mem.concat(ele.expression.properties);
422
+ if (ele.expression && ele.expression.properties)
423
+ result = result.concat(ele.expression.properties);
424
+ // date`${variable}` and so on
425
+ result = result.concat(extractTaggedTemplateValues(ele, babel, toObjectProperty));
339
426
  // recursive add inner elements stuff to values
340
427
  if (t.isJSXElement(ele)) {
341
- mem = mem.concat(getValues(ele.children, babel));
428
+ result = result.concat(getValues(ele.children, babel));
342
429
  }
343
430
 
344
- return mem;
431
+ return result;
345
432
  }, []);
346
433
  }
347
434
 
435
+ /**
436
+ * Common logic for adding a child element of Trans to the list of components to hydrate the translation
437
+ * @param {JSXElement} jsxElement
438
+ * @param {JSXElement[]} mem
439
+ */
440
+ const processJSXElement = (jsxElement, mem, t) => {
441
+ const clone = t.clone(jsxElement);
442
+ clone.children = clone.children.reduce((clonedMem, clonedChild) => {
443
+ const clonedEle = clonedChild.node ? clonedChild.node : clonedChild;
444
+
445
+ // clean out invalid definitions by replacing `{ catchDate, date, short }` with `{ catchDate }`
446
+ if (clonedEle.expression && clonedEle.expression.expressions)
447
+ clonedEle.expression.expressions = [clonedEle.expression.expressions[0]];
448
+
449
+ clonedMem.push(clonedChild);
450
+ return clonedMem;
451
+ }, []);
452
+
453
+ mem.push(jsxElement);
454
+ };
455
+
456
+ /**
457
+ * Extract the React components to pass to Trans as components
458
+ */
348
459
  function getComponents(children, babel) {
349
460
  const t = babel.types;
350
461
 
351
462
  return children.reduce((mem, child) => {
352
463
  const ele = child.node ? child.node : child;
353
464
 
465
+ if (t.isJSXExpressionContainer(ele)) {
466
+ // check for date`` and so on
467
+ if (t.isTaggedTemplateExpression(ele.expression)) {
468
+ ele.expression.quasi.expressions.forEach((expr) => {
469
+ // check for sub-expressions. This can happen with plural`` or select`` or selectOrdinal``
470
+ // these can have nested components
471
+ if (t.isTaggedTemplateExpression(expr) && expr.quasi.expressions.length) {
472
+ mem.push(...getComponents(expr.quasi.expressions, babel));
473
+ }
474
+ if (!t.isJSXElement(expr)) {
475
+ // ignore anything that is not a component
476
+ return;
477
+ }
478
+ processJSXElement(expr, mem, t);
479
+ });
480
+ }
481
+ }
354
482
  if (t.isJSXElement(ele)) {
355
- const clone = t.clone(ele);
356
- clone.children = clone.children.reduce((clonedMem, clonedChild) => {
357
- const clonedEle = clonedChild.node ? clonedChild.node : clonedChild;
358
-
359
- // clean out invalid definitions by replacing `{ catchDate, date, short }` with `{ catchDate }`
360
- if (clonedEle.expression && clonedEle.expression.expressions)
361
- clonedEle.expression.expressions = [clonedEle.expression.expressions[0]];
362
-
363
- clonedMem.push(clonedChild);
364
- return clonedMem;
365
- }, []);
366
-
367
- mem.push(ele);
483
+ processJSXElement(ele, mem, t);
368
484
  }
369
485
 
370
486
  return mem;
371
487
  }, []);
372
488
  }
373
489
 
374
- function addNeededImports(state, babel) {
375
- const t = babel.types;
376
- const importsToAdd = ['Trans'];
377
-
378
- // check if there is an existing react-i18next import
379
- const existingImport = state.file.path.node.body.find(
380
- importNode => t.isImportDeclaration(importNode) && importNode.source.value === 'react-i18next',
381
- );
382
-
383
- // append Trans to existing or add a new react-i18next import for the Trans
490
+ const icuInterpolators = ['date', 'time', 'number', 'plural', 'select', 'selectOrdinal'];
491
+ const importsToAdd = ['Trans'];
492
+
493
+ /**
494
+ * helper split out of addNeededImports to make codeclimate happy
495
+ *
496
+ * This does the work of amending an existing import from "react-i18next", or
497
+ * creating a new one if it doesn't exist
498
+ */
499
+ function addImports(state, existingImport, allImportsToAdd, t) {
500
+ // append imports to existing or add a new react-i18next import for the Trans and icu tagged template literals
384
501
  if (existingImport) {
385
- importsToAdd.forEach(name => {
502
+ allImportsToAdd.forEach((name) => {
386
503
  if (
387
504
  existingImport.specifiers.findIndex(
388
- specifier => specifier.imported && specifier.imported.name === name,
505
+ (specifier) => specifier.imported && specifier.imported.name === name,
389
506
  ) === -1
390
507
  ) {
391
508
  existingImport.specifiers.push(t.importSpecifier(t.identifier(name), t.identifier(name)));
@@ -394,9 +511,210 @@ function addNeededImports(state, babel) {
394
511
  } else {
395
512
  state.file.path.node.body.unshift(
396
513
  t.importDeclaration(
397
- importsToAdd.map(name => t.importSpecifier(t.identifier(name), t.identifier(name))),
514
+ allImportsToAdd.map((name) => t.importSpecifier(t.identifier(name), t.identifier(name))),
398
515
  t.stringLiteral('react-i18next'),
399
516
  ),
400
517
  );
401
518
  }
402
519
  }
520
+
521
+ /**
522
+ * Add `import { Trans, number, date, <etc.> } from "react-i18next"` as needed
523
+ */
524
+ function addNeededImports(state, babel, references) {
525
+ const t = babel.types;
526
+
527
+ // check if there is an existing react-i18next import
528
+ const existingImport = state.file.path.node.body.find(
529
+ (importNode) =>
530
+ t.isImportDeclaration(importNode) && importNode.source.value === 'react-i18next',
531
+ );
532
+ // check for any of the tagged template literals that are used in the source, and add them
533
+ const usedRefs = Object.keys(references).filter((importName) => {
534
+ if (!icuInterpolators.includes(importName)) {
535
+ return false;
536
+ }
537
+ return references[importName].length;
538
+ });
539
+
540
+ // combine Trans + any tagged template literals
541
+ const allImportsToAdd = importsToAdd.concat(usedRefs);
542
+
543
+ addImports(state, existingImport, allImportsToAdd, t);
544
+ }
545
+
546
+ /**
547
+ * iterate over a node detected inside a tagged template literal
548
+ *
549
+ * This is a helper function for `extractVariableNamesFromQuasiNodes` defined below
550
+ *
551
+ * this is called using reduce as a way of tricking what would be `.map()`
552
+ * into passing in the parameters needed to both modify `componentFoundIndex`,
553
+ * `stringOutput`, and `interpolatedVariableNames`
554
+ * and to pass in the dependencies babel, and type. Type is the template type.
555
+ * For "date``" the type will be `date`. for "number``" the type is `number`, etc.
556
+ */
557
+ const extractNestedTemplatesAndComponents = (
558
+ { componentFoundIndex: lastIndex, babel, stringOutput, type, interpolatedVariableNames },
559
+ node,
560
+ ) => {
561
+ let componentFoundIndex = lastIndex;
562
+ if (node.type === 'JSXElement') {
563
+ // perform the interpolation of components just as we do in a normal Trans setting
564
+ const subText = `<${componentFoundIndex}>${mergeChildren(
565
+ node.children,
566
+ babel,
567
+ )}</${componentFoundIndex}>`;
568
+ componentFoundIndex += 1;
569
+ stringOutput.push(subText);
570
+ } else if (node.type === 'TaggedTemplateExpression') {
571
+ // a nested date``/number``/plural`` etc., extract whatever is inside of it
572
+ const [variableNames, childText, newIndex] = getTextAndInterpolatedVariables(
573
+ node.tag.name,
574
+ node,
575
+ componentFoundIndex,
576
+ babel,
577
+ );
578
+ interpolatedVariableNames.push(...variableNames);
579
+ componentFoundIndex = newIndex;
580
+ stringOutput.push(childText);
581
+ } else if (node.type === 'Identifier') {
582
+ // turn date`${thing}` into `thing, date`
583
+ stringOutput.push(`${node.name}, ${type}`);
584
+ } else if (node.type === 'TemplateElement') {
585
+ // convert all whitespace into a single space for the text in the tagged template literal
586
+ stringOutput.push(node.value.cooked.replace(/\s+/g, ' '));
587
+ } else {
588
+ // unknown node type, ignore
589
+ }
590
+ return { componentFoundIndex, babel, stringOutput, type, interpolatedVariableNames };
591
+ };
592
+
593
+ /**
594
+ * filter the list of nodes within a tagged template literal to the 4 types we can process,
595
+ * and ignore anything else.
596
+ *
597
+ * this is a helper function for `extractVariableNamesFromQuasiNodes`
598
+ */
599
+ const filterNodes = (node) => {
600
+ if (node.type === 'Identifier') {
601
+ // if the node has a name, keep it
602
+ return node.name;
603
+ }
604
+ if (node.type === 'JSXElement' || node.type === 'TaggedTemplateExpression') {
605
+ // always keep interpolated elements or other tagged template literals like a nested date`` inside a plural``
606
+ return true;
607
+ }
608
+ if (node.type === 'TemplateElement') {
609
+ // return the "cooked" (escaped) text for the text in the template literal (`, ::percent` in number`${varname}, ::percent`)
610
+ return node.value.cooked;
611
+ }
612
+ // unknown node type, ignore
613
+ return false;
614
+ };
615
+
616
+ const errorOnInvalidQuasiNodes = (primaryNode) => {
617
+ const noInterpolationError = !primaryNode.quasi.expressions.length;
618
+ const wrongOrderError = primaryNode.quasi.quasis[0].value.raw.length;
619
+ const message = `${primaryNode.tag.name} argument must be interpolated ${
620
+ noInterpolationError ? 'in' : 'at the beginning of'
621
+ } "${primaryNode.tag.name}\`\`" in "${primaryNode.loc.filename}" on line ${
622
+ primaryNode.loc.start.line
623
+ }`;
624
+ if (noInterpolationError || wrongOrderError) {
625
+ throw new Error(message);
626
+ }
627
+ };
628
+
629
+ const extractNodeVariableNames = (varNode, babel) => {
630
+ const interpolatedVariableNames = [];
631
+ if (varNode.type === 'JSXElement') {
632
+ // extract inner interpolated variables and add to the list
633
+ interpolatedVariableNames.push(
634
+ ...getValues(varNode.children, babel).map((value) => value.value.name),
635
+ );
636
+ } else if (varNode.type === 'Identifier') {
637
+ // the name of the interpolated variable
638
+ interpolatedVariableNames.push(varNode.name);
639
+ }
640
+ return interpolatedVariableNames;
641
+ };
642
+
643
+ const extractVariableNamesFromQuasiNodes = (primaryNode, babel) => {
644
+ errorOnInvalidQuasiNodes(primaryNode);
645
+ // this will contain all the nodes to convert to the ICU messageformat text
646
+ // at first they are unsorted, but will be ordered correctly at the end of the function
647
+ const text = [];
648
+ // the variable names. These are converted to object references as required for the Trans values
649
+ // in getValues() (toObjectProperty helper function)
650
+ const interpolatedVariableNames = [];
651
+ primaryNode.quasi.expressions.forEach((varNode) => {
652
+ if (
653
+ !babel.types.isIdentifier(varNode) &&
654
+ !babel.types.isTaggedTemplateExpression(varNode) &&
655
+ !babel.types.isJSXElement(varNode)
656
+ ) {
657
+ throw new Error(
658
+ `Must pass a variable, not an expression to "${primaryNode.tag.name}\`\`" in "${primaryNode.loc.filename}" on line ${primaryNode.loc.start.line}`,
659
+ );
660
+ }
661
+ text.push(varNode);
662
+ interpolatedVariableNames.push(...extractNodeVariableNames(varNode, babel));
663
+ });
664
+ primaryNode.quasi.quasis.forEach((quasiNode) => {
665
+ // these are the text surrounding the variable interpolation
666
+ // so in date`${varname}, short` it would be `''` and `, short`.
667
+ // (the empty string before `${varname}` and the stuff after it)
668
+ text.push(quasiNode);
669
+ });
670
+ return { text, interpolatedVariableNames };
671
+ };
672
+
673
+ const throwOnInvalidType = (type, primaryNode) => {
674
+ if (!icuInterpolators.includes(type)) {
675
+ throw new Error(
676
+ `Unsupported tagged template literal "${type}", must be one of date, time, number, plural, select, selectOrdinal in "${primaryNode.loc.filename}" on line ${primaryNode.loc.start.line}`,
677
+ );
678
+ }
679
+ };
680
+
681
+ /**
682
+ * Retrieve the new text to use, and any interpolated variables
683
+ *
684
+ * This is used to process tagged template literals like date`${variable}` and number`${num}, ::percent`
685
+ *
686
+ * for the data example, it will return text of `{variable, date}` with a variable of `variable`
687
+ * for the number example, it will return text of `{num, number, ::percent}` with a variable of `num`
688
+ * @param {string} type the name of the tagged template (`date`, `number`, `plural`, etc. - any valid complex ICU type)
689
+ * @param {TaggedTemplateExpression} primaryNode the template expression node
690
+ * @param {int} index starting index number of components to be used for interpolations like <0>
691
+ * @param {*} babel
692
+ */
693
+ function getTextAndInterpolatedVariables(type, primaryNode, index, babel) {
694
+ throwOnInvalidType(type, primaryNode);
695
+ const componentFoundIndex = index;
696
+ const { text, interpolatedVariableNames } = extractVariableNamesFromQuasiNodes(
697
+ primaryNode,
698
+ babel,
699
+ );
700
+ const { stringOutput, componentFoundIndex: newIndex } = text
701
+ .filter(filterNodes)
702
+ // sort by the order they appear in the source code
703
+ .sort((a, b) => {
704
+ if (a.start > b.start) return 1;
705
+ return -1;
706
+ })
707
+ .reduce(extractNestedTemplatesAndComponents, {
708
+ babel,
709
+ componentFoundIndex,
710
+ stringOutput: [],
711
+ type,
712
+ interpolatedVariableNames,
713
+ });
714
+ return [
715
+ interpolatedVariableNames,
716
+ `{${stringOutput.join('')}}`,
717
+ // return the new component interpolation index
718
+ newIndex,
719
+ ];
720
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-i18next",
3
- "version": "11.8.13",
3
+ "version": "11.10.0",
4
4
  "description": "Internationalization for react done right. Using the i18next i18n ecosystem.",
5
5
  "main": "dist/commonjs/index.js",
6
6
  "types": "./index.d.ts",
@@ -31,8 +31,8 @@
31
31
  "url": "https://github.com/i18next/react-i18next.git"
32
32
  },
33
33
  "dependencies": {
34
- "@babel/runtime": "^7.13.6",
35
- "html-parse-stringify": "^3.0.0"
34
+ "@babel/runtime": "^7.14.0",
35
+ "html-parse-stringify": "^3.0.1"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@babel/cli": "^7.2.3",
@@ -48,7 +48,6 @@
48
48
  "@testing-library/jest-dom": "^5.11.6",
49
49
  "@testing-library/react": "^11.2.2",
50
50
  "@testing-library/react-hooks": "^3.4.2",
51
- "@types/react": "^16.8.2",
52
51
  "all-contributors-cli": "^6.1.1",
53
52
  "babel-core": "^7.0.0-bridge.0",
54
53
  "babel-eslint": "10.0.3",
package/react-i18next.js CHANGED
@@ -214,7 +214,7 @@
214
214
  },
215
215
  i = n.match(/<\/?([^\s]+?)[/\s>]/);
216
216
 
217
- if (i && (r.name = i[1], (voidElements[i[1].toLowerCase()] || "/" === n.charAt(n.length - 2)) && (r.voidElement = !0), r.name.startsWith("!--"))) {
217
+ if (i && (r.name = i[1], (voidElements[i[1]] || "/" === n.charAt(n.length - 2)) && (r.voidElement = !0), r.name.startsWith("!--"))) {
218
218
  var s = n.indexOf("--\x3e");
219
219
  return {
220
220
  type: "comment",
@@ -318,6 +318,7 @@
318
318
  bindI18nStore: '',
319
319
  transEmptyNodeValue: '',
320
320
  transSupportBasicHtmlNodes: true,
321
+ transWrapTextNodes: '',
321
322
  transKeepBasicHtmlNodesFor: ['br', 'strong', 'i', 'p'],
322
323
  useSuspense: true
323
324
  };
@@ -521,6 +522,8 @@
521
522
  var content = nodesToString(childChildren, i18nOptions);
522
523
  stringNode += "<".concat(childIndex, ">").concat(content, "</").concat(childIndex, ">");
523
524
  }
525
+ } else if (child === null) {
526
+ warn("Trans: the passed in value is invalid - seems you passed in a null child.");
524
527
  } else if (_typeof(child) === 'object') {
525
528
  var format = child.format,
526
529
  clone = _objectWithoutProperties(child, ["format"]);
@@ -638,7 +641,15 @@
638
641
  })));
639
642
  }
640
643
  } else if (node.type === 'text') {
641
- mem.push(node.content);
644
+ var wrapTextNodes = i18nOptions.transWrapTextNodes;
645
+
646
+ if (wrapTextNodes) {
647
+ mem.push(React__default.createElement(wrapTextNodes, {
648
+ key: "".concat(node.name, "-").concat(i)
649
+ }, node.content));
650
+ } else {
651
+ mem.push(node.content);
652
+ }
642
653
  }
643
654
 
644
655
  return mem;
@@ -923,17 +934,42 @@
923
934
  };
924
935
  }
925
936
 
937
+ var date = function date() {
938
+ return '';
939
+ };
940
+ var time = function time() {
941
+ return '';
942
+ };
943
+ var number = function number() {
944
+ return '';
945
+ };
946
+ var select = function select() {
947
+ return '';
948
+ };
949
+ var plural = function plural() {
950
+ return '';
951
+ };
952
+ var selectOrdinal = function selectOrdinal() {
953
+ return '';
954
+ };
955
+
926
956
  exports.I18nContext = I18nContext;
927
957
  exports.I18nextProvider = I18nextProvider;
928
958
  exports.Trans = Trans;
929
959
  exports.Translation = Translation;
930
960
  exports.composeInitialProps = composeInitialProps;
961
+ exports.date = date;
931
962
  exports.getDefaults = getDefaults;
932
963
  exports.getI18n = getI18n;
933
964
  exports.getInitialProps = getInitialProps;
934
965
  exports.initReactI18next = initReactI18next;
966
+ exports.number = number;
967
+ exports.plural = plural;
968
+ exports.select = select;
969
+ exports.selectOrdinal = selectOrdinal;
935
970
  exports.setDefaults = setDefaults;
936
971
  exports.setI18n = setI18n;
972
+ exports.time = time;
937
973
  exports.useSSR = useSSR;
938
974
  exports.useTranslation = useTranslation;
939
975
  exports.withSSR = withSSR;