Zum Inhalt springen

Open source / Next.js / next-tongue

next-tongue

Compiler-first, type-safe i18n for the Next.js 16 App Router, built for Cache Components (pre-alpha)

Flagship · auf dieser Website genutztAuf npm
$ npm install next-tongue   # not yet published
$ npx tongue init

Used on this site: i18n readiness (en-GB at launch) · Passt zu next-herald

README

<div align="center">

next-tongue

Compiler-first, type-safe i18n for the Next.js 16 App Router. Works with Turbopack, keeps the locale in route params, zero dependencies beyond next and react.

status: beta 0.9 license: MIT Next.js 16

</div>

[!NOTE] Beta 0.9 (API freeze). Install with npm install next-tongue. Public surfaces are frozen per API freeze. Feedback welcome in issues and the ADRs.

Why

Next.js 16 made Cache Components and Partial Prerendering (PPR) the way to build fast apps, and renamed middleware to proxy.ts. Existing i18n libraries were designed before that, and it shows:

  • next-intl (~7M downloads/week) is excellent and mature, but reading the locale in cached/static scopes has been a long-running pain point (amannn/next-intl#1493, #2412), as have proxy.ts locale routing (#1362) and dynamicParams = false SSG (#2060).
  • next-international has had no release since October 2024.
  • next-intl 4.8+ has an experimental messages.precompile option that compiles ICU at build time (~9 KB less client JS, per its blog), but the client still receives catalogs through the provider.
  • Paraglide JS 2 proves the compiler approach (tree-shaken, typed message functions) and documents a Next.js 16 setup with a [locale] root layout, an explicit locale and Cache Components (guide). Its Next plugin requires --webpack.

Positioning

next-tongue is a Next.js 16–only i18n library that compiles ICU messages ahead of time and works with Turbopack, the default bundler. Client components ship only the messages they actually call. The locale comes from route params, never from headers, so translated pages stay in the static shell under Cache Components. It is in beta (0.9). Size and speed claims ship with reproducible benchmarks against next-intl (with precompile) and Paraglide.

We make no claims about runtime speed or "only the active locale on the client" until the v0.1 benchmark is published. See the critical review.

Design principles

  1. Build time over run time. If it can be parsed, validated, typed or tree-shaken at build time, it is. (ADR 0001)
  2. The locale is a route param. Only proxy.ts touches headers and cookies, so every translated scope can be cached and prerendered. (ADR 0002)
  3. Standards, not lock-in. ICU MessageFormat, plain JSON files, any OpenAI-compatible model, MIT license.
  4. Next.js-native, nothing else. Next.js 16+ App Router only, latest minor supported, with a nightly canary run. (ADR 0006, ADR 0011)
  5. Zero dependencies. Nothing beyond the next and react peers, at runtime, build time or in the CLI. (ADR 0012)

Non-goals (v1): Pages Router, Next.js ≤ 15, client-side runtime catalogs, a hosted translation service.

Features (planned)

  • Zero dependencies beyond next and react — own small ICU parser with a documented subset, Node built-ins only in the CLI, enforced by a CI dependency budget (ADR 0012).
  • Compiled messages that work with Turbopack — codegen before bundling plus a per-file call-site loader; no bundler plugin APIs (ADR 0001).
  • Client components ship only the messages they call — each t() call imports a per-key module. The recommended pattern is to translate on the server and pass strings, which ships no message code at all (ADR 0009).
  • Params-only locale for Cache Components — the locale always comes from route params (resolved by proxy.ts), never from headers()/cookies() inside cached scopes. Static shells, PPR and output: 'export' work.
  • Database and CMS messages — async build-time sources in tongue.config.ts (compiled and typed like files), plus server-only formatMessage(locale, icu, values) for copy that changes without a rebuild (Database messages, ADR 0013).
  • Translator-friendly tooling — en-XA pseudo-localisation, tongue export/import for XLIFF 2.0 and CSV, and a per-route report of keys and bytes sent to the client (Dev tools, Translator workflow).
  • Accessible headless LocaleSwitcher — keeps the same page (localized pathnames), persists the choice, works without JS.
  • Typed Intl helpers — lists, relative time, units and display names (Intl.ListFormat, RelativeTimeFormat, DisplayNames, unit formats).
  • Translator context and stale detection — $meta comments, screenshot links and max-length limits fed into translate/export; tongue check flags translations whose source changed (hashes in tongue.lock).
  • Per-user preferred language — a documented recipe now; a helper with a signed cookie and a createProxy({ resolveLocale }) hook in v1.1, with no DB call in the proxy (User preferences).
  • Security-hardened — XSS tests for rich text and database strings, strict locale validation against path traversal and header injection, and a threat model.
  • VS Code extension (v1.1) — inline previews, missing-key diagnostics, go-to-message, hover with all locales (VS Code).
  • Dev overlay — a dev-only panel with the current locale, missing keys and the messages sent to the client; click a string to jump to its key. Stripped from production.
  • Fully typed — keys, namespaces and ICU arguments ({count, plural, ...} → count: number) are checked by TypeScript.
  • One API across the App Router — server components, client components, server actions, generateMetadata with hreflang, translated OG images and emails, locale-aware Link / redirect / useRouter, and typed translated validation errors (translateIssues, schema-agnostic).
  • CLI (Node built-ins only) — tongue build, tongue init, tongue extract, tongue check (CI gate for missing / unused / mismatched keys, plus lang/dir and bidi checks) and tongue translate (bring your own LLM key via any OpenAI-compatible endpoint, with glossary and lockfile).
  • Hydration-safe dates — an explicit timeZone in config (default 'UTC'). The server's default zone is never used, and per-user zones are scoped to dynamic boundaries (ADR 0008).
  • Fast proxy at any locale count — locale matching tables are precomputed at build time; the target is < 1 ms p50 with 150 locales.
  • Monorepo-ready — shared message packages in pnpm workspaces, with portable generated types.
  • Migration from next-intl — a guide now; a codemod after v1.0.

Quick start (planned API)

npm install next-tongue
npx tongue init
// tongue.config.ts
import { defineConfig } from 'next-tongue/config';

export default defineConfig({
  locales: ['en', 'de'],
  defaultLocale: 'en',
  messages: './messages/{locale}.json',
});
// next.config.ts
import { withTongue } from 'next-tongue/next'; // config wrapper: runs codegen, registers the call-site loader
export default withTongue({ cacheComponents: true });
// proxy.ts — locales + defaultLocale required
import { createProxy } from 'next-tongue/proxy';
export const proxy = createProxy({
  locales: ['en', 'de'],
  defaultLocale: 'en',
  prefix: 'as-needed',
});
export const config = { matcher: ['/((?!api|_next|.*\\..*).*)'] };
// Note: this matcher skips every path containing a dot (e.g. /de/blog/v1.2). See Routing & proxy.
// messages/en.json
{ "home": { "title": "Welcome, {name}!", "inbox": "{count, plural, =0 {No messages} one {# message} other {# messages}}" } }
// app/[locale]/layout.tsx (excerpt)
import { localeParams } from 'next-tongue/server';
export const generateStaticParams = localeParams; // keeps the shell static under cacheComponents
// app/[locale]/page.tsx
import { getT } from 'next-tongue/server';

export default async function Page({ params }: PageProps<'/[locale]'>) {
  const { locale } = await params;
  const t = await getT(locale, 'home');
  return <h1>{t('title', { name: 'Ada' })}</h1>; // t('titel') or a missing `name` is a type error
}
// app/[locale]/inbox-badge.tsx
'use client';
import { useT } from 'next-tongue/client';

export function InboxBadge({ count }: { count: number }) {
  const t = useT('home');
  return <span>{t('inbox', { count })}</span>;
}

See Getting started for the full walkthrough.

Comparison

Honest, feature-level comparison as of October 2026. next-tongue's column is planned, not shipped. No performance numbers are claimed; benchmarks will be published with reproducible scripts once code exists.

next-tongue (planned)next-intlParaglide JS 2gt-nextnext-internationalLingui
Statusbeta 0.9mature, very activestable, activeactive, company-backedunmaintained since Oct 2024mature, active
Approachbuild-time compiler (codegen + per-file loader)runtime catalogs; experimental messages.precompile compiles ICU at build timebuild-time compilerruntime + AI translation service; experimental compilerruntime catalogsmacros + compiled catalogs
Works with Turbopack (its compiler)goalyes (loader)Next plugin requires --webpackauto-injection is webpack-only (docs)n/aSWC plugin, pinned to Next's swc_core
Next.js App Router routing helpersyes (proxy.ts, Link, redirect)yesgeneric middleware + localizeUrlyesyesno (bring your own)
Cache Components / params-only localeyes (params-only locale)supported with caveats, via root params (see #1493)documented on Next 16.4 with Cache Componentsunverifiednoroot-params example
Client gets only the messages it callsyes, per key (all locales of used keys by default)no — provider passes catalogs (narrow with pick); precompile removes the parser, not unused messagesyes, per message (all locales by default)nopartial (per-locale import)per catalog
Typed keysyesyesyes (functions)noyesn/a (source strings / macro IDs)
Typed ICU argumentsyesopt-in (v4, via generated declarations)yesnoparams typed, no ICUn/a (args come from JS template literals)
Message formatICU MessageFormat (documented subset)ICU MessageFormatinlang message format (plugins incl. ICU)ICU / JSXcustom interpolationICU (PO / JSON)
Extraction / CI check CLIyes (extract, check)experimental useExtracted (v4.5+); no built-in CI checkvia inlang toolingyes (CLI)noyes (lingui extract)
AI translationBYO key CLIno (separate products)via inlang ecosystemcore product (service)nono
Migration from next-intlguide; codemod after v1.0—nono—no
Uses next/root-params for implicit localenot by default (ADR 0007); explicit locale argrequired for Cache Components support (#1493)no (explicit locale)unverifiednoyes, in example/docs on Next 16.3+ (lingui#2645)

Sources: each project's public docs and issue tracker, checked October 2026; see the critical review for links. Corrections welcome — open an issue if any cell is wrong.

Documentation

Also available as a GitHub wiki (same content as docs/).

Roadmap

MilestoneTheme
v0.1Prove the differentiator: own ICU parser + codegen, types, getT/useT, call-site loader, Cache Components example, published bundle benchmark vs next-intl (precompile) and Paraglide — go/no-go gate
v0.2proxy.ts (always/as-needed), navigation, metadata, static export, server formatMessage, pseudo-localisation, Intl helpers, security hardening
v0.3CLI check/extract/init, dev overlay, LocaleSwitcher, translated validation errors, OG images and emails
v0.4Localized pathnames, domains, prefix: 'never', monorepo, build-time DB/CMS sources, XLIFF/CSV, translator context, stale detection, a11y/bidi checks, tongue report
v0.5tongue translate (BYO LLM, glossary, lockfile)
v0.9Beta: API freeze, root-params decision (#24)
v1.0Stable, semver / deprecation / support policy
v1.1VS Code extension, per-user preferred language helper
post-1.0next-intl codemod (next-international codemod dropped)

Full detail with acceptance criteria: ROADMAP.md · Milestones · Project board.

Support policy

From v1.0: Semantic Versioning, documented deprecations before removal, and Next.js support as in ADR 0011 — latest Next 16.x + React ≥ 19.2 (App Router). Details: Versioning.

Contributing

Design feedback is the most valuable contribution right now. Read CONTRIBUTING.md, pick an issue labelled good first issue or comment on an ADR. Please follow the Code of Conduct; report vulnerabilities per SECURITY.md.

Support

If this project is useful to you, you can support its development with a donation: Buy me a coffee. It helps keep these tools maintained, documented and free for everyone.

License

MIT © Louis Wright (Louis Wright)