You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					732 lines
				
				25 KiB
			
		
		
			
		
	
	
					732 lines
				
				25 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								const { createMacro } = require('babel-plugin-macros');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// copy to:
							 | 
						||
| 
								 | 
							
								// https://astexplorer.net/#/gist/642aebbb9e449e959f4ad8907b4adf3a/4a65742e2a3e926eb55eaa3d657d1472b9ac7970
							 | 
						||
| 
								 | 
							
								module.exports = createMacro(ICUMacro);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function ICUMacro({ references, state, babel }) {
							 | 
						||
| 
								 | 
							
								  const {
							 | 
						||
| 
								 | 
							
								    Trans = [],
							 | 
						||
| 
								 | 
							
								    Plural = [],
							 | 
						||
| 
								 | 
							
								    Select = [],
							 | 
						||
| 
								 | 
							
								    SelectOrdinal = [],
							 | 
						||
| 
								 | 
							
								    number = [],
							 | 
						||
| 
								 | 
							
								    date = [],
							 | 
						||
| 
								 | 
							
								    select = [],
							 | 
						||
| 
								 | 
							
								    selectOrdinal = [],
							 | 
						||
| 
								 | 
							
								    plural = [],
							 | 
						||
| 
								 | 
							
								    time = [],
							 | 
						||
| 
								 | 
							
								  } = references;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // assert we have the react-i18next Trans component imported
							 | 
						||
| 
								 | 
							
								  addNeededImports(state, babel, references);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // transform Plural and SelectOrdinal
							 | 
						||
| 
								 | 
							
								  [...Plural, ...SelectOrdinal].forEach((referencePath) => {
							 | 
						||
| 
								 | 
							
								    if (referencePath.parentPath.type === 'JSXOpeningElement') {
							 | 
						||
| 
								 | 
							
								      pluralAsJSX(
							 | 
						||
| 
								 | 
							
								        referencePath.parentPath,
							 | 
						||
| 
								 | 
							
								        {
							 | 
						||
| 
								 | 
							
								          attributes: referencePath.parentPath.get('attributes'),
							 | 
						||
| 
								 | 
							
								          children: referencePath.parentPath.parentPath.get('children'),
							 | 
						||
| 
								 | 
							
								        },
							 | 
						||
| 
								 | 
							
								        babel,
							 | 
						||
| 
								 | 
							
								      );
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      // throw a helpful error message or something :)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // transform Select
							 | 
						||
| 
								 | 
							
								  Select.forEach((referencePath) => {
							 | 
						||
| 
								 | 
							
								    if (referencePath.parentPath.type === 'JSXOpeningElement') {
							 | 
						||
| 
								 | 
							
								      selectAsJSX(
							 | 
						||
| 
								 | 
							
								        referencePath.parentPath,
							 | 
						||
| 
								 | 
							
								        {
							 | 
						||
| 
								 | 
							
								          attributes: referencePath.parentPath.get('attributes'),
							 | 
						||
| 
								 | 
							
								          children: referencePath.parentPath.parentPath.get('children'),
							 | 
						||
| 
								 | 
							
								        },
							 | 
						||
| 
								 | 
							
								        babel,
							 | 
						||
| 
								 | 
							
								      );
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      // throw a helpful error message or something :)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // transform Trans
							 | 
						||
| 
								 | 
							
								  Trans.forEach((referencePath) => {
							 | 
						||
| 
								 | 
							
								    if (referencePath.parentPath.type === 'JSXOpeningElement') {
							 | 
						||
| 
								 | 
							
								      transAsJSX(
							 | 
						||
| 
								 | 
							
								        referencePath.parentPath,
							 | 
						||
| 
								 | 
							
								        {
							 | 
						||
| 
								 | 
							
								          attributes: referencePath.parentPath.get('attributes'),
							 | 
						||
| 
								 | 
							
								          children: referencePath.parentPath.parentPath.get('children'),
							 | 
						||
| 
								 | 
							
								        },
							 | 
						||
| 
								 | 
							
								        babel,
							 | 
						||
| 
								 | 
							
								        state,
							 | 
						||
| 
								 | 
							
								      );
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      // throw a helpful error message or something :)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // check for number`` and others outside of <Trans>
							 | 
						||
| 
								 | 
							
								  Object.entries({
							 | 
						||
| 
								 | 
							
								    number,
							 | 
						||
| 
								 | 
							
								    date,
							 | 
						||
| 
								 | 
							
								    time,
							 | 
						||
| 
								 | 
							
								    select,
							 | 
						||
| 
								 | 
							
								    plural,
							 | 
						||
| 
								 | 
							
								    selectOrdinal,
							 | 
						||
| 
								 | 
							
								  }).forEach(([name, node]) => {
							 | 
						||
| 
								 | 
							
								    node.forEach((item) => {
							 | 
						||
| 
								 | 
							
								      let f = item.parentPath;
							 | 
						||
| 
								 | 
							
								      while (f) {
							 | 
						||
| 
								 | 
							
								        if (babel.types.isJSXElement(f)) {
							 | 
						||
| 
								 | 
							
								          if (f.node.openingElement.name.name === 'Trans') {
							 | 
						||
| 
								 | 
							
								            // this is a valid use of number/date/time/etc.
							 | 
						||
| 
								 | 
							
								            return;
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        f = f.parentPath;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      throw new Error(
							 | 
						||
| 
								 | 
							
								        `"${name}\`\`" can only be used inside <Trans> in "${item.node.loc.filename}" on line ${item.node.loc.start.line}`,
							 | 
						||
| 
								 | 
							
								      );
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								  });
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function pluralAsJSX(parentPath, { attributes }, babel) {
							 | 
						||
| 
								 | 
							
								  const t = babel.types;
							 | 
						||
| 
								 | 
							
								  const toObjectProperty = (name, value) =>
							 | 
						||
| 
								 | 
							
								    t.objectProperty(t.identifier(name), t.identifier(name), false, !value);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // plural or selectordinal
							 | 
						||
| 
								 | 
							
								  const nodeName = parentPath.node.name.name.toLocaleLowerCase();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // will need to merge count attribute with existing values attribute in some cases
							 | 
						||
| 
								 | 
							
								  const existingValuesAttribute = findAttribute('values', attributes);
							 | 
						||
| 
								 | 
							
								  const existingValues = existingValuesAttribute
							 | 
						||
| 
								 | 
							
								    ? existingValuesAttribute.node.value.expression.properties
							 | 
						||
| 
								 | 
							
								    : [];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  let componentStartIndex = 0;
							 | 
						||
| 
								 | 
							
								  const extracted = attributes.reduce(
							 | 
						||
| 
								 | 
							
								    (mem, attr) => {
							 | 
						||
| 
								 | 
							
								      if (attr.node.name.name === 'i18nKey') {
							 | 
						||
| 
								 | 
							
								        // copy the i18nKey
							 | 
						||
| 
								 | 
							
								        mem.attributesToCopy.push(attr.node);
							 | 
						||
| 
								 | 
							
								      } else if (attr.node.name.name === 'count') {
							 | 
						||
| 
								 | 
							
								        // take the count for element
							 | 
						||
| 
								 | 
							
								        let exprName = attr.node.value.expression.name;
							 | 
						||
| 
								 | 
							
								        if (!exprName) {
							 | 
						||
| 
								 | 
							
								          exprName = 'count';
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        if (exprName === 'count') {
							 | 
						||
| 
								 | 
							
								          // if the prop expression name is also "count", copy it instead: <Plural count={count} --> <Trans count={count}
							 | 
						||
| 
								 | 
							
								          mem.attributesToCopy.push(attr.node);
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          mem.values.unshift(toObjectProperty(exprName));
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        mem.defaults = `{${exprName}, ${nodeName}, ${mem.defaults}`;
							 | 
						||
| 
								 | 
							
								      } else if (attr.node.name.name === 'values') {
							 | 
						||
| 
								 | 
							
								        // skip the values attribute, as it has already been processed into mem from existingValues
							 | 
						||
| 
								 | 
							
								      } else if (attr.node.value.type === 'StringLiteral') {
							 | 
						||
| 
								 | 
							
								        // take any string node as plural option
							 | 
						||
| 
								 | 
							
								        let pluralForm = attr.node.name.name;
							 | 
						||
| 
								 | 
							
								        if (pluralForm.indexOf('$') === 0) pluralForm = pluralForm.replace('$', '=');
							 | 
						||
| 
								 | 
							
								        mem.defaults = `${mem.defaults} ${pluralForm} {${attr.node.value.value}}`;
							 | 
						||
| 
								 | 
							
								      } else if (attr.node.value.type === 'JSXExpressionContainer') {
							 | 
						||
| 
								 | 
							
								        // convert any Trans component to plural option extracting any values and components
							 | 
						||
| 
								 | 
							
								        const children = attr.node.value.expression.children || [];
							 | 
						||
| 
								 | 
							
								        const thisTrans = processTrans(children, babel, componentStartIndex);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        let pluralForm = attr.node.name.name;
							 | 
						||
| 
								 | 
							
								        if (pluralForm.indexOf('$') === 0) pluralForm = pluralForm.replace('$', '=');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        mem.defaults = `${mem.defaults} ${pluralForm} {${thisTrans.defaults}}`;
							 | 
						||
| 
								 | 
							
								        mem.components = mem.components.concat(thisTrans.components);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        componentStartIndex += thisTrans.components.length;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      return mem;
							 | 
						||
| 
								 | 
							
								    },
							 | 
						||
| 
								 | 
							
								    { attributesToCopy: [], values: existingValues, components: [], defaults: '' },
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // replace the node with the new Trans
							 | 
						||
| 
								 | 
							
								  parentPath.replaceWith(buildTransElement(extracted, extracted.attributesToCopy, t, true));
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function selectAsJSX(parentPath, { attributes }, babel) {
							 | 
						||
| 
								 | 
							
								  const t = babel.types;
							 | 
						||
| 
								 | 
							
								  const toObjectProperty = (name, value) =>
							 | 
						||
| 
								 | 
							
								    t.objectProperty(t.identifier(name), t.identifier(name), false, !value);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // will need to merge switch attribute with existing values attribute
							 | 
						||
| 
								 | 
							
								  const existingValuesAttribute = findAttribute('values', attributes);
							 | 
						||
| 
								 | 
							
								  const existingValues = existingValuesAttribute
							 | 
						||
| 
								 | 
							
								    ? existingValuesAttribute.node.value.expression.properties
							 | 
						||
| 
								 | 
							
								    : [];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  let componentStartIndex = 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const extracted = attributes.reduce(
							 | 
						||
| 
								 | 
							
								    (mem, attr) => {
							 | 
						||
| 
								 | 
							
								      if (attr.node.name.name === 'i18nKey') {
							 | 
						||
| 
								 | 
							
								        // copy the i18nKey
							 | 
						||
| 
								 | 
							
								        mem.attributesToCopy.push(attr.node);
							 | 
						||
| 
								 | 
							
								      } else if (attr.node.name.name === 'switch') {
							 | 
						||
| 
								 | 
							
								        // take the switch for select element
							 | 
						||
| 
								 | 
							
								        let exprName = attr.node.value.expression.name;
							 | 
						||
| 
								 | 
							
								        if (!exprName) {
							 | 
						||
| 
								 | 
							
								          exprName = 'selectKey';
							 | 
						||
| 
								 | 
							
								          mem.values.unshift(t.objectProperty(t.identifier(exprName), attr.node.value.expression));
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          mem.values.unshift(toObjectProperty(exprName));
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        mem.defaults = `{${exprName}, select, ${mem.defaults}`;
							 | 
						||
| 
								 | 
							
								      } else if (attr.node.name.name === 'values') {
							 | 
						||
| 
								 | 
							
								        // skip the values attribute, as it has already been processed into mem as existingValues
							 | 
						||
| 
								 | 
							
								      } else if (attr.node.value.type === 'StringLiteral') {
							 | 
						||
| 
								 | 
							
								        // take any string node as select option
							 | 
						||
| 
								 | 
							
								        mem.defaults = `${mem.defaults} ${attr.node.name.name} {${attr.node.value.value}}`;
							 | 
						||
| 
								 | 
							
								      } else if (attr.node.value.type === 'JSXExpressionContainer') {
							 | 
						||
| 
								 | 
							
								        // convert any Trans component to select option extracting any values and components
							 | 
						||
| 
								 | 
							
								        const children = attr.node.value.expression.children || [];
							 | 
						||
| 
								 | 
							
								        const thisTrans = processTrans(children, babel, componentStartIndex);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        mem.defaults = `${mem.defaults} ${attr.node.name.name} {${thisTrans.defaults}}`;
							 | 
						||
| 
								 | 
							
								        mem.components = mem.components.concat(thisTrans.components);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        componentStartIndex += thisTrans.components.length;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      return mem;
							 | 
						||
| 
								 | 
							
								    },
							 | 
						||
| 
								 | 
							
								    { attributesToCopy: [], values: existingValues, components: [], defaults: '' },
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // replace the node with the new Trans
							 | 
						||
| 
								 | 
							
								  parentPath.replaceWith(buildTransElement(extracted, extracted.attributesToCopy, t, true));
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function transAsJSX(parentPath, { attributes, children }, babel, { filename }) {
							 | 
						||
| 
								 | 
							
								  const defaultsAttr = findAttribute('defaults', attributes);
							 | 
						||
| 
								 | 
							
								  const componentsAttr = findAttribute('components', attributes);
							 | 
						||
| 
								 | 
							
								  // if there is "defaults" attribute and no "components" attribute, parse defaults and extract from the parsed defaults instead of children
							 | 
						||
| 
								 | 
							
								  // if a "components" attribute has been provided, we assume they have already constructed a valid "defaults" and it does not need to be parsed
							 | 
						||
| 
								 | 
							
								  const parseDefaults = defaultsAttr && !componentsAttr;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  let extracted;
							 | 
						||
| 
								 | 
							
								  if (parseDefaults) {
							 | 
						||
| 
								 | 
							
								    const defaultsExpression = defaultsAttr.node.value.value;
							 | 
						||
| 
								 | 
							
								    const parsed = babel.parse(`<>${defaultsExpression}</>`, {
							 | 
						||
| 
								 | 
							
								      presets: ['@babel/react'],
							 | 
						||
| 
								 | 
							
								      filename,
							 | 
						||
| 
								 | 
							
								    }).program.body[0].expression.children;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    extracted = processTrans(parsed, babel);
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    extracted = processTrans(children, babel);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  let clonedAttributes = cloneExistingAttributes(attributes);
							 | 
						||
| 
								 | 
							
								  if (parseDefaults) {
							 | 
						||
| 
								 | 
							
								    // remove existing defaults so it can be replaced later with the new parsed defaults
							 | 
						||
| 
								 | 
							
								    clonedAttributes = clonedAttributes.filter((node) => node.name.name !== 'defaults');
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // replace the node with the new Trans
							 | 
						||
| 
								 | 
							
								  const replacePath = children.length ? children[0].parentPath : parentPath;
							 | 
						||
| 
								 | 
							
								  replacePath.replaceWith(
							 | 
						||
| 
								 | 
							
								    buildTransElement(extracted, clonedAttributes, babel.types, false, !!children.length),
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function buildTransElement(
							 | 
						||
| 
								 | 
							
								  extracted,
							 | 
						||
| 
								 | 
							
								  finalAttributes,
							 | 
						||
| 
								 | 
							
								  t,
							 | 
						||
| 
								 | 
							
								  closeDefaults = false,
							 | 
						||
| 
								 | 
							
								  wasElementWithChildren = false,
							 | 
						||
| 
								 | 
							
								) {
							 | 
						||
| 
								 | 
							
								  const nodeName = t.jSXIdentifier('Trans');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // plural, select open { but do not close it while reduce
							 | 
						||
| 
								 | 
							
								  if (closeDefaults) extracted.defaults += '}';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // convert arrays into needed expressions
							 | 
						||
| 
								 | 
							
								  extracted.components = t.arrayExpression(extracted.components);
							 | 
						||
| 
								 | 
							
								  extracted.values = t.objectExpression(extracted.values);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // add generated Trans attributes
							 | 
						||
| 
								 | 
							
								  if (!attributeExistsAlready('defaults', finalAttributes))
							 | 
						||
| 
								 | 
							
								    if (extracted.defaults.includes(`"`)) {
							 | 
						||
| 
								 | 
							
								      // wrap defaults that contain double quotes in brackets
							 | 
						||
| 
								 | 
							
								      finalAttributes.push(
							 | 
						||
| 
								 | 
							
								        t.jSXAttribute(
							 | 
						||
| 
								 | 
							
								          t.jSXIdentifier('defaults'),
							 | 
						||
| 
								 | 
							
								          t.jSXExpressionContainer(t.StringLiteral(extracted.defaults)),
							 | 
						||
| 
								 | 
							
								        ),
							 | 
						||
| 
								 | 
							
								      );
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      finalAttributes.push(
							 | 
						||
| 
								 | 
							
								        t.jSXAttribute(t.jSXIdentifier('defaults'), t.StringLiteral(extracted.defaults)),
							 | 
						||
| 
								 | 
							
								      );
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (!attributeExistsAlready('components', finalAttributes))
							 | 
						||
| 
								 | 
							
								    finalAttributes.push(
							 | 
						||
| 
								 | 
							
								      t.jSXAttribute(t.jSXIdentifier('components'), t.jSXExpressionContainer(extracted.components)),
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								  if (!attributeExistsAlready('values', finalAttributes))
							 | 
						||
| 
								 | 
							
								    finalAttributes.push(
							 | 
						||
| 
								 | 
							
								      t.jSXAttribute(t.jSXIdentifier('values'), t.jSXExpressionContainer(extracted.values)),
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // create selfclosing Trans component
							 | 
						||
| 
								 | 
							
								  const openElement = t.jSXOpeningElement(nodeName, finalAttributes, true);
							 | 
						||
| 
								 | 
							
								  if (!wasElementWithChildren) return openElement;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return t.jSXElement(openElement, null, [], true);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function cloneExistingAttributes(attributes) {
							 | 
						||
| 
								 | 
							
								  return attributes.reduce((mem, attr) => {
							 | 
						||
| 
								 | 
							
								    mem.push(attr.node);
							 | 
						||
| 
								 | 
							
								    return mem;
							 | 
						||
| 
								 | 
							
								  }, []);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function findAttribute(name, attributes) {
							 | 
						||
| 
								 | 
							
								  return attributes.find((child) => {
							 | 
						||
| 
								 | 
							
								    const ele = child.node ? child.node : child;
							 | 
						||
| 
								 | 
							
								    return ele.name.name === name;
							 | 
						||
| 
								 | 
							
								  });
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function attributeExistsAlready(name, attributes) {
							 | 
						||
| 
								 | 
							
								  return !!findAttribute(name, attributes);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function processTrans(children, babel, componentStartIndex = 0) {
							 | 
						||
| 
								 | 
							
								  const res = {};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  res.defaults = mergeChildren(children, babel, componentStartIndex);
							 | 
						||
| 
								 | 
							
								  res.components = getComponents(children, babel);
							 | 
						||
| 
								 | 
							
								  res.values = getValues(children, babel);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return res;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// eslint-disable-next-line no-control-regex
							 | 
						||
| 
								 | 
							
								const leadingNewLineAndWhitespace = new RegExp('^\n\\s+', 'g');
							 | 
						||
| 
								 | 
							
								// eslint-disable-next-line no-control-regex
							 | 
						||
| 
								 | 
							
								const trailingNewLineAndWhitespace = new RegExp('\n\\s+$', 'g');
							 | 
						||
| 
								 | 
							
								function trimIndent(text) {
							 | 
						||
| 
								 | 
							
								  const newText = text
							 | 
						||
| 
								 | 
							
								    .replace(leadingNewLineAndWhitespace, '')
							 | 
						||
| 
								 | 
							
								    .replace(trailingNewLineAndWhitespace, '');
							 | 
						||
| 
								 | 
							
								  return newText;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * add comma-delimited expressions like `{ val, number }`
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								function mergeCommaExpressions(ele) {
							 | 
						||
| 
								 | 
							
								  if (ele.expression && ele.expression.expressions) {
							 | 
						||
| 
								 | 
							
								    return `{${ele.expression.expressions
							 | 
						||
| 
								 | 
							
								      .reduce((m, i) => {
							 | 
						||
| 
								 | 
							
								        m.push(i.name || i.value);
							 | 
						||
| 
								 | 
							
								        return m;
							 | 
						||
| 
								 | 
							
								      }, [])
							 | 
						||
| 
								 | 
							
								      .join(', ')}}`;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return '';
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * this is for supporting complex icu type interpolations
							 | 
						||
| 
								 | 
							
								 * date`${variable}` and number`{${varName}, ::percent}`
							 | 
						||
| 
								 | 
							
								 * also, plural`{${count}, one { ... } other { ... }}
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								function mergeTaggedTemplateExpressions(ele, componentFoundIndex, t, babel) {
							 | 
						||
| 
								 | 
							
								  if (t.isTaggedTemplateExpression(ele.expression)) {
							 | 
						||
| 
								 | 
							
								    const [, text, index] = getTextAndInterpolatedVariables(
							 | 
						||
| 
								 | 
							
								      ele.expression.tag.name,
							 | 
						||
| 
								 | 
							
								      ele.expression,
							 | 
						||
| 
								 | 
							
								      componentFoundIndex,
							 | 
						||
| 
								 | 
							
								      babel,
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								    return [text, index];
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return ['', componentFoundIndex];
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function mergeChildren(children, babel, componentStartIndex = 0) {
							 | 
						||
| 
								 | 
							
								  const t = babel.types;
							 | 
						||
| 
								 | 
							
								  let componentFoundIndex = componentStartIndex;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return children.reduce((mem, child) => {
							 | 
						||
| 
								 | 
							
								    const ele = child.node ? child.node : child;
							 | 
						||
| 
								 | 
							
								    let result = mem;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // add text, but trim indentation whitespace
							 | 
						||
| 
								 | 
							
								    if (t.isJSXText(ele) && ele.value) result += trimIndent(ele.value);
							 | 
						||
| 
								 | 
							
								    // add ?!? forgot
							 | 
						||
| 
								 | 
							
								    if (ele.expression && ele.expression.value) result += ele.expression.value;
							 | 
						||
| 
								 | 
							
								    // add `{ val }`
							 | 
						||
| 
								 | 
							
								    if (ele.expression && ele.expression.name) result += `{${ele.expression.name}}`;
							 | 
						||
| 
								 | 
							
								    // add `{ val, number }`
							 | 
						||
| 
								 | 
							
								    result += mergeCommaExpressions(ele);
							 | 
						||
| 
								 | 
							
								    const [nextText, newIndex] = mergeTaggedTemplateExpressions(ele, componentFoundIndex, t, babel);
							 | 
						||
| 
								 | 
							
								    result += nextText;
							 | 
						||
| 
								 | 
							
								    componentFoundIndex = newIndex;
							 | 
						||
| 
								 | 
							
								    // add <strong>...</strong> with replace to <0>inner string</0>
							 | 
						||
| 
								 | 
							
								    if (t.isJSXElement(ele)) {
							 | 
						||
| 
								 | 
							
								      result += `<${componentFoundIndex}>${mergeChildren(
							 | 
						||
| 
								 | 
							
								        ele.children,
							 | 
						||
| 
								 | 
							
								        babel,
							 | 
						||
| 
								 | 
							
								      )}</${componentFoundIndex}>`;
							 | 
						||
| 
								 | 
							
								      componentFoundIndex += 1;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return result;
							 | 
						||
| 
								 | 
							
								  }, '');
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const extractTaggedTemplateValues = (ele, babel, toObjectProperty) => {
							 | 
						||
| 
								 | 
							
								  // date`${variable}` and so on
							 | 
						||
| 
								 | 
							
								  if (ele.expression && ele.expression.type === 'TaggedTemplateExpression') {
							 | 
						||
| 
								 | 
							
								    const [variables] = getTextAndInterpolatedVariables(
							 | 
						||
| 
								 | 
							
								      ele.expression.tag.name,
							 | 
						||
| 
								 | 
							
								      ele.expression,
							 | 
						||
| 
								 | 
							
								      0,
							 | 
						||
| 
								 | 
							
								      babel,
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								    return variables.map((vari) => toObjectProperty(vari));
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return [];
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Extract the names of interpolated value as object properties to pass to Trans
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								function getValues(children, babel) {
							 | 
						||
| 
								 | 
							
								  const t = babel.types;
							 | 
						||
| 
								 | 
							
								  const toObjectProperty = (name, value) =>
							 | 
						||
| 
								 | 
							
								    t.objectProperty(t.identifier(name), t.identifier(name), false, !value);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return children.reduce((mem, child) => {
							 | 
						||
| 
								 | 
							
								    const ele = child.node ? child.node : child;
							 | 
						||
| 
								 | 
							
								    let result = mem;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // add `{ var }` to values
							 | 
						||
| 
								 | 
							
								    if (ele.expression && ele.expression.name) mem.push(toObjectProperty(ele.expression.name));
							 | 
						||
| 
								 | 
							
								    // add `{ var, number }` to values
							 | 
						||
| 
								 | 
							
								    if (ele.expression && ele.expression.expressions)
							 | 
						||
| 
								 | 
							
								      result.push(
							 | 
						||
| 
								 | 
							
								        toObjectProperty(ele.expression.expressions[0].name || ele.expression.expressions[0].value),
							 | 
						||
| 
								 | 
							
								      );
							 | 
						||
| 
								 | 
							
								    // add `{ var: 'bar' }` to values
							 | 
						||
| 
								 | 
							
								    if (ele.expression && ele.expression.properties)
							 | 
						||
| 
								 | 
							
								      result = result.concat(ele.expression.properties);
							 | 
						||
| 
								 | 
							
								    // date`${variable}` and so on
							 | 
						||
| 
								 | 
							
								    result = result.concat(extractTaggedTemplateValues(ele, babel, toObjectProperty));
							 | 
						||
| 
								 | 
							
								    // recursive add inner elements stuff to values
							 | 
						||
| 
								 | 
							
								    if (t.isJSXElement(ele)) {
							 | 
						||
| 
								 | 
							
								      result = result.concat(getValues(ele.children, babel));
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return result;
							 | 
						||
| 
								 | 
							
								  }, []);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Common logic for adding a child element of Trans to the list of components to hydrate the translation
							 | 
						||
| 
								 | 
							
								 * @param {JSXElement} jsxElement
							 | 
						||
| 
								 | 
							
								 * @param {JSXElement[]} mem
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								const processJSXElement = (jsxElement, mem, t) => {
							 | 
						||
| 
								 | 
							
								  const clone = t.clone(jsxElement);
							 | 
						||
| 
								 | 
							
								  clone.children = clone.children.reduce((clonedMem, clonedChild) => {
							 | 
						||
| 
								 | 
							
								    const clonedEle = clonedChild.node ? clonedChild.node : clonedChild;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // clean out invalid definitions by replacing `{ catchDate, date, short }` with `{ catchDate }`
							 | 
						||
| 
								 | 
							
								    if (clonedEle.expression && clonedEle.expression.expressions)
							 | 
						||
| 
								 | 
							
								      clonedEle.expression.expressions = [clonedEle.expression.expressions[0]];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    clonedMem.push(clonedChild);
							 | 
						||
| 
								 | 
							
								    return clonedMem;
							 | 
						||
| 
								 | 
							
								  }, []);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  mem.push(jsxElement);
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Extract the React components to pass to Trans as components
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								function getComponents(children, babel) {
							 | 
						||
| 
								 | 
							
								  const t = babel.types;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return children.reduce((mem, child) => {
							 | 
						||
| 
								 | 
							
								    const ele = child.node ? child.node : child;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (t.isJSXExpressionContainer(ele)) {
							 | 
						||
| 
								 | 
							
								      // check for date`` and so on
							 | 
						||
| 
								 | 
							
								      if (t.isTaggedTemplateExpression(ele.expression)) {
							 | 
						||
| 
								 | 
							
								        ele.expression.quasi.expressions.forEach((expr) => {
							 | 
						||
| 
								 | 
							
								          // check for sub-expressions. This can happen with plural`` or select`` or selectOrdinal``
							 | 
						||
| 
								 | 
							
								          // these can have nested components
							 | 
						||
| 
								 | 
							
								          if (t.isTaggedTemplateExpression(expr) && expr.quasi.expressions.length) {
							 | 
						||
| 
								 | 
							
								            mem.push(...getComponents(expr.quasi.expressions, babel));
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								          if (!t.isJSXElement(expr)) {
							 | 
						||
| 
								 | 
							
								            // ignore anything that is not a component
							 | 
						||
| 
								 | 
							
								            return;
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								          processJSXElement(expr, mem, t);
							 | 
						||
| 
								 | 
							
								        });
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    if (t.isJSXElement(ele)) {
							 | 
						||
| 
								 | 
							
								      processJSXElement(ele, mem, t);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return mem;
							 | 
						||
| 
								 | 
							
								  }, []);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const icuInterpolators = ['date', 'time', 'number', 'plural', 'select', 'selectOrdinal'];
							 | 
						||
| 
								 | 
							
								const importsToAdd = ['Trans'];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * helper split out of addNeededImports to make codeclimate happy
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * This does the work of amending an existing import from "react-i18next", or
							 | 
						||
| 
								 | 
							
								 * creating a new one if it doesn't exist
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								function addImports(state, existingImport, allImportsToAdd, t) {
							 | 
						||
| 
								 | 
							
								  // append imports to existing or add a new react-i18next import for the Trans and icu tagged template literals
							 | 
						||
| 
								 | 
							
								  if (existingImport) {
							 | 
						||
| 
								 | 
							
								    allImportsToAdd.forEach((name) => {
							 | 
						||
| 
								 | 
							
								      if (
							 | 
						||
| 
								 | 
							
								        existingImport.specifiers.findIndex(
							 | 
						||
| 
								 | 
							
								          (specifier) => specifier.imported && specifier.imported.name === name,
							 | 
						||
| 
								 | 
							
								        ) === -1
							 | 
						||
| 
								 | 
							
								      ) {
							 | 
						||
| 
								 | 
							
								        existingImport.specifiers.push(t.importSpecifier(t.identifier(name), t.identifier(name)));
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    state.file.path.node.body.unshift(
							 | 
						||
| 
								 | 
							
								      t.importDeclaration(
							 | 
						||
| 
								 | 
							
								        allImportsToAdd.map((name) => t.importSpecifier(t.identifier(name), t.identifier(name))),
							 | 
						||
| 
								 | 
							
								        t.stringLiteral('react-i18next'),
							 | 
						||
| 
								 | 
							
								      ),
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Add `import { Trans, number, date, <etc.> } from "react-i18next"` as needed
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								function addNeededImports(state, babel, references) {
							 | 
						||
| 
								 | 
							
								  const t = babel.types;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // check if there is an existing react-i18next import
							 | 
						||
| 
								 | 
							
								  const existingImport = state.file.path.node.body.find(
							 | 
						||
| 
								 | 
							
								    (importNode) =>
							 | 
						||
| 
								 | 
							
								      t.isImportDeclaration(importNode) && importNode.source.value === 'react-i18next',
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								  // check for any of the tagged template literals that are used in the source, and add them
							 | 
						||
| 
								 | 
							
								  const usedRefs = Object.keys(references).filter((importName) => {
							 | 
						||
| 
								 | 
							
								    if (!icuInterpolators.includes(importName)) {
							 | 
						||
| 
								 | 
							
								      return false;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return references[importName].length;
							 | 
						||
| 
								 | 
							
								  });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // combine Trans + any tagged template literals
							 | 
						||
| 
								 | 
							
								  const allImportsToAdd = importsToAdd.concat(usedRefs);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  addImports(state, existingImport, allImportsToAdd, t);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * iterate over a node detected inside a tagged template literal
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * This is a helper function for `extractVariableNamesFromQuasiNodes` defined below
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * this is called using reduce as a way of tricking what would be `.map()`
							 | 
						||
| 
								 | 
							
								 * into passing in the parameters needed to both modify `componentFoundIndex`,
							 | 
						||
| 
								 | 
							
								 * `stringOutput`, and `interpolatedVariableNames`
							 | 
						||
| 
								 | 
							
								 * and to pass in the dependencies babel, and type. Type is the template type.
							 | 
						||
| 
								 | 
							
								 * For "date``" the type will be `date`. for "number``" the type is `number`, etc.
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								const extractNestedTemplatesAndComponents = (
							 | 
						||
| 
								 | 
							
								  { componentFoundIndex: lastIndex, babel, stringOutput, type, interpolatedVariableNames },
							 | 
						||
| 
								 | 
							
								  node,
							 | 
						||
| 
								 | 
							
								) => {
							 | 
						||
| 
								 | 
							
								  let componentFoundIndex = lastIndex;
							 | 
						||
| 
								 | 
							
								  if (node.type === 'JSXElement') {
							 | 
						||
| 
								 | 
							
								    // perform the interpolation of components just as we do in a normal Trans setting
							 | 
						||
| 
								 | 
							
								    const subText = `<${componentFoundIndex}>${mergeChildren(
							 | 
						||
| 
								 | 
							
								      node.children,
							 | 
						||
| 
								 | 
							
								      babel,
							 | 
						||
| 
								 | 
							
								    )}</${componentFoundIndex}>`;
							 | 
						||
| 
								 | 
							
								    componentFoundIndex += 1;
							 | 
						||
| 
								 | 
							
								    stringOutput.push(subText);
							 | 
						||
| 
								 | 
							
								  } else if (node.type === 'TaggedTemplateExpression') {
							 | 
						||
| 
								 | 
							
								    // a nested date``/number``/plural`` etc., extract whatever is inside of it
							 | 
						||
| 
								 | 
							
								    const [variableNames, childText, newIndex] = getTextAndInterpolatedVariables(
							 | 
						||
| 
								 | 
							
								      node.tag.name,
							 | 
						||
| 
								 | 
							
								      node,
							 | 
						||
| 
								 | 
							
								      componentFoundIndex,
							 | 
						||
| 
								 | 
							
								      babel,
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								    interpolatedVariableNames.push(...variableNames);
							 | 
						||
| 
								 | 
							
								    componentFoundIndex = newIndex;
							 | 
						||
| 
								 | 
							
								    stringOutput.push(childText);
							 | 
						||
| 
								 | 
							
								  } else if (node.type === 'Identifier') {
							 | 
						||
| 
								 | 
							
								    // turn date`${thing}` into `thing, date`
							 | 
						||
| 
								 | 
							
								    stringOutput.push(`${node.name}, ${type}`);
							 | 
						||
| 
								 | 
							
								  } else if (node.type === 'TemplateElement') {
							 | 
						||
| 
								 | 
							
								    // convert all whitespace into a single space for the text in the tagged template literal
							 | 
						||
| 
								 | 
							
								    stringOutput.push(node.value.cooked.replace(/\s+/g, ' '));
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    // unknown node type, ignore
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return { componentFoundIndex, babel, stringOutput, type, interpolatedVariableNames };
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * filter the list of nodes within a tagged template literal to the 4 types we can process,
							 | 
						||
| 
								 | 
							
								 * and ignore anything else.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * this is a helper function for `extractVariableNamesFromQuasiNodes`
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								const filterNodes = (node) => {
							 | 
						||
| 
								 | 
							
								  if (node.type === 'Identifier') {
							 | 
						||
| 
								 | 
							
								    // if the node has a name, keep it
							 | 
						||
| 
								 | 
							
								    return node.name;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  if (node.type === 'JSXElement' || node.type === 'TaggedTemplateExpression') {
							 | 
						||
| 
								 | 
							
								    // always keep interpolated elements or other tagged template literals like a nested date`` inside a plural``
							 | 
						||
| 
								 | 
							
								    return true;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  if (node.type === 'TemplateElement') {
							 | 
						||
| 
								 | 
							
								    // return the "cooked" (escaped) text for the text in the template literal (`, ::percent` in number`${varname}, ::percent`)
							 | 
						||
| 
								 | 
							
								    return node.value.cooked;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  // unknown node type, ignore
							 | 
						||
| 
								 | 
							
								  return false;
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const errorOnInvalidQuasiNodes = (primaryNode) => {
							 | 
						||
| 
								 | 
							
								  const noInterpolationError = !primaryNode.quasi.expressions.length;
							 | 
						||
| 
								 | 
							
								  const wrongOrderError = primaryNode.quasi.quasis[0].value.raw.length;
							 | 
						||
| 
								 | 
							
								  const message = `${primaryNode.tag.name} argument must be interpolated ${
							 | 
						||
| 
								 | 
							
								    noInterpolationError ? 'in' : 'at the beginning of'
							 | 
						||
| 
								 | 
							
								  } "${primaryNode.tag.name}\`\`" in "${primaryNode.loc.filename}" on line ${
							 | 
						||
| 
								 | 
							
								    primaryNode.loc.start.line
							 | 
						||
| 
								 | 
							
								  }`;
							 | 
						||
| 
								 | 
							
								  if (noInterpolationError || wrongOrderError) {
							 | 
						||
| 
								 | 
							
								    throw new Error(message);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const extractNodeVariableNames = (varNode, babel) => {
							 | 
						||
| 
								 | 
							
								  const interpolatedVariableNames = [];
							 | 
						||
| 
								 | 
							
								  if (varNode.type === 'JSXElement') {
							 | 
						||
| 
								 | 
							
								    // extract inner interpolated variables and add to the list
							 | 
						||
| 
								 | 
							
								    interpolatedVariableNames.push(
							 | 
						||
| 
								 | 
							
								      ...getValues(varNode.children, babel).map((value) => value.value.name),
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								  } else if (varNode.type === 'Identifier') {
							 | 
						||
| 
								 | 
							
								    // the name of the interpolated variable
							 | 
						||
| 
								 | 
							
								    interpolatedVariableNames.push(varNode.name);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return interpolatedVariableNames;
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const extractVariableNamesFromQuasiNodes = (primaryNode, babel) => {
							 | 
						||
| 
								 | 
							
								  errorOnInvalidQuasiNodes(primaryNode);
							 | 
						||
| 
								 | 
							
								  // this will contain all the nodes to convert to the ICU messageformat text
							 | 
						||
| 
								 | 
							
								  // at first they are unsorted, but will be ordered correctly at the end of the function
							 | 
						||
| 
								 | 
							
								  const text = [];
							 | 
						||
| 
								 | 
							
								  // the variable names. These are converted to object references as required for the Trans values
							 | 
						||
| 
								 | 
							
								  // in getValues() (toObjectProperty helper function)
							 | 
						||
| 
								 | 
							
								  const interpolatedVariableNames = [];
							 | 
						||
| 
								 | 
							
								  primaryNode.quasi.expressions.forEach((varNode) => {
							 | 
						||
| 
								 | 
							
								    if (
							 | 
						||
| 
								 | 
							
								      !babel.types.isIdentifier(varNode) &&
							 | 
						||
| 
								 | 
							
								      !babel.types.isTaggedTemplateExpression(varNode) &&
							 | 
						||
| 
								 | 
							
								      !babel.types.isJSXElement(varNode)
							 | 
						||
| 
								 | 
							
								    ) {
							 | 
						||
| 
								 | 
							
								      throw new Error(
							 | 
						||
| 
								 | 
							
								        `Must pass a variable, not an expression to "${primaryNode.tag.name}\`\`" in "${primaryNode.loc.filename}" on line ${primaryNode.loc.start.line}`,
							 | 
						||
| 
								 | 
							
								      );
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    text.push(varNode);
							 | 
						||
| 
								 | 
							
								    interpolatedVariableNames.push(...extractNodeVariableNames(varNode, babel));
							 | 
						||
| 
								 | 
							
								  });
							 | 
						||
| 
								 | 
							
								  primaryNode.quasi.quasis.forEach((quasiNode) => {
							 | 
						||
| 
								 | 
							
								    // these are the text surrounding the variable interpolation
							 | 
						||
| 
								 | 
							
								    // so in date`${varname}, short` it would be `''` and `, short`.
							 | 
						||
| 
								 | 
							
								    // (the empty string before `${varname}` and the stuff after it)
							 | 
						||
| 
								 | 
							
								    text.push(quasiNode);
							 | 
						||
| 
								 | 
							
								  });
							 | 
						||
| 
								 | 
							
								  return { text, interpolatedVariableNames };
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const throwOnInvalidType = (type, primaryNode) => {
							 | 
						||
| 
								 | 
							
								  if (!icuInterpolators.includes(type)) {
							 | 
						||
| 
								 | 
							
								    throw new Error(
							 | 
						||
| 
								 | 
							
								      `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}`,
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Retrieve the new text to use, and any interpolated variables
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * This is used to process tagged template literals like date`${variable}` and number`${num}, ::percent`
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * for the data example, it will return text of `{variable, date}` with a variable of `variable`
							 | 
						||
| 
								 | 
							
								 * for the number example, it will return text of `{num, number, ::percent}` with a variable of `num`
							 | 
						||
| 
								 | 
							
								 * @param {string} type the name of the tagged template (`date`, `number`, `plural`, etc. - any valid complex ICU type)
							 | 
						||
| 
								 | 
							
								 * @param {TaggedTemplateExpression} primaryNode the template expression node
							 | 
						||
| 
								 | 
							
								 * @param {int} index starting index number of components to be used for interpolations like <0>
							 | 
						||
| 
								 | 
							
								 * @param {*} babel
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								function getTextAndInterpolatedVariables(type, primaryNode, index, babel) {
							 | 
						||
| 
								 | 
							
								  throwOnInvalidType(type, primaryNode);
							 | 
						||
| 
								 | 
							
								  const componentFoundIndex = index;
							 | 
						||
| 
								 | 
							
								  const { text, interpolatedVariableNames } = extractVariableNamesFromQuasiNodes(
							 | 
						||
| 
								 | 
							
								    primaryNode,
							 | 
						||
| 
								 | 
							
								    babel,
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								  const { stringOutput, componentFoundIndex: newIndex } = text
							 | 
						||
| 
								 | 
							
								    .filter(filterNodes)
							 | 
						||
| 
								 | 
							
								    // sort by the order they appear in the source code
							 | 
						||
| 
								 | 
							
								    .sort((a, b) => {
							 | 
						||
| 
								 | 
							
								      if (a.start > b.start) return 1;
							 | 
						||
| 
								 | 
							
								      return -1;
							 | 
						||
| 
								 | 
							
								    })
							 | 
						||
| 
								 | 
							
								    .reduce(extractNestedTemplatesAndComponents, {
							 | 
						||
| 
								 | 
							
								      babel,
							 | 
						||
| 
								 | 
							
								      componentFoundIndex,
							 | 
						||
| 
								 | 
							
								      stringOutput: [],
							 | 
						||
| 
								 | 
							
								      type,
							 | 
						||
| 
								 | 
							
								      interpolatedVariableNames,
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								  return [
							 | 
						||
| 
								 | 
							
								    interpolatedVariableNames,
							 | 
						||
| 
								 | 
							
								    `{${stringOutput.join('')}}`,
							 | 
						||
| 
								 | 
							
								    // return the new component interpolation index
							 | 
						||
| 
								 | 
							
								    newIndex,
							 | 
						||
| 
								 | 
							
								  ];
							 | 
						||
| 
								 | 
							
								}
							 |