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'));
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
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?"
}
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?',
};
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 };
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 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 };
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]
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 };
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')
);
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;
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')
);
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)
Definitely going to star this project and stay tuned for future releases! 🙌🚀
Thank you so much for your support! 🚀