A Better Approach to Translating JS Apps
Tags: javascript, react, productivity, node
Published: 2022-07-29 11:36:15


**Summary**: Using `@formatjs/intl` and [git18n.com](https://git18n.com) for internationalization outperforms and saves time and effort compared with the commonly used approach that keeps everything in `.json` locale files. [NextJS implementation example](https://github.com/lassegit/next-app-git18n). 

---

Often when developers think about internalization, they (consciously or subconsciously) associate it with large and unmaintainable `.json` locale files containing translations. And this isn't without reason, most translation management tools are over-focused on using `.json` locale files for translations. This approach can be exemplified using the popular and quite fragmented `i18next` internationalization framework. 

An short excerpt form one of their many framework specific implementations [next-i18next](https://github.com/i18next/next-i18next). First and foremost it has the `.json` locale files:

```js
└── public
  └── locales
    ├── en
    |   └── common.json
    └── de
        └── common.json
```

And the corresponding code snippet that uses the `.json` locale files:

```js
import { useTranslation } from 'next-i18next';

export const Footer = () => {
  const { t } = useTranslation('footer');

  return (
    <footer>
      <p>{t('description')}</p>
    </footer>
  );
};
```

Side note, this requires a bit more configuration like wrapping the root app in the translation provider, which is quite common. But it also requires all "pages" to import specific translations via `serverSideTranslations`, which adds additional code and complexity to what ideally should be a simple internalization. 

This structure is perhaps technical logically, but the actual use is strenuous for developers and stakeholders alike (usually other team members provide the translations):

- `.json` locale files almost instantly becomes unmaintainable due to sheer size and the effort required to keep them up to date
- beyond the translation key, developers can only guess what `t('description')` returns and in situations with multiple related keys, this means constantly looking up keys in the `.json` locale files
- `.json` locale files ensure constant merge conflicts in developers teams
- hard to determine if one `.json` locale file is missing a translation as compared to others (faulty translations live long lives in `.json` locale files)

These are some of the drawbacks of doing internationalization with the `.json` locale file approach. It is obliviously time consuming for the developers. But the productivity killer expands outside of the developer realm and affects translators too. Often other team members are responsible for authoring the translations used. This is often done constructing large spreadsheets containing the translations and mapping it to an id key and/or page in the app. This is obliviously time consuming, error prone and creates multiple sources of truths. 

Not to mention, that whenever a stakeholder want to change a translation (even a simple misspelling) the developer is forced to be involved. Not only does this cause translation rigidity but also time and effort. 

## Internationalization without `.json` locale files

If you aren't already convinced about the benefits of not using a `.json` locale file centric approach, the ease of use and translating can perhaps convince. 

This approach requires 2 packages: the core internationalization library from [@formatjs/intl](https://formatjs.io/docs/intl) and the simple translation management utility [git18n](https://git18n.com/).

With this approach the above example would look like:

```js
import Intl from '../i18n'; // Singleton that can be used backend and frontend (doesn't get simpler)

export const Footer = () => (
  <footer>
    <p>
      {Intl.t.formatMessage({
        id: 'footer.description',
        defaultMessage: 'This is the translation for the defaultLocale.',
      })}
    </p>
  </footer>
);
```

The `Intl` class is a singleton that returns the [IntlShape object](https://formatjs.io/docs/intl#intlshape) containing all the formatters requires for date, number and text internationalization. No reason to being forced to jump between `t()` and `<Trans />` component as with `i18next`. 

Side note an [NextJS implementation example is available](https://github.com/lassegit/next-app-git18n). Other branches in the repository contains `react-intl` and `i18next` implementation examples.

This approach gives the developer both the `id` indicating feature domain and specific purpose (i.e. "description") and the `defaultMessage` containing the actual inline translation for the `defaultLocale`. This already eliminated an entire `.json` locale file for the `defaultLocale` and increases developer experience with directly understandable inline translation.

In this way the internalization is abstracted away, meaning the developer can focus on developing and simply add the `defaultMessage`. The rest is added through [git18n.com](https://git18n.com/). This benefits of this are big:

- stakeholders can change translations (even `defaultLocale` can be overwritten) without having to write a ticket with what should be changed, finding and updating the correct key in the `.json` locale files, creating a pull request and getting it reviewed (watch out, high risk of merge conflicts with `.json` locale files) and then deploying
- ever growing and unmaintainable `.json` locale files are eliminated
- [git18n.com](https://git18n.com/) functions as the source of truth, forget multiple spreadsheet, tickets with translations and `.json` locale files

In short, this massively pays of in time and effort (from feedback, I reckon likely to be counted in days rather than hours). The ease of updating translations makes it trivial to update a misspelling, an incorrect translation or improve the wording based on customer feedback or a particular marketing campaign. This makes translations "first-class citizen" in the app, as it should be.

## Short remark on git18n.com

Git18n.com is intentional simple by design and functionality. It is a translation management utility that does 2 things:

- a CLI script that effortless extracts text for translation
- simple web app to translate extracted text 

Usage is visualized on the flow chart below:

![git18n usage on flow chart](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ap0fpth4rqrkq84r1z5h.png)

The CLI script is [available on NPM](https://www.npmjs.com/package/git18n).

In later posts I will dig more into usage, implementation and comparisons with other internalization libraries (any suggestions?). If you have any suggestions, feedback or questions, please let me know in the comment box below. 

And if you are curious, please sign up on [git18n.com](https://git18n.com/) and try for yourself.

## Links

- [git18n.com](https://git18n.com/)
- [git18n NPM package](https://www.npmjs.com/package/git18n)
- [git18n docs](https://docs.git18n.com/) (note: I will update with implementation examples to feature the singleton as recommended approach)
- [NextJS implementation using singleton pattern](https://github.com/lassegit/next-app-git18n)
- [Create React App implemention using react-intl](https://github.com/lassegit/git18n-examples/tree/master/create-react-app)
- [Documentation for @formatjs/intl](https://formatjs.io/docs/intl)
- [Performance comparison](https://www.webpagetest.org/video/compare.php?tests=220729_AiDcWS_79Z,220729_BiDcEG_78N,220729_BiDc31_78P) (`i18next` significantly outperformed)
- jacqueline brandwayn, [cover photo credit](https://unsplash.com/photos/S8MSj5VzHxQ)