How to build a multilingual website in Next.js

Since the internet became widely accessible in 1991, it has experienced rapid adoption. Improvements have been made using it in almost every field. Additionally, as technology advances, its advantages become even more obvious and more people are integrating themselves into the online world. We are no longer divided by actual or imagined borders because the internet has made the world a global village. Builders and innovators must therefore get their products and applications ready for expansion into international markets. One method is to consider their users' local or regional culture and social background.

Prerequisites

You will need the following for the application that we are going to build:

  • Node.js 12.0 or later

  • Basic knowledge of Next.js

Initializing a Next.js Project

In this tutorial, we will use create-next-app to create a new Next.js project.

To make a new Next.js project called multilingual-app, run the following command.

// using npx
npx create-next-app multilingual-app

// using yarn
yarn create next-app multilingual-app

If successful, create-next-app will generate the following a Next.js app with the following file structure:

multilingual-app
├── README.md
├── node_modules
├── package.json
├── .gitignore
├── .eslintrc.json
├── next.config.js
├── public
│   ├── favicon.ico
│   ├── vercel.svg
├── syles
│   ├── globals.css
│   ├── Home.module.css
└── pages
    ├── _app.js
    ├── index.js
    ├── api
        ├── hello.js

Setting up Internationalized Routing in Next.js

To use the Next.js Internationalized Routing feature, we have to add the i18n config to our next.config.js file:

const nextConfig = {
  ...
  i18n: {
    defaultLocale: "en",
    locales: ["en", "fr", "de"],
  },
};

Here, we added the i18n config to our nextConfig object. The default locale for our application is then set to English. A locale is simply an identifier for a region. It usually consists of at least a language code and a country/region code. Next, we then specify the languages that our application supports using the locales property. In our case, our application will support English, French, and German.

Next.js supports two locale handling strategies. They are:

  • Sub-path routing: This is the default behaviour adopted by Next.js when dealing with internationalized routing. The locale is included in the URL path in this strategy. If our locale is 'de,' our URL path becomes 'example.com/de.'

  • Domain routing: In this strategy, you will be redirected to different domains based on your current location. For example, if the current locale is 'fr,' our URL becomes 'example.fr.'

To learn more about how Next.js internalized routing works visit here.

Now, let’s take a look at the index.js page of our application:

import React from "react";
import { useRouter } from "next/router";
import Link from "next/link";
import styles from "../styles/Home.module.css";

// translation content
import fr from "../public/locales/fr/common.json";
import en from "../public/locales/en/common.json";
import de from "../public/locales/de/common.json";


export default function Home() {
  const router = useRouter();
  const { locale } = router;
  const t = locale === "en" ? en : locale === "fr" ? fr : de;

  const changeLanguage = (e) => {
    const locale = e.target.value;
    router.push("/", "/", { locale });
  };

  return (
    <div className={styles.container}>
      <div className={styles.navigation_bar}>
        <p>ContentLab</p>

        <div className="links">
          <Link href="/">{t.navigation.home}</Link>
          <Link href="/">{t.navigation.services}</Link>

          <select onChange={changeLanguage}>
            <option value={"en"}>En</option>
            <option value={"fr"}>Fr</option>
            <option value={"de"}>De</option>
          </select>
        </div>
      </div>

      <main className={styles.main}>
        <h1>{t.heading}</h1>

        <p>{t.paragraph}</p>
      </main>
    </div>
  );
}

In the code above, we import our application's various localized strings. The currently active locale information is then obtained from the Next.js router. Next, we create a variable named t to filter the content and get only the desired language translation based on our current locale. Then, we create a select button that handles our page redirects based on our locales

Creating the localized strings

Next.js currently only handles internalization routing and not content translation. As a result, we have two options: create the localized strings ourselves or use existing i18n library solutions like Locize and next-intl that handle the translation for us. For the sake of brevity, we'll go with the former.

Because our application will support English, French, and German, we must create folders to house these translations. Here is how we will organize our folders:

.
└── public
    └── locales
        ├── en
        |   └── common.json
        └── de
         |  └── common.json
        └── fr
            └── common.json

Here's what our English translation content file looks like

// public/locales/en/common.json
{
    "heading": "We are ContentLab",
    "paragraph": "We're a team of experienced software professionals who work alongside expert practitioner-writers and editors, content strategists, and dedicated project managers to create practitioner-centric, expertly executed content tailored to your brand. Trusted by the world's leading tech firms",
    "navigation": {
        "home": "Home",
        "services": "Services"
    }
}

Here's what the German translation content file looks like

// public/locales/de/common.json
{
    "heading": "Wir sind ContentLab",
    "paragraph": "Wir sind ein Team aus erfahrenen Softwareexperten, die mit erfahrenen Praktikern, Autoren und Redakteuren, Content-Strategen und engagierten Projektmanagern zusammenarbeiten, um praxisorientierte, fachmännisch ausgeführte Inhalte zu erstellen, die auf Ihre Marke zugeschnitten sind. Weltweit führende Technologieunternehmen vertrauen darauf",
    "navigation": {
        "home": "Heim",
        "services": "Dienstleistungen"
    }
}

Here's what the German translation content file looks like

// public/locales/fr/common.json
{
    "heading": "Nous sommes Content Lab",
    "paragraph": "Nous sommes une équipe de professionnels du logiciel expérimentés qui travaillent aux côtés de rédacteurs et éditeurs experts, de stratèges de contenu et de chefs de projet dédiés pour créer un contenu centré sur les praticiens et exécuté de manière experte, adapté à votre marque. Approuvé par les plus grandes entreprises technologiques du monde",
    "navigation": {
        "home": "Maison",
        "services": "Prestations de service"
    }
}

Here's a live demo of our application:

https://multilingual-app.vercel.app/