Internationalization (i18n) in Next.js
- Author: Md. Saad
- Published at: May 26, 2024
- Updated at: May 26, 2024
Internationalization (often abbreviated as i18n) in the context of websites refers to designing and developing a website to be easily adapted to various languages and regions without requiring engineering changes. This involves creating a framework that supports multiple languages, date formats, currency formats, and other locale-specific elements. By internationalizing a website, we can make it accessible to a global audience, which can help expand your reach and improve user experience for visitors from different regions.
Internationalization (i18n) in Next.js applications involves translating your app's content and routing based on the user's preferred language. In this tutorial, we will discuss the step-by-step guide on how to set up i18n with Next.js app router.
Prerequisites
To follow along with this guide and code, you should have the following:
- Basic understanding of HTML, CSS, and JavaScript
- At least a little experience or knowledge of Next.js
- Node and npm or yarn installed on your local computer
Create A Next.js App
The first and foremost step to begin with is creating a Next.js App. Use the following command to create a next.js app.
npx create-next-app app-name
As it is not an article on creating the next.js app, we will not discuss it in detail. If you want to read it in detail follow the link.
Install Dependencies
Let’s install the following dependencies using the dev tools
npm install negotiator
npm install @formatjs/intl-localematcher
Add middleware.js
Middleware allows you to run code before a request is completed. Then, based on the incoming request, you can modify the response by rewriting, redirecting, modifying the request or response headers, or responding directly. We will use Middleware to automatically detect user language preferences and also user’s manually used languages.
Now create a middleware.js at the root of the project and add the following codes:
// middleware.js
import {match} from "@formatjs/intl-localematcher";
import Negotiator from "negotiator";
import {NextResponse} from "next/server";
const defaultLocale = "en";
const locales = ["en", "de", "cs"];
function getLocale(request) {
const acceptedLanguage = request.headers.get("accept-language") ?? undefined;
const headers = {"accept-language": acceptedLanguage};
const languages = new Negotiator({headers}).languages();
return match(languages, locales, defaultLocale); // en,de or cs
}
export function middleware(request) {
// get the pathname from request url
const pathname = request.nextUrl.pathname;
const pathNameIsMissingLocale = locales.every(
(locale) => !pathname.startsWith(`/${locale}`) && !pathname.startsWith(`/${locale}/`)
);
if (pathNameIsMissingLocale) {
// detect user's preference & redirect with a locale with a path eg: /en/about
const locale = getLocale(request);
return NextResponse.redirect(new URL(`/${locale}/${pathname}`, request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ["/((?!_next).*)"],
};
These codes will prioritise user language preference from the browser setting and show the site in that language. However, users can manually go to the also.
Add dictionaries.js
We can then create a getDictionary function to load the translations for the requested locale.
- First, create a [lang] folder inside the app folder. Now, this folder will become the root folder of all the routing pages in our project.
- Create a dictionaries folder inside the [lang] folder.
- Now, create dictionaries.js file inside it and add the following codes:
app/[lang]/dictionaries/dictionaries.jsx
import "server-only";
// We also get the default import for cleaner types
const dictionaries = {
en: () => import("./en.json").then((module) => module.default),
de: () => import("./de.json").then((module) => module.default),
cs: () => import("./cs.json").then((module) => module.default),
};
export const getDictionary = async (locale) => dictionaries[locale]();
- Now, create all dictionaries files inside the app/[lang]/dictionaries, and store all the translated data in a JSON file. Such as, dictionaries/en.json, dictionaries/nl.json , dictionaries/de.json
- A sample is given below:
// dictionaries/en.json
{
"welcome": "Welcome",
"contact": "Contact Us"
}
// dictionaries/nl.json
{
"welcome": "Vítejte",
"contact": "Neem contact met ons op"
}
// dictionaries/de.json
{
"welcome": "Willkommen",
"contact": "Kontact os"
}
Configure the App Router
Finally, we have to configure the app router.
- Then, transfer all the files except golbals.css and favicon.ico from the app folder inside [lang].
- To create static routes for a given set of locales, we can use generateStaticParams with any page or layout. So, Customize the layout.js
import "../globals.css";
import {Inter} from "next/font/google";
const inter = Inter({subsets: ["latin"]});
export const metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export async function generateStaticParams() {
return i18n.locales.map((locale) => ({lang: locale}));
}
export default function RootLayout({children, params}) {
return (
<html lang={params.lang}>
<body className={inter.className}>{children}</body>
</html>
);
}
- Add all the items on the app/[lang]/page.jsx file to show. Your code should be like the following codes:
// app/[lang]/page.jsx
import {getDictionary} from "./dictionaries/dictionaries";
export default async function IndexPage({params: {lang}}) {
const dictionary = await getDictionary(lang);
return (
<div>
<div className="max-w-[1400px] py-2 flex justify-center px-5 mx-auto text-center">
<div className="bg-slate-200 shadow-md py-5 px-5 w-1/3 rounded-md">
<h1 className="font-bold">Current locale: {lang}</h1>
<p>This text is rendered on the server: {dictionary.welcome}</p>
</div>
</div>
</div>
);
}
- Finally, Let’s add another page. create a contact folder inside the [lang] folder. And add page.jsx file inside it.
// app/[lang]/contact/page.js
import {getDictionary} from "../dictionaries/dictionaries";
export default async function Contact({params: {lang}}) {
const dictionary = await getDictionary(lang);
return (
<div>
<div className="max-w-[1400px] py-2 flex justify-center px-5 mx-auto text-center">
<div className="bg-slate-200 shadow-md py-5 px-5 w-1/3 rounded-md">
<h1 className="font-bold font-2xl">Current locale: {lang}</h1>
<p>This text is rendered on the server: {dictionary.contact}</p>
</div>
</div>
</div>
);
}
This will create a simple contact page with the given languages.
- Let’s style the site a little bit. I have used tailwind.css for styling it. You can use your preferred one.
- Now run the site using:
npm run dev
If your browser's preferred language is set to English then The site will be run on localhost:3000/en by default and go to the contact page using the localhost:3000/en/contact. Moreover, whenever we change the language to the preferred language it will be redirected to the translated version.
Conclusion
Congratulations, we have successfully added i18n to our next.js site. For more details, you can visit the official next.js
Want to create a multilingual site? Contact Staticmania. Our dedicated team will guide you to your journey.