A new way to handle translations in React based applications
Tags: react, reactnative, javascript, tutorial
Published: 2021-10-23 11:15:55

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](https://github.com/i18next/i18next), [i18n-js](https://github.com/fnando/i18n-js)), the approach used to access translations is as follows: 

```javascript
console.log(translationsObject.get('translations_key'));
```

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: 
- [**text-localizer**](https://www.npmjs.com/package/text-localizer); 
- [**react-text-localizer**](https://www.npmjs.com/package/react-text-localizer); 
- [**react-native-text-localizer**](https://www.npmjs.com/package/react-native-text-localizer).

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?**

<img width="100%" style="width:100%" src="https://media.giphy.com/media/cjqfY30DzIgPxkxemU/giphy.gif">

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](https://enzomanuelmangano.github.io/text-localizer/docs/api-reference/text-localizer) section of the [documentation](https://enzomanuelmangano.github.io/text-localizer/).

## GitHub Repo 

The approach used to create all of the packages was the monorepo approach. [Here you can find the Repo](https://github.com/enzomanuelmangano/text-localizer). 

## 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**](https://enzomanuelmangano.github.io/text-localizer/) (*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:

```shell
yarn add react-text-localizer
```

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

```json 
// 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?"
}
```

Note: _"help"_ contains a formatted string, that can be easily managed with the [formatTranslation](https://enzomanuelmangano.github.io/text-localizer/docs/api-reference/text-localizer#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.

```ts
// filename: src/l10n/gb.ts

import us from './us.json';

export default {
  ...us,
  question: 'Which is your favourite cat?',
};
```

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

```ts 
// 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 };
```

### Folder Structure

The project will have the following folder structure:

```
.
├── ...
├── _src
│   ├── _l10n
│   │   ├── us.json
│   │   ├── gb.ts
│   │   └── it.ts
│   ├── ...
│   └── App.tsx
└── package.json
```

## 4. Context Definition 

Once the translation files have been set up, a [Translations Context](https://enzomanuelmangano.github.io/text-localizer/docs/api-reference/react/create-translations-context) must be created.

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

```ts 
// 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 };
```

### Generated Types [ Optional ]

**The use of Generated Types is fully optional. [Check the following section to learn more about it.](https://enzomanuelmangano.github.io/text-localizer/docs/basic-tutorial/js-ts/more/generated-types)**

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

```shell
npm run generate-l10n [translationsPath]
```

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

```ts
// 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 };
```

## 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](https://enzomanuelmangano.github.io/text-localizer/docs/basic-tutorial/react/translations-provider) in this way:

```tsx 
// 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')
);
```

## 6. Usage

Finally, thanks to the following hooks:

- [useTranslationsState](https://enzomanuelmangano.github.io/text-localizer/docs/api-reference/react/hooks/use-translations-state)
- [useTranslations](https://enzomanuelmangano.github.io/text-localizer/docs/api-reference/react/hooks/use-translations)

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

```tsx 
// 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;
```

## 7. Storage [ Optional ]

If your translations lives on the backend, you can pass **optionally** the [Storage params](/docs/basic-tutorial/js-ts/more/cached-translations) in the [TranslationsProvider](https://enzomanuelmangano.github.io/text-localizer/docs/basic-tutorial/js-ts/more/cached-translations):

```tsx 
// 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')
);
```


## 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](https://github.com/enzomanuelmangano/text-localizer).