Internationalization in Next.js App Router with next-Intl

10 min read

Internationalization is a crucial feature for modern web applications aiming to reach a global audience, If you are looking for a way to support multi-languages in your Next.js 13+ application you are in the right place. This article will guide you through the seamless integration of internationalization with next-intl

Setting Up next-intl

Firstly, install the next-intl library by running pnpm add next-intl or yarn add next-intl in your project directory.

Configuration

1 messages/en.json

Messages can be provided locally or loaded from a remote data source (e.g. a translation management system).

{
	"Home": {
		"title-pre": "Multi-language website by",
		"button-1": "Start now",
		"button-2": "Learn more",
		"menu": {
			"home": "Home",
			"pricing": "Pricing",
			"about": "About"
		}
	}
}

2 next.config.mjs

Now, set up the plugin that creates an alias to provide your i18n configuration to Server Components.

import createNextIntlPlugin from "next-intl/plugin";

const withNextIntl = createNextIntlPlugin();

/** @type {import('next').NextConfig} */
const nextConfig = {};

export default withNextIntl(nextConfig);

3 18n.ts

import { notFound } from "next/navigation";
import { getRequestConfig } from "next-intl/server";
import { locales } from "../navigation";

export default getRequestConfig(async ({ locale }) => {
	// Validate that the incoming `locale` parameter is valid
	if (!locales.includes(locale as any)) notFound();

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

4 middleware.ts

The middleware matches a locale for the request and handles redirects and rewrites accordingly.

import createMiddleware from "next-intl/middleware";

export default createMiddleware({
	// A list of all locales that are supported
	locales: ["en", "pl"],

	// Used when no locale matches
	defaultLocale: "en",
});

export const config = {
	// Match only internationalized pathnames
	matcher: ["/", "/(pl|en)/:path*"],
};

Translation Provider

To use messages in Client Components you need to pass messages via provider, you can pass all messages or only those used on the client side

import { NextIntlClientProvider, useMessages } from "next-intl";

export default function RootLayout({
	children,
	params: { locale },
}: Readonly<{
	children: React.ReactNode;
	params: { locale: string };
}>) {
	const messages = useMessages();

	return (
		<html lang={locale}>
			<NextIntlClientProvider messages={messages}>
				<body className={inter.className}>{children}</body>
			</NextIntlClientProvider>
		</html>
	);
}

Using next-intl in you App

Non-async Server Components

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

export const Hero = () => {
	const t = useTranslations("Home");
	return (
		<section className="flex flex-col items-center">
			<div className="flex w-full flex-col gap-2.5 sm:flex-row sm:justify-center">
				<Link href="#">{t("button-1")}</Link>
				<Link href="#">{t("button-2")}</Link>
			</div>
		</section>
	);
};

Async Components

import Link from "next/link";
import { LocaleSwitcher } from "./locale-switcher";
import { getTranslations } from "next-intl/server";
import { getUser } from "../lib/auth";

export const Header = async () => {
	const user = getUser();
	const t = await getTranslations("Home");

	return (
		<header className="mb-8 flex items-center justify-between border-b py-4 md:mb-12 md:py-8 xl:mb-16">
			<nav className="hidden gap-12 lg:flex">
				<Link href="#" className="text-lg font-semibold text-indigo-500">
					{t("menu.home")}
				</Link>
				<Link href="#">{t("menu.pricing")}</Link>
				<Link href="#">{t("menu.about")}</Link>
			</nav>
			<UserProfile user={user} />
			<LocaleSwitcher />
		</header>
	);
};

Locale Switcher

navigation.ts

In this case, the pathnames of your app are used for each locales, for other cases check official docs

import { createSharedPathnamesNavigation } from "next-intl/navigation";

export const locales = ["en", "pl"];
export const localePrefix = "as-needed";

export const { Link, redirect, usePathname, useRouter } =
	createSharedPathnamesNavigation({ locales, localePrefix });

locale-switcher.tsx

import { usePathname, useRouter } from "../../navigation";

export const LocaleSwitcher = () => {
	const router = useRouter();
	const pathname = usePathname();

	const handleChange = (locale: string) => {
		router.push(pathname, { locale });
	};

	return (
		<div className="flex">
			<button className="text-black" onClick={() => handleChange("pl")}>
				PL
			</button>
			<button className="text-black" onClick={() => handleChange("en")}>
				EN
			</button>
		</div>
	);
};

Conclusion

And that's it! Your Next.js site is now multilingual, ready to greet visitors in their own language. For more information check official docs

LIVE DEMO