Content · Intermediate

Internationalization

Locale routing, RTL support, and currency/date/number formatting for Next.js 14+ using next-intl 3.x. Ships with message files for English, French, German, Hindi, and Arabic.

Read the Getting Access guideif you haven't yet.

1. Get the file

Sign in at marrowstack.dev, open the Internationalization block, and click Copy all files. The block ships as a set of files: lib/i18n.ts, middleware config, and messages/ JSON files for each locale.

2. Prerequisites

  • Next.js 14 or 15, App Router
  • TypeScript 5+ with strict mode

3. Install

  1. Copy the block files into your project. Place message files in messages/ at the project root.
  2. Install peer dependencies:
bash
npm install next-intl

4. Environment

VariableRequiredDefaultPurpose
NEXT_PUBLIC_DEFAULT_LOCALEnoDefault locale. Defaults to en. Must be one of: en, fr, de, hi, ar

5. Wire it in

middleware.ts
import createMiddleware from 'next-intl/middleware'
import { routing } from '@/lib/i18n'

export default createMiddleware(routing)

export const config = {
  matcher: ['/((?!api|_next|_vercel|.*\..*).*)']
}
app/[locale]/layout.tsx
import { NextIntlClientProvider } from 'next-intl'
import { getMessages } from 'next-intl/server'
import { notFound } from 'next/navigation'
import { routing } from '@/lib/i18n'

export default async function LocaleLayout({
  children,
  params: { locale },
}: {
  children: React.ReactNode
  params: { locale: string }
}) {
  if (!routing.locales.includes(locale as never)) notFound()

  const messages = await getMessages()

  return (
    <html lang={locale} dir={locale === 'ar' ? 'rtl' : 'ltr'}>
      <body>
        <NextIntlClientProvider messages={messages}>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  )
}
Usage in a page
import { useTranslations } from 'next-intl'

export default function HomePage() {
  const t = useTranslations('home')
  return <h1>{t('title')}</h1>
}

6. Adding a new locale

  1. Add the locale code to the locales array in lib/i18n.ts.
  2. Create messages/<locale>.json with translations for all keys present in messages/en.json.
  3. Set the dir attribute in your layout for RTL locales (Arabic, Hebrew, Urdu).

7. Verify it works

  1. Navigate to /fr and confirm the page renders in French.
  2. Navigate to /ar and confirm the layout direction is RTL.
  3. Call formatCurrency(1234.5, 'USD', 'de') and confirm the German currency format.

8. Failure modes & fixes

MISSING_MESSAGE: 'home.title'

Cause: A key exists in one locale's message file but is absent in another.

Fix: Keep all message files in sync. Add the missing key to the failing locale's JSON file.

locale param missing from URL

Cause: The app layout is at app/layout.tsx instead of app/[locale]/layout.tsx.

Fix: Move your root layout into app/[locale]/layout.tsx as shown in the wiring example.

Middleware not running

Cause: The matcher in middleware.ts is excluding the locale paths.

Fix: Use the matcher shown above — it excludes API routes, static files, and Next.js internals only.