Have you ever had problem with translating an React or React Native application, and always end up with problems with keys, adding extra pipes, hardcoded translations? Get in this tutorial with me and we will solve this problem for once.
The significance of translations appears to be overlooked, with the second language often treated as an afterthought in my projects; some even begin with non-English strings hardcoded in templates, only to encounter challenges later when attempting to modify them, as the lack of type safety prevents immediate detection of all occurrences, resulting in an inconsistent user experience where English words may unexpectedly appear in a Spanish interface, negatively impacting the brand's reputation.
The i18n module, while initially a convenient tool for handling translations in React and React Native apps, often falls short in several key areas. One prevalent issue is the prevalence of hardcoded strings within templates, making it challenging to maintain and update translations efficiently. Additionally, the lack of IntelliSense support hampers developers' productivity, leading to potential errors and inconsistencies in translations. Moreover, the module's limited capabilities for managing different languages result in cumbersome workflows and difficulties in maintaining language-specific content.
Another significant drawback is the cumbersome process of storing translations in separate JSON files, which can lead to cluttered project structures and version control issues. These challenges collectively contribute to a suboptimal translation experience, urging developers to seek alternative solutions for their localization needs.
In fact, doing a bullet proof translation mechanism for React and React Native projects does not need another library at all. In this tutorial we are going to solve this with a single hook:
useS
hook to use a string object.useLocale
hook to keep locale settings, and also detect locale based on
the user OS settings or browser settings.You can apply what you learn here to new projects, as well as your existing project
Install fireback v1.1.9 or later. You can find the binaries for different systems here: https://github.com/torabian/fireback/releases for installers binary, and you can place it inside your project folder and ignore it.
I use the VSCode, and "Run On Save extension" is enabled. You can run the translation from CLI as well, but having this extension makes life easier by far.
You need to make sure you have access to fireback binary, either installed globally, or you put it in the project directory, and ignored the file.
Fireback also could be installed on npm using `npm i -D fireback-tools'. Make sure you install it as dev dependency, so it's binaries do not bundle with react native apps.
export function useLocale() {
const [locale, setLocale] = useState({
lang: 'en',
region: 'us',
})
const dir = locale.lang === 'fa' || locale.lang === 'ar' ? 'rtl' : 'ltr'
// Here you can implement the rest of the necessary functionality
// for detecting the locale, or for example, extra function to set
// only the language or so.
return {lang, region, dir}
}
import { useLocale } from "./useLocale";
// This typescript generic is important, to use strongly typed translation
// keys
export function useS<T>(v: T): T {
/*
* This could be your own definition of the locale language
* This variable usually should include the current language in the app
* such as 'en', or 'fa', etc.
* you might also extend it to separate the language-locale such as en-us
* i haven't done yet due to no need
*/
const { lang } = useLocale();
/*
* Here we check if there lang is anything other than en, and in translation
* files we have such thing with a $ prefix - which auto generated by fireback
* we will access them instead. useS function will return always T,
* so all the translations will be typesafe anyway from typescript compiler
* perspective
*/
if (!lang || lang === "en") {
return v;
} else if (!v["$" + lang]) {
return v;
} else {
return v["$" + lang];
}
}
How you organize the translations is up to you, in fact. I prefer to create a strings
folder per module and place all of their translations into that directory.
This way, when I move a module folder to another project or if I want to make
it a library, I won't have to worry about the translations; they're already
solved and attached to the component or module.
On the other hand, if you want to create a strings
folder for the entire app,
nothing would stop you.
It's critical to keep in mind that the Fireback Language Editor module assumes English as the primary language of the app. Simply put, English must be present, and other languages will be synced with keys from that one.
Now, let's put the content
inside the YAML.
All translation keys must go under the content
key,
otherwise they will not be considered translations.
If you are not familiar with YAML, think of it as JSON without quotes, and remember:
content:
loading: Loading...
done: Done :)
Is different from the code below. (Basically intention is super important)
content:
loading: Loading...
done: Done :)
So far, this was all the necessary steps we needed to take for translating our app.
Now we need to use the Fireback language generator to simplify key generation for us.
fireback gen strings --path ./src/strings/strings-en.yml --langs pl,fa
This command will generate two additional files in the same directory:
strings-pl.yml
and strings-fa.yml
. If you open them, you will see identical
content to your strings-en.yml
.
Also in the same directory, you will find a translations.ts
file.
It contains content similar to the snippet below, which has generated TypeScript
constants for all three languages and exported them as strings
.
Using the CLI each time for building the translations is cumbersome. Therefore, we utilize the "Run on save" extension in VSCode, which you can install.
Then, in our settings.json
in VSCode, we need to add these rules.
(If you've never modified this file, create a folder named .vscode
at the root
of your project and add settings.json
inside it.)
{
// other configuration before
"emeraldwalk.runonsave": {
"commands": [
{
"match": "strings-([a-z][a-z]).yml$",
"cmd": "fireback gen strings --path ${file} --langs en,fa"
}
]
}
// other configuration after
}
It would basically detect any strings-xx.yml
change in the project, and will run strings
compiler for you.
At this stage, the tarnslation dictionary has been created fully, and we can
use them by useS
and access the keys on the s
object. It's good to keep
the names consistent across the entire project to make it clean.
React.js:
import { strings } from "./strings/translations";
export function MyComponent() {
const s = useS(strings)
return <p>{s.loading}</p>
}
React Native:
import { Text } from 'react-native';
import { strings } from "./strings/translations";
export function MyComponent() {
const s = useS(strings)
return <Text>{s.loading}</Text>
}
To summarize, we have just created a robust system for adding translations to our react or react native app. This method will help prevent many errors, such as missing keys in certain languages and hard-coded strings for translation.
The Fireback Strings CLI offers additional options that you may want to explore on your own. This feature is available starting from Fireback v1.1.9