Internationalization

Locale in the URL

Put the language in the first segment: /en/products, /hi/products. Everything goes under app/[locale]/ and middleware redirects /products to the best locale.

app/
└── [locale]/
    ├── layout.tsx        <html lang={locale}>
    ├── page.tsx          /en, /hi
    └── products/page.tsx /en/products, /hi/products
dictionaries/
├── en.json
└── hi.json

Choosing the Locale

Example

Middleware reads the Accept-Language header and picks the best supported locale (libraries like @formatjs/intl-localematcher do this more completely):

const SUPPORTED = ['en', 'hi', 'ta'] as const;
type Locale = (typeof SUPPORTED)[number];
const DEFAULT: Locale = 'en';

function pickLocale(acceptLanguage: string | null): Locale {
  if (!acceptLanguage) return DEFAULT;
  const ranked = acceptLanguage
    .split(',')
    .map((part) => {
      const [tag, q] = part.trim().split(';q=');
      return { lang: tag.toLowerCase().split('-')[0], q: q ? Number(q) : 1 };
    })
    .sort((a, b) => b.q - a.q);
  const hit = ranked.find((r) => (SUPPORTED as readonly string[]).includes(r.lang));
  return hit ? (hit.lang as Locale) : DEFAULT;
}

console.log(pickLocale('hi-IN,hi;q=0.9,en-US;q=0.8,en;q=0.7'));
console.log(pickLocale('fr-FR,fr;q=0.9,ta;q=0.5'));
console.log(pickLocale('de-DE'));
console.log(pickLocale(null));

Output

hi
ta
en
en

Dictionaries

// app/[locale]/dictionaries.ts
import 'server-only';

const dictionaries = {
  en: () => import('@/dictionaries/en.json').then((m) => m.default),
  hi: () => import('@/dictionaries/hi.json').then((m) => m.default),
};

export type Locale = keyof typeof dictionaries;
export const hasLocale = (l: string): l is Locale => l in dictionaries;
export const getDictionary = (locale: Locale) => dictionaries[locale]();
// app/[locale]/page.tsx
import { notFound } from 'next/navigation';
import { getDictionary, hasLocale } from './dictionaries';

export default async function Home({ params }: { params: Promise<{ locale: string }> }) {
  const { locale } = await params;
  if (!hasLocale(locale)) notFound();
  const t = await getDictionary(locale);
  return <h1>{t.home.title}</h1>; // "Today's Deals" / "आज के ऑफ़र"
}

export function generateStaticParams() {
  return [{ locale: 'en' }, { locale: 'hi' }];
}

Tip: For bigger apps, the next-intl library adds pluralisation, number/date formatting and typed message keys on top of this pattern.