Ask your question and get a summary of the document by referencing this page and the AI provider of your choice
Version History
- "Initial version"v9.5.109/26/2026
If you have an idea for improving this documentation, please feel free to contribute by submitting a pull request on GitHub.
GitHub link to the documentationCopy doc Markdown to clipboard
How to internationalize your Next.js application using Lingui in 2026
Table of Contents
What is Lingui?
Lingui is an i18n library built around macros and message extraction. You write the source text in your components ( t`Hello` , <Trans>Hello</Trans>), lingui extract collects every message into catalogs (PO files by default), and a loader compiles them to compact JavaScript. Messages use ICU MessageFormat, and Lingui supports React Server Components in the App Router.
This guide sets up Lingui in a Next.js 16 App Router project, with:
- Macros compiled by SWC, so Turbopack keeps its speed.
- Server and Client Components sharing the same
TransanduseLinguiAPI. - Locale routing through
proxy.ts:/aboutfor the default locale,/fr/aboutfor the others, and first-visit language detection. - Static rendering of every locale with
generateStaticParams. - Complete multilingual SEO: translated
generateMetadata, canonical,hreflangwithx-default, Open Graph locales, JSON-LD,sitemap.ts,robots.tsand localized 404 pages.
Looking for another library? See the next-intl guide, the next-i18next guide, or the Next.js + Intlayer guide.
Using TanStack Start? See the TanStack Start + Lingui guide. Comparing libraries? Read Lingui vs Intlayer and next-i18next vs next-intl vs Intlayer.
What the benchmark says about Lingui on Next.js
The i18n benchmark runs the same 10-page, 10-locale Next.js app with every major library and measures what the browser actually downloads.
Dynamic JSON loading
Lazy-loads translations at runtime
Scoped JSON (namespacing)
Per-page translation namespaces
I18n Performance Benchmark
What is this metric?
The total gzip-compressed size of the internationalization library bundle. It only includes the provider and content retrieval logic after tree-shaking and minification.
Why is it important?
A smaller library size reduces the initial JavaScript payload, leading to faster download and execution times on the client.
View as
Key figures for @lingui/core@6.6.0 on Next.js 16, measured on 2026-09-26 (gzip):
Open the table in a modal to view all data content clearly
| Setup | Library size | JS per page | Other-locale leak | Other-page leak |
|---|---|---|---|---|
| No i18n (base app) | - | 141.0 KB | 0% | 0% |
| Lingui (setup of this guide) | 72.1 KB | 145.4 KB | 2.8% | 89.9% |
@intlayer/lingui (compat) | 10.7 KB | 221.6 KB | 50% | 90% |
next-intlayer (native Intlayer) | 4.9 KB | 141.5 KB | 0% | 0% |
What to take away:
- One catalog per locale still leaks other pages' messages to the client provider. Keep as much text as possible in Server Components, which send rendered HTML, not catalogs.
- The Lingui runtime weighs ~72 KB gzip. The
@intlayer/linguicompat adapter cuts the runtime to ~11 KB, but in this benchmark the Next.js compat setup still ships whole catalogs to the page. The nativenext-intlayerAPI is the setup that stays at the base app size.
See the full data: Next.js benchmark report, and the benchmark repository.
Feature comparison on Next.js
How Lingui compares with next-intl and Intlayer on the features a Next.js App Router project usually needs:
Open the table in a modal to view all data content clearly
| Feature | next-intlayer (Intlayer) | Lingui | next-intl |
|---|---|---|---|
| Translations near components | ✅ Content co-located with each component | ⚠️ Source text in components, catalogs centralized | ❌ Centralized JSON |
| TypeScript integration | ✅ Auto-generated strict types | ⚠️ Macros typed, message catalogs not | ✅ Good, via AppConfig augmentation |
| Missing translation detection | ✅ TypeScript errors and build-time warnings | ⚠️ Runtime fallback to the source text | ⚠️ Runtime fallback |
| Rich content (JSX, Markdown) | ✅ Direct support | ✅ JSX inside <Trans>, no Markdown | ⚠️ Tags via t.rich, no Markdown |
| AI translation | ✅ Your own provider and API key, with app context | ❌ No | ❌ No |
| Visual editor / CMS | ✅ Local visual editor + optional CMS | ❌ Via external platforms | ❌ Via external platforms |
| Localized routing | ✅ Built-in | ❌ Write your own proxy.ts | ✅ Built-in [locale] segment |
| Pluralization | ✅ Enumeration-based | ✅ ICU, <Plural> macro | ✅ ICU |
| Content formats | ✅ .ts, .tsx, .js, .json, .md, .yaml | ✅ PO, JSON, CSV | ✅ .json, .js, .ts |
| ICU MessageFormat | ✅ Via format: "icu" | ✅ Native | ✅ Native |
| SEO helpers (hreflang, sitemap) | ✅ Metadata, sitemap and robots.txt helpers | ❌ Manual | ✅ Good |
| Server Components | ✅ Direct access in any Server Component | ⚠️ setI18n in every layout and page | ⚠️ await getTranslations() per component |
| Per-component tree-shaking | ✅ At build time (Babel / SWC) | ⚠️ One catalog per locale, per-page extractor is experimental | ⚠️ Manual, with pick() per route |
| Runtime size (gzip, benchmark) | 4.9 KB | 72.1 KB | 14.7 KB |
| Missing translations in CI | ✅ npx intlayer test | ✅ lingui compile --strict | ⚠️ Not built-in |
| Ecosystem / community | ⚠️ Smaller, growing fast | ✅ Mature | ✅ Large |
Runtime sizes come from the Next.js benchmark. For a detailed discussion, read Lingui vs Intlayer.
Other Next.js guides: next-intl, next-i18next, and Intlayer.
Practices you should follow
- Set
langanddiron<html>in the[locale]layout. - Prefer Server Components for text: they render HTML on the server and don't need the catalog on the client.
- Call
initLingui(locale)in every layout and page. Layouts don't re-render on navigation, so a page cannot rely on its layout having set the locale. - Keep one URL per locale and pre-render every locale with
generateStaticParams. - Translate your metadata in
generateMetadata, withcanonical,hreflangandx-default. - Generate a multilingual sitemap and robots.txt with the
sitemap.tsandrobots.tsconventions. - Use real links for the locale switcher, so crawlers discover every language.
- Run
lingui extractin CI so a new message never ships untranslated.
See our guide on internationalization and SEO, the hreflang guide and the Next.js multilingual SEO comparison.
Step-by-Step Guide to Set Up Lingui in a Next.js Application
Here's the project structure we'll be creating:
Copy the code to the clipboard
Install Dependencies
bashCopy codeCopy the code to the clipboard
- @lingui/core / @lingui/react: runtime,
I18nProvider,setI18nfor Server Components, and the macros (@lingui/core/macro,@lingui/react/macro). - @lingui/swc-plugin: compiles the macros inside the Next.js SWC pipeline.
- @lingui/loader: compiles
.pocatalogs on import, solingui compileis not needed. - @lingui/cli:
lingui extractto collect messages into catalogs.
@lingui/swc-pluginis a WebAssembly plugin tied to the SWC version of Next.js. If the build fails after a Next.js upgrade, update the plugin to the version listed as compatible in its README.- @lingui/core / @lingui/react: runtime,
Centralize Your Locale Configuration
A single file defines locales and URL helpers. Routing, metadata, sitemap and Lingui all read from it.
src/i18n/config.tsCopy codeCopy the code to the clipboard
Configure Lingui and Next.js
lingui.config.tsCopy codeCopy the code to the clipboard
The SWC plugin compiles the macros, and the loader compiles
.pofiles, for both Turbopack (default in Next.js 16) and webpack:next.config.tsCopy codeCopy the code to the clipboard
Add the extraction scripts:
package.jsonCopy codeCopy the code to the clipboard
Load Catalogs and Create Server Instances
Server Components have no React context, so Lingui provides
setI18nto register the instance for the current render. This module loads every catalog once per server process and creates oneI18ninstance per locale. It isserver-only: catalogs of other locales never reach the client bundle.src/i18n/appRouterI18n.tsCopy codeCopy the code to the clipboard
src/i18n/initLingui.tsCopy codeCopy the code to the clipboard
For TypeScript to accept the
.poimport, declare the module once:src/i18n/po.d.tsCopy codeCopy the code to the clipboard
Create the Client Provider
Client Components read translations from a React context. The provider receives the catalog of the active locale from the server layout, and creates its own instance once.
src/components/LinguiClientProvider.tsxCopy codeCopy the code to the clipboard
Define Dynamic Locale Routes
The
[locale]segment holds the root layout.generateStaticParamspre-renders every locale at build time, anddynamicParams = falsereturns a 404 for any other prefix.src/app/[locale]/layout.tsxCopy codeCopy the code to the clipboard
The client provider receives the whole catalog of the active locale. This is what the benchmark measures as "other-page leak". Keeping text in Server Components limits what the client actually needs. For large apps, Lingui's experimental per-page extractor (
experimental.extractorinlingui.config.ts) splits catalogs by entry point.Utilize Translations in Server Components
Server Components use the same macros as Client Components.
initLinguimust run in the page too, because a layout does not re-render when navigating between its pages.src/app/[locale]/about/page.tsxCopy codeCopy the code to the clipboard
Utilize Translations in Client Components
Client Components use the same imports. The macros read the instance from
LinguiClientProvider.src/components/Counter.tsxCopy codeCopy the code to the clipboard
Extract and Translate Your Messages
Run the extraction. Lingui writes every message found in
srcinto each locale catalog:bashCopy codeCopy the code to the clipboard
Then translate the
msgstrof each entry:src/locales/fr/messages.poCopy codeCopy the code to the clipboard
src/locales/es/messages.poCopy codeCopy the code to the clipboard
<0>placeholders keep the JSX elements of a<Trans>in place, so translators can move them without touching the markup.Set Up the Proxy for Locale Routing
OptionalNext.js 16 renamed
middleware.tstoproxy.ts. The proxy implements the "as-needed" prefix strategy:/fr/aboutis served as is;/en/aboutredirects to/about, so the default locale has a single URL;/aboutis rewritten internally to/en/about, without changing the URL;- a first visit on
/redirects to the preferred language (cookie first, thenAccept-Language).
src/i18n/negotiateLocale.tsCopy codeCopy the code to the clipboard
src/proxy.tsCopy codeCopy the code to the clipboard
Change the Language of Your Content
OptionalusePathnamereturns the URL seen by the browser (/aboutor/fr/about). Strip the locale, then build the link of each language. The switcher renders real links, so crawlers can reach every language version, and the cookie remembers the explicit choice.src/components/LocaleSwitcher.tsxCopy codeCopy the code to the clipboard
Build a Localized Link Component
Optionalsrc/components/LocalizedLink.tsxCopy codeCopy the code to the clipboard
It works from Server Components too, because it renders inside
LinguiClientProvider:tsxCopy codeCopy the code to the clipboard
Internationalize Your Metadata
OptionalEach language version can rank on its own, provided every page exposes:
- a translated
titleanddescription; - a canonical URL pointing to itself;
- one
hreflangalternate per locale, plusx-default; - Open Graph
locale,alternateLocaleandurl; - JSON-LD with
inLanguage.
generateMetadataruns outside the React tree, so it uses the server instance directly with themsgmacro:src/i18n/metadata.tsCopy codeCopy the code to the clipboard
src/app/[locale]/about/page.tsxCopy codeCopy the code to the clipboard
JSON-LD is rendered by the page itself. Page files may only export Next.js fields, so keep the component in its own file:
src/components/WebPageJsonLd.tsxCopy codeCopy the code to the clipboard
src/app/[locale]/about/page.tsxCopy codeCopy the code to the clipboard
- a translated
Internationalize Your Sitemap
OptionalThe
sitemap.tsconvention supportsalternates.languages, which Next.js renders asxhtml:linkalternates. List every URL of every locale:src/app/sitemap.tsCopy codeCopy the code to the clipboard
Internationalize Your robots.txt
OptionalPrivate routes exist in every language, so
disallowmust cover every localized path:src/app/robots.tsCopy codeCopy the code to the clipboard
Handle Localized 404 Pages
Optionalnot-found.tsxrenders inside the[locale]layout, so it has access to the client provider. The catch-all route sends unknown paths inside a locale to it. Next.js addsnoindexto 404 responses automatically.src/app/[locale]/not-found.tsxCopy codeCopy the code to the clipboard
src/app/[locale]/[...rest]/page.tsxCopy codeCopy the code to the clipboard
Access the Locale in Server Actions
OptionalServer Actions don't receive route params. The most reliable approach is to send the locale with the form, from the page that knows it:
src/app/[locale]/contact/page.tsxCopy codeCopy the code to the clipboard
src/app/actions/sendContactMessage.tsCopy codeCopy the code to the clipboard
Keep Your Macros, Cut the Runtime with Intlayer
OptionalThe
@intlayer/linguicompat adapter keeps your source untouched: macros compile as before, and the resultingi18n._(),useLingui()and<Trans>calls are served by Intlayer dictionaries. In the Next.js benchmark, the runtime drops from ~72.1 KB to ~10.7 KB gzip.On Next.js, the adapter is wired by aliasing
@lingui/coreand@lingui/reactto@intlayer/linguiinnext.config.ts(webpack and Turbopack), and by wrapping the config withwithIntlayerfromnext-intlayer/server. Keep@lingui/swc-pluginso the macros still compile first. The complete configuration is in the Lingui compat guide.As the benchmark table shows, the adapter reduces the runtime but not yet the catalog shipped to each page on Next.js. It is best used as a migration bridge: once it runs, move components one at a time to the native
useIntlayerAPI, which ships only the content each component renders. See the Next.js + Intlayer guide, Lingui vs @intlayer/lingui and all the compat adapters.Automate Your Translations Using Intlayer
OptionalLingui extracts messages, but filling dozens of catalogs by hand is where most of the time goes. Intlayer is free and open source, and its tooling works alongside Lingui:
- Translate with AI using your own API key and provider. See auto fill and the CLI.
- Keep your PO files as the source of truth with the sync PO plugin.
- Test missing translations in CI. See testing your translations.
- Audit your deployed site for missing
hreflang, wrong canonicals and locale leaks with the scan command.
Frequently Asked Questions
Yes. @lingui/react supports React Server Components. Server Components register the instance with setI18n from @lingui/react/server, Client Components read it from I18nProvider, and both use the same Trans and useLingui macros.
Server Components have no context, so the instance is registered per render. Layouts are preserved across navigations and don't re-render, so a page cannot rely on its layout to set the locale. Calling initLingui(locale) at the top of each layout and page keeps them independent.
Use @lingui/swc-plugin. It keeps the SWC pipeline and Turbopack. Adding a Babel config disables SWC in Next.js and slows builds down. The only constraint is keeping the plugin version compatible with the SWC version of your Next.js release.
Get the server instance with getI18nInstance(locale) and translate descriptors declared with the msg macro: i18n._(msg`About us`). Return alternates.canonical, alternates.languages with x-default, and openGraph.locale. Step 13 provides a reusable helper.
The benchmark measures ~72 KB gzip for the runtime. With one catalog per locale, pages weigh ~145 KB against 141 KB without i18n, but each page still receives the messages of other pages through the client provider.
Lingui fits teams that like writing source text in components and working with PO files and translators. next-intl fits teams that prefer JSON catalogs and a t("key") API tightly integrated with Next.js. next-i18next brings the i18next plugin ecosystem. See next-i18next vs next-intl vs Intlayer and the Next.js benchmark.
Comments
No comments yet. Be the first to share your thoughts.
