# Lingui ## installation - [Installation and Setup](/installation.md): Learn how to install Lingui in your project ## tutorials - [JavaScript Apps Internationalization](/tutorials/javascript.md): Learn how to use Lingui's internationalization features in your vanilla JavaScript application - [React Apps Internationalization](/tutorials/react.md): Learn how to add internationalization to a React application using Lingui - [React Native Apps Internationalization](/tutorials/react-native.md): Learn how to add internationalization to a React Native application using Lingui - [Lingui with React Server Components](/tutorials/react-rsc.md): Learn how to setup and use Lingui with RSC & Next.js ## guides - [Custom Extractor](/guides/custom-extractor.md): Learn how to write a custom message extractor for your project - [Custom Formatter](/guides/custom-formatter.md): Learn how to write a custom localization message formatter for your project - [Dynamic Loading of Message Catalogs](/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](/guides/explicit-vs-generated-ids.md): Learn about the differences between explicit and generated IDs and how to choose the right approach for your project - [Lazy Translations](/guides/lazy-translations.md): Lazy translations allow you to defer translation of a message until it is actually displayed - [Message Extraction](/guides/message-extraction.md): Learn about message extraction in i18n and how to use Lingui to extract messages from your application - [ICU MessageFormat](/guides/message-format.md): ICU MessageFormat is a flexible and powerful syntax designed to express the grammatical nuances of different languages. Its flexibility ensures that your application can handle grammatical variations, making it essential for effective internationalization. - [Monorepo](/guides/monorepo.md): If you're using lingui within a monorepo you need: - [Keeping Your Bundle Small: How Lingui Optimizes for Performance](/guides/optimizing-bundle-size.md): When you're building a modern app with internationalization (i18n), it's easy to end up with a bloated bundle. The more languages and messages you have, the more it can grow — fast. - [Pluralization](/guides/plurals.md): Learn about pluralization and how to use it in your application with Lingui - [Pseudolocalization](/guides/pseudolocalization.md): Learn how to use pseudolocalization to test the internationalization aspects of your application with Lingui - [Testing](/guides/testing.md): In a React application, components that use Trans or useLingui need access to the context provided by I18nProvider. How you wrap your component with the I18nProvider depends on the testing library you're using. ## ref - [Catalog Formats](/ref/catalog-formats.md): Learn about the different catalog formats supported by Lingui - [Lingui CLI](/ref/cli.md): Learn how to set up and use Lingui CLI to extract, merge and compile message catalogs - [Configuration](/ref/conf.md): Learn how to configure Lingui for your project - [Core API Reference](/ref/core.md): The package provides the main i18n object, which manages message catalogs, active locale, and message translation and formatting - [ESLint Plugin](/ref/eslint-plugin.md): Lingui ESLint Plugin helps you find and prevent common i18n mistakes in your code - [Vue.js Extractor](/ref/extractor-vue.md): The @lingui/extractor-vue package provides a custom extractor that handles Vue.js files. - [Webpack Loader](/ref/loader.md): The @lingui/loader is a Webpack loader for Lingui message catalogs. It offers an alternative to the lingui compile and compiles catalogs on the fly. - [Locale Detection](/ref/locale-detector.md): Detect the user's locale with the `@lingui/detect-locale` package - [Macros](/ref/macro.md): Transform JavaScript objects and JSX elements into ICU MessageFormat messages - [Metro Transformer](/ref/metro-transformer.md): Use Lingui with React Native and compile your message catalogs on the fly - [React API Reference](/ref/react.md): Reference for the Lingui React API and components - [SWC Plugin](/ref/swc-plugin.md): Use Lingui Macros in your SWC project - [Vite Plugin](/ref/vite-plugin.md): Use Lingui with Vite and compile your message catalogs on the fly ## examples - [Examples](/examples.md): Examples of using Lingui for internationalization (i18n) in various frameworks and libraries ## introduction - [Introduction](/introduction.md): Lingui is a universal, clean and readable, lightweight and powerful internationalization framework for global projects ## tools - [Crowdin](/tools/crowdin.md): Crowdin is a localization management platform that helps translate your LinguiJS-based product. Automate localization, release several multilingual versions of your app simultaneously, and provide an enhanced experience for your global customers. - [Sync & Collaboration Tools](/tools/introduction.md): While Lingui provides a powerful API for managing your translations, it doesn't provide an integrated solution for managing synchronization and collaboration with your translators. - [Translation.io](/tools/translation-io.md): Translation.io is a professional synchronization and collaboration platform that will assist your team in the translation of your Lingui application. --- # Full Documentation Content # Examples To help you get started with Lingui, we've provided some example projects that demonstrate how Lingui can be integrated into various frameworks and libraries. These projects demonstrate Lingui's versatility and show you how to use it in real-world scenarios. Check out the example projects below: * [Create React App](https://github.com/lingui/js-lingui/tree/main/examples/create-react-app) * [Vanilla JS](https://github.com/lingui/js-lingui/tree/main/examples/js) * [Next.js with Babel](https://github.com/lingui/js-lingui/tree/main/examples/nextjs-babel) * [Next.js with SWC and app router](https://github.com/lingui/js-lingui/tree/main/examples/nextjs-swc) * [React Native (uses Expo)](https://github.com/lingui/js-lingui/tree/main/examples/react-native) * [React with Vite and Babel](https://github.com/lingui/js-lingui/tree/main/examples/vite-project-react-babel) * [React with Vite and SWC](https://github.com/lingui/js-lingui/tree/main/examples/vite-project-react-swc) * [Rspack with SWC](https://github.com/lingui/js-lingui/tree/main/examples/rspack) * [Remix with Vite and Babel](https://github.com/lingui/js-lingui/tree/main/examples/remix-vite-babel) * [Tanstack Start with Vite and Babel](https://github.com/lingui/js-lingui/tree/main/examples/tanstack-start) --- # Custom Extractor Lingui's default extractor supports JavaScript (Stage 3), TypeScript, and Flow out of the box, covering most standard and modern syntax features. However, if your project relies on experimental ECMAScript syntax or custom file formats, a custom extractor gives you the flexibility to handle these scenarios. ### Why It Doesn't Use Your Babel Config?[​](#why-it-doesnt-use-your-babel-config "Direct link to Why It Doesn't Use Your Babel Config?") Babel plugins from your configuration define transformations, and some of these may interfere with or slow down the extraction process. The extractor doesn't have to transform your code, it just analyzes it. Therefore, it's designed to understand different syntax without worrying about how the code is transpiled or down-levelled. info We are constantly updating the extractor to keep up with the latest ECMAScript features. However, if you find that a recently added Stage 3 feature doesn't work as expected, please [create an issue](https://github.com/lingui/js-lingui/issues/new/choose). ## Experimental ECMAScript Syntax[​](#experimental-ecmascript-syntax "Direct link to Experimental ECMAScript Syntax") If you are using experimental features (Stage 0 - Stage 2), you'll need to configure a custom list of parser plugins. This can be done by overriding the default extractor and using the `extractFromFileWithBabel()` function: lingui.config.ts ``` import { extractFromFileWithBabel, defineConfig } from "@lingui/cli/api"; import type { ParserPlugin } from "@babel/parser"; export function getBabelParserOptions(filename: string) { // https://babeljs.io/docs/en/babel-parser#latest-ecmascript-features const parserPlugins: ParserPlugin[] = ["importAttributes", "explicitResourceManagement"]; if ([/\.ts$/, /\.mts$/, /\.cts$/, /\.tsx$/].some((r) => filename.match(r))) { parserPlugins.push("typescript"); } if ([/\.js$/, /\.jsx$/, /\.tsx$/].some((r) => filename.match(r))) { parserPlugins.push("jsx"); } return parserPlugins; } export default defineConfig({ // [...] extractors: [ { match(filename: string) { return filename.match(/\.[cm][tj]sx?$/); }, async extract(filename, code, onMessageExtracted, ctx) { return extractFromFileWithBabel(filename, code, onMessageExtracted, ctx, { // https://babeljs.io/docs/babel-parser#plugins plugins: getBabelParserOptions(filename), }); }, }, ], }); ``` ## Other Frameworks or Custom Syntax[​](#other-frameworks-or-custom-syntax "Direct link to Other Frameworks or Custom Syntax") If you're working with files that aren't valid JavaScript, you can create a custom extractor to handle them: ./my-custom-extractor.ts ``` import { extractor as defaultExtractor } from "@lingui/cli/api"; export const extractor: ExtractorType = { match(filename: string) { return filename.endsWith(".custom"); }, extract(filename: string, code: string, onMessageExtracted, ctx: ExtractorCtx) { // Transform the file to plain JS + Sourcemaps const { code: transformedCode, sourcemaps } = transformMyCustomFileToJs(filename, code); // Access Lingui config via `ctx.linguiConfig` // Reuse the default CLI extractor return defaultExtractor.extract(filename, transformedCode, onMessageExtracted, { sourcemaps, ...ctx, }); }, }; ``` ### Adding the Custom Extractor to the Configuration[​](#adding-the-custom-extractor-to-the-configuration "Direct link to Adding the Custom Extractor to the Configuration") To use the custom extractor, you need to add it to your Lingui configuration file: lingui.config.ts ``` import { extractor } from "./my-custom-extractor.ts"; import { defineConfig } from "@lingui/cli"; export default defineConfig({ // [...] extractors: [extractor], }); ``` Important If you are using TypeScript to create your extractor, you should use the `.ts` extension for your Lingui configuration file. --- # Custom Formatter If your project requires a message catalog format that Lingui doesn't [natively support](/ref/catalog-formats.md), you can create a custom formatter to handle it. A custom formatter allows you to define how extracted strings are formatted into a custom catalog format, providing flexibility for specialized workflows and integration with unique file structures. ## Overview[​](#overview "Direct link to Overview") A formatter is an object with two main functions, `parse` and `serialize`, which define how catalogs are read from and written to your custom format. The formatter can be configured directly in your `lingui.config.{ts,js}` file - no separate package is needed: lingui.config.{ts,js} ``` import { defineConfig } from "@lingui/cli"; import { extractor } from "./my-custom-extractor.ts"; export default defineConfig({ // [...] format: { catalogExtension: "json", parse: (content: string): CatalogType => JSON.parse(content), serialize: (catalog: CatalogType): string => JSON.stringify(catalog), }, }); ``` ## Reference[​](#reference "Direct link to Reference") The shape of formatter is the following: ``` export type CatalogFormatter = { catalogExtension: string; /** * Set extension used when extract to template * Omit if the extension is the same as catalogExtension */ templateExtension?: string; parse( content: string, ctx: { locale: string | null; sourceLocale: string; filename: string } ): Promise | CatalogType; serialize( catalog: CatalogType, ctx: { locale: string | null; sourceLocale: string; filename: string; existing: string | null } ): Promise | string; }; ``` Lingui Catalog is an object with the following structure: ``` export type CatalogType = { [msgId: string]: MessageType; }; type CatalogExtra = Record; export type MessageType = { message?: string; origin?: MessageOrigin[]; comments?: string[]; obsolete?: boolean; context?: string; translation?: string; /** * the generic field where * formatters can store additional data */ extra?: Extra; }; ``` Important If you are using TypeScript to create your formatter, you should use the `.ts` extension for your Lingui configuration file. --- # Dynamic Loading of Message Catalogs Internationalization in modern applications requires careful handling of localized messages to avoid bloating the initial bundle size. By default, Lingui makes it easy to load all strings for a single active locale. For even greater efficiency, developers can selectively load only the messages needed on demand using [`i18n.load`](/ref/core.md#i18n.load), ensuring minimal resource usage. The [`I18nProvider`](/ref/react.md#i18nprovider) component doesn't make assumptions about your app's structure, giving you the freedom to load only the necessary messages for the currently selected language. This guide shows how to set up dynamic loading of message catalogs, ensuring only the needed catalogs are loaded, which reduces bundle size and improves performance. ## Final i18n Loader Helper[​](#final-i18n-loader-helper "Direct link to Final i18n Loader Helper") The following code defines the complete logic for dynamically loading and activating message catalogs based on the selected locale. It ensures that only the required catalog is loaded at runtime, optimizing performance: i18n.ts ``` import { i18n } from "@lingui/core"; export const locales = { en: "English", cs: "Česky", }; export const defaultLocale = "en"; /** * We do a dynamic import of just the catalog that we need * @param locale any locale string */ export async function dynamicActivate(locale: string) { const { messages } = await import(`./locales/${locale}/messages`); i18n.load(locale, messages); i18n.activate(locale); } ``` Important Even the "default" locale requires a catalog to be loaded. Lingui aims to keep the internationalization footprint to a minimum by dropping default messages from the production build. This way, only the catalog for the active language is loaded, avoiding duplication. Without this optimization, switching from the default language (e.g. EN) to another (e.g. FR) would require loading both language catalogs. This strategy ensures efficiency by loading only the necessary messages for one language at a time. ### Usage in Your Application[​](#usage-in-your-application "Direct link to Usage in Your Application") To use the `dynamicActivate` function in your application, you must call it on application startup. The following example shows how to use it in a React application: ``` import React, { useEffect } from "react"; import App from "./App"; import { I18nProvider } from "@lingui/react"; import { i18n } from "@lingui/core"; import { defaultLocale, dynamicActivate } from "./i18n"; const I18nApp = () => { useEffect(() => { // With this method we dynamically load the catalogs dynamicActivate(defaultLocale); }, []); return ( ); }; ``` ### Optimized Bundle Structure[​](#optimized-bundle-structure "Direct link to Optimized Bundle Structure") Looking at the contents of the build directory, we find a separate chunk for each language: ``` i18n-0.c433b3bd.chunk.js i18n-1.f0cf2e3d.chunk.js main.ab4626ef.js ``` When the page is first loaded, only the main bundle and the bundle for the first language are loaded: ![Requests during the first render](/assets/images/dynamic-loading-catalogs-1-142e71bb089432f649766b3077d504c3.png) After changing the language in the UI, the second language bundle is loaded: ![Requests during the second render](/assets/images/dynamic-loading-catalogs-2-eade7ea8527b393f9da2a5053a493aa3.png) ## Dependency Tree Extractor (experimental)[​](#dependency-tree-extractor-experimental "Direct link to Dependency Tree Extractor (experimental)") The Dependency Tree Extractor is an experimental feature designed to improve message extraction by analyzing the dependency tree of your application. This tool improves the efficiency of loading localized messages by identifying only the necessary messages for each component or page, rather than extracting all messages into a single catalog. This allows for a more granular extraction process, resulting in smaller and more relevant message catalogs. For detailed guidance on message extraction, refer to the [Message Extraction](/guides/message-extraction.md) guide. --- # Explicit vs Generated IDs When internationalizing your application, you may need to decide whether to use explicit or generated IDs for your messages. In this guide, we explore the basic concepts of explicit and generated IDs, and then delve into a comprehensive comparison, highlighting the advantages and disadvantages of each approach. ## What are Explicit IDs and Generated IDs?[​](#what-are-explicit-ids-and-generated-ids "Direct link to What are Explicit IDs and Generated IDs?") ### Explicit ID[​](#explicit-id "Direct link to Explicit ID") An explicit ID, often referred to as a user-defined or custom ID, is a manually assigned identifier associated with a specific message. These IDs are typically chosen by developers and are explicitly specified within your code. The typical explicit id may look like `index.header.title` or `modal.buttons.cancel`. Lingui example: ``` LinguiJS example // extracted as { id: "index.header.title", message: "LinguiJS example", } ``` ### Generated IDs[​](#generated-ids "Direct link to Generated IDs") On the other hand, generated IDs are created automatically by the internalization library. In Lingui, such IDs are created based on the source message and [context](#context). Lingui example: ``` LinguiJS example // extracted as { id: "uxV9Xq", message: "LinguiJS example", } ``` ### Benefits of Generated IDs[​](#benefits-of-generated-ids "Direct link to Benefits of Generated IDs") * **Avoiding the "naming things" problem:** You don't need to come up with a name for each single phrase in the app. Lingui generates the IDs (in the form of short hashes) from the messages. * **Better developer experience:** Developers can focus on coding without needing to manually assign IDs, leading to a more streamlined development process. Searching for a user-facing string will lead to the place in code where it's used, as opposed to taking you to a (typically JSON) file full of translations. * **Avoiding duplicates:** Duplicate messages are merged together automatically. Your translators will not have to translate the same phrases again and again. This could lead to cost savings, especially if translators charge by word count. * **Smaller bundle:** Lingui generates short IDs such as `uxV9Xq` which are typically shorter than manually crafted IDs like `index.header.title`. This results in a smaller bundle size, optimizing your application's performance. * **Preventing ID collisions:** As your application scales, explicit IDs can potentially lead to conflicts. Lingui's generated IDs ensure you steer clear of such collisions. ### Benefits of Explicit IDs[​](#benefits-of-explicit-ids "Direct link to Benefits of Explicit IDs") * **Control:** Developers have full control over the naming and assignment of explicit IDs. This control allows for precise targeting and easy maintenance of internationalization keys. * **Loose coupling:** Explicit IDs are loosely coupled with the messages (as opposed to when they are generated from the messages). This means that if the message changes, the ID remains the same. When your team uses a TMS (Translation Management System), this makes it easier even for non-technical people to update the strings. * **Readability:** Explicit IDs often have meaningful names, making it easier for developers, translators, and content creators to understand their purpose within the codebase. * **Predictability:** Since explicit IDs are manually assigned, they remain stable across different versions of your application, reducing the likelihood of breaking changes during updates. In conclusion, the choice between these two strategies depends on your project requirements and priorities. However, it's important to note that Lingui provides the full range of benefits, especially with generated IDs. note You don't have to worry about the readability of the IDs because you would hardly see them. When extracted into a PO file, translators would see the source string and its corresponding translation, while the IDs remain behind the scenes: ``` #: src/App.tsx:1 msgid "LinguiJS example" msgstr "LinguiJS przyklad" ``` ## Context[​](#context "Direct link to Context") By default, when using generated IDs, the same text elements are extracted with the same ID, and then translated once. However, this might not always be desirable since the same text can have different meanings and translations. For example, consider the word "right" and its two possible meanings: * correct as in "you are right" * direction as in "turn right" To distinguish these two cases, you can add `context` to messages. The same text elements with different contexts are extracted with different IDs. Then, they can be translated differently and merged back into the application as different translation entries. Regardless of whether you use generated IDs or not, adding context makes the translation process less challenging and helps translators interpret the source accurately. You, in return, get translations of better quality faster and decrease the number of context-related issues you would need to solve. Examples: ``` import { Trans } from "@lingui/react/macro"; right; right; // ↓ ↓ ↓ ↓ ↓ ↓ import { Trans } from "@lingui/react"; ; ; ``` or with non-JSX macro: ``` import { msg } from "@lingui/core/macro"; const ex1 = msg({ message: `right`, context: "direction", }); const ex2 = msg({ message: `right`, context: "correctness", }); // ↓ ↓ ↓ ↓ ↓ ↓ const ex1 = { id: "d1wX4r", message: `right`, }; const ex2 = { id: "16eaSK", message: `right`, }; ``` ## Using Generated IDs[​](#using-generated-ids "Direct link to Using Generated IDs") ### With JSX Macro[​](#with-jsx-macro "Direct link to With JSX Macro") ``` import { Trans } from "@lingui/react/macro"; function render() { return ( <>

LinguiJS example

Hello {name}.

); } ``` In the example code above, the content of [`Trans`](/ref/macro.md#trans) is transformed into a message in MessageFormat syntax. By default, this message is used for generating the ID. Considering the example above, the catalog would contain these entries: ``` const catalog = [ { id: "uxV9Xq", message: "LinguiJS example", }, { id: "9/omjw", message: "Hello <0>{name}.", }, ]; ``` ### With Core Macro[​](#with-core-macro "Direct link to With Core Macro") In the following example, the message `Hello World` will be extracted and used to create an ID: ``` import { msg } from "@lingui/core/macro"; msg`Hello World`; ``` ## Using Custom IDs[​](#using-custom-ids "Direct link to Using Custom IDs") ### With JSX Macro[​](#with-jsx-macro-1 "Direct link to With JSX Macro") To use custom IDs in JSX macros, pass the ID as a prop to the component: ``` import { Trans } from "@lingui/react/macro"; function render() { return ( <>

LinguiJS example

Hello {name}.

); } ``` The messages with IDs `msg.header` and `msg.hello` will be extracted with their default values as `LinguiJS example` and `Hello <0>{name}.` respectively. ### With Core Macro[​](#with-core-macro-1 "Direct link to With Core Macro") To use custom IDs in non-JSX macros, call the [`msg`](/ref/macro.md#definemessage) function with a message descriptor object, passing the ID using the `id` property: ``` import { msg } from "@lingui/core/macro"; msg({ id: "msg.greeting", message: `Hello World` }); ``` Message `msg.greeting` will be extracted with default value `Hello World`. For all other js macros ([`plural`](/ref/macro.md#plural), [`select`](/ref/macro.md#select), [`selectOrdinal`](/ref/macro.md#selectordinal), use them inside [`msg`](/ref/macro.md#definemessage) macro to pass ID (in this case, `'msg.caption'`). ``` import { msg, plural } from "@lingui/core/macro"; msg({ id: "msg.caption", message: plural(count, { one: "# image caption", other: "# image captions", }), }); ``` ## See Also[​](#see-also "Direct link to See Also") * [Message Extraction](/guides/message-extraction.md) * [Macros Reference](/ref/macro.md) --- # Lazy Translations Lazy translation allows you to defer translation of a message until it's rendered, giving you flexibility in how and where you define messages in your code. With lazy translation, you can tag a string with the [`msg`](/ref/macro.md#definemessage) macro to create a *message descriptor* that can be saved, passed around as a variable, and rendered later. ## Usage Example[​](#usage-example "Direct link to Usage Example") To render the message descriptor as a string-only translation, pass it to the [`i18n._()`](/ref/core.md#i18n._) method: ``` import { msg } from "@lingui/core/macro"; import { i18n } from "@lingui/core"; const favoriteColors = [msg`Red`, msg`Orange`, msg`Yellow`, msg`Green`]; export function getTranslatedColorNames() { return favoriteColors.map((color) => i18n._(color)); } ``` ## Usage in React[​](#usage-in-react "Direct link to Usage in React") To render the message descriptor in a React component, pass its `id` to the [`Trans`](/ref/react.md#trans) component as a value of the `id` prop: ``` import { msg } from "@lingui/core/macro"; import { Trans } from "@lingui/react"; const favoriteColors = [msg`Red`, msg`Orange`, msg`Yellow`, msg`Green`]; export default function ColorList() { return (
    {favoriteColors.map((color) => (
  • ))}
); } ``` Important Please note that we import the `` component from `@lingui/react` to use the runtime version, as the message is already defined and we don't need the compile-time macro here. ### Picking a Message Based on a Variable[​](#picking-a-message-based-on-a-variable "Direct link to Picking a Message Based on a Variable") Sometimes you need to choose between different messages to display depending on the value of a variable. For example, imagine you have a numeric "status" code that comes from an API, and you need to display a message that represents the current status. An easy way to do this is to create an object that maps the possible values of "status" to message descriptors (tagged with the [`msg`](/ref/macro.md#definemessage) macro) and render them as needed with deferred translation: ``` import { msg } from "@lingui/core/macro"; import { useLingui } from "@lingui/react"; const statusMessages = { ["STATUS_OPEN"]: msg`Open`, ["STATUS_CLOSED"]: msg`Closed`, ["STATUS_CANCELLED"]: msg`Cancelled`, ["STATUS_COMPLETED"]: msg`Completed`, }; export default function StatusDisplay({ statusCode }) { const { _ } = useLingui(); return
{_(statusMessages[statusCode])}
; } ``` ### Memoization Pitfall[​](#memoization-pitfall "Direct link to Memoization Pitfall") In the following contrived example, we document how a welcome message will or will not be updated when locale changes. The documented behavior may not be intuitive at first, but it is expected, because of the way the `useMemo` dependencies work. To avoid bugs with stale translations, use the `t` function returned from the [`useLingui`](/ref/macro.md#uselingui) macro: it is safe to use with memoization because its reference changes whenever the Lingui context updates. Keep in mind that `useMemo` is primarily a performance optimization tool in React. Because of this, there might be no need to memoize your translations. Additionally, this issue is not present when using the `Trans` component, which we recommend using whenever possible. ``` import { i18n } from "@lingui/core"; import { msg } from "@lingui/core/macro"; import { useLingui } from "@lingui/react/macro"; const welcomeMessage = msg`Welcome!`; // ❌ Bad! This code won't work export function Welcome() { const buggyWelcome = useMemo(() => { return i18n._(welcomeMessage); }, []); return
{buggyWelcome}
; } // ❌ Bad! This code won't work either because the reference to i18n does not change export function Welcome() { const { i18n } = useLingui(); const buggyWelcome = useMemo(() => { return i18n._(welcomeMessage); }, [i18n]); return
{buggyWelcome}
; } // ✅ Good! `useMemo` consumes the `t` function from the `useLingui` macro export function Welcome() { const { t } = useLingui(); const welcome = useMemo(() => { return t(welcomeMessage); }, [t]); return
{welcome}
; } ``` --- # Message Extraction Message extraction is a key part of the internationalization process. It involves scanning your codebase to identify and extract all the defined messages, ensuring that your message catalogs stay synchronized with the source code. In practice, developers define messages directly in the source code, and the extraction tool automatically collects these messages and stores them in a message catalog for translation. Read more about the [`lingui extract`](/ref/cli.md#extract) command. ## Supported Patterns[​](#supported-patterns "Direct link to Supported Patterns") The extractor operates at a static level, meaning that it analyzes the source code without executing it. As a result, it doesn't support complex patterns or dynamic code, and only simple, statically defined messages are collected. ### Macros Usage[​](#macros-usage "Direct link to Macros Usage") > Macros are JavaScript transformers that run at build time. The value returned by a macro is inlined into the bundle instead of the original function call. The Lingui Macro provides powerful macros to transform JavaScript objects and JSX elements into [ICU MessageFormat](/guides/message-format.md) messages at compile time. Extractor supports all macro usage, such as the following examples: ``` import { t } from "@lingui/core/macro"; import { Trans } from "@lingui/react/macro"; t`Message`; t({ id: "custom.id", message: "Message with custom ID", }); const jsx = Hi, my name is {name}; ``` The extractor matches the `t` and `Trans` macro calls and extracts the messages from them. For more usage examples, see to the [Macros](/ref/macro.md) reference. ### Non-Macros Usage[​](#non-macros-usage "Direct link to Non-Macros Usage") Non-macro use is also supported, but it's not common. It's recommended to use macros. The extractor matches `i18n._` or `i18n.t` function calls. It also matches when these functions are called from other member expressions, such as `ctx.i18n.t()`. caution The extractor only matches calls by name. It doesn't check if they are really imported from Lingui packages. For example: ``` i18n._("message.id"); i18n._({ id: "message.id" }); ctx.i18n._("message.id"); ctx.i18n.t("message.id"); ctx.request.i18n.t("message.id"); // and so on ``` To ignore a specific call expression during extraction, you can add a `lingui-extract-ignore` comment: ``` /* lingui-extract-ignore */ ctx.i18n._("Message"); ``` Messages marked with this comment will be excluded from extraction. ### Explicitly Marking Messages[​](#explicitly-marking-messages "Direct link to Explicitly Marking Messages") In addition to call expressions, which are the most commonly used method, the extractor tool also supports simple string literals and message descriptors with explicit annotations. To do this, simply prefix your expression with the `/*i18n*/` comment, like so: ``` const messageDescriptor: MessageDescriptor = /*i18n*/ { id: "Description", comment: "description" }; const stringLiteral = /*i18n*/ "Message"; ``` ## Unsupported Patterns[​](#unsupported-patterns "Direct link to Unsupported Patterns") The extractor is limited to extracting messages from code that is written a certain way. It cannot extract messages from variables or function calls. It also cannot follow the program structure and get the value of a variable defined elsewhere. This means that in order for a message to be extracted, it must be defined directly in the function call: ``` // ❌ This message will not be extracted const message = "Message"; i18n._(message); // ✅ This message will be extracted i18n._("Message"); ``` ## Defining Sources for Analyzing[​](#defining-sources-for-analyzing "Direct link to Defining Sources for Analyzing") The extractor can locate source files in two ways: by specifying a glob pattern or by crawling the dependency tree. ### Glob Pattern[​](#glob-pattern "Direct link to Glob Pattern") By default, `lingui extract` uses a glob pattern to search for source files containing messages. The pattern is defined in the [`catalogs`](/ref/conf.md#catalogs) property of the Lingui configuration file in your project's root directory. ![Scheme of discovering by glob pattern](/assets/images/extractor-glob-scheme-450bc42bb7a09c538a20e45badbf087f.svg) ### Dependency Tree Crawling[​](#dependency-tree-crawling "Direct link to Dependency Tree Crawling") While the glob-based extraction process works well for most projects, it can be challenging for multi-page applications (MPAs) such as Next.js. In such cases, the glob approach generates a single catalog that includes all messages from each page, which may not be ideal for effectively managing translations. This means that the entire catalog must be loaded for each page or navigation, resulting in unnecessary loading of messages that aren't utilized on that specific page. To address this issue, an `experimental-extractor` has been introduced in Lingui v4. Experimental This is an experimental feature. Experimental features are not covered by semver and may be subject to change. This extractor uses the dependency tree of files, rather than just a glob pattern, to crawl imports and discover files more accurately. By doing so, it creates a more optimized catalog that only contains the messages needed for each page. The catalogs would still contain duplicate messages for common components, but it would be much better than the current approach. ![Scheme of discovering by dependencies](/assets/images/extractor-deps-scheme-a1918925811db3a4184d2a616a262e8f.svg) To start using `experimental-extractor`, add the following section to your Lingui configuration: ``` /** * * @type {import('@lingui/conf').LinguiConfig} */ module.exports = { // remove everethying from `catalogs` property catalogs: [], experimental: { extractor: { // glob pattern of entrypoints // this will find all nextjs pages entries: ["/src/pages/**/*.tsx"], // output pattern, this instruct extractor where to store catalogs // src/pages/faq.tsx -> src/pages/locales/faq/en.po output: "/{entryDir}/locales/{entryName}/{locale}", }, }, }; ``` Then run the following command in your terminal: ``` lingui extract-experimental ``` #### Important Notes[​](#important-notes "Direct link to Important Notes") It's worth noting that the accuracy of the catalog depends heavily on tree-shaking, a technique used by modern bundlers to remove unused code from the final bundle. If the code passed to the extractor is written in a tree-shakeable way, the user will get highly accurate catalogs. While you may think that your code is tree-shakeable, in practice tree-shaking may work differently than you expect, and some unwanted strings may be included in the catalogs. To illustrate, let's consider the following code: ``` import { msg } from "@lingui/core/macro"; export const species = { Cardano: [ { startsAt: 0, name: msg`Ghost`, icon: "Ghost", }, { startsAt: 0.000001, name: msg`Plankton`, icon: "Plankton", }, ], }; ``` On the surface, it may appear that this code can be safely removed from the final bundle if it isn't used. However, the `msg` function call may have a side effect that prevents the bundler from removing the entire `species` object from the final bundle. As a result, messages defined in this snippet may be included in more catalogs than expected. tip To avoid this issue, one solution is to wrap the `species` object inside an *Immediately Invoked Function Expression* (IIFE) and add the `/* @__PURE__ */` annotation. By adding this annotation to the IIFE, we tell the bundler that the entire `species` object can be safely removed if it is not used or exported elsewhere in the code. ## Supported Source Types[​](#supported-source-types "Direct link to Supported Source Types") The extractor supports TypeScript, Flow, and JavaScript (Stage 3) out of the box. If you are using some experimental features (Stage 0 - Stage 2) or frameworks with custom syntax such as Vue.js or Svelte, you may want to implement your own custom extractor. Visit [Custom Extractor](/guides/custom-extractor.md) to learn how to create a custom extractor. ## See Also[​](#see-also "Direct link to See Also") * [Lingui CLI Reference](/ref/cli.md) * [Macros Reference](/ref/macro.md) * [Catalog Formats](/ref/catalog-formats.md) --- # ICU MessageFormat ICU MessageFormat is a flexible and powerful syntax designed to express the grammatical nuances of different languages. Its flexibility ensures that your application can handle grammatical variations, making it essential for effective internationalization. ## Overview[​](#overview "Direct link to Overview") ### Simple text[​](#simple-text "Direct link to Simple text") Example: `Refresh inbox` ### Variables[​](#variables "Direct link to Variables") Example: `Attachment {name} saved` ### Plurals[​](#plurals "Direct link to Plurals") > Using language specific plural forms (`one`, `other`): ``` {count, plural, one {Message} other {Messages}} ``` > Using exact matches for specific counts (`=0`): ``` {count, plural, =0 {No messages} one {# message} other {# messages}} ``` > Offsetting plural forms: ``` {count, plural, offset:1 =0 {Nobody read this message} =1 {Only you read this message} one {You and # friend read this message} other {You and # friends read this message} ``` ### Select[​](#select "Direct link to Select") ``` {gender, select, male {He replied to your message} female {She replied to your message} other {They replied to your message}} ``` ### Ordinals[​](#ordinals "Direct link to Ordinals") ``` {count, selectOrdinal, one {#st message} two {#nd message} few {#rd message} other {#th message}} ``` ## See Also[​](#see-also "Direct link to See Also") * [Pluralization](/guides/plurals.md) * [ICU Playground](https://format-message.github.io/icu-message-format-for-translators/editor.html) --- # Monorepo If you're using lingui within a monorepo you need: * 1x `babel.config.js` within root * 1x `lingui.config.js` within root * And **n**-times `lingui.config.js` per package which extends/overrides from root --- # Keeping Your Bundle Small: How Lingui Optimizes for Performance When you're building a modern app with internationalization (i18n), it's easy to end up with a bloated bundle. The more languages and messages you have, the more it can grow — fast. Lingui helps you avoid this by aggressively optimizing how translations are handled in your code and in production builds. But these optimizations can also surprise you if you're not aware of how they work. This guide walks you through: * How Lingui shrinks your bundle * What tradeoffs are involved * Why development and production behave differently * How to avoid common pitfalls * And how to configure Lingui to fit your workflow ## Why does this matter?[​](#why-does-this-matter "Direct link to Why does this matter?") Imagine you have a simple message: ``` Hello world ``` If this message is included *as-is* in your code and translations, you might end up with: * The message string `"Hello world"` in your source code * The same string as a key in your translation catalog * And again as a value in your default language catalog That's three copies of the same thing — and you'll have that for *every* message. Now multiply that by hundreds of messages and a few languages, and you can see where this is going. ## So how does Lingui fix that?[​](#so-how-does-lingui-fix-that "Direct link to So how does Lingui fix that?") ### 1. It replaces messages with compact IDs[​](#1-it-replaces-messages-with-compact-ids "Direct link to 1. It replaces messages with compact IDs") When you build your app, Lingui transforms: ``` Hello world ``` into something like: ``` ``` The message is no longer part of the bundle. Instead, it's replaced by a short ID. This does two things: * Saves space (IDs are shorter than full strings) * Prevents duplication (only the translation needs to exist, not the original text) ### 2. It removes the message compiler from production[​](#2-it-removes-the-message-compiler-from-production "Direct link to 2. It removes the message compiler from production") Messages like this: ``` "{count, plural, one {# item} other {# items}}"; ``` use [ICU MessageFormat](https://formatjs.io/docs/core-concepts/icu-syntax), which needs to be *compiled* into something the Lingui runtime can use. Lingui includes a message compiler for this, but it's not small. Instead of sending that to the browser, Lingui compiles messages **ahead of time** during your build when you compile your catalogs. That way, you don't need the compiler in production at all. That's why you need to always compile your catalogs, even if they are in JSON format (not `.po` files). Compilation isn't about converting file formats — it's about transforming messages into a form the runtime can execute. note ✅ Tip: If you use the `@lingui/vite-plugin`, `@lingui/loader` or `@lingui/metro-transformer`, you don't need to run `lingui compile` manually — these plugins compile your catalogs automatically when you import catalogs. ## But wait... why does everything still work in development?[​](#but-wait-why-does-everything-still-work-in-development "Direct link to But wait... why does everything still work in development?") Here's the clever part: Lingui works differently in dev vs prod. In **development**, Lingui: * Keeps the original message (`Hello world`) in the bundle * Includes the message compiler so new messages work immediately This makes it fast to iterate. You can add a new `` and see the string in the browser right away — even if you haven't extracted or compiled anything yet. In **production**, Lingui: * Strips out all original messages * Removes the message compiler completely This means you **must** extract and compile all messages ahead of time — otherwise, Lingui won't know how to render them. ## Common Problem: “Why am I seeing weird message IDs in production?”[​](#common-problem-why-am-i-seeing-weird-message-ids-in-production "Direct link to Common Problem: “Why am I seeing weird message IDs in production?”") Let's say you add a new message: ``` This is a new message ``` Everything looks fine locally, but when you deploy, users see something like: ``` z3fd2 ``` This usually means one thing: the message wasn't extracted before building. When Lingui compiles your catalogs, it tries to match each message ID to a source message. If the message isn't there, there's nothing to fall back to — and the raw ID ends up in the UI. ### ✅ Solution: Always extract before building[​](#-solution-always-extract-before-building "Direct link to ✅ Solution: Always extract before building") Make sure your build script extracts the latest messages: ``` "build": "lingui extract-template && vite build" ``` This ensures your catalogs are in sync with your source code. ## But what if I want to load translations dynamically?[​](#but-what-if-i-want-to-load-translations-dynamically "Direct link to But what if I want to load translations dynamically?") That's where the tradeoffs come in. Lingui's design is optimized for build-time static analysis. It's great for most apps, but it can get tricky if you want to: * Load translations from a CMS * Support over-the-air (OTA) delivery of catalogs * Inject new messages at runtime In these cases, you can't rely on precompiled catalogs alone — you'll need the **runtime message compiler** again. To bring it back, use: ``` import { compileMessage } from "@lingui/message-utils/compileMessage"; i18n.setMessagesCompiler(compileMessage); ``` Just keep in mind that this will increase your bundle size again. ## Configuring Lingui for your needs[​](#configuring-lingui-for-your-needs "Direct link to Configuring Lingui for your needs") Here are a few ways to customize Lingui's behavior depending on your goals: ### 1. I want production to behave like development[​](#1-i-want-production-to-behave-like-development "Direct link to 1. I want production to behave like development") You want to keep original messages and use runtime compilation, even in production — maybe for debugging or dynamic catalogs. **How to configure:** ``` // Macro config stripMessageField: false; // Runtime setup i18n.setMessagesCompiler(compileMessage); ``` ### 2. I want full consistency between dev and prod[​](#2-i-want-full-consistency-between-dev-and-prod "Direct link to 2. I want full consistency between dev and prod") You want everything to be stripped in both environments. Useful for catching issues early. **How to configure:** ``` // Macro config stripMessageField: true; // Runtime setup i18n.setMessagesCompiler(null); ``` ### 3. I want to use default Lingui behavior[​](#3-i-want-to-use-default-lingui-behavior "Direct link to 3. I want to use default Lingui behavior") You don't change anything. Lingui automatically strips messages in production and keeps them in development. Just make sure to always run `extract-template` before building. ## Macro Configuration: A quick note[​](#macro-configuration-a-quick-note "Direct link to Macro Configuration: A quick note") Depending on your setup, Lingui macros can be used in different ways: * As a standalone **Babel plugin** * As a [**SWC plugin**](https://github.com/lingui/swc-plugin) * Through [**babel-plugin-macros**](https://github.com/kentcdodds/babel-plugin-macros/blob/main/other/docs/user.md#config) Each one configures slightly differently, so check their docs for details. --- # Pluralization Pluralization is essential for effective internationalization, allowing applications to display messages or select options based on the number. In this article, we explore various categories of pluralization, see implementation examples, and learn how to customize your application for different languages. Lingui uses the [CLDR Plural Rules](https://www.unicode.org/cldr/charts/42/supplemental/language_plural_rules.html) to determine the correct plural form for each language. In general, there are 6 plural forms (taken from the [CLDR Plurals](https://cldr.unicode.org/index/cldr-spec/plural-rules) page): * zero * one (singular) * two (dual) * few (paucal) * many (also used for fractions if they have a separate class) * other (required — general plural form — also used if the language only has a single form) info Only the *other* form is required, because it's the only common plural form used in all languages. All other plural forms depend on the language. For example, English has only two forms: *one* and *other* (1 book vs. 2 books). In Czech, we have four: *one*, *few*, *many* and *other* (1 kniha, 2 knihy, 1,5 knihy, 5 knih). Some languages have even more, such as Arabic. ## Using Plural Forms[​](#using-plural-forms "Direct link to Using Plural Forms") The good thing is that **as developers, we only need to know the plural forms of the source language**. When we use English in the source code, we'll only use *one* and *other*: ``` plural(numBooks, { one: "# book", other: "# books", }); ``` When `numBooks == 1`, this will render as *1 book* and for `numBook == 2` it will be *2 books*. Interestingly, for `numBooks == -1`, it will be *-1 book*. This is because the "one" plural form also applies to -1. It is therefore important to remember that the plural forms (such as "one" or "two") do not represent the numbers themselves, but rather *categories* of numbers. Under the hood, the [`plural`](/ref/macro.md#plural) macro is replaced with a low-level [`i18n._`](/ref/core.md#i18n._) call. In production, the example will look like this: ``` i18n._({ id: "d1wX4r", // stripped on production // message: '{numBooks, plural, one {# book} other {# books}}', values: { numBooks }, }); ``` When we extract messages from the source code using the [Lingui CLI](/ref/cli.md), we get: ``` {numBooks, plural, one {# book} other {# books}} ``` This is then translated by our Czech translator as: ``` {numBooks, plural, one {# kniha} few {# knihy} many {# knihy} other {# knih}} ``` Important The important thing is that *we don't have to change our code to support languages with different plural rules*. ## Source Code in Language Other than English[​](#source-code-in-language-other-than-english "Direct link to Source Code in Language Other than English") As developers, we only need to be aware of the plural forms for the source language. Check the [plural forms](https://www.unicode.org/cldr/charts/42/supplemental/language_plural_rules.html) for your language, and then you can use them accordingly. Here's an example in Czech: ``` plural(numBooks, { one: "# kniha", few: "# knihy", many: "# knihy", other: "# knih", }); ``` This makes Lingui also valuable for monolingual projects, meaning that you can benefit from it even if you don't translate your application. Pluralization, along with number and date formatting, is relevant to all languages. --- # Pseudolocalization Pseudo-localization is a method used to check the software's readiness to be localized. This method shows how the product's UI will look after translation. Use this feature to reduce potential rework by checking whether any source strings should be altered before the translation process begins. It also makes it easy to identify hard-coded strings and improperly concatenated strings so that they can be localized properly. > Example: Ţĥĩś ţēxţ ĩś ƥśēũďōĺōćàĺĩźēď ## Configuration[​](#configuration "Direct link to Configuration") To configure pseudolocalization, add the [`pseudoLocale`](/ref/conf.md#pseudolocale) property to your Lingui configuration file: lingui.config.{ts,js} ``` import { defineConfig } from "@lingui/cli"; export default defineConfig({ locales: ["en", "pseudo-LOCALE"], pseudoLocale: "pseudo-LOCALE", fallbackLocales: { "pseudo-LOCALE": "en", }, }); ``` The `pseudoLocale` option must be set to any string that matches a value in the [`locales`](/ref/conf.md#locales) configuration. If this is not set correctly, no folder or pseudolocalization will be created. If the `fallbackLocales` is configured, the pseudolocalization will be generated from the translated fallback locale instead. ## Generate Pseudolocalization[​](#generate-pseudolocalization "Direct link to Generate Pseudolocalization") After running the [`extract`](/ref/cli.md#extract) command, verify that the folder for the pseudolocale has been created. Pseudolocalization is automatically generated during the [`compile`](/ref/cli.md#compile) process, using the messages. ## Switch Browser Into Specified Pseudolocale[​](#switch-browser-into-specified-pseudolocale "Direct link to Switch Browser Into Specified Pseudolocale") You can switch your browser to a pseudolocale either by adjusting the browser settings or by using extensions. Extensions provide flexibility by allowing you to use any locale, while browser settings are usually limited to valid language tags (BCP 47). In such cases, the pseudolocale must be a standard locale that isn't already used in your application. For example, you could use `zu-ZA` (Zulu - South Africa). Chrome: * With extension (valid locale) - [Locale Switcher](https://chrome.google.com/webstore/detail/locale-switcher/kngfjpghaokedippaapkfihdlmmlafcc). * Without extension (valid locale) - . Firefox: * With extension (any string) - [Quick Accept-Language Switcher](https://addons.mozilla.org/en-GB/firefox/addon/quick-accept-language-switc/?src=search). * Without extension (valid locale) - [about](about:preferences#general) [:preferences](about:preferences#general) [#general](about:preferences#general) > *Language*. --- # Testing In a React application, components that use [`Trans`](/ref/react.md#trans) or [`useLingui`](/ref/react.md#uselingui) need access to the context provided by [`I18nProvider`](/ref/react.md#i18nprovider). How you wrap your component with the I18nProvider depends on the testing library you're using. Below is an example using [react-testing-library](https://testing-library.com/docs/react-testing-library/intro/) and its [wrapper-property](https://testing-library.com/docs/react-testing-library/api#wrapper): index.js ``` import React from "react"; import { getByText, render, act } from "@testing-library/react"; import { i18n } from "@lingui/core"; import { I18nProvider } from "@lingui/react"; import { messages } from "./locales/en/messages"; import { messages as csMessages } from "./locales/cs/messages"; import App from "./App"; i18n.load({ en: messages, cs: csMessages, }); const TestingProvider = ({ children }: any) => {children}; test("Content should be translated correctly in English", () => { act(() => { i18n.activate("en"); }); const { getByTestId, container } = render(, { wrapper: TestingProvider }); expect(getByTestId("h3-title")).toBeInTheDocument(); expect(getByText(container, "Language switcher example:")).toBeDefined(); }); test("Content should be translated correctly in Czech", () => { act(() => { i18n.activate("cs"); }); const { getByTestId, container } = render(, { wrapper: TestingProvider }); expect(getByTestId("h3-title")).toBeInTheDocument(); expect(getByText(container, "Příklad přepínače jazyků:")).toBeDefined(); }); ``` To avoid repeating the `TestingProvider` setup in multiple tests, consider defining a custom renderer. You can find more about this in the [react testing library - Custom Render](https://testing-library.com/docs/react-testing-library/setup#custom-render) documentation. --- # Installation and Setup Lingui is more than just a package; it's a comprehensive suite of tools designed to simplify internationalization. You have the flexibility to choose the specific tools that best fit your project's needs. Learn how to install Lingui in your project, whether you use JavaScript, React (including RSC) or React Native. Lingui also supports various transpilers and build tools, such as Babel, SWC, and Vite. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Make sure you have [Node.js](https://nodejs.org/) installed (v20 or higher). * Install [Lingui CLI](/ref/cli.md) to manage your translations and catalogs. tip Don't miss the [Lingui ESLint Plugin](/ref/eslint-plugin.md) which can help you find and prevent common i18n mistakes in your code. ## Choosing a Transpiler[​](#choosing-a-transpiler "Direct link to Choosing a Transpiler") > A transpiler converts code within a language, transforming newer features into older equivalents for compatibility, or expanding concise syntax into more verbose implementations. Lingui needs a transpiler to work. It's responsible for transforming Lingui's JS/JSX components into [ICU MessageFormat](/guides/message-format.md) and extracting message IDs. Both Babel and SWC transpilers are supported. Follow the Babel or SWC setup depending on what transpiler your project already uses. * Babel * SWC > Babel is a JavaScript transpiler that converts modern code into backward-compatible versions and allows custom syntax transformations. Lingui requires `@lingui/babel-plugin-lingui-macro` (recommended) or [`babel-plugin-macros`](https://github.com/kentcdodds/babel-plugin-macros) to perform the transformation. If you are using a framework that doesn't allow you to change the Babel configuration (e.g. Create React App > 2.0), these frameworks may support `babel-plugin-macros` out of the box. Follow these steps to set up Lingui with Babel: 1. Install the `@lingui/babel-plugin-lingui-macro` package as a development dependency: * npm * Yarn * pnpm ``` npm install --save-dev @lingui/babel-plugin-lingui-macro ``` ``` yarn add --dev @lingui/babel-plugin-lingui-macro ``` ``` pnpm add --save-dev @lingui/babel-plugin-lingui-macro ``` 2. Add `@lingui/babel-plugin-lingui-macro` to the top of the `plugins` section of your Babel config (e.g. `.babelrc`): ``` { "plugins": ["@lingui/babel-plugin-lingui-macro"] } ``` tip When using a preset, first check if it includes the `macros` plugin. If so, then you don't need to install and set up `@lingui/babel-plugin-lingui-macro`. For example, `react-scripts` already includes the `macros` plugin. caution If you're using [React Compiler](https://react.dev/learn/react-compiler), make sure that `@lingui/babel-plugin-lingui-macro` comes **before** the React Compiler plugin in the Babel plugins list. This ensures macros are expanded correctly before the compiler processes the code. > SWC is an extensible Rust-based platform for the next generation of fast developer tools. Lingui supports SWC with a dedicated plugin [`@lingui/swc-plugin`](/ref/swc-plugin.md). SWC is significantly faster than Babel and is a good choice for large projects. Follow these steps to set up Lingui with SWC: 1. Install `@lingui/swc-plugin` as a development dependency: * npm * Yarn * pnpm ``` npm install --save-dev @lingui/swc-plugin ``` ``` yarn add --dev @lingui/swc-plugin ``` ``` pnpm add --save-dev @lingui/swc-plugin ``` 2. [Add necessary configurations](/ref/swc-plugin.md#usage). caution SWC Plugin support is still experimental. Semver backwards compatibility between different `@swc/core` versions is not guaranteed. See the [SWC compatibility](/ref/swc-plugin.md#swc-compatibility) for more information. caution If you're using [React Compiler](https://react.dev/learn/react-compiler) with SWC, the `@lingui/swc-plugin` must run **before** the React Compiler plugin. This is necessary because Lingui macros need to be expanded before the React Compiler processes your code. However, in Next.js, the React Compiler is enabled via a simple boolean flag (`reactCompiler: true`) in `next.config.js`, and there's currently no way to control plugin ordering. As a result, Lingui may not work correctly with the React Compiler in SWC-based setups like Next.js. ## Basic Configuration[​](#basic-configuration "Direct link to Basic Configuration") Lingui needs a configuration file to work. The configuration file specifies the source files, message catalogs, and other settings. Let's create a basic configuration file in the root of your project (next to `package.json`): lingui.config.js ``` import { defineConfig } from "@lingui/cli"; export default defineConfig({ sourceLocale: "en", locales: ["cs", "en"], catalogs: [ { path: "/src/locales/{locale}/messages", include: ["src"], }, ], }); ``` The configuration above specifies the source locale as English and the target locales as Czech and English. According to this configuration, Lingui will extract messages from source files in the `src` directory and write them to message catalogs in `src/locales` (the English catalog would be in `src/locales/en/messages.po`, for example). See [Configuration](/ref/conf.md) for a complete reference. note Replace `src` with the name of the directory where you have the source files. The PO format is the default and recommended format for message catalogs. See the [Catalog Formats](/ref/catalog-formats.md) for other available formats. ## Build Tools[​](#build-tools "Direct link to Build Tools") ### Vite[​](#vite "Direct link to Vite") > Vite is a blazing fast frontend build tool powering the next generation of web applications. Lingui supports Vite with a dedicated plugin [`@lingui/vite-plugin`](/ref/vite-plugin.md). This plugin is responsible for extracting messages from your source code and compiling message catalogs. There are two ways to set up Lingui with Vite by using the [`@vitejs/plugin-react`](https://www.npmjs.com/package/@vitejs/plugin-react) or [`@vitejs/plugin-react-swc`](https://www.npmjs.com/package/@vitejs/plugin-react-swc). You need to choose the one that fits your project setup. * Babel (plugin-react) * SWC (plugin-react-swc) The `@vitejs/plugin-react` plugin uses Babel to transform your code. To use Lingui with Vite and Babel, follow these steps: 1. Follow the [Choosing a Transpiler](#choosing-a-transpiler) instructions. 2. Install `@lingui/vite-plugin` as a development dependency and `@lingui/react` as a runtime dependency: * npm * Yarn * pnpm ``` npm install --save-dev @lingui/vite-plugin npm install --save @lingui/react ``` ``` yarn add --dev @lingui/vite-plugin yarn add @lingui/react ``` ``` pnpm add --save-dev @lingui/vite-plugin pnpm add @lingui/react ``` 3. Setup Lingui in `vite.config.ts`: vite.config.ts ``` import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; import { lingui } from "@lingui/vite-plugin"; export default defineConfig({ plugins: [ react({ babel: { plugins: ["@lingui/babel-plugin-lingui-macro"], }, }), lingui(), ], }); ``` info The `@vitejs/plugin-react` does not use the Babel config (e.g. `babel.rc`) from your project by default. You have to enable it manually or specify Babel options directly in `vite.config.ts`. The `@vitejs/plugin-react-swc` plugin uses SWC to transform your code, which is significantly faster than Babel. To use Lingui with Vite and SWC, follow these steps: 1. Follow the [Choosing a Transpiler](#choosing-a-transpiler) instructions. 2. Install `@lingui/vite-plugin`, `@lingui/swc-plugin` as development dependencies and `@lingui/react` as a runtime dependency: * npm * Yarn * pnpm ``` npm install --save-dev @lingui/vite-plugin @lingui/swc-plugin npm install --save @lingui/react ``` ``` yarn add --dev @lingui/vite-plugin @lingui/swc-plugin yarn add @lingui/react ``` ``` pnpm add --save-dev @lingui/vite-plugin @lingui/swc-plugin pnpm add @lingui/react ``` 3. Setup Lingui in `vite.config.ts`: vite.config.ts ``` import { defineConfig } from "vite"; import react from "@vitejs/plugin-react-swc"; import { lingui } from "@lingui/vite-plugin"; export default defineConfig({ plugins: [ react({ plugins: [["@lingui/swc-plugin", {}]], }), lingui(), ], }); ``` ## See Also[​](#see-also "Direct link to See Also") * [React i18n Tutorial](/tutorials/react.md) * [React Server Components Tutorial](/tutorials/react-rsc.md) * [React Native i18n Tutorial](/tutorials/react-native.md) * [JavaScript i18n Tutorial](/tutorials/javascript.md) --- # Introduction 📖 A readable, automated, and optimized internationalization for JavaScript > **Internationalization** is the design and development of a product, application or document content that enables easy **localization** for target audiences that vary in culture, region, or language. > > — [W3C Web Internationalization FAQ](https://www.w3.org/International/questions/qa-i18n) [![GitHub stars](https://img.shields.io/github/stars/lingui/js-lingui.svg?style=social\&label=Stars)](https://github.com/lingui/js-lingui/) ## Key Features[​](#key-features "Direct link to Key Features") Lingui is an easy yet powerful internationalization framework for global projects. ### Clean and Readable[​](#clean-and-readable "Direct link to Clean and Readable") Keep your code clean and readable, while the library uses battle-tested and powerful **ICU MessageFormat** under the hood. ### Universal[​](#universal "Direct link to Universal") Use it everywhere. [`@lingui/core`](/ref/core.md) provides the essential intl functionality which works in any JavaScript project, while [`@lingui/react`](/ref/react.md) offers components for leveraging React rendering, including React Server Components (RSC) support. ### Full Rich-text Support[​](#full-rich-text-support "Direct link to Full Rich-text Support") Seamlessly use React components within localized messages, without any restrictions. Creating rich-text messages feels just like writing JSX. ### Powerful Tooling[​](#powerful-tooling "Direct link to Powerful Tooling") Manage your intl workflow with the Lingui [CLI](/ref/cli.md), [Vite Plugin](/ref/vite-plugin.md), and [ESLint Plugin](/ref/eslint-plugin.md). The CLI extracts, compiles and validates messages, while the Vite plugin compiles catalogs on the fly, and the ESLint plugin helps catch common usage errors. ### Unopinionated[​](#unopinionated "Direct link to Unopinionated") Integrate Lingui into your existing workflow. It supports explicit message keys as well as auto-generated ones. Translations are stored either in JSON or standard PO file, which is supported in almost all translation tools. ### Lightweight and Optimized[​](#lightweight-and-optimized "Direct link to Lightweight and Optimized") Core library is less than [2 kB gzipped](https://bundlephobia.com/result?p=@lingui/core), React components are additional [1.4 kB gzipped](https://bundlephobia.com/result?p=@lingui/react). ### AI Translations Ready[​](#ai-translations-ready "Direct link to AI Translations Ready") For AI to do great translations for you, context is critical. Translating UI copy is difficult because it's usually a list of short strings without enough context. Lingui's localization formats allow developers to write descriptions of where and how your keys are used. This allows both human translators and AI to make better translations. ### Active Community[​](#active-community "Direct link to Active Community") Join the growing [community of developers](/community) who are using Lingui to build global products. ## Workflow[​](#workflow "Direct link to Workflow") Using Lingui for internationalization is a straightforward process. Here's a high-level overview of the workflow. ![Workflow scheme](/assets/images/lingui-workflow-4c23edf0d9cbf3c08849eb56f11a8bbb.svg) ### Define Messages[​](#define-messages "Direct link to Define Messages") Write messages directly in your codebase using Lingui's components. This keeps your code clean and readable while embedding translations naturally. ### Extract[​](#extract "Direct link to Extract") Use the Lingui CLI to extract all translatable messages from your codebase and create or update message catalogs. This step ensures that all messages are captured and ready for translation. ### Translate[​](#translate "Direct link to Translate") Translate your message catalogs either manually or through integration with translation tools. ### Compile[​](#compile "Direct link to Compile") Use the Lingui CLI to compile your message catalogs into a format that can be used in your application. This step minimizes the size of your translation bundle and ensures that only the necessary data is bundled. ### Deploy[​](#deploy "Direct link to Deploy") Include the compiled message catalogs in your production build to ensure that users receive localized content based on their language preferences. ## Quick Overview[​](#quick-overview "Direct link to Quick Overview") ``` import React from "react"; import { Trans, Plural, useLingui } from "@lingui/react/macro"; export default function Lingui({ numUsers, name = "You" }) { const { t } = useLingui(); return (

{/* Localized messages are simply wrapped in */} Internationalization in React

{/* Element attributes are translated using t macro */} {t`Logo

{/* Variables are passed to messages in the same way as in JSX */} Hello {name}, Lingui is a readable, automated, and optimized i18n for JavaScript.

{/* React Elements inside messages works in the same way as in JSX */}

Read the documentation for more info.

{/* Plurals are managed using ICU plural rules. Nesting of i18n components is allowed. Syntactically valid message in ICU MessageFormat is guaranteed. */} Only one user is using this library! } other={ {numUsers} users are using this library! } />
); } ``` ## See Also[​](#see-also "Direct link to See Also") * [Installation and Setup](/installation.md) --- # Catalog Formats Catalog format (configured by the [`format`](/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. [![Version](https://img.shields.io/npm/v/@lingui/format-po.svg?cacheSeconds=86400)](https://www.npmjs.com/package/@lingui/format-po) [![Downloads](https://img.shields.io/npm/dw/@lingui/format-po.svg?cacheSeconds=86400)](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 | ### 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](/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. [![Version](https://img.shields.io/npm/v/@lingui/format-po-gettext.svg?cacheSeconds=86400)](https://www.npmjs.com/package/@lingui/format-po-gettext) [![Downloads](https://img.shields.io/npm/dw/@lingui/format-po-gettext.svg?cacheSeconds=86400)](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 | ### 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 ``` ### Limitations[​](#po-gettext-limitations "Direct link to Limitations") This format comes with several caveats and should only be used when using ICU plurals in PO files is not an option: * Nested/multiple plurals in a message as shown in [`plural`](/ref/macro.md#plural) are not supported because they cannot be expressed with gettext plurals. Messages containing nested/multiple formats will not be output correctly. * The [`select`](/ref/macro.md#select) and [`selectOrdinal`](/ref/macro.md#selectordinal) cannot be expressed with gettext plurals, but the original ICU format is still stored in the `msgid`/`msgstr` properties. To disable the warning that this may not be the expected behavior, add `{ disableSelectWarning: true }` to the [`format`](/ref/conf.md#format) options. * Source/development languages with more than two plurals could experience difficulties when no custom IDs are used, as gettext cannot have more than two plurals cases identifying an item (`msgid` and `msgid_plural`). * Gettext doesn't support plurals for negative and fractional numbers even though some languages have special rules for these cases. ## JSON[​](#json "Direct link to JSON") This format is used to store messages in a JSON file. There are two types of JSON format: [minimal](#minimal-style) and [lingui](#lingui-style). [![Version](https://img.shields.io/npm/v/@lingui/format-json.svg?cacheSeconds=86400)](https://www.npmjs.com/package/@lingui/format-json) [![Downloads](https://img.shields.io/npm/dw/@lingui/format-json.svg?cacheSeconds=86400)](https://www.npmjs.com/package/@lingui/format-json) ### Installation[​](#json-installation "Direct link to Installation") * npm * Yarn * pnpm ``` npm install --save-dev @lingui/format-json ``` ``` yarn add --dev @lingui/format-json ``` ``` pnpm add --save-dev @lingui/format-json ``` ### Usage[​](#json-usage "Direct link to Usage") lingui.config.{js,ts} ``` import { defineConfig } from "@lingui/cli"; import { formatter } from "@lingui/format-json"; export default defineConfig({ // [...] format: formatter({ style: "lingui" }), }); ``` ### Configuration[​](#json-configuration "Direct link to Configuration") JSON formatter accepts the following options: | Option | Type | Default | Description | | ------------- | ------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | `style` | `"lingui"` \| `"minimal"` | `"lingui"` | Specify the output style of the JSON file. `lingui` includes full Lingui-specific metadata, while `minimal` may output a more compact format | | `origins` | boolean | `true` | Include information in the JSON file about 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 | | `indentation` | number | `2` | Set the number of spaces to use for indentation in the output JSON file. This affects the readability of the file when opened in a text editor | ### Examples[​](#json-examples "Direct link to Examples") #### Minimal style[​](#minimal-style "Direct link to Minimal style") A simple JSON file where each key is a message ID and the value is the translation. The JSON is flat, and there's no reason to use nested keys. The usual motivation behind nested JSON is to save file space, but this file format is only used offline. The downside of this format is that all metadata about the message is lost. This includes the default message, the origin of the message, and any message flags (obsolete, fuzzy, etc.). ``` { "messageId": "translation" } ``` #### Lingui style[​](#lingui-style "Direct link to Lingui style") This file format simply outputs all internal data in JSON format. It's the original file format used by Lingui before support for other catalog formats was added. It might be useful for tools build on top of Lingui CLI which needs to further process catalog data. ``` { "messageId": { "translation": "Translated message", "message": "Default message", "description": "Comment for translators", "origin": [["src/App.js", 3]] }, "obsoleteId": { "translation": "Obsolete message", "origin": [["src/App.js", 3]], "obsolete": true } } ``` ## CSV[​](#csv "Direct link to CSV") The CSV format is a simple format that can be used to import and export messages from spreadsheets or other tools that support CSV files. It has two columns: message ID and message (source or translation). [![Version](https://img.shields.io/npm/v/@lingui/format-csv.svg?cacheSeconds=86400)](https://www.npmjs.com/package/@lingui/format-csv) [![Downloads](https://img.shields.io/npm/dw/@lingui/format-csv.svg?cacheSeconds=86400)](https://www.npmjs.com/package/@lingui/format-csv) ### Installation[​](#csv-installation "Direct link to Installation") * npm * Yarn * pnpm ``` npm install --save-dev @lingui/format-csv ``` ``` yarn add --dev @lingui/format-csv ``` ``` pnpm add --save-dev @lingui/format-csv ``` ### Usage[​](#csv-usage "Direct link to Usage") lingui.config.{js,ts} ``` import { defineConfig } from "@lingui/cli"; import { formatter } from "@lingui/format-csv"; export default defineConfig({ // [...] format: formatter(), }); ``` This formatter has no configurable options. ### Examples[​](#csv-examples "Direct link to Examples") ``` messageId,Message msg.common,String for translation ``` ## See Also[​](#see-also "Direct link to See Also") * [Custom Formatter](/guides/custom-formatter.md) --- # Lingui CLI The `@lingui/cli` tool provides the `lingui` command which allows you to extract messages from source files into message catalogs and compile these catalogs for production use. ## Installation[​](#installation "Direct link to Installation") 1. Install `@lingui/cli` as a development dependency: * npm * Yarn * pnpm ``` npm install --save-dev @lingui/cli ``` ``` yarn add --dev @lingui/cli ``` ``` pnpm add --save-dev @lingui/cli ``` 2. Add the following scripts to your `package.json`: package.json ``` { "scripts": { "extract": "lingui extract", "compile": "lingui compile" } } ``` tip If you use TypeScript, you can add the `--typescript` flag to the `compile` script to produce compiled message catalogs with TypeScript types: package.json ``` { "scripts": { "compile": "lingui compile --typescript" } } ``` ## Global Options[​](#global-options "Direct link to Global Options") ### `--config `[​](#--config-config "Direct link to --config-config") Path to the configuration file. If not set, the default file is loaded as described in the [Configuration](/ref/conf.md) reference. ## Commands[​](#commands "Direct link to Commands") ### `extract`[​](#extract "Direct link to extract") ``` lingui extract [files...] [--clean] [--overwrite] [--format ] [--locale ] [--convert-from ] [--verbose] [--watch [--debounce ]] ``` The `extract` command scans source files to locate and extract messages, generating separate message catalogs for each language. This process involves: 1. Extracting messages from files based on the `include` and `exclude` settings in the [`catalogs`](/ref/conf.md#catalogs) section of the configuration file. 2. Merging the newly extracted messages with any existing message catalogs. 3. Updating and saving the message catalogs. 4. Printing extraction statistics for each language, including the total number of messages and any missing translations. tip Refer to the [Message Extraction](/guides/message-extraction.md) guide to learn more about this process and the options available. #### `files`[​](#extract-files "Direct link to extract-files") Filter source paths to extract messages only from specific files. For example: ``` lingui extract src/components ``` This command extracts messages from files within the `src/components/**/*` path. You can also pass multiple paths for extraction. This feature is useful when you want to extract messages from files that are staged for commit. For example, you can use husky to automatically extract messages from staged files before committing: package.json ``` { "husky": { "hooks": { "pre-commit": "lingui extract $(git diff --name-only --staged)" } } } ``` #### `--clean`[​](#extract-clean "Direct link to extract-clean") By default, the extract command merges messages extracted from source files with existing message catalogs, ensuring that translated messages are preserved and not accidentally lost. However, over time, some messages may be removed from the source code. You can use the following option to clean up your message catalogs and remove obsolete messages. #### `--overwrite`[​](#extract-overwrite "Direct link to extract-overwrite") Update translations for [`sourceLocale`](/ref/conf.md#sourcelocale) from source. #### `--format `[​](#extract-format "Direct link to extract-format") Extract message catalogs to the specified file format (see the [`format`](/ref/conf.md#format) option for more details). #### `--locale `[​](#extract-locale "Direct link to extract-locale") Extract data for the specified locales only. #### `--convert-from `[​](#extract-convert-from "Direct link to extract-convert-from") Convert message catalogs from the previous format (see the [`format`](/ref/conf.md#format) option for more details). #### `--verbose`[​](#extract-verbose "Direct link to extract-verbose") Print additional information. #### `--watch`[​](#extract-watch "Direct link to extract-watch") Enable watch mode to monitor changes in files located in the paths specified in the configuration file or in the command itself. Note that this feature is intended for development use only, as it does not remove obsolete translations. #### `--debounce `[​](#extract-debounce "Direct link to extract-debounce") Delay the extraction by `` milliseconds, bundling multiple file changes together. ### `extract-template`[​](#extract-template "Direct link to extract-template") ``` lingui extract-template [--verbose] ``` This command extracts messages from your source files and generates a `.pot` template file. Any artifacts created by this command can be safely ignored in version control. If your message catalogs are not synchronized with the source and some messages are missing, the application will fallback to the template file. Running this command before building the application is recommended to ensure all messages are accounted for. #### `--verbose`[​](#extract-template-verbose "Direct link to extract-template-verbose") Print additional information. ### `compile`[​](#compile "Direct link to compile") ``` lingui compile [--strict] [--format ] [--verbose] [--typescript] [--namespace ] [--watch [--debounce ]] ``` Once you have all the catalogs ready and translated, you can use this command to compile all the catalogs into minified JS/TS files. It compiles message catalogs located in the [`path`](/ref/conf.md#catalogs) directory and generates minified JavaScript files. The resulting file is a string that is parsed into a plain JS object using `JSON.parse`. The output looks like this: ``` export const messages = JSON.parse(`{ // object with keys (translation ids) and values (translations) }`); ``` Messages added to the compiled file are collected in a specific order: 1. Translated messages from the specified locale. 2. Translated messages from the fallback locale for the specified locale. 3. Translated message from default fallback locale. 4. Message key. It is also possible to merge the translated catalogs into a single file per locale by specifying [`catalogsMergePath`](/ref/conf.md#catalogsmergepath) in the configuration. tip The compiled files can be safely ignored by your version control system, since these files must be created each time you deploy to production. We recommend you to create the compiled catalogs in CI as part of your deployment process. Always remember to **use compiled catalogs** in deployments. .gitignore ``` your_locale_folder/**/*.js ``` #### `--overwrite`[​](#compile-overwrite "Direct link to compile-overwrite") Overwrite source locale translations from source. #### `--strict`[​](#compile-strict "Direct link to compile-strict") Fail if a catalog has missing translations. #### `--format `[​](#compile-format "Direct link to compile-format") Format of message catalogs (see the [`format`](/ref/conf.md#format) option for more details). #### `--verbose`[​](#compile-verbose "Direct link to compile-verbose") Print additional information. #### `--namespace`[​](#compile-namespace "Direct link to compile-namespace") Specify the namespace for compiled message catalogs (see also [`compileNamespace`](/ref/conf.md#compilenamespace) for global configuration). #### `--typescript`[​](#compile-typescript "Direct link to compile-typescript") Is the same as using [`compileNamespace`](/ref/conf.md#compilenamespace) with the value "ts". Generates a `{compiledFile}.ts` file and the exported object is typed using TS. #### `--watch`[​](#compile-watch "Direct link to compile-watch") Watch mode. Watches only for changes in locale files in your defined locale catalogs. For example, `locales\en\messages.po`. #### `--debounce `[​](#compile-debounce "Direct link to compile-debounce") Delays compilation by `` milliseconds to avoid multiple compilations for subsequent file changes. ## Configuring the Source Locale[​](#configuring-the-source-locale "Direct link to Configuring the Source Locale") One limitation of checking for missing translations is that the English message catalog typically does not require translations since our source code is in English. This issue can be resolved by configuring the [`sourceLocale`](/ref/conf.md#sourcelocale) in the configuration file. ## Compiling Catalogs in CI[​](#compiling-catalogs-in-ci "Direct link to Compiling Catalogs in CI") If you're using CI, it's a good idea to add the `compile` command to your build process. Alternatively, you can also use a [Webpack loader](/ref/loader.md), [Vite plugin](/ref/vite-plugin.md) or [Metro transformer](/ref/metro-transformer.md). Depending on your localization setup, you might also want to run the `extract` command in CI and upload the extracted messages to a [translation service](/tools/introduction.md). ## See Also[​](#see-also "Direct link to See Also") * [Lingui Configuration](/ref/conf.md) * [Message Extraction](/guides/message-extraction.md) * [Catalog Formats](/ref/catalog-formats.md) * [Custom Extractor](/guides/custom-extractor.md) --- # Configuration The following reference covers all supported configuration options in Lingui. To learn more about configuring Lingui, read the [Installation and Setup](/installation.md) guide. By default, Lingui looks for the configuration in the following locations: * `lingui.config.js` or `lingui.config.ts` file exporting a configuration object (recommended). * `.linguirc` file in JSON format. * `lingui` section in `package.json`. You can also define the environment variable `LINGUI_CONFIG` with the path to your config file. In the case of TypeScript-based config you can use ESM format and *export default*. ## catalogs[​](#catalogs "Direct link to catalogs") Default value: ``` [ { path: "/locale/{locale}/messages", include: [""], exclude: ["**/node_modules/**"], }, ]; ``` The `catalogs` configuration defines the location of message catalogs and specifies which files are included when the [`extract`](/ref/cli.md#extract) command scans for messages. * `path`: the directory where the message catalogs are located. It should not end with a slash and must not include a file extension, which depends on the [`format`](#format) configuration. The `{locale}` token will be replaced by the catalog's locale. * `include` and `exclude`: these patterns specify which files to include or exclude during the extraction process. They are passed to [minimatch](https://github.com/isaacs/minimatch) for pattern matching. * [``](#rootdir): represents the root directory of the project. It is replaced with the actual root directory when the configuration is loaded. By default, [``](#rootdir) represents the configuration file's location. The `path`, `include`, and `exclude` patterns are interpreted relative to the current process CWD (current working directory). The `{name}` token in the `path` will be replaced with the catalog name. Be sure to include the `{name}` pattern in the source path as well, as it acts like a `*` glob pattern: ``` { "catalogs": [ { "path": "/components/{name}/locale/{locale}", "include": ["/components/{name}/"] } ] } ``` #### Examples[​](#examples "Direct link to Examples") Let's assume we use `locales: ["en", "cs"]` and `format: "po"` in all examples. #### All catalogs in one directory[​](#all-catalogs-in-one-directory "Direct link to All catalogs in one directory") ``` { "catalogs": [ { "path": "locales/{locale}" } ] } ``` ``` locales/ ├── en.po └── cs.po ``` #### Catalogs in separate directories[​](#catalogs-in-separate-directories "Direct link to Catalogs in separate directories") ``` { catalogs: [ { path: "locales/{locale}/messages", }, ]; } ``` ``` locales ├── en/ │ └── messages.po └── cs/ └── messages.po ``` #### Separate catalogs per component, placed inside component directory[​](#separate-catalogs-per-component-placed-inside-component-directory "Direct link to Separate catalogs per component, placed inside component directory") ``` { "catalogs": [ { "path": "components/{name}/locale/{locale}", "include": ["components/{name}/"] } ] } ``` ``` components/ ├── RegistrationForm/ │ ├── locale/ │ │ ├── en.po │ │ └── cs.po │ ├── RegistrationForm.test.js │ └── RegistrationForm.js └── LoginForm/ ├── locale/ │ ├── en.po │ └── cs.po ├── LoginForm.test.js └── LoginForm.js ``` #### Separate catalogs per component, placed inside shared directory[​](#separate-catalogs-per-component-placed-inside-shared-directory "Direct link to Separate catalogs per component, placed inside shared directory") ``` { "catalogs": [ { "path": "locale/{locale}/{name}", "include": ["components/{name}/"] } ] } ``` ``` . ├── locale/ │ ├── en/ │ │ ├── RegistrationForm.po │ │ └── LoginForm.po │ └── cs/ │ ├── RegistrationForm.po │ └── LoginForm.po └── components/ ├── RegistrationForm/ │ ├── RegistrationForm.test.js │ └── RegistrationForm.js └── LoginForm/ ├── LoginForm.test.js └── LoginForm.js ``` ## locales[​](#locales "Direct link to locales") Default value: `[]` The locale tags used in the project. The [`extract`](/ref/cli.md#extract) and [`compile`](/ref/cli.md#compile) commands write a catalog for each locale specified. Each locale should be a valid [BCP-47 code](http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html): ``` { "locales": ["en", "cs"] } ``` ## fallbackLocales[​](#fallbacklocales "Direct link to fallbackLocales") Default value: `{}` Translations from `fallbackLocales` are used if the translation for the given locale is missing. It uses [CLDR Parent Locales](https://github.com/unicode-cldr/cldr-core/blob/master/supplemental/parentLocales.json) by default, unless you override it with a `false` to use the default message or message ID instead: ``` { "fallbackLocales": false } ``` The `fallbackLocales` object allows to configure fallback locales for each locale instance: ``` { "fallbackLocales": { "en-US": ["en-GB", "en"], "es-MX": "es" } } ``` In this example, if any translation isn't found on `en-US`, then it will search on `en-GB`, after that if not found we'll search in `en`. it's also possible to configure a `default` fallback locale for all locales: ``` { "fallbackLocales": { "en-US": ["en-GB", "en"], "es-MX": "es", "default": "en" } } ``` ## sourceLocale[​](#sourcelocale "Direct link to sourceLocale") Default value: `""` `sourceLocale` specifies the default language of message IDs in your source files. The catalog for `sourceLocale` doesn't need actual translations since message IDs are used as-is by default. However, you can still override any message ID by providing a custom translation. The main difference between `sourceLocale` and [`fallbackLocales`](#fallbacklocales) is their purpose: `sourceLocale` defines the language used for message IDs, while `fallbackLocales` provides alternative translations when specific messages are missing for a particular locale. ## pseudoLocale[​](#pseudolocale "Direct link to pseudoLocale") Default value: `""` Locale used for pseudolocalization. For example, when you set `pseudoLocale: "en"`, all messages in the `en` catalog will be pseudo-localized. The locale must be included in the `locales` config. Read more about [Pseudolocalization](/guides/pseudolocalization.md). ## catalogsMergePath[​](#catalogsmergepath "Direct link to catalogsMergePath") Default value: `""` Define the path where translated catalogs are merged into a single file per locale during the [`compile`](/ref/cli.md#compile) process. #### Example[​](#example "Direct link to Example") Let's assume we have [separate catalogs per component, placed inside shared directory](#separate-catalogs-per-component-placed-inside-shared-directory). Using the `catalogsMergePath`, separate catalogs can be merged into a single file: ``` { "catalogs": [ { "path": "/locale/{locale}/{name}", "include": ["components/{name}/"] } ], + "catalogsMergePath": "locales/{locale}" } ``` ``` . ├── locale/ │ ├── en/ │ │ ├── RegistrationForm.po - │ │ ├── RegistrationForm.js │ │ ├── LoginForm.po - │ │ └── LoginForm.js │ └── cs/ │ ├── RegistrationForm.po - │ ├── RegistrationForm.js │ ├── LoginForm.po - │ └── LoginForm.js + ├── locales/ + │ ├── en.js + │ └── cs.js └── components/ ├── RegistrationForm/ │ ├── RegistrationForm.test.js │ └── RegistrationForm.js └── LoginForm/ ├── LoginForm.test.js └── LoginForm.js ``` ## compileNamespace[​](#compilenamespace "Direct link to compileNamespace") Default value: `cjs` Specify namespace for exporting compiled messages. See [`compile`](/ref/cli.md#compile) command. #### cjs[​](#cjs "Direct link to cjs") Use CommonJS exports: ``` /* eslint-disable */module.exports={messages: {"..."}} ``` #### es[​](#es "Direct link to es") Use ES6 named export: ``` /* eslint-disable */export const messages = {"..."} ``` #### ts[​](#ts "Direct link to ts") Use ES6 named export + `.ts` file with an additional `{compiledFile}.d.ts` file: ``` /* eslint-disable */export const messages = {"..."} ``` ``` import { Messages } from '@lingui/core'; declare const messages: Messages; export { messages }; ``` #### json[​](#json "Direct link to json") ``` {"messages": {"..."}} ``` #### (window|global).(.\*)[​](#windowglobal "Direct link to (window|global).(.*)") Assign compiled messages to `window` or `global` object. Specify an identifier after `window` or `global` to which the catalog is assigned, e.g. `window.i18n`. For example, setting [`compileNamespace`](#compilenamespace) to `window.i18n` creates file similar to this: ``` /* eslint-disable */window.i18n={messages: {"..."}} ``` ## extractorParserOptions[​](#extractorparseroptions "Direct link to extractorParserOptions") Default value: `{}` Specify additional options used to parse source files when extracting messages. ``` { "extractorParserOptions": { "tsExperimentalDecorators": false, "flow": false } } ``` #### tsExperimentalDecorators[​](#tsexperimentaldecorators "Direct link to tsExperimentalDecorators") Default value: `false` By default, standard decorators (Stage3) are applied to TS files. Enable this if you want to use TypeScript's experimental decorators. #### flow[​](#flow "Direct link to flow") Default value: `false` Lingui does not ship with [Flow](https://flow.org/) typing. However, you can use Lingui in projects written in Flow. Enable this option to tell the extractor that your sources use Flow syntax. ## compilerBabelOptions[​](#compilerbabeloptions "Direct link to compilerBabelOptions") Default value: ``` { "minified": true, "jsescOption": { "minimal": true } } ``` Specify extra babel options used to generate files when messages are being compiled. We use internally `@babel/generator` that accepts some configuration for generating code with/out ASCII characters. These are all the options available: [jsesc](https://github.com/mathiasbynens/jsesc). ``` { "compilerBabelOptions": { "jsescOption": { "minimal": false } } } ``` This example configuration will compile with escaped ASCII characters ([jsesc#minimal](https://github.com/mathiasbynens/jsesc#minimal)). ## format[​](#format "Direct link to format") Default value: `po` Message catalog format. The `po` formatter is used by default. Other formatters are available as separate packages. lingui.config.{js,ts} ``` import { defineConfig } from "@lingui/cli"; import { formatter } from "@lingui/format-po"; export default defineConfig({ // [...] format: formatter({ lineNumbers: false }), }); ``` Read more about available formatters in [Catalog Formats](/ref/catalog-formats.md) or create your own [Custom Formatter](/guides/custom-formatter.md). ## orderBy[​](#orderby "Direct link to orderBy") Default value: `message` Order of messages in catalog: #### message[​](#message "Direct link to message") Sort by source message. #### messageId[​](#messageid "Direct link to messageId") Sort by the message ID, `js-lingui-id` will be used if no custom id provided. #### origin[​](#origin "Direct link to origin") Sort by message origin (e.g. `App.js:3`) ## rootDir[​](#rootdir "Direct link to rootDir") Default: The root of the directory containing your Lingui configuration file or the `package.json`. This is the directory where the Lingui CLI scans for messages in your source files during the extraction process. Note that using `` as a string token in any other path-based config settings will refer back to this value. ## runtimeConfigModule[​](#runtimeconfigmodule "Direct link to runtimeConfigModule") Default value: `["@lingui/core", "i18n"]` This setting specifies the module path for the exported `i18n` object. The first value in the array is the module path, and the second is the name of the import. This configuration is essential for [macros](/ref/macro.md) that need to reference the global `i18n` object. You only need to set this value if you use custom object created using [`setupI18n`](/ref/core.md#setupi18n): For example, if you have a custom module that exports the `i18n` object: ``` import { i18n } from "./custom-i18n-config"; ``` ``` { "runtimeConfigModule": ["./custom-i18n-config", "i18n"] } ``` You may use a different named export: ``` import { myI18n } from "./custom-i18n-config"; ``` ``` { "runtimeConfigModule": ["./custom-i18n-config", "myI18n"] } ``` In more advanced scenarios, you may need to change the module from which the [`Trans`](/ref/macro.md#trans) or [`useLingui`](/ref/macro.md#uselingui) macros are imported: ``` import { Trans, useLingui } from "./custom-config"; ``` ``` { "runtimeConfigModule": { "Trans": ["./custom-config", "Trans"], "useLingui": ["./custom-config", "useLingui"] } } ``` ## extractors[​](#extractors "Direct link to extractors") Default value: `[babel]` Extractors it's the way to customize which extractor you want for your codebase. ``` { "extractors": [ myCustomExtractor, ] } ``` See the [Custom Extractor](/guides/custom-extractor.md) guide for instructions on creating your own extractor. ## macro.corePackage[​](#macrocorepackage "Direct link to macro.corePackage") Default value: `["@lingui/macro", "@lingui/core/macro"]` Allows customizing the Core Macro package name that the Lingui macro detects. ``` // lingui.config { macro: { corePackage: ["@lingui/myMacro"]; } } // app.tsx import { msg } from "@lingui/myMacro"; msg`Hello`; // <-- would be correctly picked up by macro ``` This setting mostly useful for external framework integrations. ## macro.jsxPackage[​](#macrojsxpackage "Direct link to macro.jsxPackage") Default value: `["@lingui/macro", "@lingui/react/macro"]` Allows customizing the JSX Macro package name that the Lingui macro detects. ``` // lingui.config { macro: { jsxPackage: ["@lingui/myMacro"]; } } // app.tsx import { Trans } from "@lingui/myMacro"; Hello; // <-- would be correctly picked up by macro ``` This setting mostly useful for external framework integrations. --- # Core API Reference The `@lingui/core` package provides the main i18n object which manages message catalogs, active locale as well as translation and formatting of messages. ## Installation[​](#installation "Direct link to Installation") * npm * Yarn * pnpm ``` npm install --save @lingui/core ``` ``` yarn add @lingui/core ``` ``` pnpm add @lingui/core ``` ## Overview[​](#overview "Direct link to Overview") The `@lingui/core` package provides a global instance of the `i18n` object, which you can import and use directly: ``` import { i18n } from "@lingui/core"; /** * Load messages for the requested locale and activate it. * This function isn't part of the Lingui because there are * many ways how to load messages — from REST API, from file, from cache, etc. */ async function activate(locale: string) { const { messages } = await import(`${locale}/messages.js`); i18n.loadAndActivate({ locale, messages }); } activate("cs"); // returns the Czech translation of "Hello World" const translation = i18n._("Hello World"); ``` Advanced If you prefer not to use the global `i18n` instance and want to set up your own, you can utilize the [`setupI18n`](#setupi18n) method. Additionally, you'll need to configure the [`runtimeConfigModule`](/ref/conf.md#runtimeconfigmodule) to ensure that macros work properly. ## Class `i18n()`[​](#i18n "Direct link to i18n") ### `i18n.loadAndActivate(options)`[​](#i18n.loadAndActivate "Direct link to i18n.loadAndActivate") The `options` parameter is an object with the following properties: * `locale`: The initial active locale. * `locales`: A list of alternative locales ([BCP-47](http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html) language tags) used for number and date formatting. * `messages`: The **compiled** message catalog. It allows to set (overwrite) the catalog for a given locale and activate the locale: ``` import { i18n } from "@lingui/core"; const { messages } = await import(`${locale}/messages.js`); i18n.loadAndActivate({ locale, messages }); ``` ### `i18n.load(allMessages: AllMessages)`[​](#i18n.load\(allMessages\) "Direct link to i18n.load(allMessages)") ### `i18n.load(locale: string, messages: Messages)`[​](#i18n.load "Direct link to i18n.load") Load messages for given locale or load multiple message catalogs at once. When some messages for the provided locale are already loaded, calling `i18n.load` will merge the new messages with the existing ones using `Object.assign`. ``` import { i18n } from "@lingui/core"; /** * This is just an example of what the catalog looks like internally. * Formatting of string messages only works in development. See note below. */ const messagesEn = { Hello: "Hello", "Good bye": "Good bye", "My name is {name}": "My name is {name}", }; const messagesCs = { Hello: "Ahoj", "Good bye": "Nashledanou", "My name is {name}": "Jmenuji se {name}", }; i18n.load({ en: messagesEn, cs: messagesCs, }); // This is the same as loading message catalogs separately per language: // i18n.load('en', messagesEn) // i18n.load('cs', messagesCs) ``` tip Don't write catalogs manually. The code above is an example of message catalogs. In real applications, messages are loaded from external message catalogs generated by the [`compile`](/ref/cli.md#compile) command or by using tools such as [Vite Plugin](/ref/vite-plugin.md), [Webpack Loader](/ref/loader.md), or [Metro Transformer](/ref/metro-transformer.md). Formatting of messages as strings (e.g: `"My name is {name}"`) works in development only, when messages are parsed on the fly. In production, however, messages must be compiled using the [`compile`](/ref/cli.md#compile) command. Here's how the same example would look in a real application: ``` import { i18n } from "@lingui/core"; // File generated by `lingui compile` import { messages as messagesEn } from "./locale/en/messages.js"; i18n.load("en", messagesEn); ``` ### `i18n.setMessagesCompiler(compiler)`[​](#i18n.setMessagesCompiler "Direct link to i18n.setMessagesCompiler") Registers a `MessageCompiler` to enable the use of uncompiled catalogs at runtime. In production builds, the `MessageCompiler` is typically excluded to reduce bundle size. By default, message catalogs should be precompiled during the build process. However, if you need to compile catalogs at runtime, you can use this method to set a message compiler. Example usage: ``` import { compileMessage } from "@lingui/message-utils/compileMessage"; i18n.setMessagesCompiler(compileMessage); ``` ### `i18n.activate(locale[, locales])`[​](#i18n.activate "Direct link to i18n.activate") Activate the specified locale and any alternate locales. After calling this method, calling `i18n._` will return messages in the activated locale. ``` import { i18n } from "@lingui/core"; i18n.activate("en"); i18n._("Hello"); // Return "Hello" in English i18n.activate("cs"); i18n._("Hello"); // Return "Hello" in Czech ``` ### `i18n._(messageId[, values[, options]])`[​](#i18n._ "Direct link to i18n._") The core method for translating and formatting messages. * `messageId`: a unique message ID which identifies message in catalog. * `values`: an object containing variables used in translated message. * `options.message`: the default translation (optional). This is mostly used when application doesn't use message IDs in natural language (e.g.: `msg.id` or `Component.title`). * `options.formats`: custom format definitions for dates and times (optional). An object where keys are format names and values are either `Intl.DateTimeFormatOptions` or `Intl.NumberFormatOptions`. ``` import { i18n } from "@lingui/core"; // Simple message i18n._("Hello"); // Message with variables i18n._("My name is {name}", { name: "Tom" }); // Message with custom messageId i18n._("msg.id", { name: "Tom" }, { message: "My name is {name}" }); const date = new Date("2014-12-06"); const time = new Date("2014-12-06T17:40:00Z"); // Short date format i18n._("It starts on {someDate, date, short}", { someDate: date }); // Short time format i18n._("It starts on {someTime, time, short}", { someTime: time }); // Date skeleton format i18n._("It starts on {someDate, date, ::GrMMMdd}", { someDate: date }); // Custom date format i18n._("It starts on {someDate, date, myStyle}", { someDate: date }, { formats: { myStyle: { day: "numeric" } } }); ``` ### `i18n._(messageDescriptor)`[​](#i18n_messagedescriptor "Direct link to i18n_messagedescriptor") `messageDescriptor` is an object with a message ID, default message and other parameters. It's useful when you need to use the declared message later in the code. ``` import { i18n } from "@lingui/core"; // Simple message i18n._({ id: "Hello" }); // Simple message using custom ID i18n._({ id: "msg.hello", message: "Hello" }); // Message with variable i18n._({ id: "My name is {name}", values: { name: "Tom" } }); // Message with comment, custom ID and variable i18n._({ id: "msg.name", message: "My name is {name}", comment: "Message showing the passed in name", values: { name: "Tom" }, }); ``` Read more about [Message Descriptor](/ref/macro.md#core-macros). ### `i18n.t(...)`[​](#i18n.t "Direct link to i18n.t") Alias for [`i18n._`](#i18n._). ``` import { i18n } from "@lingui/core"; i18n.t({ id: "Hello" }); ``` ### `i18n.date(value: string | Date[, format: Intl.DateTimeFormatOptions])`[​](#i18n.date "Direct link to i18n.date") Format a date using the conventional format for the active language. * `date`: a `Date` object to be formatted. When `date` is a string, the `Date` object is created using `new Date(date)`. * `format`: an optional object that is passed to the `options` argument of the [`Intl.DateTimeFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat) constructor. This allows for customization of the date formatting. ``` import { i18n } from "@lingui/core"; const d = new Date("2021-07-23T16:23:00"); i18n.activate("en"); i18n.date(d); // Returns "7/23/2021" i18n.date(d, { timeStyle: "medium" }); // Returns "4:23:00 PM" i18n.date(d, { dateStyle: "medium", timeStyle: "medium" }); // Returns "Jul 23, 2021, 4:23:00 PM" i18n.activate("cs"); i18n.date(d); // Returns "23. 7. 2021" ``` ### `i18n.number(value: number[, format: Intl.NumberFormatOptions])`[​](#i18n.number "Direct link to i18n.number") Format a number using the conventional format for the active language. * `number`: a number to be formatted. * `format`: an optional object that is passed to the `options` argument of the [`Intl.NumberFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat) constructor. This allows for customization of the date formatting. ``` import { i18n } from "@lingui/core"; i18n.activate("en"); i18n.number(12345.678); // Returns "12,345.678" i18n.number(12345.678, { style: "currency", currency: "USD" }); // Returns "$12,345.68" i18n.activate("cs"); i18n.number(12345.678); // Returns "12 345,678" i18n.number(12345.678, { style: "currency", currency: "CZK" }); // Returns "12 345,68 Kč" ``` ## `setupI18n([options])`[​](#setupi18n "Direct link to setupi18n") Initialize and return a new `I18n` instance. Typically, you should call this function only once and then use the returned `i18n` object throughout your entire codebase. info You typically don't need to set up your own `i18n` instance. In most cases, you can use the global `i18n` object exported from `@lingui/core` directly. However, if you need to do this, make sure to also configure the [`runtimeConfigModule`](/ref/conf.md#runtimeconfigmodule) to ensure macros work properly. ``` import { setupI18n } from "@lingui/core"; const i18n = setupI18n(); ``` The factory function accepts an optional `options` parameter, which can be used to configure the initial state of the `i18n` instance. ### `options.locale`[​](#optionslocale "Direct link to optionslocale") Initial active locale. ``` import { setupI18n } from "@lingui/core"; const i18n = setupI18n({ locale: "en" }); // This is a shortcut for: // const i18n = setupI18n() // i18n.activate("en") ``` ### `options.locales`[​](#optionslocales "Direct link to optionslocales") List of alternative locales ([BCP-47](http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html) language tags) which are used for number and date formatting (some countries use more than one number/date format). If not set, the active locale is used instead. ``` import { setupI18n } from "@lingui/core"; const i18n = setupI18n({ locale: "ar", locales: ["en-UK", "ar-AS"], }); // This is a shortcut for: // const i18n = setupI18n() // i18n.activate("ar", ["en-UK", "ar-AS"]) ``` ### `options.messages`[​](#optionsmessages "Direct link to optionsmessages") Initial [`Messages`](#messages). ``` import { setupI18n } from "@lingui/core"; const messages = { en: require("./locale/en/messages").messages, // your path to compiled messages here cs: require("./locale/cs/messages").messages, // your path to compiled messages here }; const i18n = setupI18n({ messages }); // This is a shortcut for: // const i18n = setupI18n() // i18n.load(messages) ``` ### `options.missing`[​](#optionsmissing "Direct link to optionsmissing") A custom message to be returned when a translation is missing. This feature is useful for debugging: ``` import { setupI18n } from "@lingui/core"; const i18n = setupI18n({ missing: "🚨" }); i18n._("missing translation") === "🚨"; // Returns the custom missing message ``` Alternatively, `missing` can be a function that receives the active locale and message ID as arguments: ``` import { setupI18n } from "@lingui/core"; function missing(locale, id) { alert(`Translation in ${locale} for ${id} is missing!`); return id; } const i18n = setupI18n({ missing }); i18n._("missing translation"); // Triggers an alert ``` ## AllMessages[​](#allmessages "Direct link to AllMessages") The `AllMessages` parameter in the [`I18n.load`](#i18n.load) method is of the following type: ``` type AllMessages = { [locale: string]: CompiledMessage }; // Example const messages: AllMessages = { en: { messages: { Hello: "Hello", "Good bye": "Good bye", }, }, cs: { messages: { Hello: "Ahoj", "Good bye": "Nashledanou", }, }, }; ``` ## Messages[​](#messages "Direct link to Messages") Type of messages in [`AllMessages`](#allmessages). It's a mapping of a message ID to a translation in given language. This may be a function if messages are compiled. ``` type Messages = { [messageId: string]: string | Function }; // Example const messagesEn: Messages = { Hello: "Hello", "Good bye": "Good bye", }; ``` ## Events[​](#events "Direct link to Events") ### `change`[​](#change "Direct link to change") The `change` event is triggered **after** changing the locale or loading a new message catalog. No arguments are passed to this event. ### `missing`[​](#missing "Direct link to missing") The `missing` event is triggered when a translation is requested using [`i18n._`](/ref/core.md#i18n._) that does not exist in the messages of the active locale.The event provides information about the locale and the missing message ID. ``` i18n.on("missing", (event) => { alert(`Translation in ${event.locale} for ${event.id} is missing!`); }); ``` --- # ESLint Plugin Lingui provides an ESLint plugin to help you find common Lingui usage errors in your code. [![npm-version](https://img.shields.io/npm/v/eslint-plugin-lingui?logo=npm\&cacheSeconds=1800)](https://www.npmjs.com/package/eslint-plugin-lingui) [![npm-downloads](https://img.shields.io/npm/dt/eslint-plugin-lingui?cacheSeconds=500)](https://www.npmjs.com/package/eslint-plugin-lingui) ## Installation[​](#installation "Direct link to Installation") Install [ESLint](http://eslint.org): * npm * Yarn * pnpm ``` npm install --save-dev eslint ``` ``` yarn add --dev eslint ``` ``` pnpm add --save-dev eslint ``` Next, install `eslint-plugin-lingui`: * npm * Yarn * pnpm ``` npm install --save-dev eslint-plugin-lingui ``` ``` yarn add --dev eslint-plugin-lingui ``` ``` pnpm add --save-dev eslint-plugin-lingui ``` info If you have installed ESLint globally (using the `-g` flag), you must also install `eslint-plugin-lingui` globally. ## Usage[​](#usage "Direct link to Usage") ### Flat Config (`eslint.config.js`)[​](#flat-config "Direct link to flat-config") Version 8 of ESLint introduced a new configuration format called [Flat Config](https://eslint.org/docs/latest/use/configure/configuration-files). Flat config files represent plugins and parsers as JavaScript objects. #### Recommended Setup[​](#recommended-setup "Direct link to Recommended Setup") To enable all the recommended rules for the plugin, add the following config: ``` import pluginLingui from "eslint-plugin-lingui"; export default [ pluginLingui.configs["flat/recommended"], // Any other config... ]; ``` #### Custom Setup[​](#custom-setup "Direct link to Custom Setup") Alternatively, you can load the plugin and configure only the rules you want to use: ``` import pluginLingui from "eslint-plugin-lingui"; export default [ { plugins: { lingui: pluginLingui, }, rules: { "lingui/t-call-in-function": "error", }, }, // Any other config... ]; ``` ### Legacy Config (`.eslintrc`)[​](#legacy-eslintrc "Direct link to legacy-eslintrc") The legacy configuration format has been deprecated by ESLint, but it's still supported. If you're using the legacy format, you can use the following configuration. #### Recommended Setup[​](#recommended-setup-1 "Direct link to Recommended Setup") To enable all the recommended rules for the plugin, add `plugin:lingui/recommended` to the `extends` section: ``` { "extends": ["plugin:lingui/recommended"] } ``` #### Custom Setup[​](#custom-setup-1 "Direct link to Custom Setup") Alternatively, add `lingui` to the `plugins` section of your `.eslintrc` configuration file. You can omit the `eslint-plugin-` prefix: ``` { "plugins": ["lingui"] } ``` In the rules section, configure the rules you want to use: ``` { "rules": { "lingui/no-unlocalized-strings": 2, "lingui/t-call-in-function": 2, "lingui/no-single-variables-to-translate": 2, "lingui/no-expression-in-message": 2, "lingui/no-single-tag-to-translate": 2, "lingui/no-trans-inside-trans": 2 } } ``` tip See the [official repository](https://github.com/lingui/eslint-plugin) for more information about the rules. --- # Vue.js Extractor The `@lingui/extractor-vue` package provides a custom extractor that handles Vue.js files. ## Installation[​](#installation "Direct link to Installation") * npm * Yarn * pnpm ``` npm install --save-dev @lingui/extractor-vue ``` ``` yarn add --dev @lingui/extractor-vue ``` ``` pnpm add --save-dev @lingui/extractor-vue ``` ## Usage[​](#usage "Direct link to Usage") It is required that you use JavaScript or TypeScript for your Lingui configuration. lingui.config.{js,ts} ``` import { defineConfig } from "@lingui/cli"; import { vueExtractor } from "@lingui/extractor-vue"; import babel from "@lingui/cli/api/extractors/babel"; export default defineConfig({ locales: ["en", "nb"], sourceLocale: "en", catalogs: [ { path: "/src/{locale}", include: ["/src"], }, ], extractors: [babel, vueExtractor], }); ``` ## See Also[​](#see-also "Direct link to See Also") * [Message Extraction](/guides/message-extraction.md) * [Custom Extractor](/guides/custom-extractor.md) --- # Webpack Loader The `@lingui/loader` is a Webpack loader for Lingui message catalogs. It offers an alternative to the [`lingui compile`](/ref/cli.md#compile) and compiles catalogs on the fly. It enables you to `import` `.po` files directly, instead of running `lingui compile` and `import`ing the resulting JavaScript (or TypeScript) files. ## Installation[​](#installation "Direct link to Installation") Install `@lingui/loader` as a development dependency: * npm * Yarn * pnpm ``` npm install --save-dev @lingui/loader ``` ``` yarn add --dev @lingui/loader ``` ``` pnpm add --save-dev @lingui/loader ``` ## Usage[​](#usage "Direct link to Usage") Simply prepend `@lingui/loader!` in front of path to message catalog you want to import. Here's an example of dynamic import: ``` export async function dynamicActivate(locale: string) { const { messages } = await import(`@lingui/loader!./locales/${locale}/messages.po`); i18n.load(locale, messages); i18n.activate(locale); } ``` Remember that the file extension is mandatory. note Catalogs with the `.json` extension are treated differently by Webpack. They load as ES module with default export, so your import should look like this: ``` const { messages } = (await import(`@lingui/loader!./locales/${locale}/messages.json`)).default; ``` ## See Also[​](#see-also "Direct link to See Also") * [Dynamic Loading of Message Catalogs](/guides/dynamic-loading-catalogs.md) * [Catalog Formats](/ref/catalog-formats.md) --- # Locale Detection The `@lingui/detect-locale` is a lightweight package *(only \~1 kB Gzip)* providing several methods and helpers to determine the user's locale using different detection strategies. Most of the detectors accept custom document, location or window objects as parameters, which is especially useful for testing purposes or when implementing server-side detection strategies. ## Installation[​](#installation "Direct link to Installation") * npm * Yarn * pnpm ``` npm install --save @lingui/detect-locale ``` ``` yarn add @lingui/detect-locale ``` ``` pnpm add @lingui/detect-locale ``` ## Reference[​](#reference "Direct link to Reference") ### `detect`[​](#detect "Direct link to detect") The `detect` method accepts multiple detectors as arguments and returns the first valid locale detected. ### `multipleDetect`[​](#multipledetect "Direct link to multipledetect") The `multipleDetect` method also accepts multiple detectors as arguments and returns an array with all locales detected by each detector. ### `fromCookie(key: string)`[​](#fromCookie "Direct link to fromCookie") Accepts a key as a parameter and retrieves the locale value from the browser's cookies based on that key. ### `fromHtmlTag(tag: string)`[​](#fromHtmlTag "Direct link to fromHtmlTag") Looks for the specified attribute in the HTML document (commonly `lang` or `xml:lang`) to detect the locale. ### `fromNavigator()`[​](#fromNavigator "Direct link to fromNavigator") Retrieves the user's language setting from the browser, compatible with older browsers such as IE11. ### `fromPath(localePathIndex: number)`[​](#fromPath "Direct link to fromPath") Splits `location.pathname` into an array, requiring you to specify the index where the locale is located. ### `fromStorage(key: string, { useSessionStorage: boolean })`[​](#fromStorage "Direct link to fromStorage") Searches for the item with the specified key in `localStorage` by default. If the `useSessionStorage` parameter is passed, it will search in `sessionStorage`. ### `fromSubdomain(localeSubdomainIndex: number)`[​](#fromSubdomain "Direct link to fromSubdomain") Splits `location.href` by subdomain segments, requiring the index where the locale is specified. ### `fromUrl(parameter: string)`[​](#fromUrl "Direct link to fromUrl") Uses a query string parser to find the locale by the specified parameter in the URL. ## Usage Examples[​](#usage-examples "Direct link to Usage Examples") ### Usage with `detect`[​](#usage-with-detect "Direct link to usage-with-detect") ``` import { detect, fromUrl, fromStorage, fromNavigator } from "@lingui/detect-locale"; // can be a function with custom logic or just a string, `detect` method will handle it const DEFAULT_FALLBACK = () => "en"; const result = detect(fromUrl("lang"), fromStorage("lang"), fromNavigator(), DEFAULT_FALLBACK); console.log(result); // "en" ``` ### Usage with `multipleDetect`[​](#usage-with-multipledetect "Direct link to usage-with-multipledetect") ``` import { multipleDetect, fromUrl, fromStorage, fromNavigator } from "@lingui/detect-locale"; // can be a function with custom logic or just a string, `detect` method will handle it const DEFAULT_FALLBACK = () => "en"; const result = multipleDetect(fromUrl("lang"), fromStorage("lang"), fromNavigator(), DEFAULT_FALLBACK); console.log(result); // ["en", "es"] ``` --- # Macros Lingui Macro provides powerful macros to transform JavaScript objects and JSX elements into [ICU MessageFormat](/guides/message-format.md) messages at compile time. It provides a more efficient and developer-friendly way to handle internationalization in your project. The benefits of using macros: * You don't have to learn ICU MessageFormat syntax. You always use familiar JS and JSX code. * Components and functions are type checked. * Short IDs are generated for your messages. * Additional validation of plural rules is performed during transformation. * Non-essential data is removed from the production build (e.g. comments and default messages) to save a few bytes. There are two types of macros: [Core Macros](#core-macros) (JS) and [React Macros](#react-macros) (JSX). ## Core Macros[​](#core-macros "Direct link to Core Macros") Core (JS) Macros can be used in any JavaScript context (e.g. outside JSX). All JS macros are transformed into a *Message Descriptor* wrapped inside of [`i18n._`](/ref/core.md#i18n._) call: ``` import { t } from "@lingui/core/macro"; t`Attachment ${name} saved`; // ↓ ↓ ↓ ↓ ↓ ↓ import { i18n } from "@lingui/core"; i18n._( /*i18n*/ { id: "nwR43V", message: "Attachment {name} saved", values: { name }, } ); ``` `/*i18n*/` comment In the example above you may notice the `/*i18n*/` comment in the macro output. This comment tells the extract plugin that the following object should be collected into the message catalog. *Message Descriptor* is an object with a message ID, default message and other parameters: ``` type MessageDescriptor = { id: string; message?: string; values?: Record; comment?: string; }; ``` The `id` is the message ID and is the only parameter required. The `id` and `message` are extracted into the message catalog. Only `id` and `values` are used at runtime, all other attributes are removed from the production code for size optimization. You don't need to specify the ID manually. By default, Macro will automatically create a short ID from your message. However, you can explicitly specify a custom ID. Read more about [Explicit vs Generated IDs](/guides/explicit-vs-generated-ids.md). ### `t`[​](#t "Direct link to t") The most common macro for messages. It transforms tagged template literal into message in ICU MessageFormat: ``` import { t } from "@lingui/core/macro"; const message = t`Hello World`; // ↓ ↓ ↓ ↓ ↓ ↓ import { i18n } from "@lingui/core"; const message = i18n._( /*i18n*/ { id: "mY42CM", message: "Hello World", } ); ``` Message variables are supported: ``` import { t } from "@lingui/core/macro"; const message = t`My name is ${name}`; // ↓ ↓ ↓ ↓ ↓ ↓ import { i18n } from "@lingui/core"; const message = i18n._( /*i18n*/ { id: "mVmaLu", message: "My name is {name}", values: { name }, } ); ``` In fact, any expression can be used inside template literal. However, only simple variables are referenced by name in a transformed message. All other expressions are referenced by their numeric index: ``` import { t } from "@lingui/core/macro"; const message = t`Today is ${new Date()}`; // ↓ ↓ ↓ ↓ ↓ ↓ import { i18n } from "@lingui/core"; const message = i18n._( /*i18n*/ { id: "2aJT27", message: "Today is {0}", values: { 0: new Date() }, } ); ``` info By default the `i18n` object is imported from [`@lingui/core`](/ref/core.md). If you are using a custom instance of the `i18n` object, you need to set [`runtimeConfigModule`](/ref/conf.md#runtimeconfigmodule) or pass a custom instance to [`t`](/ref/macro.md#t). It's also possible to pass custom `id` and `comment` for translators by calling `t` macro with a message descriptor: ``` import { t } from "@lingui/core/macro"; const message = t({ id: "msg.hello", comment: "Greetings at the homepage", message: `Hello ${name}`, }); // ↓ ↓ ↓ ↓ ↓ ↓ import { i18n } from "@lingui/core"; const message = i18n._( /*i18n*/ { id: "msg.hello", comment: "Greetings at the homepage", message: "Hello {name}", values: { name }, } ); ``` In this case, `message` is used as the default message, and it's transformed as if it were wrapped in a `t` macro. `message` also accepts any other macros: ``` import { t } from "@lingui/core/macro"; const message = t({ id: "msg.plural", message: plural(value, { one: "...", other: "..." }), }); // ↓ ↓ ↓ ↓ ↓ ↓ import { i18n } from "@lingui/core"; const message = i18n._( /*i18n*/ { id: "msg.plural", message: "{value, plural, one {...} other {...}}", values: { value }, } ); ``` ### `plural`[​](#plural "Direct link to plural") Pluralization is a common problem in i18n. Different languages have different rules for plural form (e.g. English has only `one` and `other`, while Czech has `one`, `few`, `many` and `other`). The `plural` macro is used to handle this. ``` plural(value: string | number, options: Object) ``` The `value` specifies the plural form or cardinal number. The second argument, `options`, is an object with available plural forms: ``` import { plural } from "@lingui/core/macro"; const message = plural(count, { one: "# Book", other: "# Books", }); // ↓ ↓ ↓ ↓ ↓ ↓ import { i18n } from "@lingui/core"; const message = i18n._( /*i18n*/ { id: "V/M0Vc", message: "{count, plural, one {# Book} other {# Books}}", values: { count }, } ); ``` tip Choose the plural forms used in your source code based on the pluralization rules of your source locale. If you need to add variables in plural form, you can use template string literals. This time you don't need the [`t`](/ref/macro.md#t) macro, because template strings are transformed automatically: ``` import { plural } from "@lingui/core/macro"; const message = plural(count, { one: `${name} has # friend`, other: `${name} has # friends`, }); // ↓ ↓ ↓ ↓ ↓ ↓ import { i18n } from "@lingui/core"; const message = i18n._( /*i18n*/ { id: "CvuUwE", message: "{count, plural, one {{name} has # friend} other {{name} has # friends}}", values: { count, name }, } ); ``` Plurals can also be nested to form complex messages. Here's an example using two counters: ``` import { plural } from "@lingui/core/macro"; const message = plural(numBooks, { one: plural(numArticles, { one: `1 book and 1 article`, other: `1 book and ${numArticles} articles`, }), other: plural(numArticles, { one: `${numBooks} books and 1 article`, other: `${numBooks} books and ${numArticles} articles`, }), }); // ↓ ↓ ↓ ↓ ↓ ↓ // Generated message was wrapped for better readability import { i18n } from "@lingui/core"; const message = i18n._( /*i18n*/ { id: "XnUh4j", message: `{numBooks, plural, one {{numArticles, plural, one {1 book and 1 article} other {1 book and {numArticles} articles} }} other {{numArticles, plural, one {{numBooks} books and 1 article} other {{numBooks} books and {numArticles} articles} }} }`, values: { numBooks, numArticles }, } ); ``` This is just one example of how macros can be combined to create a complex message. However, simple is better, because in the end it's the translator who has to translate these long and complex strings. tip Use `plural` inside [`t`](#t) or [`defineMessage`](#definemessage) macro if you want to add custom `id`, `context` or `comment` for translators. ``` const message = t({ id: "my.custom.id", comment: "My Comment", message: plural(count, { one: "# Book", other: "# Books", }), }); ``` ### `selectOrdinal`[​](#selectordinal "Direct link to selectordinal") SelectOrdinal is a variation of the [`plural`](#plural) macro. It's used to handle ordinal numbers (e.g. 1st, 2nd, 3rd, 4th, etc.). ``` selectOrdinal(value: string | number, options: Object) ``` The `value` specifies the ordinal number. The second argument, `options`, is an object with available ordinal forms: ``` import { selectOrdinal } from "@lingui/core/macro"; const message = selectOrdinal(count, { one: "#st", two: "#nd", few: "#rd", other: "#th", }); // ↓ ↓ ↓ ↓ ↓ ↓ import { i18n } from "@lingui/core"; const message = i18n._( /*i18n*/ { id: "V8xI3w", message: "{count, selectOrdinal, one {#st} two {#nd} few {#rd} other {#th}}", values: { count }, } ); ``` tip Use `selectOrdinal` inside [`t`](#t) or [`defineMessage`](#definemessage) macro if you want to add custom `id`, `context` or `comment` for translators. ``` const message = t({ id: "my.custom.id", comment: "My Comment", message: selectOrdinal(count, { one: "#st", two: "#nd", few: "#rd", other: "#th", }), }); ``` ### `select`[​](#select "Direct link to select") The `select` macro is used to handle different forms of a message based on a value. ``` select(value: string | number, options: Object) ``` It works like a switch statement - it selects one of the forms provided in the `options` object based on the `value`: ``` import { select } from "@lingui/core/macro"; const message = select(gender, { male: "he", female: "she", other: "they", }); // ↓ ↓ ↓ ↓ ↓ ↓ import { i18n } from "@lingui/core"; const message = i18n._( /*i18n*/ { id: "VRptzI", message: "{gender, select, male {he} female {she} other {they}}", values: { gender }, } ); ``` tip Use `select` inside [`t`](#t) or [`defineMessage`](#definemessage) macro if you want to add custom `id`, `context` or `comment` for translators. ``` const message = t({ id: "my.custom.id", comment: "My Comment", message: select(gender, { male: "he", female: "she", other: "they", }), }); ``` ### `defineMessage` / `msg`[​](#definemessage "Direct link to definemessage") The `defineMessage` (alias: `msg`) macro allows to define a message for later use. It has the same signature as `t` and returns a `MessageDescriptor` that you can pass to `i18n._` to get a translated string at any time later. This is useful for [Lazy Translations](/guides/lazy-translations.md). In other words, `t` returns a translated string at the time when it's called, while `msg` returns a `MessageDescriptor` that can produce translated strings later: ``` import { i18n } from "@lingui/core"; import { defineMessage } from "@lingui/core/macro"; // define message const message = defineMessage`Hello World`; // use it later i18n._(message); // ↓ ↓ ↓ ↓ ↓ ↓ import { i18n } from "@lingui/core"; const message = /*i18n*/ { id: "mY42CM", message: "Hello World", }; // use it later i18n._(message); ``` You can also use a shorter alias of the `defineMessage` macro: ``` import { msg } from "@lingui/core/macro"; const message = msg`Hello World`; // ↓ ↓ ↓ ↓ ↓ ↓ const message = /*i18n*/ { id: "mY42CM", message: "Hello World", }; ``` The `defineMessage` macro also supports a `MacroMessageDescriptor` object as input. This can be used to provide additional information for the message such as comment or context: ``` type MacroMessageDescriptor = { id?: string; message?: string; comment?: string; context?: string; }; ``` Either the `id` or `message` property is required. `id` is a custom message ID. If it isn't set, the `message` (and `context` if present) will be used to generate an ID. Read more about [Explicit vs Generated IDs](/guides/explicit-vs-generated-ids.md). ``` import { defineMessage } from "@lingui/core/macro"; const message = defineMessage({ id: "Navigation / About", message: "About us", }); // ↓ ↓ ↓ ↓ ↓ ↓ const message = /*i18n*/ { id: "Navigation / About", message: "About us", }; ``` The `message` is a default message. Any JS macro can be used here. Template string literals don't need to be tagged with [`t`](#t). ``` import { defineMessage } from "@lingui/core/macro"; const name = "Joe"; const message = defineMessage({ message: `Welcome, ${name}!`, }); // ↓ ↓ ↓ ↓ ↓ ↓ const message = /*i18n*/ { id: "dgJjNB", message: "Welcome, {name}", values: { name, }, }; ``` The `comment` is a comment for translators. It's extracted into the message catalog, and it gives translators additional information about the message. It's removed from the production code: ``` import { defineMessage } from "@lingui/core/macro"; const message = defineMessage({ comment: "Link in navigation pointing to About page", message: "About us", }); // ↓ ↓ ↓ ↓ ↓ ↓ const message = /*i18n*/ { id: "+mNwru", comment: "Link in navigation pointing to About page", message: "About us", }; ``` Note In the production build, the macro is stripped of `message`, `comment` and `context` properties: ``` import { defineMessage } from "@lingui/core/macro"; const message = defineMessage({ id: "msg.navigation.about", comment: "Link in navigation pointing to About page", message: "About us", context: "Context about the link", }); // process.env.NODE_ENV === "production" // ↓ ↓ ↓ ↓ ↓ ↓ const message = /*i18n*/ { id: "msg.navigation.about", }; ``` `message` and `comment` are used in message catalogs only. `context` is used only for generating ID and is stripped from the output. ## React Macros[​](#react-macros "Direct link to React Macros") React (JSX) Macros are used in JSX elements and are transformed into the [`Trans`](/ref/react.md#trans) component imported from the [`@lingui/react`](/ref/react.md) package. ### `Trans`[​](#trans "Direct link to trans") The `Trans` macro is used to translate static messages, messages with variables and messages with inline markup: ``` import { Trans } from "@lingui/react/macro"; Refresh inbox; // ↓ ↓ ↓ ↓ ↓ ↓ import { Trans } from "@lingui/react"; ; ``` Available Props: | Prop Name | Type | Description | | --------- | ------ | ------------------------------------------------------ | | `id` | string | Custom message ID | | `comment` | string | Comment for translators | | `context` | string | Allows to extract the same messages with different IDs | | `render` | func | Custom render callback to render translation | #### `id`[​](#id "Direct link to id") Each message in the catalog is identified by a message ID. While macro uses message (and `context` property if provided) to generate the ID, it's possible to provide custom ID. Read more about [Explicit vs Generated IDs](/guides/explicit-vs-generated-ids.md). ``` import { Trans } from "@lingui/react/macro"; Attachment {name} saved.; // ↓ ↓ ↓ ↓ ↓ ↓ import { Trans } from "@lingui/react"; ; ``` #### `comment`[​](#comment "Direct link to comment") Comment for translators to give them additional information about the message. It will be visible in the [TMS](/tools/introduction.md) if it is supported, and in the [catalog format](/ref/catalog-formats.md). It will be removed from production code. #### `context`[​](#context "Direct link to context") Allows to extract the same messages with different IDs. It is useful when the same message has different meanings in different contexts. See [Context](/guides/explicit-vs-generated-ids.md#context) for more details. Similarly to [`comment`](#comment), it will be added to the message catalog, visible in TMS and will be removed from the production code: ``` import { Trans } from "@lingui/react/macro"; right; right; // ↓ ↓ ↓ ↓ ↓ ↓ import { Trans } from "@lingui/react"; ; ; ``` This macro is particularly useful if the message contains inline markup: ``` import { Trans } from "@lingui/react/macro"; Read the docs. ; // ↓ ↓ ↓ ↓ ↓ ↓ import { Trans } from "@lingui/react"; }} />; ``` Components and HTML tags are replaced by dummy indexed tags (`<0>`) which has several advantages: * Both custom React components and built-in HTML tags are supported. * Changing component props doesn't break translation. * The message is extracted as a whole sentence (this seems to be obvious, but most i18n libs simply split the message into pieces by tags and translate them separately). #### `render`[​](#render "Direct link to render") Custom render callback to render translation. This prop is passed directly to the [`Trans`](/ref/react.md#trans) component from the [`@lingui/react`](/ref/react.md) package. ### `Plural`[​](#plural-1 "Direct link to plural-1") The `Plural` JSX macro is used to handle plural forms. It's similar to the [`plural`](#plural) core macro, but is used in JSX elements. ``` import { Plural } from "@lingui/react/macro"; ; // ↓ ↓ ↓ ↓ ↓ ↓ import { Trans } from "@lingui/react"; ; ``` Available Props: | Prop name | Type | Description | | ----------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `other` | string | *(required)* general *plural* form | | `value` | number | *(required)* Value is mapped to plural form below | | `format` | string\|Object | Number format passed as options to [`Intl.NumberFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat) | | `offset` | number | Offset of value when calculating plural forms | | `zero` | string | Form for empty `value` | | `one` | string | *Singular* form | | `two` | string | *Dual* form | | `few` | string | *Paucal* form | | `many` | string | *Plural* form | | `_` | string | Exact match form, corresponds to `=N` rule | | `id` | string | Custom message ID | | `comment` | string | Comment for translators | | `context` | string | Allows to extract the same messages with different IDs | | `render` | func | Custom render callback to render translation | Exact matches in MessageFormat syntax are expressed as `=int` (e.g. `=0`), but in React this isn't a valid prop name. Therefore, exact matches are expressed as `_int` prop (e.g. `_0`). This is commonly used in combination with `offset` prop. `offset` affects only plural forms, not exact matches. ``` import { Plural } from "@lingui/react/macro"; `one` plural form one="You and # other guest arrived" // when value >= 3 other="You and # other guests arrived" />; /* This is transformed to Trans component with ID: {count, plural, offset:1 _0 {Nobody arrived} _1 {Only you arrived} one {You and # other guest arrived} other {You and # other guests arrived}} */ ``` ### `SelectOrdinal`[​](#selectordinal-1 "Direct link to selectordinal-1") The `SelectOrdinal` JSX macro is used to handle ordinal numbers. It's similar to the [`selectOrdinal`](#selectordinal) core macro, but is used in JSX elements. ``` import { SelectOrdinal } from "@lingui/react/macro"; // count == 1 -> 1st // count == 2 -> 2nd // count == 3 -> 3rd // count == 4 -> 4th ; ``` Available Props: | Prop name | Type | Description | | ----------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `value` | number | *(required)* Value is mapped to plural form below | | `other` | string | *(required)* general *plural* form | | `offset` | number | Offset of value for plural forms | | `zero` | string | Form for empty `value` | | `one` | string | *Singular* form | | `two` | string | *Dual* form | | `few` | string | *Paucal* form | | `many` | string | *Plural* form | | `_` | string | Exact match form, correspond to `=N` rule. (e.g: `_0`, `_1`) | | `format` | string\|Object | Number format passed as options to [`Intl.NumberFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat) | ### `Select`[​](#select-1 "Direct link to select-1") The `Select` JSX macro is used to handle different forms of a message based on a value. It's similar to the [`select`](#select) core macro, but is used in JSX elements. ``` import { Select } from "@lingui/react/macro"; // gender == "female" -> Her book // gender == "male" -> His book // gender == "non-binary" -> Their book