First Working Translation with Next.js
This guide takes an existing TypeScript Next.js 16 App Router application from one message to one rendered translation. It uses Server Components on the Node runtime. The Next.js package README and API reference cover Client Components, server actions, and production options after this first path works.
Page outline
Before installing the native CLI, check Platform support.
Use Node.js >=22.0.0 and an application on one of the published targets.
1. Install the packages
pnpm add @palamedes/core @palamedes/react @palamedes/runtime @palamedes/next-plugin server-only
pnpm add -D @palamedes/cli @palamedes/configThe Next integration requires Next.js 16. Keep next, react, and
react-dom from the App Router application itself.
2. Describe the catalogs
# palamedes.yaml
locales: [en, de]
source-locale: en
catalogs:
- path: src/locales/{locale}
include: [app, src]3. Wire the Next plugin
// next.config.mjs
import { withPalamedes } from "@palamedes/next-plugin";
export default withPalamedes({});withPalamedes() transforms macros and automatically compiles and loads the
configured PO catalogs. The application chooses its locale; no catalog imports,
loader maps, or client catalog boundary are required.
4. Create a request-local server runtime
Add one server-only module. The adapter loads the active locale lazily and shares immutable catalog content across requests. The returned instance keeps locale, time zone, and callbacks local to the current Next render.
// src/lib/i18n.server.ts
import "server-only";
import { cache } from "react";
import { createNextServerI18n } from "@palamedes/next-plugin/server";
export const createActiveServerI18n = cache(() => createNextServerI18n({ locale: "de" }));This smallest path deliberately fixes the locale to de. Replace that choice
with your cookie, route, subdomain, or account policy before adding a locale
switcher; Locale strategies shows the trade-offs.
5. Write and extract one Server Component message
// src/app/page.tsx
import { t } from "@palamedes/core/macro";
import { createActiveServerI18n } from "../lib/i18n.server";
function translateWelcome() {
return t`Welcome to Palamedes`;
}
export default async function Page() {
await createActiveServerI18n();
return <h1>{translateWelcome()}</h1>;
}Run extraction:
pnpm exec pmds extractIt creates src/locales/en.po and src/locales/de.po.
6. Translate and run
In src/locales/de.po, set the extracted message's translation:
msgid "Welcome to Palamedes"
msgstr "Willkommen bei Palamedes"Then start the application:
pnpm devThe / route renders Willkommen bei Palamedes. That proves macro
transformation, catalog extraction, .po loading, and request-local App Router
rendering work together.
Add client code or server actions next
Client Components automatically await their selected compiled fragments for the
locale in <html lang>. Set that attribute from the same locale policy used on
the server. A locale change requires a document navigation. Optional
data-palamedes-time-zone on <html> selects the client formatting time zone.
Server Functions and Actions run in separate requests. Expose
initializeServerFunctionI18n() from src/palamedes.server.ts, resolve the
request locale there, and await createNextServerI18n({ locale }). The plugin
automatically discovers and invokes this conventional entry before action code.
Keep ordinary error.tsx and global-error.tsx views independent of translated
catalogs. A required fragment failure stops the dependent module and reaches
Next error handling; a missing compiled entry throws. Offer a full document
reload to recover after deployment or network failures, because the module
loader can cache rejected imports. Error UI should use a generic explanation
rather than display internal message IDs or raw diagnostic text.