Skip to main content
Localizia

Internationalization

Overview

The app is fully internationalized with next-intl. It ships with en and es; every page lives under src/app/[locale]/ and is rendered in the reader's language. Routing and locales are defined in src/i18n/routing.ts.

Messages

UI strings live in messages/en.json and messages/es.json, grouped by namespace. Never hardcode user-visible text:

// Client component
const t = useTranslations("marketing")
return <h1>{t("home.headline")}</h1>

// Server component
const t = await getTranslations("marketing")

Add every new key to both en.json and es.json in the same change. A test (messages-parity.test.ts) fails the build if the two files drift out of sync, so missing translations can't slip through.

Always use the navigation helpers from @/i18n/navigationLink, redirect, useRouter, usePathname — never the raw Next.js equivalents. They keep the active locale in the URL automatically:

import { Link } from "@/i18n/navigation"
<Link href="/pricing"></Link>   // resolves to /en/pricing or /es/pricing

Formatting

Format dates, numbers, and currencies with next-intl's formatter, not toLocaleDateString or Intl directly:

const format = useFormatter()
format.dateTime(date, { dateStyle: "long" })

The user's language drives everything

A signed-in user's saved locale preference drives the entire UI — panel and public pages alike — and their emails. The preference wins over the URL locale; the proxy (src/proxy.ts) redirects to it. A public language switcher persists the choice before navigating.

Localized errors and emails

  • tRPC errors carry translation keys, not English strings (see The tRPC API), so the UI translates them.
  • Email templates accept a locale prop and render in the recipient's language (see Email).

RTL-readiness

Use CSS logical properties (ps/pe, ms/me, border-s) rather than directional ones (pl/pr) in your styling layer, so a right-to-left locale lays out correctly if you add one.

Adding a locale

Add the locale to src/i18n/routing.ts, create its messages/<locale>.json (keys matching the others), and — for these docs — a src/content/docs/<locale>/ folder. Missing doc pages fall back to the default locale automatically.