Claude Code for next-intl: Internationalization for Next.js — Claude Skills 360 Blog
Blog / Frontend / Claude Code for next-intl: Internationalization for Next.js
Frontend

Claude Code for next-intl: Internationalization for Next.js

Published: April 22, 2027
Read time: 7 min read
By: Claude Skills 360

next-intl provides internationalization for Next.js App Router — createNavigation({ locales, defaultLocale }) generates typed Link, redirect, usePathname, and useRouter that handle locale prefixes automatically. useTranslations("namespace") returns a typed t() function in Client and Server Components. t("key", { count }) handles ICU pluralization: { count, plural, one {# item} other {# items} }. useFormatter() returns locale-aware format.dateTime(date), format.number(n, { style: "currency" }), and format.list(arr) functions. Messages live in messages/en.json and messages/de.json. createNextIntlPlugin() wraps next.config.js. Middleware with createMiddleware detects browser locale from Accept-Language and redirects to the correct prefix. TypeScript autocompletion works with useTranslations<Messages>(). Claude Code generates next-intl routing configuration, message catalogs, typed translation hooks, pluralization patterns, and locale-aware formatters.

CLAUDE.md for next-intl

## next-intl Stack
- Version: next-intl >= 3.14
- Config: createNextIntlPlugin() in next.config.ts — i18n.ts file with locales + defaultLocale
- Routing: createNavigation({ locales, defaultLocale }) → typed Link, redirect, useRouter, usePathname
- Server: import { getTranslations } from "next-intl/server"; const t = await getTranslations("namespace")
- Client: const t = useTranslations("namespace") — re-renders on locale change
- Format: const format = useFormatter(); format.dateTime(date, { dateStyle: "short" })
- ICU: "{count, plural, =0 {no items} one {# item} other {# items}}"
- Types: TypeScript autocomplete via messages/en.json interface declaration
- Middleware: createMiddleware({ locales, defaultLocale }) in middleware.ts

Project Structure

src/
  i18n/
    routing.ts          — locales, defaultLocale, pathnames
    navigation.ts       — createNavigation exports
    request.ts          — getRequestConfig
  app/
    [locale]/
      layout.tsx        — NextIntlClientProvider
      page.tsx
  middleware.ts         — locale detection + redirect
  messages/
    en.json
    de.json
    fr.json
    ja.json

i18n Configuration

// src/i18n/routing.ts — locale configuration
import { defineRouting } from "next-intl/routing"

export const routing = defineRouting({
  locales: ["en", "de", "fr", "ja"] as const,
  defaultLocale: "en",
  // Prefix strategy: "always" (default) | "as-needed" (no prefix for default)
  localePrefix: "as-needed",
  // Typed pathnames — same path, different URL per locale
  pathnames: {
    "/": "/",
    "/about": {
      en: "/about",
      de: "/uber-uns",
      fr: "/a-propos",
      ja: "/について",
    },
    "/blog": "/blog",
    "/blog/[slug]": "/blog/[slug]",
    "/products": {
      en: "/products",
      de: "/produkte",
      fr: "/produits",
      ja: "/製品",
    },
  },
})

export type AppLocale = (typeof routing.locales)[number]
export type AppPathnames = keyof typeof routing.pathnames
// src/i18n/navigation.ts — typed navigation utilities
import { createNavigation } from "next-intl/navigation"
import { routing } from "./routing"

// Typed Link, redirect, useRouter, usePathname
export const { Link, redirect, useRouter, usePathname, getPathname } =
  createNavigation(routing)
// src/i18n/request.ts — server-side locale resolution
import { getRequestConfig } from "next-intl/server"
import { routing } from "./routing"

export default getRequestConfig(async ({ requestLocale }) => {
  let locale = await requestLocale

  // Validate that the locale is supported
  if (!locale || !routing.locales.includes(locale as any)) {
    locale = routing.defaultLocale
  }

  return {
    locale,
    messages: (await import(`../../messages/${locale}.json`)).default,
    // Time zone for server-side date formatting
    timeZone: "UTC",
  }
})

Middleware

// middleware.ts — locale detection and routing
import createMiddleware from "next-intl/middleware"
import { routing } from "./src/i18n/routing"

export default createMiddleware(routing)

export const config = {
  // Match all routes except static files
  matcher: [
    "/",
    "/(de|fr|ja|en)/:path*",
    "/((?!_next|_vercel|.*\\..*).*)".
  ],
}

Locale Layout

// app/[locale]/layout.tsx — provide messages to Client Components
import { NextIntlClientProvider } from "next-intl"
import { getMessages, getLocale } from "next-intl/server"
import { notFound } from "next/navigation"
import { routing } from "@/i18n/routing"
import type { AppLocale } from "@/i18n/routing"

export function generateStaticParams() {
  return routing.locales.map(locale => ({ locale }))
}

export default async function LocaleLayout({
  children,
  params,
}: {
  children: React.ReactNode
  params: Promise<{ locale: string }>
}) {
  const { locale } = await params

  if (!routing.locales.includes(locale as AppLocale)) {
    notFound()
  }

  // Pass all messages to Client Components
  const messages = await getMessages()

  return (
    <html lang={locale} dir={locale === "ar" ? "rtl" : "ltr"}>
      <body>
        <NextIntlClientProvider messages={messages}>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  )
}

Message Catalogs

// messages/en.json
{
  "nav": {
    "home": "Home",
    "about": "About",
    "products": "Products",
    "blog": "Blog",
    "pricing": "Pricing",
    "signIn": "Sign in",
    "signUp": "Get started"
  },
  "common": {
    "loading": "Loading...",
    "error": "Something went wrong",
    "retry": "Try again",
    "cancel": "Cancel",
    "save": "Save changes",
    "delete": "Delete",
    "edit": "Edit",
    "back": "Back"
  },
  "cart": {
    "title": "Shopping cart",
    "empty": "Your cart is empty",
    "itemCount": "{count, plural, =0 {No items} one {# item} other {# items}}",
    "subtotal": "Subtotal",
    "total": "Total",
    "checkout": "Checkout",
    "addedToCart": "{name} added to cart",
    "removeItem": "Remove {name} from cart"
  },
  "product": {
    "addToCart": "Add to cart",
    "outOfStock": "Out of stock",
    "reviewCount": "{count, plural, =0 {No reviews} one {# review} other {# reviews}}",
    "rating": "Rated {rating} out of 5",
    "price": "{price, number, ::currency/USD}"
  },
  "errors": {
    "required": "{field} is required",
    "minLength": "{field} must be at least {min} characters",
    "invalidEmail": "Please enter a valid email address",
    "404": "Page not found",
    "500": "Server error"
  }
}

Server and Client Components

// app/[locale]/products/page.tsx — Server Component translations
import { getTranslations } from "next-intl/server"
import { setRequestLocale } from "next-intl/server"

export default async function ProductsPage({
  params,
}: {
  params: Promise<{ locale: string }>
}) {
  const { locale } = await params
  // Enable static rendering with locale
  setRequestLocale(locale)

  const t = await getTranslations("product")

  return (
    <div>
      <h1>Products</h1>
      <p>{t("addToCart")}</p>
    </div>
  )
}

// components/CartBadge.tsx — Client Component translations
"use client"
import { useTranslations, useFormatter } from "next-intl"

export function CartBadge({ count, total }: { count: number; total: number }) {
  const t = useTranslations("cart")
  const format = useFormatter()

  return (
    <div>
      {/* ICU pluralization */}
      <span>{t("itemCount", { count })}</span>

      {/* Locale-aware currency formatting */}
      <span>{format.number(total / 100, { style: "currency", currency: "USD" })}</span>
    </div>
  )
}

// components/LocaleSwitcher.tsx — language picker
"use client"
import { useLocale, useTranslations } from "next-intl"
import { useRouter, usePathname } from "@/i18n/navigation"
import { routing } from "@/i18n/routing"

export function LocaleSwitcher() {
  const locale = useLocale()
  const router = useRouter()
  const pathname = usePathname()

  const localeNames: Record<string, string> = {
    en: "English",
    de: "Deutsch",
    fr: "Français",
    ja: "日本語",
  }

  return (
    <select
      value={locale}
      onChange={e => router.replace(pathname, { locale: e.target.value })}
      className="input text-sm"
    >
      {routing.locales.map(loc => (
        <option key={loc} value={loc}>
          {localeNames[loc]}
        </option>
      ))}
    </select>
  )
}

For the react-i18next alternative when an established i18n library with a larger plugin ecosystem, lazy loading of translation namespaces with Suspense, and compatibility with frameworks beyond Next.js is preferred — react-i18next is framework-agnostic and works in React Native, Gatsby, and Vite apps while next-intl is Next.js-specific, see the react-i18next guide. For the Paraglide.js alternative when compile-time message optimization generating typed message functions with zero-runtime overhead is preferred — Paraglide statically extracts translations at build time with smaller bundles than next-intl’s runtime JSON loading, see the Paraglide guide. The Claude Skills 360 bundle includes next-intl skill sets covering routing, message catalogs, and formatting. Start with the free tier to try Next.js i18n generation.

Keep Reading

Frontend

Claude Code for Chart.js Advanced: Custom Plugins and Mixed Charts

Advanced Chart.js patterns with Claude Code — chart.register() for tree-shaking, mixed chart types combining bar and line, custom plugin API with beforeDraw and afterDatasetsDraw hooks, ScriptableContext for computed colors, ChartDataLabels plugin for value labels, chartjs-plugin-zoom for pan and zoom, custom gradient fills via ctx.createLinearGradient, ChartJS annotation plugin for threshold lines, streaming data with chartjs-plugin-streaming, and react-chartjs-2 with useRef and chart instance.

6 min read Jun 27, 2027
Frontend

Claude Code for Nivo: Rich SVG and Canvas Charts

Build rich data visualizations with Nivo and Claude Code — ResponsiveLine and ResponsiveBar for adaptive charts, ResponsiveHeatMap for matrix data, ResponsiveTreeMap for hierarchal data, ResponsiveSunburst for nested proportions, ResponsiveChord for relationship diagrams, ResponsiveCalendar for activity heat maps, ResponsiveNetwork for force graphs, NivoTheme for consistent styling, tooltip customization with sliceTooltip, and motion config for spring animations.

6 min read Jun 26, 2027
Frontend

Claude Code for Victory Charts: React Native and Web Charts

Build cross-platform charts with Victory and Claude Code — VictoryChart, VictoryLine, VictoryBar, and VictoryScatter for web and React Native, VictoryPie for donut charts, VictoryArea for stacked areas, VictoryAxis for custom axes, VictoryTooltip and VictoryVoronoiContainer for hover tooltips, VictoryBrushContainer for range selection, VictoryZoomContainer for pan and zoom, VictoryLegend for series labels, custom theme with VictoryTheme, and VictoryStack for grouped bars.

6 min read Jun 25, 2027

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free