NextjsTranslations & Dictionaries

Translations & Dictionaries

Once routing tells you which locale a page should render in, you still need the actual translated text. The pattern nearly every i18n approach converges on is the same: a dictionary — a plain JSON (or JS/TS) file per locale, mapping keys to translated strings — loaded on demand for whichever locale the current request needs.
The dictionary pattern

messages/en.json

JSON
{
  "HomePage": {
    "title": "Welcome",
    "subtitle": "Learn Next.js, step by step"
  }
}

messages/de.json

JSON
{
  "HomePage": {
    "title": "Willkommen",
    "subtitle": "Next.js Schritt für Schritt lernen"
  }
}
Namespacing keys by page or feature (HomePage, Navbar, and so on) keeps large dictionaries navigable and avoids collisions between unrelated strings that happen to share a short key like title.
Loading the right dictionary on the server

app/[locale]/page.tsx — loading a dictionary manually

TSX
async function getDictionary(locale: string) {
  return import(`../../../messages/${locale}.json`).then((m) => m.default)
}

export default async function HomePage({
  params,
}: {
  params: { locale: string }
}) {
  const dict = await getDictionary(params.locale)

  return (
    <div>
      <h1>{dict.HomePage.title}</h1>
      <p>{dict.HomePage.subtitle}</p>
    </div>
  )
}
Using a library: next-intl's useTranslations

Hand-loading dictionaries works, but it's repetitive across every page and doesn't give Client Components an ergonomic way to reach the same strings. Libraries like next-intl wrap the dictionary pattern in a hook that works in both Server and Client Components.

A component using next-intl

TSX
import { useTranslations } from 'next-intl'

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

  return (
    <div>
      <h1>{t('title')}</h1>
      <p>{t('subtitle')}</p>
    </div>
  )
}

Approach

Server Components

Client Components

Setup effort

Manual dictionary loading

Yes

Needs prop-drilling or context

Low, but repetitive

next-intl (useTranslations)

Yes

Yes, same hook

One-time provider/config setup

Note
The mechanism — loading a JSON file and looking up a key — is the easy part. What's genuinely hard at scale is translation management: keeping every locale's dictionary in sync as strings are added or changed, catching missing keys before they ship, and getting accurate human translations into the file in the first place. Most teams eventually reach for a translation management platform (Lokalise, Crowdin, Phrase, etc.) rather than hand-editing JSON files once there's more than a couple of languages and a few dozen strings.
  • A translation dictionary is typically one JSON file per locale, with keys namespaced by page or feature.

  • The dictionary for the current locale is loaded in a Server Component (or via generateStaticParams) and threaded down to whatever needs it.

  • Libraries like next-intl's useTranslations hook wrap that lookup and work in both Server and Client Components.

  • Beyond a handful of strings, keeping dictionaries in sync across locales is usually the harder problem — not the loading mechanism itself.