Skip to content

Latest commit

 

History

History
220 lines (163 loc) · 6.12 KB

i18n.md

File metadata and controls

220 lines (163 loc) · 6.12 KB

Internationalization (i18n)

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.

Usage

The most important thing to remember is the following command:

npm run i18n

It does many things for you.

Using translation keys

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');

Pluralization

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": ""
}

Nesting

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": ""
    }
  }
}

Helper functions

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);

Use cases

Adding new translation

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.

Removing translation

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.

Renaming translation key

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.

Fixing merge conflicts

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.

CI

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.

Namespaces

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.

Troubleshooting

If the extractor generates an output you didn't expect, the problem is probably in your code.

No pluralization

// 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.

Pluralization with extras

// 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.