Internationalization in Next.js App Router with next-Intl
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