Better Programming

Advice for programmers.

Lightweight and Type-Safe Localisation in React

Marten Gartner
Better Programming
Published in
4 min readApr 4, 2022

--

Image by Dmitry Ratushny from unsplash

Localisation (or multi-language support) is a core part of nearly all production-ready apps. Mostly, it’s a time-consuming task to keep all translations up to date and it may even stop your coding flow if you need to keep translations up to date while writing code.

In my experience, I often ran into typos in language files and untranslated strings while referring to a missing language key. I observed that it’s too easy to mix keys or to add duplicates.

Therefore, I implemented a lightweight, type-safe localisation for React.
It uses the React context API and hooks to make the usage inside functional components easy and forces translations and key lookups to be type-safe using Typescript. If you’re interested, let’s dive into the implementation!

Implement Localisation in a Context

First, let’s look at the actual store of the current locale selected by the user. I decided to save this information in a React context because it should be accessible in the whole app, and there are fewer changes in the current locale expected, so no fear of performance issues.

The selected locale is saved in the Localstorage, and loaded when the context first mounts, to keep the user’s decisions. If no locale could be loaded from the Localstorage, the default one is used (in my case mostly English). In the Localstorage, only the localeId (e.g., en_US) is stored instead of the whole locale.

The context file exports a Provider which we put around our whole example app. In this context, I provide the available locales, the current locale, and a function to set the current locale to be accessed in the components.

To inject the LocaleContextProvider into any position in the React component tree, props.children are simply set as React children and are therefore passed through. The whole context is shown in the following gist:

That’s it for the context side, now let’s switch to the language resources.

The Language Files

As language files, we use simple.ts files which export a JS object containing lookup keys and their translations. For each locale, one file is added that exports the particular translation object. For typing performance, I experienced that autocompletion improves significantly if all your lang keys start with a prefix, e.g., lang. That worked quite well for me and makes it easy to differentiate translations from other variables.

Add type-safety

Since I’m using JS objects for our translation maps, the implementation can benefit from enabling type checking via Typescript. To avoid creating a dedicated type that contains all keys required for a translation to be complete (trust me, I did this, it’s annoying…), I use the typeof operator to get the type of our default, English translation, and force all other translations to have the same type.

In this case, this means that all keys of the default translation need to be available in the other translation. Consequently, this leads to proper syntax highlighting and build errors for missing or wrong translation keys.

Usage in apps

After implementing the translation and localisation, the last step is to include the translations in our app. Therefore, I have defined some useful hooks that can be used in every component (inside the component tree of the LocaleContext). The following hooks are available in my default implementation and can be extended easily:

  • useLanguage: Returns the translation object of the current locale
  • useLocales: Returns the available locales
  • useSetLocale: Returns a function that sets a new locale

In the following example, the usage of all these three hooks is depicted. Via a button click, the app iterates through all available locales and changes the current locale accordingly. The translations can be used as normal variables of type string in any jsx or other function logic.

Here comes the main clue of using Typescript: you can’t do any typos in the language keys, since the translation is typed and only allows to access existing keys, and all translations need to be complete, so there are no half-baked translations available.

Summary

In this story, I want to share my implementation of a lightweight, type-safe localisation with React contexts and hooks. The implementation allows easy use of translated strings, forces translations to be complete (otherwise typescript will blame you), and removes the possibility of typos while using translations. Even if the setup for translation seems to be comparatively complex, the benefits worked for me quite well.

The whole code is available on stackblitz.

I hope you enjoyed this small dive into my approach to localisation in
React.

Please feel free to share any feedback, I would appreciate it and update the story accordingly.

--

--

Marten Gartner
Marten Gartner

Written by Marten Gartner

I’m a dad, software developer and researcher on network performance. Writing about high-speed frontend-dev, neworking, productivity and homeoffice with kids.