--- name: lingui-framework-setup description: Set up Lingui in a React framework. Use when adding Lingui to a Next.js App Router, Vite, React Router 7, Remix, or TanStack Start project, when wiring locale detection, locale-prefixed URLs, or SSR locale resolution, or when a working setup breaks after a framework or build-tool upgrade. --- # Lingui Framework Setup Correct Lingui setup is framework-shaped: what works in a Vite SPA breaks in React Server Components, and a module-level `i18n` singleton that is fine in the browser bleeds locales across requests under SSR. This skill routes to a framework-native recipe instead of a generic `I18nProvider` answer. **Work in this order: detect the stack → apply the common steps → follow the framework reference → run the verification sequence.** ## Step 1 — Detect the stack Read `package.json` and the build config before recommending anything. Never guess the compiler — the macro transform silently does nothing when wired into the wrong one. | Signal | Stack | Reference | |---|---|---| | `next` in deps, `app/` or `src/app/` with `layout.tsx` | Next.js App Router | [nextjs-app-router.md](references/nextjs-app-router.md) | | `next` in deps, `pages/` with `_app.tsx`, no `app/` | Next.js Pages Router | Supported by Lingui, brief notes in [nextjs-app-router.md](references/nextjs-app-router.md) | | `@tanstack/react-start` in deps | TanStack Start (SSR) | [tanstack-start.md](references/tanstack-start.md) | | `@react-router/dev` in devDeps + `react-router.config.*` | React Router 7 framework mode (SSR) | [react-router-remix.md](references/react-router-remix.md) | | `@remix-run/dev` ≥ 2.7 in devDeps + `vite` | Remix v2 (Vite build, SSR) | [react-router-remix.md](references/react-router-remix.md) | | `vite` + `react`, none of the above | Vite SPA (incl. declarative React Router, TanStack Router) | [vite-spa.md](references/vite-spa.md) | Compiler detection (decides which macro-transform package to install): | Signal | Compiler | Macro transform | |---|---|---| | `@vitejs/plugin-react-swc` | SWC | `@lingui/swc-plugin` (pin exactly — see the swc-plugin-compatibility skill) | | `@vitejs/plugin-react` `^5` or lower | Babel | `@lingui/babel-plugin-lingui-macro` via the plugin's `babel` option | | `@vitejs/plugin-react` `^6`+, Vite 8 | Babel, standalone pass | v6 removed the `babel` option — run the macro as its own pass: `@rolldown/plugin-babel` + `linguiTransformerBabelPreset()`. Keeps the stock React plugin | | `@vitejs/plugin-react` `^6`+, Vite ≤ 7 | Babel, standalone pass | Same, via `vite-plugin-babel` (no Rolldown). Switching to `@vitejs/plugin-react-swc` also works, at the cost of an exact pin | | Next.js, no `.babelrc` | SWC | `@lingui/swc-plugin` via `experimental.swcPlugins` | | Next.js with `.babelrc` | Babel | `@lingui/babel-plugin-lingui-macro` in `.babelrc` (a Babel config disables Next's SWC) | One more distinction that changes everything: `react-router` in deps **without** `@react-router/dev` is a declarative SPA (`` in JSX) — that is the Vite SPA reference, not the framework-mode one. Route-module `loader`/`+types` code written into a declarative SPA is dead on arrival. ## Step 2 — Common steps (every framework) **Version gate.** Lingui 6 is current: ESM-only, requires Node ≥ 22.19 (or ≥ 24). Install `@lingui/core@^6 @lingui/react@^6` and dev `@lingui/cli@^6`. If the project cannot meet the Node requirement, pin every `@lingui/*` package to `^5` — do not mix majors. Do not install `@lingui/macro`: since v5 the macros live at `@lingui/react/macro` and `@lingui/core/macro`. **Per-stack extras** (details in each reference): | Stack | Extra runtime | Extra dev | |---|---|---| | Next.js App Router | — | `@lingui/swc-plugin` (exact pin) | | Vite SPA | `@lingui/detect-locale` | `@lingui/vite-plugin` + transform plugin per compiler | | React Router 7 / Remix | — | `@lingui/vite-plugin`, `@lingui/format-po`, transform plugin | | TanStack Start | — | `@lingui/vite-plugin`, transform plugin | `@lingui/detect-locale` is browser-only (`navigator`, `localStorage`, `window.location`). Never install it on an SSR stack — it throws on the server or desyncs hydration; SSR stacks resolve locale from the request (cookie / `Accept-Language`) instead. **Config and catalogs.** Create `lingui.config.ts` with `sourceLocale`, `locales`, and one `catalogs` entry; the lingui-best-practices skill owns catalog hygiene (build-script wiring, gitignore rules, CI drift check) and the single-sourced locale module (`locales.ts` with `resolveLocale`, `getDirection`, `localeDisplayName`) — reuse both, do not re-derive them. **Two catalog-loading models.** On Vite-based stacks, `@lingui/vite-plugin` compiles `.po` on import — `await import('./locales/en/messages.po')` just works, there are no compiled artifacts to script or gitignore. Next.js has no Vite pipeline: run `lingui compile` prepended to `dev`/`build` scripts and import the compiled catalogs (`@lingui/loader` is webpack-only and Turbopack is the Next 15/16 default, so a loader-based setup is a trap). ## Step 3 — Framework reference Read the matching reference file fully before editing the project. Every reference follows the same section contract, in order: **packages → build-tool integration → `lingui.config` → locale resolution → provider/layout wiring → routing strategies → language switcher → gotchas → verification** If the project needs locale-prefixed URLs, every reference offers the same three strategies — unprefixed source locale, all locales prefixed, or no URL locale (cookie/storage only). Restructuring routes is invasive: ask the user which strategy they want before moving files. ## Step 4 — Verify Run in this order; each step must pass before the next: ```bash npx lingui extract --clean # catalogs regenerate; count matches expectations npx lingui compile # only on stacks with compiled catalogs (Next.js) npx tsc --noEmit # types resolve, incl. catalog imports npm run build # macro wiring errors surface here ``` **Then prove the transform ran.** A green build does not: a mis-wired macro transform is silently a no-op and the app ships in the source language. Translate one string in a non-source catalog, run the app in that locale, and confirm the translation reaches the browser (each reference gives the stack's exact check — `curl` the server-rendered HTML on SSR stacks, hard-load a deep link on the SPA). The setup is done when you have seen a translated string, not when the build is green. Raw untranslated text at that point is a compiler wiring problem — wrong plugin, bare-string SWC entry, or the `@vitejs/plugin-react@6` babel trap; see the swc-plugin-compatibility skill. ## Adding a new framework Another stack (a new meta-framework, a new build tool) costs two edits: one row in the Step 1 detection table, and one file under `references/` following the Step 3 section contract. Framework-specific facts go in the reference; anything true of every stack — the version gate, the two catalog-loading models, the SSR `detect-locale` rule — stays here, stated once. Every non-obvious rule carries a one-sentence why. ## Related skills - **lingui-best-practices** — macro selection, catalog hygiene, the shared locale module. This skill assumes it; don't duplicate it. - **swc-plugin-compatibility** — SWC plugin version pinning and the silent-failure modes of a mis-wired transform. - **migrate-i18next-to-lingui** — if the project already has i18next, migrate first, then return here for framework wiring.