# Lingui Best Practices

> Implement or review Lingui message macros, catalog configuration, locale loading, and React or JavaScript integration. Use for Lingui-specific code and runtime issues, not plain text translation or another i18n library. For command-only catalog maintenance, use the project workflow.

- Skill: `adonis0123/lingui-best-practices` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add adonis0123/lingui-best-practices`
- Raw SKILL.md: https://api.skillmd.com/api/skills/adonis0123/lingui-best-practices/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: Adonis0123 (https://skillmd.com/u/adonis0123)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/adonis0123/lingui-best-practices

---


# Lingui Best Practices

Lingui is a powerful internationalization (i18n) framework for JavaScript. This skill covers best practices for implementing i18n in React and vanilla JavaScript applications.

## Scope Before Setup

Reuse the project's existing Lingui version, framework, catalog layout, and initialization. A message edit does not require reinstalling packages or rebuilding the provider. Inspect the relevant imports and lockfile before applying version-sensitive examples; read only the reference needed for the current problem. The provider example below is a client-side starting point, not a replacement for an established RSC setup.

## Quick Start Workflow

For a new integration, the main pieces are below. For an existing integration, work only on the pieces affected by the request:

1. Wrap your app in `I18nProvider`
2. Mark messages for translation using macros (`Trans`, `t`, etc.)
3. Extract messages: `lingui extract`
4. Translate the catalogs
5. Compile catalogs: `lingui compile`
6. Load and activate locale in your app

## Core Packages

Import from these packages:

```jsx
// React macros (recommended)
import { Trans, Plural, Select, useLingui } from "@lingui/react/macro";

// Core macros for vanilla JS
import { t, msg, plural, select } from "@lingui/core/macro";

// Runtime (rarely used directly)
import { I18nProvider } from "@lingui/react";
import { i18n } from "@lingui/core";
```

## Setup I18nProvider

Wrap your application with `I18nProvider`:

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

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

function App() {
  return <I18nProvider i18n={i18n}>{/* Your app */}</I18nProvider>;
}
```

## Translating UI Text

### Use Trans for JSX Content

The `Trans` macro is the primary way to translate JSX:

```jsx
import { Trans } from "@lingui/react/macro";

// Simple text
<Trans>Hello World</Trans>

// With variables
<Trans>Hello {userName}</Trans>

// With components (rich text)
<Trans>
  Read the <a href="/docs">documentation</a> for more info.
</Trans>

// Extracted as: "Read the <0>documentation</0> for more info."
```

**When to use**: For any translatable text in JSX elements.

### Use useLingui for Non-JSX

For strings outside JSX (attributes, alerts, function calls):

```jsx
import { useLingui } from "@lingui/react/macro";

function MyComponent() {
  const { t } = useLingui();

  const handleClick = () => {
    alert(t`Action completed!`);
  };

  return (
    <div>
      <img src="..." alt={t`Image description`} />
      <button onClick={handleClick}>{t`Click me`}</button>
    </div>
  );
}
```

**When to use**: Element attributes, alerts, function parameters, any non-JSX string.

## Next.js App Router / RSC Rules

When using Lingui in Next.js App Router with server components:

1. Initialize locale on both server layouts and server pages. Do not rely on layout-only initialization.
2. Ensure server `initLingui(locale)` activates locale before binding context:
   - `i18n.activate(locale)`
   - `setI18n(i18n)`
3. In shared server components, prefer `useLingui`/`Trans` from `@lingui/react/macro` for translated strings.
4. If you hit `Attempted to call a translation function without setting a locale`, first verify initialization order before changing catalogs.

Example:

```tsx
import { setI18n } from "@lingui/react/server";

export function initLingui(locale: AppLocale) {
  const i18n = getI18nInstance(locale);
  i18n.activate(locale);
  setI18n(i18n);
  return i18n;
}
```

### Use msg for Lazy Translations

When you need to define messages at module level or in arrays/objects:

```jsx
import { msg } from "@lingui/core/macro";
import { useLingui } from "@lingui/react";

// Module-level constants
const STATUSES = {
  active: msg`Active`,
  inactive: msg`Inactive`,
  pending: msg`Pending`,
};

function StatusList() {
  const { _ } = useLingui();

  return Object.entries(STATUSES).map(([key, message]) => (
    <div key={key}>{_(message)}</div>
  ));
}
```

**When to use**: Module-level constants, arrays of messages, conditional message selection.

## Pluralization

Use the `Plural` macro for quantity-dependent messages:

```jsx
import { Plural } from "@lingui/react/macro";

<Plural
  value={messageCount}
  one="You have # message"
  other="You have # messages"
/>;
```

The `#` placeholder is replaced with the actual value.

### Exact Matches

Use `_N` syntax for exact number matches (takes precedence over plural forms):

```jsx
<Plural value={count} _0="No messages" one="One message" other="# messages" />
```

### With Variables and Components

Combine with `Trans` for complex messages:

```jsx
<Plural
  value={count}
  one={`You have # message, ${userName}`}
  other={
    <Trans>
      You have <strong>#</strong> messages, {userName}
    </Trans>
  }
/>
```

## Formatting Dates and Numbers

Use `i18n.date()` and `i18n.number()` for locale-aware formatting:

```jsx
import { useLingui } from "@lingui/react/macro";

function MyComponent() {
  const { i18n } = useLingui();
  const lastLogin = new Date();

  return <Trans>Last login: {i18n.date(lastLogin)}</Trans>;
}
```

These use the browser's `Intl` API for proper locale formatting.

## Message IDs and Context

### Explicit IDs

Provide a custom ID for stable message keys:

```jsx
<Trans id="header.welcome">Welcome to our app</Trans>
```

### Context for Disambiguation

When the same text has different meanings, use `context`:

```jsx
<Trans context="direction">right</Trans>
<Trans context="correctness">right</Trans>
```

These create separate catalog entries.

### Comments for Translators

Add context for translators:

```jsx
<Trans comment="Greeting shown on homepage">Hello World</Trans>
```

## Configuration

Basic `lingui.config.js`:

```js
import { defineConfig } from "@lingui/cli";

export default defineConfig({
  sourceLocale: "en",
  locales: ["en", "es", "fr", "de"],
  catalogs: [
    {
      path: "<rootDir>/src/locales/{locale}/messages",
      include: ["src"],
      exclude: ["**/node_modules/**"],
    },
  ],
});
```

For detailed configuration patterns, see [configuration.md](references/configuration.md).

## Best Practices

### Always Use Macros

Prefer macros over runtime components. Macros are compiled at build time, reducing bundle size:

```jsx
// ✅ Good - uses macro
import { Trans } from "@lingui/react/macro";

// ❌ Avoid - runtime only
import { Trans } from "@lingui/react";
```

### Keep Messages Simple

Avoid complex expressions in messages - they'll be replaced with placeholders:

```jsx
// ❌ Bad - loses context
<Trans>Hello {user.name.toUpperCase()}</Trans>;
// Extracted as: "Hello {0}"

// ✅ Good - clear variable name
const userName = user.name.toUpperCase();
<Trans>Hello {userName}</Trans>;
// Extracted as: "Hello {userName}"
```

### Use Trans for JSX, t for Strings

Choose the right tool:

```jsx
// ✅ For JSX content
<h1>
  <Trans>Welcome</Trans>
</h1>;

// ✅ For string values
const { t } = useLingui();
<img alt={t`Profile picture`} />;
```

### Don't Use Macros at Module Level

Macros need component context - use `msg` instead:

```jsx
// ❌ Bad - won't work
import { t } from "@lingui/core/macro";
const LABELS = [t`Red`, t`Green`, t`Blue`];

// ✅ Good - use msg for lazy translation
import { msg } from "@lingui/core/macro";
const LABELS = [msg`Red`, msg`Green`, msg`Blue`];
```

### Use the ESLint Plugin

Install and configure `eslint-plugin-lingui` to catch common mistakes automatically:

```bash
npm install --save-dev eslint-plugin-lingui
```

```js
// eslint.config.js
import pluginLingui from "eslint-plugin-lingui";

export default [pluginLingui.configs["flat/recommended"]];
```

## Common Patterns

### Dynamic Locale Switching

```jsx
import { i18n } from "@lingui/core";

async function changeLocale(locale) {
  const { messages } = await import(`./locales/${locale}/messages`);
  i18n.load(locale, messages);
  i18n.activate(locale);
}
```

### Loading Catalogs Dynamically

```jsx
import { useEffect } from "react";
import { i18n } from "@lingui/core";

function loadCatalog(locale) {
  return import(`./locales/${locale}/messages`);
}

function App() {
  useEffect(() => {
    loadCatalog("en").then((catalog) => {
      i18n.load("en", catalog.messages);
      i18n.activate("en");
    });
  }, []);

  return <I18nProvider i18n={i18n}>{/* ... */}</I18nProvider>;
}
```

### Memoization with useLingui

When using memoization, use the `t` function from the macro version:

```jsx
import { useLingui } from "@lingui/react/macro";
import { msg } from "@lingui/core/macro";
import { useMemo } from "react";

const welcomeMessage = msg`Welcome!`;

function MyComponent() {
  const { t } = useLingui(); // Macro version - reference changes with locale

  // ✅ Safe - t reference updates with locale
  const message = useMemo(() => t(welcomeMessage), [t]);

  return <div>{message}</div>;
}
```

## Troubleshooting

If you encounter issues:

1. **Messages not extracted**: Check `include` patterns in `lingui.config.js`
2. **Translations not applied**: Ensure catalogs are compiled with `lingui compile`
3. **Runtime errors**: Verify `I18nProvider` wraps your app
4. **Type errors**: Run `lingui compile --typescript` for TypeScript projects

For detailed common mistakes and pitfalls, see [common-mistakes.md](references/common-mistakes.md).

