Setting up Internationalization in React using react-18next 🇺🇸🇫🇷

Setting up Internationalization in React using react-18next 🇺🇸🇫🇷

Nowadays, websites and applications can be accessed from different parts and regions of the world. So, it is very essential to make your website easily accessible to anyone regardless of their location.

In this article, I’ll share the importance of internationalization and how to set up internationalization in a ReactJS application.

Prerequisites

This tutorial assumes the reader has the following:

  1. Node installed on their local machine
  2. yarn or npm installed on their local machine (npm comes pre-installed with node)
  3. Basic knowledge of HTML, CSS, Javascript and React.

What is Internationalization?

Before we proceed, let’s first understand the meaning of Internationalization!

According to Wikipedia, Internationalization is the process of designing a software application so that it can be adapted to various languages and regions without engineering changes.

Basically, Internationalization is the process of making your website or applications usable by all people irrespective of the country or region they find themselves. There are so many benefits of internationalization, but here are some of my favourites:

  • It helps in customer and user retention, which leads to an increase in overall customer satisfaction.
  • It saves time and cost as you don’t have to develop a separate website for people in separate regions.
  • It makes maintenance of the website easier.

Getting Started

For this tutorial, I’ve created a simple login page which we’ll be using to set up internationalization in our app. You can find and clone the project to your machine using this Github Link.

There are so many tools out there that can be used to set up internationalization, but we’ll be using react-18next due to its ease of setup and the vast amount of plugins that can be integrated into it.

Installing Dependencies

Let’s start by installing react-18next and a plugin in our project using the command below:

yarn add react-i18next i18next i18next-browser-languagedetector

I’ll be explaining what the plugin is for in the coming section.

Setting up react-i18next

After the installation is completed, we need to set up some configurations for react-18next before we can begin using it. For that, create a file named i18n.js in your src directory and paste the code snippet below:

// i18n.js

import i18n from "i18next";
import { initReactI18next } from "react-i18next";

i18n
  .use(initReactI18next) // passes i18n down to react-i18next
  .init({
    resources: {},
    lng: "en",
    keySeparator: ".",
  });

export default i18n;

Here, we imported the i18n instance and initReactI18next from i18next and react-i18next respectively. We then went on to initialize the instance by adding other parameters needed for configuration.

Next, we need to create a new directory called locales, this directory would contain all our translations for different regions or languages that our application uses. For this tutorial, we’ll be sticking with two languages, mainly English and French. The locales directory would contain the translation files for our respective locale like so:-

Adding our locales

Paste the code snippet below into the English locale

{
    "login": {
        "heading": "Login to your account",
        "submit": "Login",
        "input": {
            "email": "Email",
            "emailPlaceholder": "Email Placeholder",
            "password": "Password",
            "passwordPlaceholder": "Password Placeholder",
            "toggle_password": "Toggle Password"
        },
        "errors": {
            "email": "Email is required",
            "password": "Password is required",
            "passwordLength": "Password length should be at least 6 characters"
        },
        "forgot_password": "Forgot Password"
    }
}

Paste the code snippet below into the French locale

{
    "login": {
        "heading": "Connectez-vous à votre compte",
        "submit": "connexion",
        "input": {
            "email": "E-mail",
            "emailPlaceholder": "Espace réservé d'e-mail",
            "password": "Mot de passe",
            "passwordPlaceholder": "Espace réservé pour le mot de passe",
            "toggle_password": "basculer le mot de passe"
        },
        "errors": {
            "email": "L'e-mail est requis",
            "password": "Mot de passe requis",
            "passwordLength": "La longueur du mot de passe doit être d'au moins 6 caractères"
        },
        "forgot_password": "Mot de passe oublié"
    }
}

The translations are represented in a key-value pair which allows us to manage the text for different parts of our app in an efficient manner. The keys would later be referenced when we want to set up dynamic translations in our components.

Now that we have the translations for our locales, we need to import them inside our i18n.js file and include it in the resources property like this:

import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import EN_TRANSLATION from "./locales/en/translation.json";
import FR_TRANSLATION from "./locales/fr/translation.json";

i18n
  .use(initReactI18next)
  .init({
    resources: {
      en: {
        translation: EN_TRANSLATION,
      },
      fr: {
        translation: FR_TRANSLATION,
      },
    },
    lng: "en",
    keySeparator: ".",
  });

export default i18n;

Now that we have our i18n.js file set up, we need to import the i18n instance we just configured in app.js so that it can get bundled during compilation. Import i18n.js below in your app.js file like this:

import {
  Center,
  ChakraProvider,
  Flex,
  Heading,
  Select,
} from "@chakra-ui/react";
import Login from "./components/login";
import "./i18n";

function App() {
  return (
    <ChakraProvider>
      <Flex
        as="nav"
        mx="5rem"
        height="80px"
        justifyContent="space-between"
        alignItems="center"
      >
        <Heading as="h4" size="md">
          Logo
        </Heading>
        <Select
          width="150px"
          defaultValue={i18n.language}
          onChange={handleSelectChange}
        >
          <option value="en">English</option>
          <option value="fr">French</option>
        </Select>
      </Flex>
      <Center height="calc(100vh - 80px)">
        <Login />
      </Center>
    </ChakraProvider>
  );
}

export default App;

Save the changes and view them in the browser.

Using the translations in our components

Now that our app works and we’ve set up react-i18next, we need to make use of the translations in our components in order for them to dynamically change to their respective locales depending on the region the user is in.

react-i18next offers us three different methods for accessing translations, namely:

  1. Using the useTranslation hook
  2. Using the withTranslation HOC
  3. Using the Translation render prop

Using the useTranslation hook

The t function is the main function in i18next to translate content. We’ll be importing the useTranslation hook from react-i18next which allows us to destructure the t function from the hook. Add the lines of code below in our login.js file.

import React from "react";
import {
  Box,
  Flex,
  Button,
  Heading,
  FormControl,
  Input,
  FormLabel,
  FormErrorMessage,
  Link as ChakraLink,
} from "@chakra-ui/react";
import { useForm } from "react-hook-form";
import PasswordInput from "./passwordInput";
import { useTranslation } from "react-i18next";


const Login = () => {
  const { t } = useTranslation();
  const {
    handleSubmit,
    register,
    formState: { errors, isSubmitting },
  } = useForm();

  const onSubmit = (values) => {
    return new Promise((resolve) => {
      setTimeout(() => {
        alert(JSON.stringify(values, null, 2));
        resolve();
      }, 3000);
    });
  };

  return (
    <Box maxW="400px" w="full">
      <Heading
        fontWeight={700}
        fontSize="24px"
        fontFamily="Source Sans Pro"
        mb={10}
        textAlign="center"
      >
        {t("login.heading")}
      </Heading>
      <form onSubmit={handleSubmit(onSubmit)}>
        <Box p={4}>
          <FormControl mb={6} isInvalid={errors.email}>
            <FormLabel htmlFor="email" color="#100B05" fontSize="14px">
              {t("login.input.email")}
            </FormLabel>
            <Input
              id="email"
              type="email"
              placeholder={t("login.input.emailPlaceholder")}
              {...register("email", {
                required: t("login.errors.email"),
              })}
              isRequired
            />
            <FormErrorMessage>
              {errors.email && errors.email.message}
            </FormErrorMessage>
          </FormControl>
          <FormControl mb={2} isInvalid={errors.password}>
            <FormLabel htmlFor="password" color="#100B05" fontSize="14px">
              {t("login.input.password")}
            </FormLabel>
            <PasswordInput register={register} />
            <FormErrorMessage>
              {errors.password && errors.password.message}
            </FormErrorMessage>
          </FormControl>
          <Flex justify="flex-end">
            <ChakraLink color="brand" fontSize="sm">
              {t("login.forgot_password")}
            </ChakraLink>
          </Flex>
          <Button
            mt={10}
            type="submit"
            colorScheme="teal"
            w="full"
            isLoading={isSubmitting}
          >
            {t("login.submit")}
          </Button>
        </Box>
      </form>
    </Box>
  );
};

export default Login;

Using the withTranslation HOC

react-i18next also offers a HOC that can be used for situations where the hook isn’t the preferred style. Add the code below in our passwordInput.js file.

import React from "react";
import {
  Input,
  InputGroup,
  IconButton,
  InputRightElement,
  useDisclosure,
} from "@chakra-ui/react";
import { ViewIcon, ViewOffIcon } from "@chakra-ui/icons";
import { withTranslation } from "react-i18next";

const PasswordInput = ({ register, t }) => {
  const { isOpen, onToggle } = useDisclosure();
  return (
    <InputGroup>
      <Input
        id="password"
        type={isOpen ? "text" : "password"}
        placeholder={t("login.input.passwordPlaceholder")}
        {...register("password", {
          required: t("login.errors.password"),
          minLength: {
            value: 6,
            message: t("login.errors.passwordLength"),
          },
        })}
        isRequired
      />
      <InputRightElement h="full">
        <IconButton
          bg="none"
          width="32px"
          onClick={onToggle}
          height="40px"
          aria-label={t("login.input.toggle_password")}
          icon={
            isOpen ? (
              <ViewIcon size={18} color="#B8B8B8" />
            ) : (
              <ViewOffIcon size={18} color="#B8B8B8" />
            )
          }
          _hover={{
            bg: "none",
          }}
          _active={{
            bg: "none",
          }}
        />
      </InputRightElement>
    </InputGroup>
  );
};

export default withTranslation()(PasswordInput);

Save the changes and check the result in the browser.

Language Switcher

Now, we’ll notice when we change the language in the select dropdown nothing happens, that’s because we need to create a handleChangeLanguage function that handles changing the user language whenever the language is changed. Add the function below to our i18n.js file

export const handleChangeLanguage = async (language = "en") => {
  await i18n.changeLanguage(
    language,
    console.log("Language Changed to: " + language)
  );
};

Next, import the function in app.js, then add it to the onChange handler for the select component used in changing the language. We’ll also need to import the i18n.js instance in order for us to access the current language and use it as a default value for the dropdown.

import {
  Center,
  ChakraProvider,
  Flex,
  Heading,
  Select,
} from "@chakra-ui/react";
import Login from "./components/login";
import i18n, { handleChangeLanguage } from "./i18n";
import "./i18n";

function App() {
  const handleSelectChange = (event) =>
    handleChangeLanguage(event.target.value);

  return (
    <ChakraProvider>
      <Flex
        as="nav"
        mx="5rem"
        height="80px"
        justifyContent="space-between"
        alignItems="center"
      >
        <Heading as="h4" size="md">
          Logo
        </Heading>
        <Select
          width="150px"
          defaultValue={i18n.language}
          onChange={handleSelectChange}
        >
          <option value="en">English</option>
          <option value="fr">French</option>
        </Select>
      </Flex>
      <Center height="calc(100vh - 80px)">
        <Login />
      </Center>
    </ChakraProvider>
  );
}

export default App;

Now, when you save the changes and change the language in the browser, we’ll notice the language changes as expected.

Plugins

One other cool feature react-i18next offers is the use of different plugins that can be added to the i18n instance during initialization. One popular plugin is the i18next-browser-languagedetector which can be used to automatically detect the language of the user based on the browser’s locale. In this case, we don’t have to hardcode the language to the English locale all the time, we can make it dynamic so that the user’s locale is selected when needed. For us to add this plugin to the i18n instance, we’ll need to import it below and also make some changes to our configurations:

import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import EN_TRANSLATION from "./locales/en/translation.json";
import FR_TRANSLATION from "./locales/fr/translation.json";
import LanguageDetector from "i18next-browser-languagedetector";

i18n
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    resources: {
      en: {
        translation: EN_TRANSLATION,
      },
      fr: {
        translation: FR_TRANSLATION,
      },
    },
    fallbackLng: "en",
    keySeparator: ".",
  });

export const handleChangeLanguage = async (language = "en") => {
  await i18n.changeLanguage(
    language,
    console.log("Language Changed to: " + language)
  );
};

export default i18n;

We used the Language-Detector plugin and also changed the lng property to fallbackLng. This is because we’re no longer hardcoding our locale to en, instead, we want to use it as a fallback language should in case translations are unavailable for the locale selected. The Language-Detector plugin also helps us with persisting the selected locale in Local Storage so we don’t have to go through the process of changing locales again the next time we use the app.

Now save the changes and view the result in the browser, we’ll notice our app still works as expected. Also, when we change the language and refresh the app we notice the language stays the same.

Conclusion

In this article, I explained the benefits of setting up Internationalization in your applications and how you can go about setting up react-i18next in your react projects. I hope you found this guide helpful and that you would be able to set up internationalization easily in your applications moving forward. You can find the complete code for the tutorial on GitHub.

Feel free to reach out to me via LinkedIn or Twitter if there are any questions.

Resources