gilbsgilbs / babel-plugin-i18next-extract

Babel plugin that statically extracts i18next and react-i18next translation keys.
https://i18next-extract.netlify.com
MIT License
161 stars 37 forks source link

Align Trans key extraction with react-i18next for elements with i18nIsDynamicList #206

Open ParallelUniv3rse opened 2 years ago

ParallelUniv3rse commented 2 years ago

Describe the bug

Given a simple test case:

<ul i18nIsDynamicList>
   <li>one</li>
   <li>two</li>
</ul>

react-i18n extracts <0></0> whereas this plugin extracts <0><0>one</0><1>two</1></0>

How to reproduce

Use i18nIsDynamicList prop on an element with multiple regular children. Expressions with arrays seem to not be broken.

Babel configuration:

{
        presets: ['next/babel'],
        plugins: [
            [
                'i18next-extract',
                {
                    locales: ['cs', 'en'],

                    keyAsDefaultValue: ['cs'],

                    useI18nextDefaultValue: false,

                    // Disable keySeparator and nsSeparator since they could conflict with the actual value:
                    keySeparator: null,
                    nsSeparator: null,

                    // Ignore plurals and contexts. We can't use natural keys for those.
                    keyAsDefaultValueForDerivedKeys: false,

                    discardOldKeys: true,

                    ...options,
                },
            ],
        ],
    }

Expected behavior

Plugin extracts correct string without children same as in react-i18n test file

What actually happens

Extracted string does not match.

Your environment

Additional context

changing the formatJSXElementKey function as follows fixes the issue, though react-i18next also checks if the element is not a component, since the i18nIsDynamicList attribute should apply only to real dom nodes. I'm not certain how to create the same check here.

function formatJSXElementKey(path, index, config) {
  const openingElement = path.get('openingElement');
  const closingElement = path.get('closingElement');
  let resultTagName = `${index}`; // Tag name we will use in the exported file

  const tagName = openingElement.get('name');

  if (openingElement.get('attributes').length === 0 && tagName.isJSXIdentifier() && config.transKeepBasicHtmlNodesFor.includes(tagName.node.name) && !hasChildren(path)) {
    // The tag name should not be transformed to an index
    resultTagName = tagName.node.name;

    if (closingElement.node === null) {
      // opening tag without closing tag (e.g. <br />)
      return `<${resultTagName}/>`;
    }
  } // it's nested. let's recurse.
  const dynamicAttr = findJSXAttributeByName(path, 'i18nIsDynamicList');

  return `<${resultTagName}>${dynamicAttr ? '' : parseTransComponentKeyFromChildren(path, config)}</${resultTagName}>`;
}