Skip to main content
Back to Blog
Tutorials
3 min read
November 18, 2024

How to Add Internationalization (i18n) to Next.js

Add multi-language support to your Next.js app. Locale routing, translated content, language switcher, and SEO for multilingual sites.

Ryel Banfield

Founder & Lead Developer

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.

i18ninternationalizationNext.jslocalizationtutorial

Ready to Start Your Project?

RCB Software builds world-class websites and applications for businesses worldwide.

Get in Touch

Related Articles