Lightweight and Type-Safe Localisation in React
Using React Context API and Hooks
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 localeuseLocales
: Returns the available localesuseSetLocale
: 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.