On one of my projects at work, we used i18next library (along with react-i18next) to manage internationalization in a React application. To implement it, it is usually necessary to do this:
Define an i18next instance
Define the resources
Import the i18n instance
Pass the translation function to a component via special HOC/React Hook
Or
Either way, the component gets the t function. The function accepts a path to a localization string (see JSON files above), and an object with variables, which are put in the slots (defined in double curly braces) in the string.
The whole approach looks reasonable, but there are some problems with it:
Path to a localization string is a plain string, so it is easy to make typos. It is also easy to forget to define a namespace or make typos in it as well. It is necessary to thoroughly check each part of the path to make sure it leads to a correct localization string.
The packages are heavy: the described approach in total takes ~55.1 KiB, ~51 KiB of which is the packages' code. Check out "Measuring application sizes" section below for more information.
It is hard to keep the files' structure synchronized between locales. A sub-tree can be missed in a resource, and nothing will tell about that until missing keys feature will handle it (if enabled).
We wrote that application using JavaScript back then. After several years I learned TypeScript and realized, how to solve all these problems.
I like the API of the t function (path to a localization string and a bunch of variables to "hydrate" it), so I decided to copy it in a standalone package, that is known as "hydrate‑text". Now let's implement our own i18n solution with it.
Define supported languages and a default one
Define a "dictionary" (a structure, that will be used to type checking our localization resources)
as const expressions require all the resources to strictly follow the structure.
Note: it can be hard to fill in these strings manually, so I just assign empty strings to them and pass the whole structure to this function below.
Define types for the resources
Define the resources (I called them dictionaries for consistency) for the supported languages
Create i18n context, context provider and React Hook
Wrap the application in the context provider
Use the React Hook to get the translation function
The path to a localization string is not a plain string now, but a structure, that won't let you make a typo.
The described approach adds ~8.3 KiB to the bundle (the heaviest part is get function from Lodash, that takes ~6.8 KiB). It is a way smaller than the previous one, and it is fully customizable on any level. Check out "Measuring application sizes" section below for more information.
Dictionary type guarantees, that all resources (dictionaries) have the same structure.
In some cases, it is necessary to provide React Router routes with variables, like this:
To achieve this, an ability to replace default variable markers was added. In the source code it is called "interpolation options" (this name was taken from i18next"Interpolation" page):
If it is necessary to do this in several places, it is better to use another function from hydrate‑text - configureHydrateText, which will return hydrateText function bound to the chosen variable markers:
The markers still can be changed via the third argument, but I can hardly imagine, when it can be useful 🙂
Later on I found a built-in function generatePath, but the examples above are still valid as an illustration of the variable markers changing flexibility.