# Lingui
> Lingui is a lightweight, open-source internationalization (i18n) library for JavaScript and TypeScript. It brings compile-time macros and a CLI for message extraction to React, React Native, Vue, SolidJS, Astro, Svelte, and Node.js.
## Introduction
- [Introduction](https://lingui.dev/introduction.md): What Lingui is and how it works. A lightweight i18n library for JavaScript and TypeScript with compile-time macros and a message extraction CLI
## Installation
- [Installation and Setup](https://lingui.dev/installation.md): Learn how to install Lingui in your project
## Tutorials
- [JavaScript Apps Internationalization](https://lingui.dev/tutorials/javascript.md): Learn how to use Lingui's internationalization features in your vanilla JavaScript application
- [React Apps Internationalization](https://lingui.dev/tutorials/react.md): Learn how to add internationalization to a React application using Lingui
- [React Native Apps Internationalization](https://lingui.dev/tutorials/react-native.md): Learn how to add internationalization to a React Native application using Lingui
- [Next.js App Router i18n with React Server Components](https://lingui.dev/tutorials/react-rsc.md): Add internationalization to a Next.js App Router app with Lingui. Set up the SWC plugin, load catalogs in server and client components and switch languages
- [Solid Apps Internationalization](https://lingui.dev/tutorials/solid.md): Learn how to add internationalization to a SolidJS application using Lingui
## Guides
- [Custom Extractor](https://lingui.dev/guides/custom-extractor.md): Learn how to write a custom message extractor for your project
- [Custom Formatter](https://lingui.dev/guides/custom-formatter.md): Learn how to write a custom localization message formatter for your project
- [Dynamic Loading of Message Catalogs](https://lingui.dev/guides/dynamic-loading-catalogs.md): Learn how to set up dynamic loading of message catalogs in Lingui to reduce bundle size and improve performance
- [Explicit vs Generated IDs](https://lingui.dev/guides/explicit-vs-generated-ids.md): Compare explicit and generated message IDs in Lingui i18n, understand the trade-offs, and choose the best approach for your localization workflow
- [Lazy Translations](https://lingui.dev/guides/lazy-translations.md): Lazy translations allow you to defer translation of a message until it is actually displayed
- [Message Extraction](https://lingui.dev/guides/message-extraction.md): Learn about message extraction in i18n and how to use Lingui to extract messages from your application
- [ICU MessageFormat](https://lingui.dev/guides/message-format.md): The ICU MessageFormat syntax Lingui uses for messages. Variables, plurals, select and ordinals explained with examples
- [Monorepo](https://lingui.dev/guides/monorepo.md): How to organize message catalogs, extraction and the shared runtime when Lingui is used across several apps and packages in a monorepo
- [Keeping Your Bundle Small: How Lingui Optimizes for Performance](https://lingui.dev/guides/optimizing-bundle-size.md): How Lingui keeps your bundle small by compiling messages to compact IDs and dropping the message compiler in production, and how to configure that behavior
- [Pluralization](https://lingui.dev/guides/plurals.md): Learn about pluralization and how to use it in your application with Lingui
- [Pseudolocalization](https://lingui.dev/guides/pseudolocalization.md): Learn how to use pseudolocalization to test the internationalization aspects of your application with Lingui
- [Testing](https://lingui.dev/guides/testing.md): How to test React components that use Trans or useLingui by wrapping them in I18nProvider with React Testing Library
- [Translator-friendly Messages](https://lingui.dev/guides/translator-friendly-messages.md): Write Lingui messages that give translators and AI the context they need - whole sentences, named placeholders and tags, comments, and context - and enforce it with ESLint
- [Typed Message IDs](https://lingui.dev/guides/typed-message-ids.md): Learn how to opt in to type-safe message IDs for autocomplete and compile-time validation
## API Reference
- [Catalog Formats](https://lingui.dev/ref/catalog-formats.md): Learn about the different catalog formats supported by Lingui
- [Lingui CLI](https://lingui.dev/ref/cli.md): Learn how to set up and use Lingui CLI to extract, check, merge and compile message catalogs
- [Configuration](https://lingui.dev/ref/conf.md): Learn how to configure Lingui for your project
- [Core API Reference](https://lingui.dev/ref/core.md): The package provides the main i18n object, which manages message catalogs, active locale, and message translation and formatting
- [ESLint Plugin](https://lingui.dev/ref/eslint-plugin.md): Lingui ESLint Plugin helps you find and prevent common i18n mistakes in your code
- [Vue.js Extractor](https://lingui.dev/ref/extractor-vue.md): Extract messages from Vue single-file components with @lingui/extractor-vue. Installation, configuration and options
- [Webpack-compatible loader](https://lingui.dev/ref/loader.md): Import PO catalogs directly and compile them on the fly with @lingui/loader in Webpack, Rspack and Rsbuild, instead of running lingui compile
- [Locale Detection](https://lingui.dev/ref/locale-detector.md): Detect the user's locale with the `@lingui/detect-locale` package
- [Macros](https://lingui.dev/ref/macro.md): Transform JavaScript objects and JSX elements into ICU MessageFormat messages
- [Metro Transformer](https://lingui.dev/ref/metro-transformer.md): Use Lingui with React Native and compile your message catalogs on the fly
- [React API Reference](https://lingui.dev/ref/react.md): Reference for the Lingui React API and components
- [Solid API Reference](https://lingui.dev/ref/solid.md): Reference for the Lingui SolidJS API and components
- [SWC Plugin](https://lingui.dev/ref/swc-plugin.md): Use Lingui Macros in your SWC project
- [Vite Plugin](https://lingui.dev/ref/vite-plugin.md): Use Lingui with Vite and compile your message catalogs on the fly
## Examples
- [Examples](https://lingui.dev/examples.md): Examples of using Lingui for internationalization (i18n) in various frameworks and libraries
## Sync & Collaboration Tools
- [Crowdin](https://lingui.dev/tools/crowdin.md): Translate a Lingui project with Crowdin. Sync PO catalogs with the Crowdin CLI or the GitHub, GitLab and Bitbucket integrations and deliver them over the air
- [Sync & Collaboration Tools](https://lingui.dev/tools/introduction.md): How to manage translations for a Lingui project. Manual catalog editing versus translation management systems, and the tools that integrate with Lingui
- [Translation.io](https://lingui.dev/tools/translation-io.md): Sync Lingui catalogs with Translation.io. Push source messages, pull translations, and give translators ICU syntax highlighting and plural management
## i18n with AI
- [i18n with AI](https://lingui.dev/ai-tools.md): Give AI coding agents accurate, up-to-date Lingui knowledge with Agent Skills, llms.txt context files, and the Context7 MCP server
## Comparisons
- [Lingui vs i18next](https://lingui.dev/misc/i18next.md): How Lingui compares with i18next and react-i18next. Message syntax, plurals, extraction tooling, catalog formats, bundle size and React Server Components
- [Lingui vs react-intl](https://lingui.dev/misc/react-intl.md): How Lingui compares with react-intl from FormatJS. Both use ICU MessageFormat, so the differences are in rich text, macros, extraction tooling and bundle size
## Optional
- [GitHub repository](https://github.com/lingui/js-lingui): Source code, issues and discussions
- [npm: @lingui/core](https://www.npmjs.com/package/@lingui/core): The runtime package; @lingui/react adds the React bindings and @lingui/cli the command line tool
- [Agent Skills](https://github.com/lingui/skills): Lingui best practices packaged as Agent Skills for coding agents
- [Context7](https://context7.com/lingui/js-lingui): The current documentation served over MCP
---
# Full Documentation Content
# i18n with AI
AI coding agents and chat tools have become an essential part of the development workflow, and internationalization is no exception. However, most AI models have limited or outdated knowledge of Lingui's APIs and best practices. This page covers the resources available to give your AI tools accurate, up-to-date Lingui knowledge so they can help you ship localized products faster.
## Agent Skills[](#agent-skills "Direct link to Agent Skills")
[Lingui Agent Skills](https://github.com/lingui/skills) are reusable, procedural knowledge packages for AI coding agents. They provide best practices, common patterns, and troubleshooting guides that agents can follow when working with Lingui - reducing hallucinations and incorrect API usage.
### Installation[](#installation "Direct link to Installation")
The skills follow the [Agent Skills format](https://agentskills.io) and work with Claude Code, Cursor, Codex, Gemini CLI, GitHub Copilot, and other compatible agents. Use whichever installer matches your tool - each one installs the whole set.
**Skills CLI:**
```
npx skills add lingui/skills
```
**Claude Code plugin** - all skills load automatically and stay up to date via `/plugin marketplace update`:
```
/plugin marketplace add lingui/skills
/plugin install lingui@lingui-skills
```
**Plugins CLI** - auto-detects your installed agent tools and installs through each one's native plugin system:
```
npx plugins add lingui/skills
```
**Gemini CLI extension:**
```
gemini extensions install https://github.com/lingui/skills
```
**GitHub CLI** (v2.90+) - installs for GitHub Copilot or any other supported agent. Use `--agent
{/*
ph() gives placeholders explicit names when using expressions.
This improves context for translators.
*/}
Read the documentation. Read the documentation.
See all unread messages
{" or "}
mark them as read.
{messagesCount === 1
? `There's ${messagesCount} message in your inbox.`
: `There are ${messagesCount} messages in your inbox.`}
See all unread messages
{" or "}
mark them as read.
{messagesCount === 1
? `There's ${messagesCount} message in your inbox.`
: `There are ${messagesCount} messages in your inbox.`}
{favoriteColors.map((color) => (
);
}
```
Important
Please note that we import the `
` with no props, share a single placeholder without error.
In TypeScript projects, declare the attribute so it type-checks on every element:
lingui.d.ts
```
import "react";
declare module "react" {
interface Attributes {
_t?: string;
}
}
```
Named tags are supported by the Babel macro and the [SWC Plugin](https://lingui.dev/ref/swc-plugin.md#jsx-placeholder-naming-in-trans).
Enforce with ESLint
[`no-unnamed-tag-placeholders`](https://github.com/lingui/eslint-plugin/blob/main/docs/rules/no-unnamed-tag-placeholders.md) reports any element inside `Trans` that would still be extracted as a numeric tag. It is not in the recommended config, so enable it once your project has the config above.
## Explain What the Message Means[](#explain-what-the-message-means "Direct link to Explain What the Message Means")
Placeholders and tags tell the translator what goes into the sentence. Two more pieces of information tell them what the sentence is for.
### Comment[](#comment "Direct link to Comment")
A [`comment`](https://lingui.dev/ref/macro.md#comment) is a free-form note for the translator. It ends up in the catalog and in your TMS, and is removed from production code:
```
import { t } from "@lingui/core/macro";
import { Trans } from "@lingui/react/macro";
t({
message: "Post",
comment: "Verb: button that publishes the article. Keep it short, max 8 characters.",
});
{/* Localized messages are wrapped in
{/* Element attributes are translated using the t macro */}
>
);
}
```
Custom IDs are supported as well:
```
{name}
```
## External Message Catalog[](#external-message-catalog "Direct link to External Message Catalog")
Let's say our application has been internationalized and we want to send the messages to the translator.
FormatJS ships a CLI: `formatjs extract` collects the source messages into a single JSON file, and `formatjs compile` turns the translated files you get back into the format react-intl consumes, verifying the ICU syntax on the way. Formatters for Crowdin, Lokalise, Smartling and Transifex are built in. Keeping the translated files in step with the source, and noticing which translations are stale, happens outside the CLI, usually in the translation platform.
Lingui's [CLI](https://lingui.dev/ref/cli.md) closes that loop itself. `lingui extract` merges new messages into every locale's catalog, keeps the existing translations and marks removed messages as obsolete, so each PO file always mirrors the current source and stale entries are visible at a glance. It supports both the Babel and SWC ecosystems for extracting messages.
## Compiling Messages[](#compiling-messages "Direct link to Compiling Messages")
Parsing and formatting ICU messages is the largest part of an i18n runtime. Lingui compiles each message at build time into a small structure that the runtime fills with values, so neither a parser nor a MessageFormat compiler ships to the browser. This is what `lingui compile` does by default, with nothing to opt into. Plural rules come from `Intl.PluralRules`, so there is no locale data to load by hand.
react-intl parses messages at runtime by default. The FormatJS CLI can pre-compile messages to an AST with `formatjs compile --ast`, and pairing that with the parser-free build of `@formatjs/icu-messageformat-parser` cuts the bundle by about 40% according to the FormatJS docs. The formatter itself stays in the bundle.
## Summary[](#summary "Direct link to Summary")
Both libraries use ICU MessageFormat and have similar low-level APIs, so porting from one to the other is mostly mechanical. What differs is how much of the work the library takes off your hands.
**Lingui:**
* Macros generate the message syntax from JSX and template literals. Rich text is written as plain JSX, and there is no ICU string to hand-write or keep in step with the code.
* Extraction and compilation are part of the core toolchain, and the CLI keeps every locale's catalog in step with the source. Catalogs are PO files by default, with JSON, CSV and custom formats available.
* Catalogs are compiled ahead of time. Core and React bindings together stay around 4 kB gzipped.
* The same core serves vanilla JS, Node.js, React Native, Vue, SolidJS, Astro and Svelte.
**react-intl:**
* Is one of the longest-standing i18n libraries for React, used in many production websites.
* Is part of FormatJS, whose `@formatjs/intl` core also powers `vue-intl` and whose polyfills fill gaps in the `Intl` API.
* Has lots of resources available online.
Because the message syntax is shared, an existing react-intl codebase can move to Lingui without rewriting its messages. Each `FormattedMessage` becomes a `Trans` with the same message, and from then on the ICU string is generated for you.
---
# Catalog Formats
Catalog format (configured by the [`format`](https://lingui.dev/ref/conf.md#format) option) refers to the offline catalog file format. This format is never used in production, because the catalog is compiled into a JS module.
The reason for this build step is that the choice of catalog format depends on the individual internationalization workflow. On the other hand, the runtime catalog should be as simple as possible so that it can be parsed quickly without additional overhead.
## PO[](#po "Direct link to PO")
PO files are translation files used by [gettext](https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html) internationalization system. This is the **recommended** and the **default** catalog format in Lingui.
[](https://www.npmjs.com/package/@lingui/format-po) [](https://www.npmjs.com/package/@lingui/format-po)
The advantages of this format are:
* readable even for large messages
* supports comments for translators
* supports metadata (origin, flags)
* supports contexts
* standard format supported by many localization tools
### Installation[](#po-installation "Direct link to Installation")
* npm
* Yarn
* pnpm
```
npm install --save-dev @lingui/format-po
```
```
yarn add --dev @lingui/format-po
```
```
pnpm add --save-dev @lingui/format-po
```
### Usage[](#po-usage "Direct link to Usage")
lingui.config.{js,ts}
```
import { defineConfig } from "@lingui/cli";
import { formatter } from "@lingui/format-po";
export default defineConfig({
// [...]
format: formatter({ lineNumbers: false }),
});
```
### Configuration[](#po-configuration "Direct link to Configuration")
PO formatter accepts the following options:
| Option | Type | Default | Description |
| ----------------------------- | ------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `origins` | boolean | `true` | Include comments in the PO file that indicate where each message is used in the source code. This provides additional context during the translation |
| `lineNumbers` | boolean | `true` | Include line numbers in the origin comments. This makes it easier to locate messages in the source code |
| `printLinguiId` | boolean | `false` | Add a `js-lingui-id: hash` comment to each message in the PO file. This ID is a hash generated by Lingui |
| `explicitIdAsDefault` | boolean | `false` | Use the `msgid` as is for messages with explicit IDs. The formatter will add the `js-lingui-explicit-id` flag for such strings |
| `customHeaderAttributes` | `{[key: string]: string}` | `{}` | Allows adding custom key-value pairs to the PO file header |
| `printPlaceholdersInComments` | boolean \| `{ limit?: number }` | `true` | Print values for unnamed placeholders as comments for each message. This can give translators more context for better translations. By default, the first 3 placeholders are displayed |
| `foldLength` | number | `0` | Maximum line width before folding long strings. When a string exceeds this length, it will be split across multiple lines. Set to `0` to disable folding (strings will only break on actual newlines) |
| `compactMultiline` | boolean | `true` | When `true`, multiline strings start with content on the first line. When `false`, uses GNU gettext's traditional format with an empty first line. The compact format is recommended as it's compatible with translation platforms that may strip empty first lines |
### Examples[](#po-examples "Direct link to Examples")
```
#: src/App.js:3
#. Comment for translators
msgid "messageId"
msgstr "Translated Message"
#: src/App.js:3
#, obsolete
msgid "obsoleteId"
msgstr "Obsolete Message"
```
Messages with context are exported in the following way:
```
#: src/Inbox.js:12
msgctxt "my context"
msgid "msg.inbox"
msgstr "Message Inbox"
```
Messages with plurals are exported in [ICU MessageFormat](https://lingui.dev/guides/message-format.md):
```
msgid "{count, plural, one {Message} other {Messages}}"
msgstr "{count, plural, one {Message} other {Messages}}"
```
Messages with placeholders:
```
t`Hello ${user.name} ${value}`;
```
are exported as:
```
#. placeholder {0}: user.name
msgid "Hello {0} {value}"
msgstr "Hello {0} {value}"
```
## PO with gettext Plurals[](#po-gettext "Direct link to PO with gettext Plurals")
When using localization backends that don't understand the ICU plural syntax exported by the default `po` formatter, **po-gettext** can be used to read and write to PO files using gettext-native plurals.
[](https://www.npmjs.com/package/@lingui/format-po-gettext) [](https://www.npmjs.com/package/@lingui/format-po-gettext)
### Installation[](#po-gettext-installation "Direct link to Installation")
* npm
* Yarn
* pnpm
```
npm install --save-dev @lingui/format-po-gettext
```
```
yarn add --dev @lingui/format-po-gettext
```
```
pnpm add --save-dev @lingui/format-po-gettext
```
### Usage[](#po-gettext-usage "Direct link to Usage")
lingui.config.{js,ts}
```
import { defineConfig } from "@lingui/cli";
import { formatter } from "@lingui/format-po-gettext";
export default defineConfig({
// [...]
format: formatter({ lineNumbers: false }),
});
```
### Configuration[](#po-gettext-configuration "Direct link to Configuration")
The PO Gettext formatter accepts the following options:
| Option | Type | Default | Description |
| ---------------------- | ------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `origins` | boolean | `true` | Include comments in the PO file that indicate where each message is used in the source code. This provides additional context during the translation |
| `lineNumbers` | boolean | `true` | Include line numbers in the origin comments. This makes it easier to locate messages in the source code |
| `disableSelectWarning` | boolean | `false` | Disable warnings about unsupported `Select` features encountered in catalogs. This can be useful if you're aware of the limitation and want to suppress related warnings |
| `customICUPrefix` | string | `"js-lingui:"` | Override the default prefix for ICU and plural comments in the final PO catalog |
| `mergePlurals` | boolean | `false` | Combine plural entries that have the same content but different variables into a single PO entry |
| `foldLength` | number | `0` | Maximum line width before folding long strings. When a string exceeds this length, it will be split across multiple lines. Set to `0` to disable folding |
| `compactMultiline` | boolean | `true` | When `true`, multiline strings start with content on the first line. When `false`, uses GNU gettext's traditional format with an empty first line |
### Examples[](#po-gettext-examples "Direct link to Examples")
With this format, plural messages are exported in the following ways, depending on whether an explicit ID is set:
* Message **with custom ID "my\_message"** that is pluralized on property "*someCount*".
```
#. js-lingui:pluralize_on=someCount
msgid "my_message"
msgid_plural "my_message_plural"
msgstr[0] "Singular case"
msgstr[1] "Case number {someCount}"
```
Note that `msgid_plural` was created by appending a `_plural` suffix.
* Message **without custom ID** that is pluralized on property "*anotherCount*".
To allow matching this PO item to the appropriate catalog entry when deserializing, the original ICU message is also stored in the generated comment.
```
#. js-lingui:icu=%7BanotherCount%2C+plural%2C+one+%7BSingular+case%7D+other+%7BCase+number+%7BanotherCount%7D%7D%7D&pluralize_on=anotherCount
msgid "Singular case"
msgid_plural "Case number {anotherCount}"
msgstr[0] "Singular case"
msgstr[1] "Case number {anotherCount}"
```
Note how `msgid` and `msgid_plural` were extracted from the original message.
* Message **with a custom comment prefix**.
Some TMS might modify the ICU comment by attempting to split lines to be 80 characters or less, or have trouble reading lingui comments because of the `js-lingui:` prefix. To change the prefix, set `customICUPrefix` to modify the prefix for ICU comments.
```
# with default prefix
#. js-
#. lingui:icu=%7BanotherCount%2C+plural%2C+one+%7BSingular+case%7D+other+%7BCase+number+%7BanotherCount%7D%7D%7D&pluralize_on=anotherCount
# customICUPrefix = jsi18n:
#. jsi18n:icu=%7BanotherCount%2C+plural%2C+one+%7BSingular+case%7D+other+%7BCase+number+%7BanotherCount%7D%7D%7D&pluralize_on=anotherCount
```
#### Duplicate Plurals[](#duplicate-plurals "Direct link to Duplicate Plurals")
When two calls to `plural` or `
` with no props, share a single placeholder without error.
See [Translator-friendly Messages](https://lingui.dev/guides/translator-friendly-messages.md#name-your-tags) for when and why to name tags.
Enforce Named Placeholders with ESLint
You can enforce that all JSX tags in `Message Inbox
Message Inbox
```
To make this heading translatable, simply wrap it in the [`Trans`](https://lingui.dev/ref/macro.md#trans) macro:
```
import { Trans } from "@lingui/react/macro";
;
```
Using JSX Macros is the easiest way to translate your React components. It handles translations of messages, including variables and other React components.
### Macros vs. Components[](#macros-vs-components "Direct link to Macros vs. Components")
If you're wondering what [Macros](https://lingui.dev/ref/macro.md) are and the difference between macros and runtime components, here's a quick explanation.
In general, macros are executed at compile time and serve to transform the source code to make the message writing process easier. Under the hood, all JSX macros are transformed into the runtime component [`Trans`](https://lingui.dev/ref/react.md#trans) (imported from `@lingui/react`).
Below is a brief example demonstrating this transformation:
```
import { Trans } from "@lingui/react/macro";
it's not exactly what I had in my mind.
{t`Other Item`}
See all unread messages {" or "} mark them as read.
{messagesCount === 1 ? `There's ${messagesCount} message in your inbox.` : `There are ${messagesCount} messages in your inbox.`}
See all unread messages {" or "} mark them as read.
``` Although it looks complex, there's really nothing special here. Just wrap the content of the paragraph in [`Trans`](https://lingui.dev/ref/macro.md#trans) and let the macro do the magic: ```
{messagesCount === 1 ? `There's ${messagesCount} message in your inbox.` : `There are ${messagesCount} messages in your inbox.`}
``` This message is a bit special, because it depends on the value of the `messagesCount` variable. Most languages use different forms of words when describing quantities - this is called [pluralization](https://lingui.dev/guides/plurals.md). What's tricky is that different languages use different number of plural forms. For example, English has only two forms - singular and plural - as we can see in the example above. However, Czech language has three plural forms. Some languages have up to 6 plural forms and some don't have plurals at all! info Lingui uses `Intl.PluralRules` which is supported in [every modern browser](https://caniuse.com/intl-pluralrules) and can be polyfilled for older. So you don't need to setup anything special. ### English Plural Rules[](#english-plural-rules "Direct link to English Plural Rules") How do we know which plural form we should use? It's very simple: we, as developers, only need to know plural forms of the language we use in our source. Our component is written in English, so looking at [English plural rules](http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#en) we'll need just two forms: `one` > Singular form `other` > Plural form We don't need to select these forms manually. We'll use [`Plural`](https://lingui.dev/ref/macro.md#plural-1) component, which takes a `value` prop and based on the active language, selects the right plural form: ``` import { Plural } from "@lingui/solid/macro";