Open kitsunekyo opened 1 year ago
Experiencing the same issue in my project.
This is also true when you implement the collectUnusedTranslations
script (see below as implemented on my side).
const { collectUnusedTranslations } = require('i18n-unused');
const { sync: globSync } = require('glob');
const dictionaries = globSync('./src/i18n/dictionaries/*.json', {realpath: true});
const sources = globSync('./src/**/*.+(tsx|ts|jsx|js|es6)', {realpath: true});
const handleTranslations = async () => {
const unusedTranslations = await collectUnusedTranslations(
dictionaries,
sources,
{}
);
return unusedTranslations;
}
handleTranslations().then(result => { console.log('unused:', result) });
Same to me but with .tsx
I had this issue and mostly fixed it with the translationKeyMatcher config:
translationKeyMatcher: /['"](translation|prefixes|like|tab|here)\.([\w\.]+)['"]/gi
Seems like the default value for translationKeyMatcher
, which is /(?:[$ .](_|t|tc|i18nKey))\(.*?\)/gi
, isn't working - it gives no results
It's looking for $
, space
or .
characters preceding the t
function which isn't always present.
I tried the one provided in the comment above - but, it struggles with several cases:
t('myKey', {
someToken: 123
})
t(somePredicate ? 'keyTrue' : 'keyFalse')
For example, given:
{
"keyA": "x",
"keyAB": "x",
}
And code of:
t('keyAB')
The tool won't flag that keyA
is unused because it's a substring of keyAB
.
e.g.
matchViewport('someString') // matcher sees `t('someString')` here and marks it as a used key
I've added a custom translationKeyMatcher
that is sort of working....
translationKeyMatcher: /(?:[ ={:]t\(|i18nKey=)'\w+'[,)]?/gi,
Some gotchas to note as this is very basic...
t('key'
or i18next='key'
are the only ways keys are consumed (as we only use this in our codebase, it optionally looks for either a closing bracket or comma after the key string)I've also had to refactor some of my keys to support these gotchas:
- t(predicate ? 'key1' : 'key2')
+ predicate ? t('key1') : t('key2')
Making sure my keys aren't substrings
- "myControl": "value",
+ "myControlLabel": "value",
"myControlTooltip": "tooltip",
Having the exactly same issue in a React+TS project
after a good chat with my friend chatGPT, we (they) came up with this:
translationKeyMatcher:
// this regex was generated by chatGPT, and has the following features:
// * only match for `t()` and `tToDocLanguage()` calls
// * preceded by a space, opening parenthesis, or opening curly brace
// * also match for `i18nKey` prop in JSX
// * captures double quotes, single quotes, and backticks
// * works with optional chaining e.g. `t?.()`
// * works with multiline strings e.g. `t(\n"key")`
/(?<=[\s{(])(?:t|tToDocLanguage)\??\.?\(\s*["'`]?([\s\S]+?)["'`]?\s*\)|i18nKey="([\s\S]+?)"/g,
we have a custom function used in the codebase, tToDocLanguage
which we wanted to match on too, but if you don't you can remove the |tToDocLanguage
part.
works really well for us, only downside is it doesn't match on dynamic strings (e.g. t(`foo.${bar}`)
), so we just have a list of those in our excludeKey
array.
here's some test code to verify it works as expected for your use case:
const regex = /(?<=[\s{(])(?:t|tToDocLanguage)\??\.?\(\s*["'`]?([\s\S]+?)["'`]?\s*\)|i18nKey="([\s\S]+?)"/g;
const text = `
t("1.basic.doublequotes.string")
t?.("2.basic.doublequotes.string.with.optional.chaining")
t('3.basic.singlequotes.string')
t?.('4.basic.singlequotes.string.with.optional.chaining')
t(\`5.basic.backtick.string\`)
t?.(\`6.basic.backtick.string.with.optional.chaining\`)
const multilineString = \`abc \${t(
"7.multiline.string",
)}\`;
tToDocLanguage("8.custom.func.beginning.with.t")
<Element i18nKey="9.i18nKey.string">
`;
const matches = [...text.matchAll(regex)];
const keys = matches.map(match => match[1] || match[2]);
// output each key on a new line
keys.forEach(key => console.log(key));
// outputs:
// '1.basic.doublequotes.string'
// '2.basic.doublequotes.string.with.optional.chaining'
// '3.basic.singlequotes.string'
// '4.basic.singlequotes.string.with.optional.chaining'
// '5.basic.backtick.string'
// '6.basic.backtick.string.with.optional.chaining'
// '7.multiline.string",'
// '8.custom.func.beginning.with.t'
// '9.i18nKey.string'
hope that helps some folks!
I've adjusted the regex a bit to also include t
methods where you use variables,
/t\(\s*["'`]?([\s\S]+?)["'`]?\s*(?:\)|,)|i18nKey="([\s\S]+?)"/gi
Now this would be matched correctly as well
t(`translation.key`, { variable: x })
i18next-react project setup:
translation files
(reduced for brevity)
example file that uses translations
i have installed
i18n-unused
as dev dependency and runyarn i18n-unused display-unused
this prints almost all translations as unused. what i noticed is that it works with translations that are in javascript (likeuseEffect
, or other pure javascript contexts), but it does not seem to work injsx
at all.