Skip to content

prince0xdev/next-intl-tutorial

Repository files navigation

How to Add Internationalization (i18n) to Your Next.js App with next-intl

Read on Medium

Have you ever built a cool Next.js app… only to realize it only works in one language?
That’s fine if your users all speak English.
But what if tomorrow you want to reach users in France, Nigeria, or Brazil?

That’s where internationalization (i18n) comes in.
In this tutorial, I’ll show you step by step how to integrate next-intl into your Next.js project — with code, explanations, and screenshots.

Let’s go 🚀


🛠️ Step 1: Create a Next.js Project

If you don’t already have one, run:

npx create-next-app my-app
cd my-app

📦 Step 2: Install next-intl

Inside your project folder, run:

npm install next-intl

📂 Step 3: Create Translation Files

Create a new folder called messages/ inside your project.
Add your translation files:

messages/en.json

{
  "greeting": "Hello 👋",
  "welcome": "Welcome to my Next.js multilingual app!",
  "description": "This app is powered by Next.js 15 and next-intl.",
  "cta": "Start exploring features below 🚀",
  "feature1": "🌍 Switch between English and French instantly.",
  "feature2": "⚡ Enjoy blazing fast rendering with Turbopack.",
  "feature3": "🛠️ Built with love using React, TypeScript & Tailwind CSS."
}

messages/fr.json

{
  "greeting": "Salut 👋",
  "welcome": "Bienvenue sur mon application multilingue Next.js !",
  "description": "Cette application fonctionne avec Next.js 15 et next-intl.",
  "cta": "Commencez à explorer les fonctionnalités ci-dessous 🚀",
  "feature1": "🌍 Passez instantanément de l'anglais au français.",
  "feature2": "⚡ Profitez d’un rendu ultra-rapide grâce à Turbopack.",
  "feature3": "🛠️ Développée avec amour en React, TypeScript et Tailwind CSS."
}

📸 Screenshot idea: Show the messages/ folder in your project with both files.


⚙️ Step 4: Set Up next-intl Provider

In Next.js App Router, wrap your app with NextIntlClientProvider and load messages dynamically.

app/[locale]/layout.tsx

import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import { NextIntlClientProvider } from "next-intl";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

type Props = {
  children: React.ReactNode;
  params: { locale: string };
};

export default async function RootLayout({
  children,
  params: { locale },
}: Props) {
  const messages = (await import(`../../messages/${locale}.json`)).default;

  return (
    <html lang={locale}>
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        <NextIntlClientProvider messages={messages} locale={locale}>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  );
}

🌐 Step 5: Configure Routing and Middleware

To enable locale-based routing, add these files:

i18n/routing.ts

import { defineRouting } from "next-intl/routing";

export const routing = defineRouting({
  locales: ["en", "fr"],
  defaultLocale: "en",
});

middleware.ts

import createMiddleware from "next-intl/middleware";
import { routing } from "./i18n/routing";

export default createMiddleware(routing);

export const config = {
  matcher: "/((?!api|trpc|_next|_vercel|.*\\..*).*)",
};

This ensures your app automatically detects and routes users based on their language.


📝 Step 6: Use Translations in Your Components

app/[locale]/page.tsx

import LanguageSwitcher from "@/components/language-switcher";
import { useTranslations } from "next-intl";

export default function HomePage() {
  const t = useTranslations();

  return (
    <main className="p-8 max-w-3xl mx-auto">
      <LanguageSwitcher />
      <h1 className="text-4xl font-bold mb-4">{t("greeting")}</h1>
      <p className="text-lg mb-6">{t("welcome")}</p>
      <section className="bg-gray-100 p-6 rounded-2xl shadow-md">
        <p className="mb-4 text-gray-700">{t("description")}</p>
        <h2 className="text-2xl font-semibold mb-2">{t("cta")}</h2>
        <ul className="list-disc pl-6 space-y-2 text-gray-800">
          <li>{t("feature1")}</li>
          <li>{t("feature2")}</li>
          <li>{t("feature3")}</li>
        </ul>
      </section>
    </main>
  );
}

🌍 Step 7: Add a Language Switcher

components/language-switcher.tsx

"use client";
import { usePathname, useRouter } from "next/navigation";
import { useLocale } from "next-intl";
import { Globe } from "lucide-react";

export default function LanguageSwitcher() {
  const router = useRouter();
  const pathname = usePathname();
  const locale = useLocale();

  const switchTo = (lang: string) => {
    if (lang !== locale) {
      const segments = pathname.split("/");
      segments[1] = lang;
      router.replace(segments.join("/"));
    }
  };

  return (
    <div className="flex items-center">
      <div
        className="flex items-center bg-white rounded-full px-4 py-1 shadow-md cursor-pointer select-none border-[1px] border-neutral-200"
        onClick={() => switchTo(locale === "en" ? "fr" : "en")}
        style={{ minWidth: 60 }}
      >
        <Globe className="w-5 h-5 mr-2 text-neutral-300" />
        <span className="font-semibold text-gray-700">
          {locale.toUpperCase()}
        </span>
      </div>
    </div>
  );
}

📸 Screenshot idea: Show the switcher in action.


🎯 Final Thoughts

Internationalization is not just about translation.
It’s about respecting your users and making them feel like your app was built for them.

And here’s the best part: even as a junior developer, adding i18n to your projects instantly makes them look more professional and production-ready.

So next time you start a project → don’t forget to think global 🌍.


👋 Let’s Connect

If this tutorial helped you, feel free to:

  • ⭐ Check out my projects on GitHub
  • 💬 Connect with me on LinkedIn
  • 📝 Read more articles on Medium

We’re all learning. Let’s grow together 🚀

About

How to Add Internationalization (i18n) to Your Next.js App with next-intl (Step by Step)

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published