DEV Community

Cover image for A new way to handle translations in React based applications
Enzo Manuel Mangano
Enzo Manuel Mangano

Posted on • Edited on

A new way to handle translations in React based applications

I have been working on many Javascript/Typescript projects over the past few years. Especially on React Native and React. For these projects, strings localization is always a given. However what I never understood is the standard way to handle translations in Javascript applications.

In most packages (i18next, i18n-js), the approach used to access translations is as follows:

console.log(translationsObject.get('translations_key'));
Enter fullscreen mode Exit fullscreen mode

The main problems with this approach are that:

  • There is no effective compile-time support to catch errors if the key is wrong;
  • There is no way of knowing whether the key being accessed is set for all languages;
  • There is no warning if you try to access an unsupported locale;
  • The refactoring of translation keys can easily lead to problems that cannot be intercepted at compile-time.

These issues are what motivated me to create a set of npm packages:

The main goals I set for myself when creating the main package were four:

  • It must be TypeSafe;
  • It must not increase the size of the bundle;
  • It must not impact the start-up time in any way;
  • It must adapt to several use cases without affecting the architecture of the project.

The Result?

The first obvious benefit is that you finally have a decent developer experience.

Disclaimer: The package is ready to be used but it may not have all the features you expect since it has been published very recently (I have planned the implementation of all the missing features). You can find all the features of the package in the API Reference section of the documentation.

GitHub Repo

The approach used to create all of the packages was the monorepo approach. Here you can find the Repo.

How to use it?

In this section I will explain how to quickly setup the react-text-localizer package. But if you're also interested in the other use cases or if you prefer a more complete explanation, take a look at the documentation (don't be scared, I did my best to make it easy for anyone to understand).

1. Installation

In order to unlock the Text Localizer features, simply run:

yarn add react-text-localizer
Enter fullscreen mode Exit fullscreen mode

2. Strings

Now you need to create the files where the translations will be stored.
Therefore create in the src directory the l10n folder and add the translation files. In this tutorial we're going to use these files:

  • us.json;
  • gb.ts;
  • it.ts.

JSON

The use of JSON files to store translations is certainly the most common and the simplest.

// filename: src/l10n/us.json 

{
  "welcome": "Welcome on the Text Localizer Docs",
  "help": "Do you need some help about {{ topic }}?",
  "question": "Which is your favorite cat?"
}
Enter fullscreen mode Exit fullscreen mode

Note: "help" contains a formatted string, that can be easily managed with the formatTranslation function.

Javascript or Typescript

Although the JSON is by far the most widely used solution in javascript for saving translation files, there are many use cases where using a JS or TS file can be just as useful.

For example, in cases where only a few strings differ, using a typescript file can be extremely useful.

// filename: src/l10n/gb.ts

import us from './us.json';

export default {
  ...us,
  question: 'Which is your favourite cat?',
};
Enter fullscreen mode Exit fullscreen mode

What if your strings lives on the backend?

With Text Localizer, it is simply a matter of creating a js/ts file and exporting the function with which the translations for that country are fetched.

// filename: src/l10n/it.ts 

// In this example fetchItTranslations simply returns a plain object.
// The point is that it could be any asynchronous function
// where maybe using axios, apollo or any other client.
const fetchItTranslations = async () => ({
  welcome: 'Benvenuto sulla documentazione di Text Localizer',
  help: 'Hai bisogno di aiuto riguardo {{ topic }}?',
  question: 'Qual è il tuo gatto preferito?',
});

export { fetchItTranslations };
Enter fullscreen mode Exit fullscreen mode

 Folder Structure

The project will have the following folder structure:

.
├── ...
├── _src
│   ├── _l10n
│   │   ├── us.json
│   │   ├── gb.ts
│   │   └── it.ts
│   ├── ...
│   └── App.tsx
└── package.json
Enter fullscreen mode Exit fullscreen mode

4. Context Definition

Once the translation files have been set up, a Translations Context must be created.

Then in the l10n folder create an index.ts file with this code.

// filename: src/l10n/index.ts

import { createTranslationsContext } from 'react-text-localizer';
import { fetchItTranslations } from './it';

const translationsContext = createTranslationsContext({
  it: fetchItTranslations,
  gb: import('./l10n/gb'),
  us: import('./l10n/us.json'),
});

export { translationsContext };
Enter fullscreen mode Exit fullscreen mode

Generated Types [ Optional ]

The use of Generated Types is fully optional. Check the following section to learn more about it.

If you want to use a Generated Types approach, you can do so by simply running the following command (Note: if the script fails, make sure that the "module" specified in the tsconfig.json file is "CommonJS" (at least while the script is running) :

npm run generate-l10n [translationsPath]
Enter fullscreen mode Exit fullscreen mode

Once the translations.d.ts is fully created, you can pass the Languages and AppTranslations types to the TextLocalizer in the Context Definition:

// filename: src/l10n/index.ts

import { createTranslationsContext } from 'react-text-localizer';
import { fetchItTranslations } from './it';

const translationsContext = createTranslationsContext<
  Languages,
  AppTranslations
>({
  it: fetchItTranslations,
  gb: import('./l10n/gb'),
  us: import('./l10n/us.json'),
});

export { translationsContext };
Enter fullscreen mode Exit fullscreen mode

5. Translations Provider

In order to make the strings accessible throughout the component tree, it is necessary to wrap the application's root component with the TranslationsProvider in this way:

// filename: src/index.tsx

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { TranslationsProvider } from 'react-text-localizer';
import { translationsContext } from './l10n';

ReactDOM.render(
  <React.StrictMode>
    <TranslationsProvider context={translationsContext} language="en">
      <App />
    </TranslationsProvider>
  </React.StrictMode>,
  document.getElementById('root')
);
Enter fullscreen mode Exit fullscreen mode

6. Usage

Finally, thanks to the following hooks:

It's possible to access the translations' state and the translations themselves respectively.

// filename: src/App.tsx

import { useTranslations, useTranslationsState } from 'react-text-localizer';
import './App.css';
import { translationsContext } from './l10n';

function App() {
  const { welcome } = useTranslations(translationsContext);
  const { isLoading } = useTranslationsState(translationsContext);

  if (isLoading) return null;

  return (
    <div className="App">
      <header className="App-header">
        <p>
          Edit <code>src/App.tsx</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://enzomanuelmangano.github.io/text-localizer/"
          target="_blank"
          rel="noopener noreferrer"
        >
          {welcome}
        </a>
      </header>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

7. Storage [ Optional ]

If your translations lives on the backend, you can pass optionally the Storage params in the TranslationsProvider:

// filename: src/index.tsx

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { TranslationsProvider } from 'react-text-localizer';
import { translationsContext } from './l10n';

ReactDOM.render(
  <React.StrictMode>
    <TranslationsProvider
      context={translationsContext}
      language="gb"
      storage={localStorage}
    >
      <App />
    </TranslationsProvider>
  </React.StrictMode>,
  document.getElementById('root')
);
Enter fullscreen mode Exit fullscreen mode

Conclusion

The package makes it very easy to handle translation strings. Unfortunately, there are some features missing in the package (which I plan to add soon) such as the management of plurals.
I decided to make the package public and share it with the community as soon as possible and I took this decision to receive as much feedback as possible. If you have feedback, feel free to leave any feedback you want in the comments section.

P.S: If you think the project is interesting, consider leaving a GitHub star here.

Top comments (2)

Collapse
 
iamludal profile image
Ludal 🚀

Definitely going to star this project and stay tuned for future releases! 🙌🚀

Collapse
 
enzomanuelmangano profile image
Enzo Manuel Mangano

Thank you so much for your support! 🚀