Next.js Localization: How to Build a Multilingual Website with Next-Intl

Posted by Nirmalya Ghosh on November 4, 2021

GSD

If you are thinking of launching your website to customers around the world, then there is a good chance that you will have to serve content in multiple languages. There are many languages used all over the world and some people might feel more comfortable reading content in their native language. If you are not serving your content in those languages, you may lose potential customers.

In this article, you will learn about how to use ButterCMS along with your Next.js application to serve content in multiple languages and turn those potential users into paying clients. We will start by discussing the benefits of a multilingual site, then look into how to create one using Next.js and ButterCMS.

Benefits of localizing website content

According to W3Techs, over 60% of websites across the world serve content in English. Even though that sounds like a lot, there's a good chance that it’s still better to serve your content in multiple languages if you intend on engaging with an international audience.  

Some of the benefits of localizing content by creating a multilingual website are as follows:

  1. Expand your customer base, and thereby increasing revenue. For a little bit of perspective, when looking at Internet World Stats' 2020 Internet World Users by Language statistics, you can see that only 25.9% of internet users are native English speakers, which means the remaining 3.4 billion internet (74.1%) users browse in languages other than English. This is a massive opportunity for growth.

  2. SEO (Search Engine Optimization) for all regions will increase the traffic to your website and reduce bounce rates. According to Hubspot, “If your content doesn’t match the visitors you’re attracting, they’ll quickly leave”. So, it is essential that you perform optimizations based on the regions where you provide your services.

  3. Create awareness, recognition, and trust for your international users. For reference, as reported by Slator.com according to the CSA's 2020 Can't Read, Won't Buy report, it was found that 76% of consumers would rather read product information in their native tongue and about 40% of internet users won’t buy from websites that aren’t in their native language. 

Discover how ButterCMS works with your Next.js app.

Tutorial: Building a multilingual website with Next-Intl & ButterCMS

Pre-requisites

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

The code for the application that you will be building as a part of this article is hosted on GitHub. The demo for the application is hosted on Vercel.

Bootstrapping a new Next.js application

In this section, you will learn how to bootstrap a basic Next.js application.

Next.js is a very popular framework built on top of the React.js library and it provides the best Development Experience for building applications. It offers a bunch of features like:

The recommended way to bootstrap a Next.js application is by using its official CLI tool. To create a new Next.js application, run the following command in your terminal:

yarn create next-app

You will be asked the name of the application that you are creating:

~/Development/bin ❯ yarn create next-app

success Installed "create-next-app@11.1.2" with binaries:
      - create-next-app

✔ What is your project named? nextjs-buttercms-i18n

In this case, I entered the name “nextjs-buttercms-i18n”. However, you can choose whatever name you want to use for your application.

The Next.js CLI will generate the following files and directories for us:

.
├── README.md
├── next.config.js
├── node_modules
├── package.json
├── pages
├── public
├── styles
└── yarn.lock

4 directories, 4 files

You can start the development server of the application using the following command:

yarn dev

Our new Next.js application will be up and running on http://localhost:3000.

Screenshot: Welcome to Next.js screen

Create a static page with hard-coded content in English

In this section, you will modify the landing page and add content in English.

You can modify the content of the pages/index.js file by entering some text in English like the following:

export default function Home() {
 return (
   <div>
     <h1>This is the heading</h1>
     <p>
       This is the dummy content of the page. This is just a placeholder
       content for this page and is written in English. We will be using the
       translations API of both ButterCMS and Next.js to build a multilingual
       website.
     </p>
   </div>
 );
}

Now, if you visit http://localhost:3000, you will see the following screen:

Screenshot: This is a heading page

Note that I have added some basic styles to make this page look better. But, let’s not worry about them for the sake of this article.

Use the Internationalized Routing feature of Next.js to add translations

In this section, you will learn how to use the Internationalized Routing feature of Next.js to add translations to your application.

To start using the feature, you will have to add the `i18n` config to your next.config.js file:

// next.config.js

module.exports = {
 reactStrictMode: true,
 i18n: {
   locales: ["en", "fr"],
   defaultLocale: "en",
   domains: [
     {
       domain: "website.com",
       defaultLocale: "en",
     },
     {
       domain: "website.fr",
       defaultLocale: "fr",
     },
   ],
 },
};

Let’s look at the keys that the `i18n` config uses.

  • locales: All the locales that your application supports.
  • defaultLocale: This is the default locale of your application.
  • domains: This is the list of the locale domains and their default locales.

Note that in this post, we won’t work with domains. We will only work with locales.

Locale strategies of Next.js

Next.js has two locale strategies:

  • Sub-path routing: In this strategy, if the current locale is `fr` and if you visit https://website.com/articles/1, you will be redirected to https://website.com/fr/articles/1. For the default locale, no redirection occurs.
  • Domain routing: In this strategy, you will be redirected to different domains based on the current locale. For example, if the current locale is `fr` and if you visit https://website.com/articles/1, you will be redirected to https://website.fr/articles/1. This is shown in the example which has been provided above.

We will be discussing the sub-path routing strategy in this article.

Add the Navbar component

In this section, you will add the Navbar component, which will have links to different pages of the application. It will also have a LocalSwitcher component, which will be responsible for switching the current locale of the application.

Let’s start by adding a new file components/navbar.js with the basic blocks of a typical navbar:

// components/navbar.js

import React from "react";
import Link from "next/link";
import LocaleSwitcher from "./locale-switcher";

export default function Navbar() {
 return (
   <nav>
     <Link href="/">
       <a>Home</a>
     </Link>
     <Link href="/ssg">
       <a>SSG</a>
     </Link>
     <Link href="/ssr">
       <a>SSR</a>
     </Link>
     <LocaleSwitcher />
   </nav>
 );
}

Let’s add the LocaleSwitcher component in the components/locale-switcher.js file:

// components/locale-switcher.js

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

export default function LocaleSwitcher() {
 const { locales, locale, pathname, query, asPath } = useRouter();
 const otherLocales = locales.filter((l) => l !== locale); // Find all the locales apart from the current locale.

 return (
   <>
     {otherLocales.map((locale) => {
       return (
         <Link
           key={locale}
           href={{ pathname, query }}
           as={asPath}
           locale={locale}
         >
           <a>Switch to &quot;{locale}&quot;</a>
         </Link>
       );
     })}
   </>
 );
}

Both of these components are very basic and add minimal functionality to the application.

You will also need to import the Navbar component into the pages/_app.js file:

// pages/_app.js

import Navbar from "../components/navbar";
import "../styles/globals.css";

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

export default MyApp;

You can also add two more files for SSG and SSR (static site generation and server-side rendering) :

// pages/ssg.js

export default function Page() {
return (
  <div>
    <h1>Article title</h1>
    <p>This is the body of the article.</p>
  </div>
);
}

export const getStaticProps = ({ locale, locales }) => {
return {
  props: {
    locale,
    locales,
  },
};
};
// pages/ssr.js

export default function Page() {
 return (
   <div>
     <h1>Article title</h1>
     <p>This is the body of the article.</p>
   </div>
 );
}

export const getServerSideProps = ({ locale, locales }) => {
 return {
   props: {
     locale,
     locales,
   },
 };
};

Now, if you visit http://localhost:3000, you will see the following:

Screenshot: This is a heading page with a top navbar

Note that some styles have been added to make the navbar look better.

You can play with the application and notice how the URL changes when you click on the LocaleSwitcher.

Integrate Next-Intl with our Next.js application

Until now, you have implemented internationalized routing for the application. But, you haven’t added any translations for the texts. In this section, you will install and integrate the next-intl translation package with the Next.js application to support multiple locales.

You can start by installing the next-intl package by running the following command from the root of the project inside a terminal:

yarn add next-intl

Next, you can add translations to the pages/_app.js file:

// pages/_app.js

import { NextIntlProvider } from "next-intl";
import Navbar from "../components/navbar";
import "../styles/globals.css";

function MyApp({ Component, pageProps }) {
 return (
   <NextIntlProvider messages={pageProps.messages}>
     <Navbar />
     <Component {...pageProps} />
   </NextIntlProvider>
 );
}

export default MyApp;

Next, you can add translations to the pages/index.js file:

// pages/index.js

import { useTranslations } from "next-intl";

export default function Home() {
 const t = useTranslations("index");

 return (
   <div>
     <h2>From next-intl</h2>
     <h1>{t("heading")}</h1>
     <p>{t("content")}</p>
   </div>
 );
}

export function getStaticProps({ locale }) {
 return {
   props: {
     messages: {
       ...require(`../messages/index/${locale}.json`),
       ...require(`../messages/navbar/${locale}.json`),
     },
   },
 };
}

You can also modify the pages/ssg.js and pages/ssr.js files:

// pages/ssg.js

import { useTranslations } from "next-intl";

export default function Page() {
 const t = useTranslations("ssg");

 return (
   <div>
     <h2>From next-intl</h2>
     <h1>{t("heading")}</h1>
     <p>{t("content")}</p>
   </div>
 );
}

export const getStaticProps = ({ locale, locales }) => {
 return {
   props: {
     messages: {
       ...require(`../messages/ssg/${locale}.json`),
       ...require(`../messages/navbar/${locale}.json`),
     },
   },
 };
};
// pages/ssr.js

import { useTranslations } from "next-intl";

export default function Page() {
 const t = useTranslations("ssr");

 return (
   <div>
     <h2>From next-intl</h2>
     <h1>{t("heading")}</h1>
     <p>{t("content")}</p>
   </div>
 );
}

export const getServerSideProps = ({ locale, locales }) => {
 return {
   props: {
     messages: {
       ...require(`../messages/ssr/${locale}.json`),
       ...require(`../messages/navbar/${locale}.json`),
     },
   },
 };
};

Let’s also add translations to our Navbar and LocaleSwitcher components.

// components/navbar.js

import React from "react";
import Link from "next/link";
import LocaleSwitcher from "./locale-switcher";
import { useTranslations } from "next-intl";

export default function Navbar() {
 const t = useTranslations("navbar");

 return (
   <nav>
     <Link href="/">
       <a>{t("home")}</a>
     </Link>
     <Link href="/ssg">
       <a>{t("ssg")}</a>
     </Link>
     <Link href="/ssr">
       <a>{t("ssr")}</a>
     </Link>
     <LocaleSwitcher />
   </nav>
 );
}
// components/locale-switcher.js

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

export default function LocaleSwitcher() {
 const { locales, locale, pathname, query, asPath } = useRouter();
 const otherLocales = locales.filter((l) => l !== locale); // Find all the locales apart from the current locale.
 const t = useTranslations("navbar");

 return (
   <>
     {otherLocales.map((locale) => {
       return (
         <Link
           key={locale}
           href={{ pathname, query }}
           as={asPath}
           locale={locale}
         >
           <a>{t("switchLocale", { locale })}</a>
         </Link>
       );
     })}
   </>
 );
}

You will also need to add the translation strings:

// messages/index/en.json

{
 "index": {
   "heading": "This is the heading",
   "content": "This is the dummy content of the page. This is just placeholder content for this page and is written in English. We will be using the translations API of both ButterCMS and Next.js to build a multilingual website."
 }
}
// messages/index/fr.json

{
 "index": {
   "heading": "C'est le titre",
   "content": "Il s'agit du contenu factice de la page. Ceci est juste un contenu d'espace réservé pour cette page et est écrit en anglais. Nous utiliserons l'API de traduction de ButterCMS et Next.js pour créer un site Web multilingue."
 }
}

You can add the rest of the translations similarly. Feel free to check out this commit for more details.

Now, if you visit http://localhost:3000, you should be able to see the translated application.

Create a new page in ButterCMS

In this section, you will learn about how to create a new page in ButterCMS and modify the schema of a page.

You can create a new page by visiting this link or by clicking on the New Content Type button on the Content Types page. You can also click on the + icon next to the Page Types menu under Content Types in the sidebar menu:

Screenshot: Content type button in ButterCMS interface

The schema of the page that we are going to create looks like this:

  • Name: This is required and of type Short Text.
  • Content: This is required and of type Long text.

Start by selecting the Short Text field type from the left:

Screenshot: ButterCMS short text field

Next, you need to enter the details of the fields:

Screenshot: ButterCMS enter the different "name" fields details

Next, select the Long Text field type from the left:

Screenshot: ButterCMS long text field

Enter the appropriate details for the fields:

Screenshot: ButterCMS enter the different "content" fields

Next, click on the Create Page Type button on the top right and enter the name of the Page Type:

Screenshot: Create page type button in ButterCMS

Note the Page Type API key is build_a_multilingual_website_with_buttercms, which has been generated by ButterCMS for us. We will fetch pages of this type for our application later.

You can now use the new page type to create a new page. Select the new page type that you created from the sidebar menu:

Screenshot: ButterCMS sidebar pages menu

Next, click on the New Page button on the top right to create a new page:

Screenshot: Create a new page in ButterCMS

Next, add the Page Title and API Slug for your new page and click on the Save Page Metadata button:

Screenshot: ButterCMS home page meta data fields

Enter the Name and Content for the new page and click on the Publish button on the top right:

Screenshot: ButterCMS home page name and content fields

Discover how ButterCMS works with your Next.js app.

Integrate ButterCMS with the Next.js application

In this section, you will learn about how to integrate ButterCMS with your Next.js application. You will also use the ButterCMS package to fetch data.

Let’s start by installing the ButterCMS package into our application. From the root of your project, run the following command:

yarn add buttercms

You can create a new file at libs/buttercms.js for storing the ButterCMS client:

// libs/buttercms.js

import ButterCMS from "buttercms";

// ButterCMS Read API Token from https://buttercms.com/settings/
const butterCMSClient = ButterCMS("BUTTERCMS_READ_API_TOKEN");

export default butterCMSClient;

You can now use the ButterCMSClient to fetch data in your application. Let’s fetch some data in the pages/index.js file.

// pages/index.js

import { useTranslations } from "next-intl";
import butterCMSClient from "../libs/buttercms";

export default function Home({ fields }) {
 const t = useTranslations("index");

 return (
   <div>
     <h2>From next-intl</h2>
     <h3>{t("heading")}</h3>
     <p>{t("content")}</p>

     <h2>From ButterCMS</h2>
     <h3>{fields.name}</h3>
     <p>{fields.content}</p>
   </div>
 );
}

export const getStaticProps = async ({ locale }) => {
const { data } = await butterCMSClient.page.retrieve(
   "build_a_multilingual_website_with_buttercms",
   "home-page",
   {
     locale,
   }
 );
 const { fields } = data.data;

 return {
   props: {
     messages: {
       ...require(`../messages/index/${locale}.json`),
       ...require(`../messages/navbar/${locale}.json`),
     },
     fields,
   },
 };
};

Now, if you visit http://localhost:3000, you should be able to view the post from ButterCMS:

Screenshot: Home page view of the post

Use ButterCMS’s API to add translations

For using ButterCMS’s Localization API, you will need to subscribe to one of the plans:

Screenshot: ButterCMS subscription options

Once you have subscribed to a plan, you will need to visit the locales page and add the necessary locales:

Screenshot: Add a locale in ButterCMS

In my case, I have added the English (en) and French (fr) locales. You can add as many as you need for your application.

Once you are done with that, go back to the page that you had created initially and select the French locale from the dropdown, and add the necessary content for both the fields:

Screenshot: Newly added French and English locales in ButterCMS

I used Google Translate to translate my English content to French:

Screenshot: Home page fields in ButterCMS swtiching from english to french

Now, if we visit http://localhost:3000, we should be able to view the translated content:

Screenshot gif: Changing the language of a page from English to French by clicking a button

Extend the translation functionality to other pages

Let’s create a new page in ButterCMS to store the translations for the SSG Page. First, you can enter the English content:

Screenshot: SSG page in ButterCMS for English Content

Next, you can enter the French content:

Screenshot: SSG page in ButterCMS for French content

Note that I have used Google Translate to translate the content from English to French. If you would like to set up an auto-translation workflow, check out our Write API for Translating Content tutorial. 

Next, go to the pages/ssg.js file and add the translations in the getStaticProps function. The file will look like this:

// pages/ssg.js

import { useTranslations } from "next-intl";
import butterCMSClient from "../libs/buttercms";

export default function Page({ fields }) {
 const t = useTranslations("ssg");

 return (
   <div>
     // Other stuffs

     <h2>From ButterCMS</h2>
     <h3>{fields.name}</h3>
     <p>{fields.content}</p>
   </div>
 );
}

export const getStaticProps = async ({ locale }) => {
  const { data } = await butterCMSClient.page.retrieve(
   "build_a_multilingual_website_with_buttercms",
   "ssg-page",
   {
     locale,
   }
 );
 const { fields } = data.data;

 return {
   props: {
     // Other stuffs

     fields,
   },
 };
};

Now, if you visit http://localhost:3000/ssg, you should be able to view the translated content:

Screenshot: Resulting webpage changing from English to French

 

You can extend this functionality to the other pages as well.

Conclusion

In an application, there are certain texts that you want to be translated and fetched from a CMS. These include page headings, descriptions, etc. There is also certain content that doesn’t need to depend on the CMS. This includes text used on buttons, navbars, and footers.

In this article, you learned how you can use both the Localization API of ButterCMS as well as the Next.js translations API and next-intl package to build a translated application.

Now, you can build your multilingual website and turn those potential clients into active, paying customers.

Make sure you receive the freshest Butter product updates.
    
Nirmalya Ghosh

Nirmalya Ghosh is a Frontend Developer and Designer based in India. He likes good design and is currently working mostly with React. You can find more details about him at nirmalyaghosh.com.

ButterCMS is the #1 rated Headless CMS

Related articles

Don’t miss a single post

Get our latest articles, stay updated!