FlagshipPre-alpha · planned APINext.js 16 · MIT

next-tonguei18n, compiled. For the Next.js 16 App Router.

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.

npm install next-tongue # not yet published

Not published to npm yet. No library code exists; everything here is the planned API from the README and design docs, open for feedback.

messages/en.json · ICU plural playground
{ "inbox": "{count, plural, =0 {No messages} one {# message} other {# messages}}" }
1

t('title', { name })

Welcome, Ada!

t('inbox', { count })

1 message

plural category: one · typed as count: number

Illustration: the README's example messages rendered in your browser with Intl.PluralRules. next-tongue is not published yet.

01 · The problem

Next.js 16 changed the rules.

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

next-intl

Excellent and mature, but reading the locale in cached/static scopes has been a long-running pain point, as have proxy.ts locale routing and dynamicParams = false SSG.

next-international

No release since October 2024.

next-intl 4.8+ precompile

Compiles ICU at build time, but the client still receives catalogs through the provider.

Paraglide JS 2

Proves the compiler approach with typed, tree-shaken message functions, but its Next plugin requires --webpack.

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.

02 · How it works

A config wrapper, codegen and a per-file loader.

1

Codegen before bundling

tongue build (or withTongue when next.config loads) parses messages with its own ICU parser, validates plurals and arguments, and writes .tongue/types.d.ts, per-key modules and a manifest.

2

Per-file call-site loader

A Turbopack/webpack loader rewrites t('inbox') in client files into a static import of #tongue/msg/home/inbox. No bundler plugin APIs.

3

Only what you call ships

The bundler dedupes and tree-shakes per key across routes. Translating on the server and passing strings ships no message code at all.

How next-tongue compiles messagesmessages/*.json and build-time sources go through the own ICU parser and validation, then tongue build writes types.d.ts, per-key modules, namespace modules and a manifest. A per-file loader rewrites client t() calls into static imports of per-key modules. messages/*.jsonICU MessageFormat sources (DB/CMS)async, build time own ICU parservalidate plurals + args .tongue/types.d.ts .tongue/msg/{ns}/{key}.js .tongue/ns/{ns}.js .tongue/manifest.json TypeScripttyped keys + args client filet('inbox', { count }) per-file loaderturbopack.rules / webpack static import, per keyimport { inbox } from '#tongue/msg/home/inbox'
Based on docs/architecture.md (planned design).

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
  5. Zero dependencies. Nothing beyond the next and react peers, at runtime, build time or in the CLI. ADR 0012

03 · Features planned

Everything in the README, nothing more.

Zero dependencies

Own small ICU parser with a documented subset, Node built-ins only in the CLI, enforced by a CI dependency budget.

Compiled, works with Turbopack

Codegen before bundling plus a per-file call-site loader; no bundler plugin APIs.

Client ships only what it calls

Each t() call imports a per-key module. Translate on the server and pass strings to ship no message code at all.

Params-only locale

The locale always comes from route params (resolved by proxy.ts), never from headers() or cookies() inside cached scopes. Static shells, PPR and output: 'export' work.

Fully typed

Keys, namespaces and ICU arguments ({count, plural, ...} → count: number) are checked by TypeScript.

Database and CMS messages

Async build-time sources in tongue.config.ts, compiled and typed like files, plus server-only formatMessage for copy that changes without a rebuild.

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.

One API across the App Router

Server components, client components, server actions, generateMetadata with hreflang, translated OG images and emails, locale-aware Link, redirect and useRouter.

CLI on Node built-ins

tongue build, init, extract, check (CI gate for missing, unused and mismatched keys) and translate with any OpenAI-compatible endpoint.

Hydration-safe dates

An explicit timeZone in config (default 'UTC'). The server's default zone is never used.

Accessible LocaleSwitcher

Headless, keeps the same page with localized pathnames, persists the choice, works without JS.

Security-hardened

XSS tests for rich text and database strings, strict locale validation against path traversal and header injection, and a threat model.

04 · Quick start planned API

From install to typed output.

// tongue.config.ts
import { defineConfig } from 'next-tongue/config';

export default defineConfig({
  locales: ['en', 'de'],
  defaultLocale: 'en',
  messages: './messages/{locale}.json',
});

Full walkthrough: Getting started · API reference · CLI

05 · Configuration

tongue.config.ts reference

OptionTypeDefaultNotes
localesreadonly string[]— (required)Validated as BCP 47. Becomes the Locale union type.
defaultLocaleLocale— (required)The source language for extract/translate.
messagesstring'./messages/{locale}.json'Must contain {locale}; may contain {namespace}.
fallbackPartial<Record<Locale, Locale>>CLDR parent localesResolved at compile time, so there is no runtime lookup.
timeZoneIANA zone string'UTC'Passed explicitly to every Intl.DateTimeFormat; the server's default zone is never used.
sourcesArray<() => Promise<…>>[]Build-time database/CMS messages, merged after files, then compiled and typed.
pseudoobject | booleanfalseAdds a pseudo-locale (default en-XA) generated from defaultLocale.
client.split'none' | 'locale''none''locale' (opt-in, after the benchmark) splits per-key modules per locale.
devOverlaybooleantrueDev-only overlay; never included in production builds.
routing.prefix'always' | 'as-needed' | 'never''as-needed''as-needed' redirects /{defaultLocale}/* to /*.
routing.localeDetectionbooleantrueOnly the proxy reads headers/cookies.
routing.pathnamesRecord<string, …>undefinedTyped: Link href only accepts known internal pathnames when set.
outDirstring'.tongue'Git-ignore it.
strict.*'error' | 'warn' | 'off'see docsUsed by both the build and tongue check.

Abridged. Full reference in docs/configuration.md.

06 · FAQ

Straight answers.

From docs/faq.md. Something missing? Open an issue.

Is this usable today?

No. It is pre-alpha with no published code. Follow the roadmap.

Why not just contribute to next-intl?

next-intl is great and mature. next-tongue explores a different core trade-off (build-time compilation that works with Turbopack, message-level client tree-shaking, params-only locale and zero dependencies) which would be a breaking redesign there. A migration guide exists; a next-intl codemod is planned after v1.0.

Why not Paraglide?

Paraglide pioneered compiled, tree-shaken messages and documents a Next.js 16 setup with Cache Components, but its Next plugin requires --webpack. next-tongue applies the compiler idea with a Turbopack-compatible loader and Next.js-specific pieces built in: proxy.ts routing, metadata/hreflang, tongue check.

Does it require Cache Components?

No. It works with or without cacheComponents: true; it just never breaks it.

Can I load messages from a database or CMS?

Yes, two ways: async build-time sources in tongue.config.ts (compiled and typed like files, need a rebuild to change), or the server-only formatMessage(locale, icu, values) for copy that changes at request time (untyped). No catalog is sent to the browser.

Will there be benchmarks?

Yes, in v0.1: a published bundle benchmark against next-intl (with precompile) and Paraglide is the go/no-go gate. Until then there are no size or speed claims.

Which Next.js versions?

The latest 16.x minor, tested nightly against canary. Pages Router is not supported.

07 · Roadmap

Where it's going.

  1. v0.1

    Prove 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

    next up
  2. v0.2

    proxy.ts (always/as-needed), navigation, metadata, static export, server formatMessage, pseudo-localisation, Intl helpers, security hardening

  3. v0.3

    CLI check/extract/init, dev overlay, LocaleSwitcher, translated validation errors, OG images and emails

  4. v0.4

    Localized pathnames, domains, prefix: 'never', monorepo, build-time DB/CMS sources, XLIFF/CSV, translator context, stale detection, a11y/bidi checks, tongue report

  5. v0.5

    tongue translate (BYO LLM, glossary, lockfile)

  6. v0.9

    Beta: API freeze, root-params decision

  7. v1.0

    Stable, semver guarantees

  8. v1.1

    VS Code extension, per-user preferred language helper

  9. post-1.0

    next-intl codemod

Scope freeze: no new features before v0.1 ships. Acceptance criteria in ROADMAP.md.

Support this project

Support open-source security tools

I build free, open-source tools that help developers ship safer websites. Every coffee helps keep them maintained, documented and free for everyone.

Buy me a coffee (opens Stripe in a new tab)

Secure checkout by Stripe

Portrait of Louis Wright

About the author

Louis Wright

Cyber security researcher and software engineer based in the UK.

More tools by Louis Wright

58 tools

Sentinels security scanners & CI gates · 20