Why I wrote "hydrate‑text" library

Internationalization with i18next

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:

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.

Link to full source code.

The problems

The whole approach looks reasonable, but there are some problems with it:

We wrote that application using JavaScript back then. After several years I learned TypeScript and realized, how to solve all these problems.

The solution

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.

Link to full source code.

Now all the problems are solved:

Keeping text organized

Even if localization is not necessary, I believe it is still a good idea to keep text organized. In this case, the following approach can be used:

Replacing route variables

In some cases, it is necessary to provide React Router routes with variables, like this:

Language
TypeScript
// Given
"/posts/:id";

// Needed (for example, `id` is 10)
"/posts/10";

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

Language
TypeScript
// "/posts/10"
hydrateText(
	"/posts/:id",
	{ id: 10 },
	{
		prefix: ":",
		suffix: "",
	},
);

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:

Language
TypeScript
const hydrateRoute = configureHydrateText({
	prefix: ":",
	suffix: "",
});

// "/posts/10"
hydrateRoute("/posts/:id", { id: 10 });

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.

Measuring application sizes

i18next approach

In each case, check console results and sum up dist/assets/index.<hash>.js and dist/assets/vendor.<hash>.js sizes.

  1. Build the application as is: 1.34 KiB + 181.54 KiB.
  2. Replace <App /> by null, comment out i18n and App imports and build the application: 0.14 KiB + 127.59 KiB.

The sizes were double-checked with filesize VS Code extension.

We miss withTranslation and useTranslation import costs, but I don't think it drastically changes the picture.

hydrate‑text approach

In each case, check console results and sum up dist/assets/index.<hash>.js and dist/assets/vendor.<hash>.js sizes.

  1. Build the application as is: 2.16 KiB + 133.91 KiB.
  2. Replace get(dictionary, pathOrText, pathOrText) by pathOrText, comment out get import and build the application: 1.25 KiB + 128.01 KiB.
  3. Replace <I18nProvider><App /></I18nProvider> by null, comment out I18nProvider and App imports and build the application: 0.14 KiB + 127.59 KiB.

The sizes were double-checked with filesize VS Code extension.