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/CHANGELOG.md +16 -0
- package/LICENSE +1 -1
- package/README.md +10 -3
- package/dist/amd/react-i18next.js +38 -2
- package/dist/amd/react-i18next.min.js +1 -1
- package/dist/commonjs/Trans.js +11 -1
- package/dist/commonjs/context.js +1 -0
- package/dist/commonjs/index.js +38 -1
- package/dist/es/Trans.js +11 -1
- package/dist/es/context.js +1 -0
- package/dist/es/index.js +19 -1
- package/dist/umd/react-i18next.js +38 -2
- package/dist/umd/react-i18next.min.js +1 -1
- package/icu.macro.js +370 -52
- package/package.json +3 -4
- package/react-i18next.js +38 -2
- package/react-i18next.min.js +1 -1
- package/src/Trans.js +11 -4
- package/src/context.js +7 -6
- package/src/index.js +9 -0
- package/ts4.1/icu.macro.d.ts +104 -0
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
|
|
9
|
-
|
|
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)
|
|
365
|
+
if (t.isJSXText(ele) && ele.value) result += trimIndent(ele.value);
|
|
296
366
|
// add ?!? forgot
|
|
297
|
-
if (ele.expression && 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)
|
|
369
|
+
if (ele.expression && ele.expression.name) result += `{${ele.expression.name}}`;
|
|
300
370
|
// add `{ val, number }`
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
428
|
+
result = result.concat(getValues(ele.children, babel));
|
|
342
429
|
}
|
|
343
430
|
|
|
344
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
35
|
-
"html-parse-stringify": "^3.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]
|
|
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
|
-
|
|
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;
|