This app uses i18next-parser
tool and you also have to use it very regularly.
It is an extractor that crawls the code, finds all the used translation keys,
and updates translation files. So you don't need to update them by hand. Just
run that command and that's it. Also, if you forget to run it the CI will
reject your commit.
The most important thing to remember is the following command:
npm run i18n
It does many things for you.
Remember that translation keys must be string literals in the code. So variables or string interpolation are not allowed.
// Don'ts
t(LANG);
t(lang);
t(`languages.${lang}`);
// Do's
t('some-translation-key');
When you want to use pluralization, you must use count
parameter. Don't use
amount
or number
. Then run npm run i18n
and it will create both singular
and plural stubs for your translations.
t('some-translation-key', { count: 1 });
{
"some-translation-key_one": "",
"some-translation-key_other": ""
}
When you have multiple translation keys that belong together, you can use
nesting. Then run npm run i18n
and it will create stubs for all of your
translation keys and keep them grouped in the translation files.
t('some.translation.key');
{
"some": {
"translation": {
"key": ""
}
}
}
There are situations where you need to translate something that is stored in
a variable. E.g. status of the concept. In these situations, you need to use
if-else jungles. To keep your code clean, there is
common/utils/translation-helpers.ts
where you can create a helper function
and put your if-else jungle there. There are already some, so in the
most common use cases, the helper function you are looking for already exists.
If not, just follow the example of others.
Then you can simply pass your variable to that helper function.
translateLanguage(lang, t);
When you want to add a new translation, just write your code:
<button>{t('some-translation-key')}</button>
Then run npm run i18n
. It creates subs for that translation key in every
translation file.
{
"some-translation-key": ""
}
Then you only have to write the actual text between the quotation marks. Or if you don't have the translation, just left it empty.
When you want to remove some code block that contains translation keys, just
remove that code block. Then you run npm run i18n
and it finds out if these
translation keys are used somewhere else or not.
If it finds out that there is a translation key in the translation files that
is never used, it will move it to ${domain}_old.json
file. Review these files
and remove the unused translations. You don't have to commit these files into
git.
When you are refactoring translation keys or something, edit your code first
and then run npm run i18n
. If adds the stubs for new translation keys and
moves unused ones to ${domain}_old.json
files. Then you have to manually move
these texts under new translation keys.
Translation keys in the translation files are sorted alphabetically so merge
conflicts are pretty rare. But sometimes this happens. npm run i18n
will
fail if the translation files don't contain valid JSON. So you have to
"fix" the conflict by hand. Just keep both versions and ensure that it is still
a valid JSON.
Then fix all the conflicts in your code and finally run npm run i18n
. It will
update the translation files, sort translation keys, and so on. Review the
changes and commit if they look good.
If you forgot to run npm run i18n
your commit will not get passed from CI.
It will check that no new translation keys are missing and there are no old unused translation keys in the translation files.
To fix this, you just need to run npm run i18n
and push changes.
Translations are divided into multiple namespaces. And there is a translation
file for each namespace. npm run i18n
recognizes namespaces automatically so
just use them.
In your code, you can specify the default namespace so that you don't need to
pass the namespace every time. It is only default, so you can always use another
namespace when you want. You just need to pass it to the t
function.
const { t } = useTranslation('default-namespace');
t('some.translation.key', { ns: 'another-namespace' });
At the moment, we load all namespaces into memory. But in the future, it might
be useful to load only some namespaces. This happens inside of the
getServerSideProps
of each page. This way, it is possible to divide
translations into the common namespace and page-specific namespaces. This is the
most common approach.
Another approaches also exist and it is not easy to say which one is the best. It's not either easy to determine if some text should be in this or that namespace. That's why there will always be space for refactoring in the translations.
The third approach (what I recommend) is that general things are in the common namespace and all the rest are in the terminology namespace for this project. In other applications, there will also be the same common namespace and the other namespace for the rest of the translations. This way it would be possible to cache translations instead of passing them down on every page change.
If the extractor generates an output you didn't expect, the problem is probably in your code.
// expected
{
"some-translation-key_one": "",
"some-translation-key_other": ""
}
// actual
{
"some-translation-key": ""
}
The problem is that you don't pass count
argument to the t
function.
// expected
{
"some-translation-key_one": "",
"some-translation-key_other": ""
}
// actual
{
"some-translation-key": "",
"some-translation-key_one": "",
"some-translation-key_other": ""
}
The problem is that you have passed count
argument to the t
function
correctly in one place but not in another place.