Internationalization allows your website to serve content in multiple languages. Here is how to implement it with the Next.js App Router using next-intl.
Step 1: Install next-intl
pnpm add next-intl
Step 2: Define Your Locales
// i18n/config.ts
export const locales = ["en", "es", "fr", "de"] as const;
export type Locale = (typeof locales)[number];
export const defaultLocale: Locale = "en";
export const localeNames: Record<Locale, string> = {
en: "English",
es: "Español",
fr: "Français",
de: "Deutsch",
};
Step 3: Create Translation Files
// messages/en.json
{
"common": {
"home": "Home",
"about": "About",
"services": "Services",
"contact": "Contact",
"blog": "Blog",
"getStarted": "Get Started",
"learnMore": "Learn More"
},
"home": {
"title": "We Build Websites That Drive Results",
"subtitle": "Custom web design and development for businesses that want to grow online.",
"cta": "Start Your Project"
},
"contact": {
"title": "Get in Touch",
"name": "Your Name",
"email": "Email Address",
"message": "Message",
"send": "Send Message",
"success": "Thank you! We'll be in touch soon."
}
}
// messages/es.json
{
"common": {
"home": "Inicio",
"about": "Nosotros",
"services": "Servicios",
"contact": "Contacto",
"blog": "Blog",
"getStarted": "Comenzar",
"learnMore": "Más información"
},
"home": {
"title": "Construimos Sitios Web que Generan Resultados",
"subtitle": "Diseño y desarrollo web personalizado para empresas que quieren crecer en línea.",
"cta": "Iniciar Su Proyecto"
},
"contact": {
"title": "Contáctenos",
"name": "Su Nombre",
"email": "Correo Electrónico",
"message": "Mensaje",
"send": "Enviar Mensaje",
"success": "¡Gracias! Nos pondremos en contacto pronto."
}
}
Step 4: Configure next-intl
// i18n/request.ts
import { getRequestConfig } from "next-intl/server";
import { routing } from "./routing";
export default getRequestConfig(async ({ requestLocale }) => {
let locale = await requestLocale;
if (!locale || !routing.locales.includes(locale as any)) {
locale = routing.defaultLocale;
}
return {
locale,
messages: (await import(`../messages/${locale}.json`)).default,
};
});
// i18n/routing.ts
import { defineRouting } from "next-intl/routing";
import { createNavigation } from "next-intl/navigation";
export const routing = defineRouting({
locales: ["en", "es", "fr", "de"],
defaultLocale: "en",
});
export const { Link, redirect, usePathname, useRouter } =
createNavigation(routing);
Step 5: Set Up Middleware
// middleware.ts
import createMiddleware from "next-intl/middleware";
import { routing } from "./i18n/routing";
export default createMiddleware(routing);
export const config = {
matcher: ["/((?!api|_next|.*\\..*).*)"],
};
Step 6: Update the App Directory Structure
Move your pages into a [locale] directory:
app/
[locale]/
layout.tsx
page.tsx
about/
page.tsx
contact/
page.tsx
Root Layout
// app/[locale]/layout.tsx
import { NextIntlClientProvider } from "next-intl";
import { getMessages } from "next-intl/server";
import { notFound } from "next/navigation";
import { routing } from "@/i18n/routing";
export default async function LocaleLayout({
children,
params,
}: {
children: React.ReactNode;
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
if (!routing.locales.includes(locale as any)) {
notFound();
}
const messages = await getMessages();
return (
<html lang={locale}>
<body>
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
</body>
</html>
);
}
Step 7: Use Translations in Components
Server Components
import { useTranslations } from "next-intl";
export default function HomePage() {
const t = useTranslations("home");
return (
<section className="py-20 text-center">
<h1 className="text-5xl font-bold">{t("title")}</h1>
<p className="mt-4 text-lg text-gray-500">{t("subtitle")}</p>
<a
href="/contact"
className="mt-8 inline-block rounded-lg bg-blue-600 px-8 py-3 text-white"
>
{t("cta")}
</a>
</section>
);
}
Client Components
"use client";
import { useTranslations } from "next-intl";
export function ContactForm() {
const t = useTranslations("contact");
return (
<form>
<label>{t("name")}</label>
<input type="text" />
<label>{t("email")}</label>
<input type="email" />
<label>{t("message")}</label>
<textarea />
<button type="submit">{t("send")}</button>
</form>
);
}
Step 8: Build a Language Switcher
"use client";
import { useLocale } from "next-intl";
import { useRouter, usePathname } from "@/i18n/routing";
import { localeNames, type Locale } from "@/i18n/config";
export function LanguageSwitcher() {
const locale = useLocale();
const router = useRouter();
const pathname = usePathname();
function handleChange(newLocale: string) {
router.replace(pathname, { locale: newLocale as Locale });
}
return (
<select
value={locale}
onChange={(e) => handleChange(e.target.value)}
className="rounded-lg border px-2 py-1 text-sm dark:border-gray-700 dark:bg-gray-800"
>
{Object.entries(localeNames).map(([code, name]) => (
<option key={code} value={code}>
{name}
</option>
))}
</select>
);
}
Step 9: SEO for Multilingual Sites
// app/[locale]/layout.tsx
import { getTranslations } from "next-intl/server";
export async function generateMetadata({
params,
}: {
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: "home" });
return {
title: t("title"),
description: t("subtitle"),
alternates: {
canonical: `https://yoursite.com/${locale}`,
languages: {
en: "https://yoursite.com/en",
es: "https://yoursite.com/es",
fr: "https://yoursite.com/fr",
de: "https://yoursite.com/de",
},
},
};
}
Need a Multilingual Website?
We build multilingual websites for businesses serving international markets. Contact us for a consultation.