1 | const { createMacro } = require('babel-plugin-macros');
|
2 |
|
3 |
|
4 |
|
5 | module.exports = createMacro(ICUMacro);
|
6 |
|
7 | function ICUMacro({ references, state, babel }) {
|
8 | const {
|
9 | Trans = [],
|
10 | Plural = [],
|
11 | Select = [],
|
12 | SelectOrdinal = [],
|
13 | number = [],
|
14 | date = [],
|
15 | select = [],
|
16 | selectOrdinal = [],
|
17 | plural = [],
|
18 | time = [],
|
19 | } = references;
|
20 |
|
21 |
|
22 | addNeededImports(state, babel, references);
|
23 |
|
24 |
|
25 | [...Plural, ...SelectOrdinal].forEach((referencePath) => {
|
26 | if (referencePath.parentPath.type === 'JSXOpeningElement') {
|
27 | pluralAsJSX(
|
28 | referencePath.parentPath,
|
29 | {
|
30 | attributes: referencePath.parentPath.get('attributes'),
|
31 | children: referencePath.parentPath.parentPath.get('children'),
|
32 | },
|
33 | babel,
|
34 | );
|
35 | } else {
|
36 |
|
37 | }
|
38 | });
|
39 |
|
40 |
|
41 | Select.forEach((referencePath) => {
|
42 | if (referencePath.parentPath.type === 'JSXOpeningElement') {
|
43 | selectAsJSX(
|
44 | referencePath.parentPath,
|
45 | {
|
46 | attributes: referencePath.parentPath.get('attributes'),
|
47 | children: referencePath.parentPath.parentPath.get('children'),
|
48 | },
|
49 | babel,
|
50 | );
|
51 | } else {
|
52 |
|
53 | }
|
54 | });
|
55 |
|
56 |
|
57 | Trans.forEach((referencePath) => {
|
58 | if (referencePath.parentPath.type === 'JSXOpeningElement') {
|
59 | transAsJSX(
|
60 | referencePath.parentPath,
|
61 | {
|
62 | attributes: referencePath.parentPath.get('attributes'),
|
63 | children: referencePath.parentPath.parentPath.get('children'),
|
64 | },
|
65 | babel,
|
66 | state,
|
67 | );
|
68 | } else {
|
69 |
|
70 | }
|
71 | });
|
72 |
|
73 |
|
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 |
|
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 | });
|
98 | }
|
99 |
|
100 | function pluralAsJSX(parentPath, { attributes }, babel) {
|
101 | const t = babel.types;
|
102 | const toObjectProperty = (name, value) =>
|
103 | t.objectProperty(t.identifier(name), t.identifier(name), false, !value);
|
104 |
|
105 |
|
106 | const nodeName = parentPath.node.name.name.toLocaleLowerCase();
|
107 |
|
108 |
|
109 | const existingValuesAttribute = findAttribute('values', attributes);
|
110 | const existingValues = existingValuesAttribute
|
111 | ? existingValuesAttribute.node.value.expression.properties
|
112 | : [];
|
113 |
|
114 | let componentStartIndex = 0;
|
115 | const extracted = attributes.reduce(
|
116 | (mem, attr) => {
|
117 | if (attr.node.name.name === 'i18nKey') {
|
118 |
|
119 | mem.attributesToCopy.push(attr.node);
|
120 | } else if (attr.node.name.name === 'count') {
|
121 |
|
122 | let exprName = attr.node.value.expression.name;
|
123 | if (!exprName) {
|
124 | exprName = 'count';
|
125 | }
|
126 | if (exprName === 'count') {
|
127 |
|
128 | mem.attributesToCopy.push(attr.node);
|
129 | } else {
|
130 | mem.values.unshift(toObjectProperty(exprName));
|
131 | }
|
132 | mem.defaults = `{${exprName}, ${nodeName}, ${mem.defaults}`;
|
133 | } else if (attr.node.name.name === 'values') {
|
134 |
|
135 | } else if (attr.node.value.type === 'StringLiteral') {
|
136 |
|
137 | let pluralForm = attr.node.name.name;
|
138 | if (pluralForm.indexOf('$') === 0) pluralForm = pluralForm.replace('$', '=');
|
139 | mem.defaults = `${mem.defaults} ${pluralForm} {${attr.node.value.value}}`;
|
140 | } else if (attr.node.value.type === 'JSXExpressionContainer') {
|
141 |
|
142 | const children = attr.node.value.expression.children || [];
|
143 | const thisTrans = processTrans(children, babel, componentStartIndex);
|
144 |
|
145 | let pluralForm = attr.node.name.name;
|
146 | if (pluralForm.indexOf('$') === 0) pluralForm = pluralForm.replace('$', '=');
|
147 |
|
148 | mem.defaults = `${mem.defaults} ${pluralForm} {${thisTrans.defaults}}`;
|
149 | mem.components = mem.components.concat(thisTrans.components);
|
150 |
|
151 | componentStartIndex += thisTrans.components.length;
|
152 | }
|
153 | return mem;
|
154 | },
|
155 | { attributesToCopy: [], values: existingValues, components: [], defaults: '' },
|
156 | );
|
157 |
|
158 |
|
159 | parentPath.replaceWith(buildTransElement(extracted, extracted.attributesToCopy, t, true));
|
160 | }
|
161 |
|
162 | function selectAsJSX(parentPath, { attributes }, babel) {
|
163 | const t = babel.types;
|
164 | const toObjectProperty = (name, value) =>
|
165 | t.objectProperty(t.identifier(name), t.identifier(name), false, !value);
|
166 |
|
167 |
|
168 | const existingValuesAttribute = findAttribute('values', attributes);
|
169 | const existingValues = existingValuesAttribute
|
170 | ? existingValuesAttribute.node.value.expression.properties
|
171 | : [];
|
172 |
|
173 | let componentStartIndex = 0;
|
174 |
|
175 | const extracted = attributes.reduce(
|
176 | (mem, attr) => {
|
177 | if (attr.node.name.name === 'i18nKey') {
|
178 |
|
179 | mem.attributesToCopy.push(attr.node);
|
180 | } else if (attr.node.name.name === 'switch') {
|
181 |
|
182 | let exprName = attr.node.value.expression.name;
|
183 | if (!exprName) {
|
184 | exprName = 'selectKey';
|
185 | mem.values.unshift(t.objectProperty(t.identifier(exprName), attr.node.value.expression));
|
186 | } else {
|
187 | mem.values.unshift(toObjectProperty(exprName));
|
188 | }
|
189 | mem.defaults = `{${exprName}, select, ${mem.defaults}`;
|
190 | } else if (attr.node.name.name === 'values') {
|
191 |
|
192 | } else if (attr.node.value.type === 'StringLiteral') {
|
193 |
|
194 | mem.defaults = `${mem.defaults} ${attr.node.name.name} {${attr.node.value.value}}`;
|
195 | } else if (attr.node.value.type === 'JSXExpressionContainer') {
|
196 |
|
197 | const children = attr.node.value.expression.children || [];
|
198 | const thisTrans = processTrans(children, babel, componentStartIndex);
|
199 |
|
200 | mem.defaults = `${mem.defaults} ${attr.node.name.name} {${thisTrans.defaults}}`;
|
201 | mem.components = mem.components.concat(thisTrans.components);
|
202 |
|
203 | componentStartIndex += thisTrans.components.length;
|
204 | }
|
205 | return mem;
|
206 | },
|
207 | { attributesToCopy: [], values: existingValues, components: [], defaults: '' },
|
208 | );
|
209 |
|
210 |
|
211 | parentPath.replaceWith(buildTransElement(extracted, extracted.attributesToCopy, t, true));
|
212 | }
|
213 |
|
214 | function transAsJSX(parentPath, { attributes, children }, babel, { filename }) {
|
215 | const defaultsAttr = findAttribute('defaults', attributes);
|
216 | const componentsAttr = findAttribute('components', attributes);
|
217 |
|
218 |
|
219 | const parseDefaults = defaultsAttr && !componentsAttr;
|
220 |
|
221 | let extracted;
|
222 | if (parseDefaults) {
|
223 | const defaultsExpression = defaultsAttr.node.value.value;
|
224 | const parsed = babel.parse(`<>${defaultsExpression}</>`, {
|
225 | presets: ['@babel/react'],
|
226 | filename,
|
227 | }).program.body[0].expression.children;
|
228 |
|
229 | extracted = processTrans(parsed, babel);
|
230 | } else {
|
231 | extracted = processTrans(children, babel);
|
232 | }
|
233 |
|
234 | let clonedAttributes = cloneExistingAttributes(attributes);
|
235 | if (parseDefaults) {
|
236 |
|
237 | clonedAttributes = clonedAttributes.filter((node) => node.name.name !== 'defaults');
|
238 | }
|
239 |
|
240 |
|
241 | const replacePath = children.length ? children[0].parentPath : parentPath;
|
242 | replacePath.replaceWith(
|
243 | buildTransElement(extracted, clonedAttributes, babel.types, false, !!children.length),
|
244 | );
|
245 | }
|
246 |
|
247 | function buildTransElement(
|
248 | extracted,
|
249 | finalAttributes,
|
250 | t,
|
251 | closeDefaults = false,
|
252 | wasElementWithChildren = false,
|
253 | ) {
|
254 | const nodeName = t.jSXIdentifier('Trans');
|
255 |
|
256 |
|
257 | if (closeDefaults) extracted.defaults += '}';
|
258 |
|
259 |
|
260 | extracted.components = t.arrayExpression(extracted.components);
|
261 | extracted.values = t.objectExpression(extracted.values);
|
262 |
|
263 |
|
264 | if (!attributeExistsAlready('defaults', finalAttributes))
|
265 | if (extracted.defaults.includes(`"`)) {
|
266 |
|
267 | finalAttributes.push(
|
268 | t.jSXAttribute(
|
269 | t.jSXIdentifier('defaults'),
|
270 | t.jSXExpressionContainer(t.StringLiteral(extracted.defaults)),
|
271 | ),
|
272 | );
|
273 | } else {
|
274 | finalAttributes.push(
|
275 | t.jSXAttribute(t.jSXIdentifier('defaults'), t.StringLiteral(extracted.defaults)),
|
276 | );
|
277 | }
|
278 |
|
279 | if (!attributeExistsAlready('components', finalAttributes))
|
280 | finalAttributes.push(
|
281 | t.jSXAttribute(t.jSXIdentifier('components'), t.jSXExpressionContainer(extracted.components)),
|
282 | );
|
283 | if (!attributeExistsAlready('values', finalAttributes))
|
284 | finalAttributes.push(
|
285 | t.jSXAttribute(t.jSXIdentifier('values'), t.jSXExpressionContainer(extracted.values)),
|
286 | );
|
287 |
|
288 |
|
289 | const openElement = t.jSXOpeningElement(nodeName, finalAttributes, true);
|
290 | if (!wasElementWithChildren) return openElement;
|
291 |
|
292 | return t.jSXElement(openElement, null, [], true);
|
293 | }
|
294 |
|
295 | function cloneExistingAttributes(attributes) {
|
296 | return attributes.reduce((mem, attr) => {
|
297 | mem.push(attr.node);
|
298 | return mem;
|
299 | }, []);
|
300 | }
|
301 |
|
302 | function findAttribute(name, attributes) {
|
303 | return attributes.find((child) => {
|
304 | const ele = child.node ? child.node : child;
|
305 | return ele.name.name === name;
|
306 | });
|
307 | }
|
308 |
|
309 | function attributeExistsAlready(name, attributes) {
|
310 | return !!findAttribute(name, attributes);
|
311 | }
|
312 |
|
313 | function processTrans(children, babel, componentStartIndex = 0) {
|
314 | const res = {};
|
315 |
|
316 | res.defaults = mergeChildren(children, babel, componentStartIndex);
|
317 | res.components = getComponents(children, babel);
|
318 | res.values = getValues(children, babel);
|
319 |
|
320 | return res;
|
321 | }
|
322 |
|
323 | const leadingNewLineAndWhitespace = /^\n\s+/g;
|
324 | const trailingNewLineAndWhitespace = /\n\s+$/g;
|
325 | function trimIndent(text) {
|
326 | const newText = text
|
327 | .replace(leadingNewLineAndWhitespace, '')
|
328 | .replace(trailingNewLineAndWhitespace, '');
|
329 | return newText;
|
330 | }
|
331 |
|
332 |
|
333 |
|
334 |
|
335 | function mergeCommaExpressions(ele) {
|
336 | if (ele.expression && ele.expression.expressions) {
|
337 | return `{${ele.expression.expressions
|
338 | .reduce((m, i) => {
|
339 | m.push(i.name || i.value);
|
340 | return m;
|
341 | }, [])
|
342 | .join(', ')}}`;
|
343 | }
|
344 | return '';
|
345 | }
|
346 |
|
347 |
|
348 |
|
349 |
|
350 |
|
351 |
|
352 | function mergeTaggedTemplateExpressions(ele, componentFoundIndex, t, babel) {
|
353 | if (t.isTaggedTemplateExpression(ele.expression)) {
|
354 | const [, text, index] = getTextAndInterpolatedVariables(
|
355 | ele.expression.tag.name,
|
356 | ele.expression,
|
357 | componentFoundIndex,
|
358 | babel,
|
359 | );
|
360 | return [text, index];
|
361 | }
|
362 | return ['', componentFoundIndex];
|
363 | }
|
364 |
|
365 | function mergeChildren(children, babel, componentStartIndex = 0) {
|
366 | const t = babel.types;
|
367 | let componentFoundIndex = componentStartIndex;
|
368 |
|
369 | return children.reduce((mem, child) => {
|
370 | const ele = child.node ? child.node : child;
|
371 | let result = mem;
|
372 |
|
373 |
|
374 | if (t.isJSXText(ele) && ele.value) result += trimIndent(ele.value);
|
375 |
|
376 | if (ele.expression && ele.expression.value) result += ele.expression.value;
|
377 |
|
378 | if (ele.expression && ele.expression.name) result += `{${ele.expression.name}}`;
|
379 |
|
380 | result += mergeCommaExpressions(ele);
|
381 | const [nextText, newIndex] = mergeTaggedTemplateExpressions(ele, componentFoundIndex, t, babel);
|
382 | result += nextText;
|
383 | componentFoundIndex = newIndex;
|
384 |
|
385 | if (t.isJSXElement(ele)) {
|
386 | result += `<${componentFoundIndex}>${mergeChildren(
|
387 | ele.children,
|
388 | babel,
|
389 | )}</${componentFoundIndex}>`;
|
390 | componentFoundIndex += 1;
|
391 | }
|
392 |
|
393 | return result;
|
394 | }, '');
|
395 | }
|
396 |
|
397 | const extractTaggedTemplateValues = (ele, babel, toObjectProperty) => {
|
398 |
|
399 | if (ele.expression && ele.expression.type === 'TaggedTemplateExpression') {
|
400 | const [variables] = getTextAndInterpolatedVariables(
|
401 | ele.expression.tag.name,
|
402 | ele.expression,
|
403 | 0,
|
404 | babel,
|
405 | );
|
406 | return variables.map((vari) => toObjectProperty(vari));
|
407 | }
|
408 | return [];
|
409 | };
|
410 |
|
411 |
|
412 |
|
413 |
|
414 | function getValues(children, babel) {
|
415 | const t = babel.types;
|
416 | const toObjectProperty = (name, value) =>
|
417 | t.objectProperty(t.identifier(name), t.identifier(name), false, !value);
|
418 |
|
419 | return children.reduce((mem, child) => {
|
420 | const ele = child.node ? child.node : child;
|
421 | let result = mem;
|
422 |
|
423 |
|
424 | if (ele.expression && ele.expression.name) mem.push(toObjectProperty(ele.expression.name));
|
425 |
|
426 | if (ele.expression && ele.expression.expressions)
|
427 | result.push(
|
428 | toObjectProperty(ele.expression.expressions[0].name || ele.expression.expressions[0].value),
|
429 | );
|
430 |
|
431 | if (ele.expression && ele.expression.properties)
|
432 | result = result.concat(ele.expression.properties);
|
433 |
|
434 | result = result.concat(extractTaggedTemplateValues(ele, babel, toObjectProperty));
|
435 |
|
436 | if (t.isJSXElement(ele)) {
|
437 | result = result.concat(getValues(ele.children, babel));
|
438 | }
|
439 |
|
440 | return result;
|
441 | }, []);
|
442 | }
|
443 |
|
444 |
|
445 |
|
446 |
|
447 |
|
448 |
|
449 | const processJSXElement = (jsxElement, mem, t) => {
|
450 | const clone = t.clone(jsxElement);
|
451 | clone.children = clone.children.reduce((clonedMem, clonedChild) => {
|
452 | const clonedEle = clonedChild.node ? clonedChild.node : clonedChild;
|
453 |
|
454 |
|
455 | if (clonedEle.expression && clonedEle.expression.expressions)
|
456 | clonedEle.expression.expressions = [clonedEle.expression.expressions[0]];
|
457 |
|
458 | clonedMem.push(clonedChild);
|
459 | return clonedMem;
|
460 | }, []);
|
461 |
|
462 | mem.push(jsxElement);
|
463 | };
|
464 |
|
465 |
|
466 |
|
467 |
|
468 | function getComponents(children, babel) {
|
469 | const t = babel.types;
|
470 |
|
471 | return children.reduce((mem, child) => {
|
472 | const ele = child.node ? child.node : child;
|
473 |
|
474 | if (t.isJSXExpressionContainer(ele)) {
|
475 |
|
476 | if (t.isTaggedTemplateExpression(ele.expression)) {
|
477 | ele.expression.quasi.expressions.forEach((expr) => {
|
478 |
|
479 |
|
480 | if (t.isTaggedTemplateExpression(expr) && expr.quasi.expressions.length) {
|
481 | mem.push(...getComponents(expr.quasi.expressions, babel));
|
482 | }
|
483 | if (!t.isJSXElement(expr)) {
|
484 |
|
485 | return;
|
486 | }
|
487 | processJSXElement(expr, mem, t);
|
488 | });
|
489 | }
|
490 | }
|
491 | if (t.isJSXElement(ele)) {
|
492 | processJSXElement(ele, mem, t);
|
493 | }
|
494 |
|
495 | return mem;
|
496 | }, []);
|
497 | }
|
498 |
|
499 | const icuInterpolators = ['date', 'time', 'number', 'plural', 'select', 'selectOrdinal'];
|
500 | const importsToAdd = ['Trans'];
|
501 |
|
502 |
|
503 |
|
504 |
|
505 |
|
506 |
|
507 |
|
508 | function addImports(state, existingImport, allImportsToAdd, t) {
|
509 |
|
510 | if (existingImport) {
|
511 | allImportsToAdd.forEach((name) => {
|
512 | if (
|
513 | existingImport.specifiers.findIndex(
|
514 | (specifier) => specifier.imported && specifier.imported.name === name,
|
515 | ) === -1
|
516 | ) {
|
517 | existingImport.specifiers.push(t.importSpecifier(t.identifier(name), t.identifier(name)));
|
518 | }
|
519 | });
|
520 | } else {
|
521 | state.file.path.node.body.unshift(
|
522 | t.importDeclaration(
|
523 | allImportsToAdd.map((name) => t.importSpecifier(t.identifier(name), t.identifier(name))),
|
524 | t.stringLiteral('react-i18next'),
|
525 | ),
|
526 | );
|
527 | }
|
528 | }
|
529 |
|
530 |
|
531 |
|
532 |
|
533 | function addNeededImports(state, babel, references) {
|
534 | const t = babel.types;
|
535 |
|
536 |
|
537 | const existingImport = state.file.path.node.body.find(
|
538 | (importNode) =>
|
539 | t.isImportDeclaration(importNode) && importNode.source.value === 'react-i18next',
|
540 | );
|
541 |
|
542 | const usedRefs = Object.keys(references).filter((importName) => {
|
543 | if (!icuInterpolators.includes(importName)) {
|
544 | return false;
|
545 | }
|
546 | return references[importName].length;
|
547 | });
|
548 |
|
549 |
|
550 | const allImportsToAdd = importsToAdd.concat(usedRefs);
|
551 |
|
552 | addImports(state, existingImport, allImportsToAdd, t);
|
553 | }
|
554 |
|
555 |
|
556 |
|
557 |
|
558 |
|
559 |
|
560 |
|
561 |
|
562 |
|
563 |
|
564 |
|
565 |
|
566 | const extractNestedTemplatesAndComponents = (
|
567 | { componentFoundIndex: lastIndex, babel, stringOutput, type, interpolatedVariableNames },
|
568 | node,
|
569 | ) => {
|
570 | let componentFoundIndex = lastIndex;
|
571 | if (node.type === 'JSXElement') {
|
572 |
|
573 | const subText = `<${componentFoundIndex}>${mergeChildren(
|
574 | node.children,
|
575 | babel,
|
576 | )}</${componentFoundIndex}>`;
|
577 | componentFoundIndex += 1;
|
578 | stringOutput.push(subText);
|
579 | } else if (node.type === 'TaggedTemplateExpression') {
|
580 |
|
581 | const [variableNames, childText, newIndex] = getTextAndInterpolatedVariables(
|
582 | node.tag.name,
|
583 | node,
|
584 | componentFoundIndex,
|
585 | babel,
|
586 | );
|
587 | interpolatedVariableNames.push(...variableNames);
|
588 | componentFoundIndex = newIndex;
|
589 | stringOutput.push(childText);
|
590 | } else if (node.type === 'Identifier') {
|
591 |
|
592 | stringOutput.push(`${node.name}, ${type}`);
|
593 | } else if (node.type === 'TemplateElement') {
|
594 |
|
595 | stringOutput.push(node.value.cooked.replace(/\s+/g, ' '));
|
596 | } else {
|
597 |
|
598 | }
|
599 | return { componentFoundIndex, babel, stringOutput, type, interpolatedVariableNames };
|
600 | };
|
601 |
|
602 |
|
603 |
|
604 |
|
605 |
|
606 |
|
607 |
|
608 | const filterNodes = (node) => {
|
609 | if (node.type === 'Identifier') {
|
610 |
|
611 | return node.name;
|
612 | }
|
613 | if (node.type === 'JSXElement' || node.type === 'TaggedTemplateExpression') {
|
614 |
|
615 | return true;
|
616 | }
|
617 | if (node.type === 'TemplateElement') {
|
618 |
|
619 | return node.value.cooked;
|
620 | }
|
621 |
|
622 | return false;
|
623 | };
|
624 |
|
625 | const errorOnInvalidQuasiNodes = (primaryNode) => {
|
626 | const noInterpolationError = !primaryNode.quasi.expressions.length;
|
627 | const wrongOrderError = primaryNode.quasi.quasis[0].value.raw.length;
|
628 | const message = `${primaryNode.tag.name} argument must be interpolated ${
|
629 | noInterpolationError ? 'in' : 'at the beginning of'
|
630 | } "${primaryNode.tag.name}\`\`" in "${primaryNode.loc.filename}" on line ${
|
631 | primaryNode.loc.start.line
|
632 | }`;
|
633 | if (noInterpolationError || wrongOrderError) {
|
634 | throw new Error(message);
|
635 | }
|
636 | };
|
637 |
|
638 | const extractNodeVariableNames = (varNode, babel) => {
|
639 | const interpolatedVariableNames = [];
|
640 | if (varNode.type === 'JSXElement') {
|
641 |
|
642 | interpolatedVariableNames.push(
|
643 | ...getValues(varNode.children, babel).map((value) => value.value.name),
|
644 | );
|
645 | } else if (varNode.type === 'Identifier') {
|
646 |
|
647 | interpolatedVariableNames.push(varNode.name);
|
648 | }
|
649 | return interpolatedVariableNames;
|
650 | };
|
651 |
|
652 | const extractVariableNamesFromQuasiNodes = (primaryNode, babel) => {
|
653 | errorOnInvalidQuasiNodes(primaryNode);
|
654 |
|
655 |
|
656 | const text = [];
|
657 |
|
658 |
|
659 | const interpolatedVariableNames = [];
|
660 | primaryNode.quasi.expressions.forEach((varNode) => {
|
661 | if (
|
662 | !babel.types.isIdentifier(varNode) &&
|
663 | !babel.types.isTaggedTemplateExpression(varNode) &&
|
664 | !babel.types.isJSXElement(varNode)
|
665 | ) {
|
666 | throw new Error(
|
667 | `Must pass a variable, not an expression to "${primaryNode.tag.name}\`\`" in "${primaryNode.loc.filename}" on line ${primaryNode.loc.start.line}`,
|
668 | );
|
669 | }
|
670 | text.push(varNode);
|
671 | interpolatedVariableNames.push(...extractNodeVariableNames(varNode, babel));
|
672 | });
|
673 | primaryNode.quasi.quasis.forEach((quasiNode) => {
|
674 |
|
675 |
|
676 |
|
677 | text.push(quasiNode);
|
678 | });
|
679 | return { text, interpolatedVariableNames };
|
680 | };
|
681 |
|
682 | const throwOnInvalidType = (type, primaryNode) => {
|
683 | if (!icuInterpolators.includes(type)) {
|
684 | throw new Error(
|
685 | `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}`,
|
686 | );
|
687 | }
|
688 | };
|
689 |
|
690 |
|
691 |
|
692 |
|
693 |
|
694 |
|
695 |
|
696 |
|
697 |
|
698 |
|
699 |
|
700 |
|
701 |
|
702 | function getTextAndInterpolatedVariables(type, primaryNode, index, babel) {
|
703 | throwOnInvalidType(type, primaryNode);
|
704 | const componentFoundIndex = index;
|
705 | const { text, interpolatedVariableNames } = extractVariableNamesFromQuasiNodes(
|
706 | primaryNode,
|
707 | babel,
|
708 | );
|
709 | const { stringOutput, componentFoundIndex: newIndex } = text
|
710 | .filter(filterNodes)
|
711 |
|
712 | .sort((a, b) => {
|
713 | if (a.start > b.start) return 1;
|
714 | return -1;
|
715 | })
|
716 | .reduce(extractNestedTemplatesAndComponents, {
|
717 | babel,
|
718 | componentFoundIndex,
|
719 | stringOutput: [],
|
720 | type,
|
721 | interpolatedVariableNames,
|
722 | });
|
723 | return [
|
724 | interpolatedVariableNames,
|
725 | `{${stringOutput.join('')}}`,
|
726 |
|
727 | newIndex,
|
728 | ];
|
729 | }
|