#nextjs #i18n #localization #translation

Day 26 — Internationalization (i18n): Going Global

🧭 Day 26 — Internationalization (i18n): Going Global

Zero to Hero — Hands-on Next.js Tutorial

Users prefer apps in their native language. Next.js supports i18n routing out of the box (e.g., /en/about, /es/about), but we need to implement the translation logic ourselves.


🟦 1. Directory Structure

We move everything inside a [lang] dynamic segment.

src/
  app/
    [lang]/
      page.tsx
      layout.tsx

Now params.lang is available to every page and layout!


🟩 2. Middleware for Detection

We need Middleware to detect the user’s preferred language (Accept-Language header) and redirect them if the locale is missing.

// middleware.ts
import { match } from '@formatjs/intl-localematcher'
import Negotiator from 'negotiator'

let locales = ['en', 'es', 'fr']
let defaultLocale = 'en'

export function middleware(request) {
  const pathname = request.nextUrl.pathname
  
  // Check if there is any supported locale in the pathname
  const pathnameIsMissingLocale = locales.every(
    (locale) => !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`
  )
 
  // Redirect if there is no locale
  if (pathnameIsMissingLocale) {
    const locale = getLocale(request) // Implement using Negotiator
    return NextResponse.redirect(
      new URL(`/${locale}/${pathname}`, request.url)
    )
  }
}

🟧 3. Dictionaries

The simplest way to handle text is JSON files on the server.

dictionaries/en.json:

{ "hello": "Hello World", "cart": "Add to Cart" }

dictionaries/es.json:

{ "hello": "Hola Mundo", "cart": "Añadir al Carrito" }

Fetching the dictionary:

// src/app/[lang]/get-dictionary.ts
import 'server-only'

const dictionaries = {
  en: () => import('./dictionaries/en.json').then((module) => module.default),
  es: () => import('./dictionaries/es.json').then((module) => module.default),
}
 
export const getDictionary = async (locale) => dictionaries[locale]()

🟥 4. Use in Component

import { getDictionary } from './get-dictionary'
 
export default async function Page({ params: { lang } }) {
  const dict = await getDictionary(lang)
  return <button>{dict.cart}</button> // "Add to Cart" or "Añadir al Carrito"
}

🧪 Challenge: Day 26

  1. Move your page.tsx into [lang].
  2. Create en.json and es.json with a greeting.
  3. Update middleware to enforce the prefix.
  4. Visit / -> Redirects to /en.
  5. Visit /es -> Shows “Hola Mundo”.

See you tomorrow for Performance Optimization! 🏎️