Back

Mastering Multilingual Websites: Internationalization in Next.js 14

Mastering Multilingual Websites: Internationalization in Next.js 14

In the rapidly evolving digital landscape, creating websites that cater to a global audience is not just an option but a necessity. Internationalization, or i18n, plays a pivotal role in web development, ensuring that your content reaches a global audience in different languages and regions seamlessly. This article will show you how to work with i18n in Next.js.

With the release of Next.js 14, the popular React framework takes a giant leap forward in providing enhanced i18n capabilities out of the box. Next.js 14 App router provides a straightforward approach to handling internalization on the server side and building complete multilingual sites.

In this article, we will see the complete guide to using internalization in Next.js 14 from start to end that can be used for creating multi-language sites.

Setting Up Next.js 14 with Built-in i18n

Before we dive into next.js internalization, let’s set up a Next.js project.

Step 1. Create a new Next.js project Open your terminal and run the following commands to create a new next.js project and change its directory:

npx create-next-app myapp
cd myapp

Step 2. Install Next.js 14 and next-intl package Next, install the latest version of Next.js (version 14) and the next-intl package by running the following command:

npm install next@latest next-intl

The above command will ensure that the Next.js is installed with its latest features including i18n along with next-intl. Why are we using next-intl? Because it integrates with the App Router by using a [locale] dynamic segment so that we can use this segment to provide content in different languages (like /en, /nl, etc.).

Step 3. Enable i18n Support in Next.js 14 Open your next.config.js file, and add the following configuration to enable i18n:

// next.config.js

const withNextIntl = require('next-intl/plugin')();
 
module.exports = withNextIntl({
  // Other Next.js configuration ...
});

In the above, we have now enabled i18n support in Next.js 14 by configuring the next.config.js file. The code utilizes the withNextIntl function from the 'next-intl' plugin to enhance Next.js with internationalization features.

Further, the withNextIntl function wraps the existing Next.js configuration, allowing for additional i18n-related settings while preserving other project configurations.

Step 4. Set Up the messages Folder As next.js doesn’t support auto-translation, we must manually add the different locale files to a folder. To do this, create a messages folder in the root directory of your Next.js project.

Inside the messages folder, create separate JSON files for each locale (e.g., en.json, fr.json, nl.json). Add the content in each file as:

  • messages/fr.json
{
  "Index": {
    "title": "Internationalisation avec Next.js 14",
    "description": "Un tutoriel complet pour tous !"
  }
}
  • messages/en.json
{
  "Index": {
    "title": "Next.js 14 Internationalization",
    "description": "A complete tutorial for all!"
  }
}
  • messages/nl.json
{
  "Index": {
    "title": "Next.js 14 Internationalisatie",
    "description": "Een volledige handleiding voor iedereen!"
  }
}

And that’s all! We have successfully set up i18n in Next.js 14 to make our application support internalization.

Language Routing and Slugs with Built-in i18n

Now, our setup is complete, and it’s time to add language-specific routing and the configuration of language slugs without the need for additional libraries.

This step is crucial to dynamically generate routes for different locales as it enhances the user experience by providing content in their preferred language.

Before getting started, let’s see the project structure:

Project Structure

Let’s start with the language routing and slugs.

Step 1. i18n.ts for Dynamic Message Loading Create a new file inside src/ and configure the i18n.ts file to dynamically load messages based on the locale:

// src/i18n.ts
import { notFound } from "next/navigation";
import { getRequestConfig } from 'next-intl/server';

const locales = ['en', 'fr', 'nl'];

export default getRequestConfig(async ({ locale }) => {
  if (!locales.includes(locale as any)) notFound();

  return {
    messages: (await import(`../messages/${locale}.json`)).default
  };
});

Above, we have used the getRequestConfig function, an asynchronous function that takes a locale parameter, checked against a predefined array of valid locales. In the function, if the locale is not found, it triggers a notFound function.

The function dynamically imports all our JSON files corresponding to the locale from the messages folder using await import. It returns an object with the messages property containing the default export of the imported module.

We have now set up a dynamic i18n configuration that dynamically loads messages based on the chosen locale as per user request, ensuring that the application adapts its content seamlessly for different language preferences.

Step 2. Creating a Middleware We will create a middleware.ts file inside the src/ to match the locales. This will allow redirecting the user based on the locale inside middleware.

import createMiddleware from 'next-intl/middleware';
 
export default createMiddleware({
  //Add locales you want in the app
  locales: ['en', 'fr', 'nl'],
 
  // default locale if no match
  defaultLocale: 'fr'
});
 
export const config = {
  // Match only internationalized pathnames
  matcher: ['/', '/(fr|nl|en)/:path*']
};

In the above file, we added three different languages: English, French, and Dutch (Netherlands).

Step 3. Configure the app language The locale matched by the middleware is available via the locale param and can be used to configure the document language.

Create a [locale] inside the app/ and add your layout.tsx and page.tsx files. Open layout.tsx and modify the default file code with the following code:

export default function LocaleLayout({children, params: {locale}}) {
  return (
    <html lang={locale}>
      <body>{children}</body>
    </html>
  );
}

Also, modify page.tsx with the following:

import { useTranslations } from "next-intl";

export default function Index() {
  const translated = useTranslations("Index");

  return (
    <div>
      <h1>{translated("title")}</h1>
      <h2>{translated("description")}</h2>
    </div>
  );
}

Above, we have used the useTranslations hook from next-intl to access translated messages. It provides a concise and organized way to handle multilingual content. Then the translated is obtained from the hook that takes a key, such as ‘title’ or ‘description,’ and retrieves the corresponding translation from the three .json message files.

And that’s all! You have now successfully implemented the language routes and slugs inside your Next.js 14 app.

Step 4. Run the app

To run the app, run the following command:

npm run dev

Visit the URL like localhost:port/en, localhost:port/fr, localhost:port/nl. You will see the below output:

output1

Implementing Language Switching with Built-in i18n

To seamlessly move to different URLs, let’s create a Switcher component. Inside the src/components, create a file called Switcher.tsx and add the following code:

// src/components/Switcher.tsx
import "../app/globals.css";
import Link from "next/link";

const Switcher = () => {
  return (
    <div className="space-x-3">
      <Link
        href="/en"
        className="bg-blue-500 text-white font-semibold rounded-md p-3"
      >
        EN
      </Link>
      <Link
        href="/fr"
        className="bg-blue-500 text-white font-semibold rounded-md p-3"
      >
        FR
      </Link>
      <Link
        href="/nl"
        className="bg-blue-500 text-white font-semibold rounded-md p-3"
      >
        NL
      </Link>
    </div>
  );
};

export default Switcher;

In the Switcher component, we have used the next/link to dynamically switch to different locales. For styling, we are using the Tailwind CSS library.

Let’s add the Switcher component to the layout.tsx to render throughout our app.

import Switcher from "@/components/Switcher";
import "../globals.css";

const LocaleLayout = ({ children, params: { locale } }) => {
  return (
    <div className="flex gap-10 flex-col m-10 p-10">
      <Switcher /> // render switcher
      {children}
    </div>
  );
};

export default LocaleLayout;

After running the npm run dev command, you will see the following output:

output2

Content Translation with and without Libraries

Internationalization (i18n) libraries are crucial in translating content for multilingual websites. While we can manually add translated content to our websites, it becomes a difficult task for a large application, and that’s where these libraries come into play.

Various i18n libraries are available, including react-intl, i18next, etc. These libraries are useful in providing features like translation, messaging, dynamic content translation, etc.

Manual translation without library

To add translated content to our application, we can manually handle content translation for static pages by creating separate folders for each locale (language), like pages/fr/index.js with the content as:

// pages/fr/index.js
const HomePage = () => {
  return (
    <div>
      <h1>Internationalisation avec Next.js 14</h1>
      <h3>Un tutoriel complet pour tous !</h3>
    </div>
  );
};

export default HomePage;

Adding translated content through this approach becomes challenging for dynamic content and might not be scalable for large applications. Therefore, it’s always recommended to use libraries.

Using react-intl for translation

The library react-intl (now Format.JS) is a very powerful i18n library that helps in setting up internationalization in any project, whether it’s React or an advanced one, i.e., Next.js.

Installation for react-intl is very simple. Just run the following command to install the library:

npm i react-intl

Above, we have used the inbuilt i18n of Next.js for our application. To use the react-intl, modify the [locale]/page.tsx as follows:

// src/[locale]/page.tsx
import '../styles/globals.css';
import { useIntl, FormattedMessage } from 'react-intl';

const LocalePage = () => {
  const intl = useIntl();
  
  return (
    <div>
      <h1>{intl.formatMessage({ id: 'Index.title' })}</h1>
      <h3>{intl.formatMessage({ id: 'Index.description' })}</h3>
    </div>
  );
};

export default LocalePage;

Conclusion

In this article, we’ve learned about internationalization in Next.js 14 and how it’s essential for reaching a global audience. From setting up with next-intl to language-specific routing and dynamic slugs, the framework simplifies multilingual web development. We also learned to create a dynamic language switcher that enhances the user experience.

We examined content translation options, emphasizing the efficiency of the react-intl library. Introducing next-i18n-router extended language routing, providing automatic language detection and streamlined route generation. As you apply these insights, Next.js 14 becomes a powerful ally in creating accessible and user-friendly multilingual websites.

Gain control over your UX

See how users are using your site as if you were sitting next to them, learn and iterate faster with OpenReplay. — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay