Next.jsnext-intli18n

Implementing Multilingual Support in Next.js with next-intl

A guide to implementing efficient multilingual support in a Next.js App Router project using next-intl, including directory structure, routing, language switching, and best practices.

RaytonX
6 min read

next-intl is an internationalization (i18n) library designed specifically for the Next.js App Router. It is lightweight, easy to use, and deeply integrated with the App Router.

1. Installation

Install next-intl using pnpm.

2. Configuration Overview

  • messages/: Stores translation files such as en.json and zh.json
  • src/i18n/: Contains language-related configuration and helper functions

Steps to Set Up Multilingual Support

  1. Integrate the next-intl plugin in next.config.js

    import type { NextConfig } from "next";
    import createNextIntlPlugin from "next-intl/plugin";
     
    const nextConfig: NextConfig = {};
     
    const withNextIntl = createNextIntlPlugin();
    export default withNextIntl(nextConfig);
  2. Use TypeScript files like en.ts and zh.ts instead of JSON for translation content to improve flexibility

  3. Create a getI18n function to dynamically load translation objects

    // src/i18n/index.ts
    import { en } from "./en";
    import { zh } from "./zh";
     
    const locales = { zh, en };
     
    export function getI18n(locale: string) {
      return locales[locale as keyof typeof locales] || locales["en"];
    }
  4. Define supported locales and routing settings in routing.ts

    // src/i18n/routing.ts
    import { defineRouting } from "next-intl/routing";
     
    export const routing = defineRouting({
      // A list of all locales that are supported
      locales: ["en", "zh"],
     
      // Used when no locale matches
      defaultLocale: "en",
      localeDetection: false,
    });
  5. Load language messages dynamically in request.ts based on the incoming locale

    // src/i18n/request.ts
    import { hasLocale } from "next-intl";
    import { getRequestConfig } from "next-intl/server";
    import { routing } from "./routing";
     
    export default getRequestConfig(async ({ requestLocale }) => {
      // Typically corresponds to the `[locale]` segment
      const requested = await requestLocale;
      const locale = hasLocale(routing.locales, requested)
        ? requested
        : routing.defaultLocale;
     
      return {
        locale,
        messages: (await import(`../../messages/${locale}.json`)).default,
      };
    });
  6. Wrap navigation methods like Link in navigation.ts to automatically handle locale prefixes

    // src/i18n/navigation.ts
    import { createNavigation } from "next-intl/navigation";
    import { routing } from "./routing";
     
    // Lightweight wrappers around Next.js' navigation
    // APIs that consider the routing configuration
    export const { Link, redirect, usePathname, useRouter, getPathname } =
      createNavigation(routing);

Locale-Aware Layout

In src/app/[locale]/layout.tsx, set the <html lang="xx"> attribute according to the current locale and wrap the app with NextIntlClientProvider.

// src/app/[locale]/layout.tsx
import { routing } from "@/i18n/routing";
import { NextIntlClientProvider, hasLocale } from "next-intl";
import { notFound } from "next/navigation";
 
export default async function LocaleLayout({
  children,
  params,
}: {
  children: React.ReactNode;
  params: Promise<{ locale: string }>;
}) {
  const { locale } = await params;
  if (!hasLocale(routing.locales, locale)) {
    notFound();
  }
 
  return (
    <html lang={locale}>
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        <NextIntlClientProvider>{children}</NextIntlClientProvider>
      </body>
    </html>
  );
}

3. Using Translations

  • Use useTranslations in components to access localized strings

    import {useTranslations} from 'next-intl';
     
    function About() {
      const t = useTranslations('About');
      return <h1>{t('title')}</h1>;
    }
  • For complex content structures, use getI18n(locale) to load translation objects

    import { useLocale } from "next-intl";
     
    export default function Header() {
      const locale = useLocale();
      const t = getI18n(locale);
    }
    return (
      <div>
        {t.services.items.map((service, index) => (
          <Card key={index}>
              {service.description}
          </Card>
        ))}
      </div>
    )
  • The custom Link component automatically handles language-prefixed routes

    import { Link } from "@/i18n/navigation";
     
    <Link href={posts[0].url}
      className="inline-flex items-center px-6 py-3 rounded-lg bg-blue-600 text-white font-medium hover:bg-blue-700 transition-colors"
    >
      Read More →
    </Link>

With this setup, next-intl provides a clean and scalable solution for adding multilingual support to Next.js applications.