Adding i18n to the Fuel Tracker App
Since the beginning when I started building the Fuel Tracker App I knew I'd add translations some day. The time has come and I'll share how I did it.
First thing we'll do is install what we need, and in this case is i18next
and react-i18next
.
npm i i18next react-i18next
Next up is adding the i18n.ts file to the root directory.
import i18next from "i18next";
import { initReactI18next } from "react-i18next";
import englishTranslations from "./src/client/translations/en/translations.json";
import spanishTranslations from "./src/client/translations/es/translations.json";
import frenchTranslations from "./src/client/translations/fr/translations.json";
const resources = {
en: {
translation: englishTranslations,
},
es: {
translation: spanishTranslations,
},
fr: {
translation: frenchTranslations,
},
};
i18next.use(initReactI18next).init({
resources,
lng: "en",
fallbackLng: "en",
interpolation: {
escapeValue: false,
},
});
In this case we import what is needed, including the translation files (we'll see them now), create the resources to pass to i18next and use it. The code is very self explanatory, the only thing that is maybe confusing is the interpolation.
In i18next, interpolation allows you to insert dynamic values into your translation strings.For example, if you have a translation like this:
{
"welcome": "Welcome, {{name}}!"
}
You can pass a value for name
:
i18next.t('welcome', { name: 'John' });
This will output: Welcome, John!
.
The interpolation
option in i18next controls how these dynamic values are handled.
In this case, setting escapeValue: false
prevents automatic escaping of values, which is useful in environments like React where escaping is already handled.
When I refer to automatic escaping, I mean the process of converting potentially unsafe characters (such as <
, >
, &
, etc.) into their safe HTML equivalents (<
, >
, &
, etc.) to prevent security vulnerabilities, such as Cross-Site Scripting (XSS) attacks.
The translation files are really easy to create manually, but it's even more easy if you use a tool like poeditor to handle all your translations.
I'll be writing a post on how to use it for your projects in the near future (but I can tell you it's an easy tool to use).
Once we have all the translation files, we create a translations folder and place it inside our src folder.
I like to create folders for every language (en, es, fr, etc...) and place the translations.json (you can name it the way you like) inside it. Maybe you want to be more specific with the translations and have different names for the same language (global.json, settings.json, etc...).
This is how the translations file looks:
{
"global.accept": "Accept",
"global.account-info": "Account Information",
"global.account-settings": "Account Settings",
...
}
With this done, there's something we cannot forget, and that is to import the i18n file into our App.tsx
. We'll need to add import "../../i18n";
with all the other imports at the top.
Now we'll add a language selector to our navigation bar.
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { Select } from "@mantine/core";
import { Globe } from "@phosphor-icons/react";
const languages = [
{ value: "en", label: "English" },
{ value: "es", label: "Español" },
{ value: "fr", label: "Français" },
];
const LanguageSwitcher = () => {
const { i18n } = useTranslation();
useEffect(() => {
const savedLanguage = localStorage.getItem("language");
if (savedLanguage) {
i18n.changeLanguage(savedLanguage);
}
}, [i18n]);
const handleLanguageChange = (value: string | null) => {
if (value) {
i18n.changeLanguage(value);
localStorage.setItem("language", value);
}
};
return (
<Select
size="xs"
w={120}
value={i18n.language}
onChange={handleLanguageChange}
data={languages}
leftSection={<Globe size={14} />}
styles={(theme) => ({
input: {
"&:focus": {
borderColor: theme.colors.indigo[5],
},
},
})}
/>
);
};
export default LanguageSwitcher;
There are a few things happening here:
First of all, we create the array of languages to pass to the Select
component and invoke i18n.
Second, check if there's any saved language in localStorage
and set it if there is.
Third, the function to handle the language change, which will also change the language set in localStorage
.
Fourth and last, the Select
component (you can style it the way you want to fit your app or whatever 😜).
It looks like this:
Now that i18n is configured, it's time for the last step which is adding the translations to all the files!
We do this by simply declaring the t variable like this:
const { t } = useTranslation();
Once done, we simply add the translations in this format:
return (
<Container size={"lg"} my={"xl"}>
<Flex my={"xl"} align={"center"} direction={"column"} wrap={"wrap"}>
<Title>{t("global.my-garage")}</Title> ⬅️
...
</Flex>
</Container>
I hope you liked!
Salut, Jordi.
Subscribe to my newsletter
Read articles from Jordi Ollé Ballesté directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Jordi Ollé Ballesté
Jordi Ollé Ballesté
Always evolving Full-Stack Developer 👨🏼💻 Mountain addict 🗻