Multi-Language Next.js Website using i18next – RTL Support

Internationalization is an important factor if our website is targeting a global audience. People love to interact with a website in their own local language. Here, we will discuss the steps to implement a multi-language website in a Next.js website using the i18next. We will also cover the Right To Left / RTL support.

Prerequisites

I believe that the reader has a basic understanding of the following things:-

  • Creating an application using the Next.js framework
  • Working with the Next.js framework
  • Usage of getStaticProps, getServerSideProps in Next.js

What we will learn

In this article, we will learn to:-

  • Why i18next?
  • Create a Next.js app,
  • Install and setup i18next in the app
  • Adding the locales/language strings
  • Switching between the locales
  • Implementing RTL

After completing the article, we will learn to create a Next.js app with the workflow given below.

Why i18next?

Next.js does have built-in support for implementing a multi-language website.

But does not handle any management of translation content, or the actual translation functionality itself. All Next.js does is keep your locales and URLs in sync.

The i18next package handles the management of translation content, and components/hooks to translate your React components – while fully supporting SSG/SSR, multiple namespaces, code splitting, etc.

So let us start creating our Next.js Multi-language website with RTL support using the i18Next.

Create a Next.js app

We can easily create a Next.js app using the NPX tool.

npx create-next-app nextjs-i18n-multi-language-demo

The above command will create a new Next.js app with the name nextjs-i18n-multi-language-demo. Now direct to the project directory and open it with Visual Studio Code.

cd nextjs-i18n-multi-language-demo
code .

Install the next-i18next package

We are using the package next-i18next to translate our Next.js app. The package will handle the locales and changes the language strings on our pages.

The below command will install this package in our app.

npm i next-i18next

Wrap entire Next.js app with the appWithTranslation HOC

The next-i18next package is providing us appWithTranslation, a Higher Order Component(HOC).

We need to wrap the entire app with this higher-order component. Open the _app.js file inside the pages directory and wrap it with appWithTranslation as below.

import "../styles/globals.css";
import { appWithTranslation } from "next-i18next";
import "bootstrap/dist/css/bootstrap.min.css";

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />;
}

export default appWithTranslation(MyApp);

Note:- Here I am also importing the Bootstrap CSS file. You have to install bootstrap to do this. The below command will install Bootstrap in our app.

npm i [email protected]

Create the configuration for the next-i18next

Now create a configuration file for the next-i18next. Create a file next-i18next.config.js in the project root and add the configuration below.

const path = require("path");

module.exports = {
  i18n: {
    locales: ["default", "en", "ar"],
    defaultLocale: "default",
    localeDetection: false,
    localePath: path.resolve("./public/locales"),
  },
};

Let us have a look at the configuration in detail.

locales:- An array containing the locales we need on our website. Here I am adding the English – “en” and Arabic – “ar”. Other than the two, I am also adding a value “default”. Refer to the workaround to read more about adding a “default” locale.

defaultLocale:- The default locale to be shown.

localePath:- We are storing the locales inside the public/locales directory. So, add this path here, path.resolve("./public/locales"). We will see this in the upcoming steps.

Now import the i18next configuration we have created to the next.config.js which is our app’s main configuration.

So that the next.config.js will be the same as below.

/** @type {import('next').NextConfig} */
const { i18n } = require("./next-i18next.config");

const nextConfig = {
  reactStrictMode: true,
  swcMinify: true,
  i18n,
};

module.exports = nextConfig;

Add the locales

Now let us start adding the locales in our app. Here we are adding English and Arabic language strings.

So we will create /public/locales/default/common.json, /public/locales/en/common.json, and /public/locales/ar/common.json to store the different language strings. Note that the /en/common.json and /default/common.json will be the same in our case.

The file structure will look the same as below.

.
└── public
    └── locales
        └── defualt
        |    └── common.json
        ├── en
        |   └── common.json
        └── ar
            └── common.json

The /public/locale/defeault/common.json file will contain the locale strings to be shown as default. This is given below.

{
  "home": {
    "Home title": "Home title",
    "Home description": "Home description"
  },
  "about": {
    "About title": "About title",
    "About description": "About description"
  },
  "header": {
    "Home": "Home",
    "About": "About"
  }
}

Our /public/locale/en/common.json file will contain the locale strings to be shown when selecting the English language. This is given below.

{
  "home": {
    "Home title": "Home title",
    "Home description": "Home description"
  },
  "about": {
    "About title": "About title",
    "About description": "About description"
  },
  "header": {
    "Home": "Home",
    "About": "About"
  }
}

The /public/locale/ar/common.json file will contain the locale strings to be shown when selecting the Arabic language. Refer to the below code.

{
  "home": {
    "Home title": "عنوان المنزل",
    "Home description": "وصف المنزل"
  },
  "about": {
    "About title": "حول العنوان",
    "About description": "حول الوصف"
  },
  "header": {
    "Home": "مسكن",
    "About": "حول"
  }
}

Navigation component and locale switcher

Now we need to code a Navigation component where we will display the name of our website, links to the pages, and a locale switcher.

The locale switcher is used to toggle between the languages. We can code the locale switcher as another component.

Now let us code the Navigation component first inside the /components/navigation.js file.

import LocaleSwitcher from "./locale-switcher";
import Link from "next/link";
import { useRouter } from "next/router";
import { useTranslation } from "next-i18next";
import { useEffect } from "react";

export default function Navigation() {
  const router = useRouter();

  const { t } = useTranslation("");

  return (
    <nav className="navbar navbar-expand-lg bg-light">
      <div className="container-fluid">
        <Link href="/">
          <a className="navbar-brand">Next.js Multi-Language</a>
        </Link>
        <div className="navbar-collapse" id="navbarText">
          <ul className="navbar-nav me-auto mb-2 mb-lg-0">
            <li className="nav-item">
              <Link href="/">
                <a
                  className={`nav-link ${
                    router?.pathname === "/" ? "active" : ""
                  }`}
                >
                  {t("header.Home")}
                </a>
              </Link>
            </li>
            <li className="nav-item">
              <Link href="/about">
                <a
                  className={`nav-link ${
                    router?.pathname === "/about" ? "active" : ""
                  }`}
                >
                  {t("header.About")}
                </a>
              </Link>
            </li>
          </ul>
          <LocaleSwitcher />
        </div>
      </div>
    </nav>
  );
}

You can see the contents such as {t("header.Home")} in the above file. We will discuss it later.

Now let us code the locale switcher component.

Locale switcher component

We are getting the locales that we have mentioned in the i18next configuration file here. Then map through each locale item and clicking each will link as below.

 <Link href={{ pathname, query }} as={asPath} locale={locale}>

The above link will change the locale URL of our app to the corresponding locale we have selected.

The complete code for the LocaleSwitcher component is given below.

import Link from "next/link";
import { useRouter } from "next/router";

export default function LocaleSwitcher() {
  const router = useRouter();

  const { locales, locale: activeLocale } = router;

  const otherLocales = locales?.filter(
    (locale) => locale !== activeLocale && locale !== "default"
  );

  return (
    <span className="text-muted cursor-pointer">
      {otherLocales?.map((locale) => {
        const { pathname, query, asPath } = router;
        return (
          <span key={"locale-" + locale}>
            <Link href={{ pathname, query }} as={asPath} locale={locale}>
              <a>
                {locale === "en" ? "English" : locale === "ar" ? "عربى" : null}
              </a>
            </Link>
          </span>
        );
      })}
    </span>
  );
}

Home and About page with i18next translation

So we have set up the i18next in our Next.js app. Now let us create a Home page, and an about page and add some contents that translate with change in locale.

I believe that you already know how to create pages in a Next.js app. Here, let’s have a look at the translation part.

First import the useTranslation hook from the next-i18next package.

import { useTranslation } from "next-i18next";

Now we can use a function t() to get the language strings that we have added in the locale file inside the locales directory.

For example, the code below will fetch the home.Home title string from the corresponding locale that we will select (en or ar).

  const { t } = useTranslation();

  return (
    <>
        <h1>{t("home.Home title")}</h1>
    </>
  );

We also need to import serverSideTranslations from the next-i18next/serverSideTranslations.

import { serverSideTranslations } from "next-i18next/serverSideTranslations";

Then pass the ...(await serverSideTranslations(locale, ["common"])) with the props inside the getStaticProps or getServerSideProps.

In our app, we are using the getStaticProps. Refer the below code.

export async function getStaticProps({ locale }) {
  return {
    props: {
      ...(await serverSideTranslations(locale, ["common"])),
    },
  };
}

The code for the entire home page is given below.

import { useTranslation } from "next-i18next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import Navigation from "../components/navigation";

export default function Home() {
  const { t } = useTranslation();

  return (
    <>
      <Navigation />
      <div className="mt-5">
        <h1>{t("home.Home title")}</h1>
        <p>{t("home.Home description")}</p>
      </div>
    </>
  );
}

export async function getStaticProps({ locale }) {
  return {
    props: {
      ...(await serverSideTranslations(locale, ["common"])),
    },
  };
}

In the same manner, we can code the about page. The code for the about page will is given below.

import { useTranslation } from "react-i18next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import Navigation from "../components/navigation";

export default function About() {
  const { t } = useTranslation("");

  return (
    <>
      <Navigation />
      <div className="mt-5">
        <h1>{t("about.About title")}</h1>
        <p>{t("about.About description")}</p>
      </div>
    </>
  );
}

export async function getStaticProps({ locale }) {
  return {
    props: {
      ...(await serverSideTranslations(locale, ["common"])),
    },
  };
}

Enabling the RTL support

So we have created a multi-language website in Next.js using i18next. It will change the language of the content by switching the locale from the Navigation section.

But we need to do one more thing. Languages like Arabic needs RTL support. The content should be aligned from Right To Left.

So let us implement the RTL in our Next.js app.

HTML has built-in support for RTL. The below code will align the content from Right To Left.

<html dir="rtl" lang="ar">
</html>

We can implement the same logic in our Next.js app by creating a _document.js file inside the pages directory and adding the below code.

import Document, { Html, Main, NextScript, Head } from "next/document";

class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const initialProps = await Document.getInitialProps(ctx);
    return { ...initialProps, locale: ctx?.locale || "en" };
  }

  render() {
    return (
      <Html
        dir={this.props.locale === "ar" ? "rtl" : "ltr"}
        lang={this.props.locale}
      >
        <Head></Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

export default MyDocument;

This will enable the RTL feature in our Next.js app. But only after refreshing the page, we can see the difference.

To overcome this issue, we can use the useEffect hook. The below code will inject the dir and lang attributes to the <html> tag with a change in locale.

  useEffect(() => {
    let dir = router.locale == "ar" ? "rtl" : "ltr";
    let lang = router.locale == "ar" ? "ar" : "en";
    document.querySelector("html").setAttribute("dir", dir);
    document.querySelector("html").setAttribute("lang", lang);
  }, [router.locale]);

The code document.querySelector("html").setAttribute("dir", "rtl") will give dir="rtl" to the <html> tag.

So, when the locale is changing, the below code will give attributes dir and lang to the <html> tag of our app.

We need to add this code to our Navigation component. So that the entire Navigation component will become like the below.

import LocaleSwitcher from "./locale-switcher";
import Link from "next/link";
import { useRouter } from "next/router";
import { useTranslation } from "next-i18next";
import { useEffect } from "react";

export default function Navigation() {
  const router = useRouter();

  const { t } = useTranslation("");

  useEffect(() => {
    let dir = router.locale == "ar" ? "rtl" : "ltr";
    let lang = router.locale == "ar" ? "ar" : "en";
    document.querySelector("html").setAttribute("dir", dir);
    document.querySelector("html").setAttribute("lang", lang);
  }, [router.locale]);

  return (
    <nav className="navbar navbar-expand-lg bg-light">
      <div className="container-fluid">
        <Link href="/">
          <a className="navbar-brand">Next.js Multi-Language</a>
        </Link>
        <div className="navbar-collapse" id="navbarText">
          <ul className="navbar-nav me-auto mb-2 mb-lg-0">
            <li className="nav-item">
              <Link href="/">
                <a
                  className={`nav-link ${
                    router?.pathname === "/" ? "active" : ""
                  }`}
                >
                  {t("header.Home")}
                </a>
              </Link>
            </li>
            <li className="nav-item">
              <Link href="/about">
                <a
                  className={`nav-link ${
                    router?.pathname === "/about" ? "active" : ""
                  }`}
                >
                  {t("header.About")}
                </a>
              </Link>
            </li>
          </ul>
          <LocaleSwitcher />
        </div>
      </div>
    </nav>
  );
}

Codesandbox

Refer to the CodeSandbox link to view the live app. You can clone this project to your CodeSandbox account and edit the code also.

https://codesandbox.io/s/gifted-wind-f74pko

GitHub

You can always refer to the GitHub repository to clone this project, refer to the code and work on top of it.

https://github.com/techomoro/nextjs-i18next-multi-language-demo

Summary

In this article, we discussed the steps to implement multi-language in a Next.js website using the i18next. We also implemented the RTL support.

One thought on “Multi-Language Next.js Website using i18next – RTL Support

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.