# Lingui: message-as-source i18n for React, Vue and SolidJS

> Lingui keeps translatable text in the component and generates catalogs from it. The trade-off is a build-time macro pipeline and ESM-only packages.

**lingui/js-lingui** — 🌍 📖 A readable, automated, and optimized (2 kb) internationalization for JavaScript

- Repository: https://github.com/lingui/js-lingui
- Website: https://lingui.dev
- Stars: 5,904 · Forks: 461
- Language: TypeScript
- License: MIT
- Published: 2026-09-22 · Updated: 2026-09-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/lingui-js-lingui

## The problem Lingui solves: keys that nobody can read

Key-based i18n asks you to invent an identifier, put the actual sentence in a JSON file, and reference the identifier from the component. The README contrasts the two directly: one line holds t("dashboard.welcome.title"), the other holds <Trans>Welcome back, {name}</Trans>. The second version puts the copy in the diff, so a reviewer sees the wording change rather than a key rename.

That is the whole pitch. Lingui is for teams whose reviewers, designers and translators all need to see the same sentence, and for codebases where keys drift out of sync with the text they point at. It is not for teams who want translation strings to be editable without touching source files, because in Lingui the source file is the string.

The README states the core is about 2 kB gzipped, and the project reports 6M+ npm downloads a month. Both numbers come from the project's own page; the repository does not include a methodology for either.

## Macros at build time, catalogs compiled ahead of runtime

The mechanism has three stages and they are worth separating, because most confusion about Lingui comes from mixing them up.

First, macros. The Trans component imported from @lingui/react/macro is not a runtime component. It is rewritten by @lingui/babel-plugin-lingui-macro or @lingui/swc-plugin during transpilation. The README is explicit that macros need one of those two plugins. This is why the text can stay in the component: the build step is what turns it into a lookup.

Second, extraction. Running the CLI walks the source, finds macro usages, and writes catalogs. The default format is PO, with JSON, CSV and custom formats also supported. Message IDs are described as stable hashes generated at build time, and the documentation covers explicit IDs as an alternative when you need to control them.

Third, compilation. Catalogs are compiled ahead of time, so the runtime ships without a MessageFormat parser. That is the reason the core stays small: parsing happened on your machine, not in the user's browser.

Rich text follows the same path. A link inside a Trans block becomes a numbered tag in the catalog, so the Czech example in the README shows msgid "Read the <0>documentation</0> for more info." with the tag standing in for the anchor element. Translators move tags around; they never see JSX.

## Installing Lingui and extracting your first catalog

The README's quick start installs the runtime packages and the CLI separately, because the CLI is a development dependency.

```bash
npm install @lingui/core @lingui/react
npm install --save-dev @lingui/cli
```

After that you add the macro plugin for your transpiler and create a lingui.config.js. The installation guide at lingui.dev/installation covers both, and the README points there rather than reproducing the config in full. Then wrap text in Trans. This is the README's own example, trimmed to the parts that matter for a first run:

```jsx
import { i18n } from "@lingui/core"
import { I18nProvider } from "@lingui/react"
import { Trans } from "@lingui/react/macro"
import { messages } from "./locales/en/messages"

i18n.load("en", messages)
i18n.activate("en")
```

The two CLI commands are the whole workflow. Extract reads the source and writes catalogs; compile turns translated catalogs into the runtime output the application imports.

```bash
npx lingui extract
npx lingui compile
```

After extract, a catalog appears under src/locales/<locale>/messages.po. You should see a msgid containing your English sentence and a line comment pointing at the source file and line number. If the file is empty, the macro plugin is not running, which is the most common first-run failure.

Platform examples live in the repository's examples directory: examples/vite-project-react-swc, examples/nextjs-swc, examples/react-native, examples/remix-vite-babel and others. Reading the matching example is faster than adapting a different bundler's setup.

## Node 22.19, ESM-only packages and the CommonJS exception

The requirements section is short and unusually strict. Node.js 22.19 or newer. Lingui 6 packages are ESM-only, with one exception: @lingui/metro-transformer stays CommonJS. The README says modern bundlers and Node.js versions with require(esm) handle this transparently, and links a migration guide for version 6.

If your application is still CommonJS and your toolchain predates require(esm), this is not a configuration problem you can paper over. It is a version boundary. The migration guide exists because moving from Lingui 5 to 6 is a real migration, not a dependency bump.

@lingui/react supports React 16.14 and newer, including React 19, so the React side is not the constraint. The Node version and the module format are.

The second constraint is the macro plugin. There is no documented path where Trans works without Babel or SWC rewriting it. A project that has removed Babel and does not use SWC cannot adopt Lingui's macro syntax as written.

## Where Lingui is the wrong choice

The extract-and-compile workflow is manual. The README shows npx lingui extract and npx lingui compile as separate commands, and the Vite plugin compiles catalogs on the fly, but extraction is still a step you schedule. If your release process cannot run a CLI before the build, you will ship stale catalogs. The README does not document a rollback path for a bad extraction, so a catalog that loses messages is recovered from version control, not from the tool.

Generated hash IDs are stable but opaque. The README notes explicit IDs are available when you need them, which is an admission that the default is not always what you want. If your translation vendor matches strings by ID across projects, hashes are harder to reconcile than human-readable keys.

Teams that need to edit copy without a code change should look elsewhere. In Lingui the sentence lives in the component, so changing it means a commit, a re-extract and a re-translation pass. That is the design, not a bug, but it rules out workflows where a content editor owns the strings.

Finally, the extraction story is language-specific. Vue single-file components go through @lingui/extractor-vue, while Astro and Svelte support comes through community packages rather than first-party ones. The README is clear about that split, and it means those two frameworks carry more risk than React or SolidJS.

## Lingui compared with key-based libraries like i18next

The difference is where the source of truth lives. i18next and similar key-based libraries put the English string in a JSON resource file and reference it by key from the component. Lingui puts the English string in the component and generates the resource file from it.

The practical consequences run in both directions. In Lingui, running lingui extract regenerates the catalog, so new messages appear and removed ones are marked obsolete without manual bookkeeping, as the README puts it. In a key-based setup, that bookkeeping is yours: delete the key, delete the entry, hope nothing still references it.

On the other side, key-based libraries do not require a macro plugin. Their runtime parses messages, which is why their bundles carry a parser and Lingui's does not. Lingui trades build complexity for runtime size and for readable diffs.

There is a middle path inside Lingui itself: explicit IDs. If you want the stability of named keys with the readability of inline text, the documentation's explicit-vs-generated-ids guide covers that configuration, and it is the setting most likely to matter to a team migrating from a key-based library.

## Licence, maintenance and upgrade cost

Lingui is MIT licensed, stated both in the README and in the repository's LICENSE file. MIT permits commercial use and modification; it also means no warranty, and the project's own README carries no support commitment beyond the Discord server and issue tracker. That is a normal arrangement for a library of this size, but it is worth naming when the decision is about a dependency that touches every user-facing string.

The repository is not archived, and the last push was on 2026-09-22. Recent releases are v6.5.0 on 2026-07-06, v6.6.0 on 2026-07-24 and v6.7.0 on 2026-09-11, so the 6.x line is moving.

Upgrade cost is concentrated in the major versions. The README links a migration guide specifically for version 6, driven by the ESM-only change and the Node.js 22.19 floor. Minor releases within 6.x should be cheaper, but the CLI and the macro plugin version together: a mismatch between @lingui/cli and the Babel or SWC plugin is a class of bug that only shows up at build time. Pin them to the same version in your lockfile.

## Conclusion

Adopt Lingui when your team wants the English sentence to live in the component and your build already runs Babel or SWC, because the macro plugin is not optional. Skip it if you need a CommonJS runtime, if you are below Node.js 22.19, or if you cannot add a CLI step to CI, since extract and compile are manual commands. Before committing, run npx lingui extract on one real component and read the generated src/locales/en/messages.po to confirm the numbered tags and msgid hashes match what you expect.

## FAQ

### How is Lingui used?

You install @lingui/core and @lingui/react, add the Babel or SWC macro plugin, and wrap translatable text in the Trans macro. Then npx lingui extract writes PO catalogs and npx lingui compile turns them into the runtime output your app imports.

### What is the best translation library for React?

The README does not rank libraries, but it draws one distinction clearly: key-based libraries keep text in a JSON file referenced by key, while Lingui keeps the sentence in the component and generates the catalog from it. Which one fits depends on whether reviewers need to see the copy in the diff.

### What is a good JavaScript library for language translation?

Lingui works in any JavaScript project through @lingui/core, and the README lists React, React Native, Vue, SolidJS, Astro, Svelte and Node.js as targets. Vue goes through @lingui/extractor-vue, while Astro and Svelte rely on community packages.

## Sources

- [License: MIT](https://github.com/lingui/js-lingui/blob/main/LICENSE)
- [lingui/js-lingui on GitHub](https://github.com/lingui/js-lingui)
- [Project website](https://lingui.dev)
- [README](https://github.com/lingui/js-lingui/blob/main/README.md)
- [Releases](https://github.com/lingui/js-lingui/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/lingui-js-lingui
