Skip to main content

Migration guide from 5.x to 6.x

This guide will help you migrate from Lingui 5.x to 6.x. It covers the most important changes and breaking changes.

Need to upgrade an older project to v5 first? See our older migration guide.

If you're looking for 5.x documentation, you can find it here.

Node.js Version

The minimum supported version of Node.js in Lingui v6 is v22.19+.

ESM-Only Distribution

Lingui 6.0 is now distributed as ESM-only (ECMAScript Modules). ESM is the official, standardized module system for JavaScript, and the entire ecosystem is converging toward this standard.

Why This Change

Previously, Lingui shipped dual builds (both ESM and CommonJS), which created significant drawbacks:

  • Nearly doubled package sizes
  • Maintenance complexity with conditionals and workarounds
  • Subtle bugs from module duplication and dependency resolution issues

By moving to ESM-only, Lingui becomes smaller, simpler, and more future-proof. Thanks to Node.js improvements like require(esm), this transition is now seamless for most users.

What Changed

All Lingui packages have been converted to ESM-only distribution, except @lingui/metro-transformer, which remains CommonJS for compatibility reasons. Note that this package still uses ESM dependencies internally, which may affect its consumers in certain edge cases.

Migration

For most users, no changes are required. Modern bundlers (Vite, Webpack, esbuild, Rollup) and Node.js versions that support require() for ESM modules (22.19+, or 24+) handle ESM imports transparently.

Deprecated format String and formatOptions Removed

The deprecated format (as a string) and formatOptions configuration options have been removed.

What Changed

The old configuration style using a format string and separate options object is no longer supported:

lingui.config.{js,ts}
export default {
locales: ["en", "cs"],
format: "po",
formatOptions: { lineNumbers: true },
};

Migration

Update your configuration to use the formatter function instead:

lingui.config.{js,ts}
import { defineConfig } from "@lingui/cli";
import { formatter } from "@lingui/format-po";

export default defineConfig({
locales: ["en", "cs"],
format: formatter({ lineNumbers: true }),
});

If you only have format: "po" with no formatOptions in your configuration, you can simply remove this line entirely. Lingui defaults to the po formatter when no format is specified.

If you use a different formatter or have formatOptions, you need to import the formatter from its package and pass any options to the function:

  • PO format: import { formatter } from "@lingui/format-po"
  • PO Gettext format: import { formatter } from "@lingui/format-po-gettext"
  • JSON format: import { formatter } from "@lingui/format-json"
  • CSV format: import { formatter } from "@lingui/format-csv"

See Catalog Formats for more details on available formatters and their options.

Message ID Generation Changed to URL-Safe Format

Generated message IDs now use URL-safe Base64 encoding (characters - and _ instead of + and /), conforming to RFC 4648, Section 5.

What Changed

Message IDs now use URL-safe Base64 with these character substitutions:

  • +-, /_, and = padding removed
  • Example: a+b/c=a-b_c

Migration

PO format with bundler loaders: No migration needed.

Using lingui compile: Recompile your catalogs after upgrading:

lingui compile

JSON, CSV, or PO-Gettext formats: Manually update IDs in your catalog files using search-and-replace:

  1. Replace + with -
  2. Replace / with _
  3. Remove trailing =

Deprecated @lingui/macro Package No Longer Maintained

The @lingui/macro package is no longer maintained and will not receive any updates. This package was deprecated in Lingui 5.0 when macros were split into separate entry points from existing packages.

What Changed

In Lingui 5.0, the macros were split to allow using Lingui in non-React projects without pulling in React dependencies:

  • React (JSX) macros moved to @lingui/react/macro
  • Core (JS) macros moved to @lingui/core/macro

The @lingui/macro package continued to work in v5 but emitted deprecation warnings. Starting in v6, it is no longer maintained and will not be compatible with future releases.

Migration

If you were still using @lingui/macro, see the v5 migration guide for detailed migration instructions and available codemods.

Deprecated Intl Wrappers

Lingui wrappers around Intl (i18n.date, i18n.number, and shared helpers) are deprecated. Use native Intl.* APIs instead.

Lingui only ever wrapped two Intl APIs (date and number), so it was unclear what was supported and users had to look elsewhere for things like Intl.ListFormat. The wrappers also increase bundle size - class methods cannot be tree-shaken and add maintenance. Lingui is focusing on message extraction and catalogs; formatting dates and numbers is the job of the native Intl API. The deprecated methods will be removed in a future major release.

Migration

Replace wrapper calls with Intl.DateTimeFormat and Intl.NumberFormat, passing i18n.locale:

  • i18n.date(date, options)new Intl.DateTimeFormat(i18n.locale, options).format(date)
  • i18n.number(n, options)new Intl.NumberFormat(i18n.locale, options).format(n)

Plain JS/TS:

const dateFormatter = new Intl.DateTimeFormat(i18n.locale, { dateStyle: "medium" });
const numberFormatter = new Intl.NumberFormat(i18n.locale, { style: "currency", currency: "EUR" });
// dateFormatter.format(date) - numberFormatter.format(total)

React: Memoize formatters with useMemo so they update when the locale changes:

const { i18n } = useLingui();
const dateFormatter = useMemo(() => new Intl.DateTimeFormat(i18n.locale, { dateStyle: "medium" }), [i18n.locale]);

YAML Configuration Support Removed

Support for YAML configuration files has been removed. The underlying configuration library has been changed from cosmiconfig to lilconfig, a zero-dependency alternative with the same API.

What Changed

YAML configuration files (.linguirc.yaml, .linguirc.yml) are no longer supported. Lingui now only supports:

  • lingui.config.js or lingui.config.ts (recommended)
  • .linguirc in JSON format
  • lingui section in package.json

Migration

Convert your YAML configuration to JavaScript/TypeScript or JSON format.

Before (.linguirc.yaml):

locales:
- en
- cs
sourceLocale: en
catalogs:
- path: src/locales/{locale}/messages
include:
- src

After (lingui.config.ts):

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

export default defineConfig({
locales: ["en", "cs"],
sourceLocale: "en",
catalogs: [
{
path: "src/locales/{locale}/messages",
include: ["src"],
},
],
});

Using TypeScript configuration with defineConfig is recommended as it provides type safety and better IDE support.

Vite Version for @lingui/vite-plugin

The minimum supported Vite version for @lingui/vite-plugin is now 6.3+ (compatible range: ^6.3.0 || ^7 || ^8).

What Changed

@lingui/vite-plugin adopted Vite hook filters for better performance and compatibility with newer Vite/Rolldown internals. As a result, older Vite versions are no longer supported by the plugin.

Migration

  • If you use Vite 6, upgrade to 6.3 or newer
  • If you use Vite 5 or earlier, upgrade to a supported Vite major (6.3+, 7, or 8)

Create React App (CRA) Example Removed

The Create React App example has been removed from the repository.

CRA is officially deprecated and no longer aligns with the modern JavaScript ecosystem. It lacks support for modern ESM patterns and could slow down Lingui's evolution toward current best practices.

For new projects or migrations, we recommend using Vite with Babel or SWC, which are closer to today's standards. See our examples for working setups.

Babel Macro Plugin Deprecated

The babel-plugin-macros integration is deprecated and will be removed in a future release.

The babel-plugin-macros package is no longer actively maintained, and its primary adoption driver was CRA. With CRA deprecated, there's little reason to continue supporting this integration path.

Switch to the dedicated Babel or SWC plugins:

  • Babel: Use @lingui/babel-plugin-lingui-macro directly in your Babel config
  • SWC: Use @lingui/swc-plugin

See Installation for configuration details.

extract and stripMessageField Replaced by descriptorFields

The legacy metadata transformation flags were consolidated into a single descriptorFields option.

What Changed

The following options are no longer supported: extract, stripMessageField. Use descriptorFields instead (supported by both @lingui/babel-plugin-lingui-macro and @lingui/swc-plugin):

  • "auto" (default): keeps all fields in development and only id in production
  • "all": keeps id, message, context, and comment
  • "id-only": keeps only id
  • "message": keeps id, message, and context
note

When using "message", context is preserved as well to ensure message identity remains correct.

Migration

If you've been using extract or stripMessageField in your configuration, use this mapping for your new configuration:

  • extract: true -> descriptorFields: "all"
  • stripMessageField: true -> descriptorFields: "id-only"

Deprecated extractorParserOptions

The top-level extractorParserOptions configuration option is deprecated and will be removed in a future release. Parser options should be passed directly to the extractor implementation instead. The old configuration style allowed specifying parser options at the root level:

lingui.config.{js,ts}
import { defineConfig } from "@lingui/cli";

export default defineConfig({
locales: ["en", "cs"],
extractorParserOptions: {
tsExperimentalDecorators: true,
flow: false,
},
});

Migration

Pass parser options to the extractor instead. For the default Babel extractor, use createBabelExtractor from @lingui/cli/api/extractors/babel and pass a parserOptions object:

lingui.config.{js,ts}
import { defineConfig } from "@lingui/cli";
import { createBabelExtractor } from "@lingui/cli/api/extractors/babel";

export default defineConfig({
locales: ["en", "cs"],
extractors: [
createBabelExtractor({
parserOptions: {
tsExperimentalDecorators: true,
flow: false,
},
}),
],
});

If you use multiple extractors (e.g. Babel and Vue), include each in the extractors array and pass parser options only to the Babel extractor. See Configuration for the full reference.

Deprecated vueExtractor

The vueExtractor export from @lingui/extractor-vue is deprecated. Use the new createVueExtractor() factory function instead. The old configuration used the vueExtractor export directly:

lingui.config.{js,ts}
import { defineConfig } from "@lingui/cli";
import { vueExtractor } from "@lingui/extractor-vue";

export default defineConfig({
locales: ["en", "cs"],
extractors: [vueExtractor],
});

Migration

Use createVueExtractor() instead. It accepts an options object. If your project uses Vue's Reactivity Transform, enable reactivityTransform so message IDs match between extraction and runtime:

lingui.config.{js,ts}
import { defineConfig } from "@lingui/cli";
import { createVueExtractor } from "@lingui/extractor-vue";

export default defineConfig({
locales: ["en", "cs"],
extractors: [createVueExtractor({ reactivityTransform: true })],
});

The option is opt-in (reactivityTransform: false by default) to avoid breaking existing setups.

Deprecated localeData API Removed

The localeData-related types and methods that were deprecated in Lingui v4 have been fully removed from @lingui/core.

What Changed

The following exports have been removed from @lingui/core:

  • LocaleData type
  • AllLocaleData type
  • I18nProps.localeData property
  • i18n.localeData getter
  • i18n.loadLocaleData() method

These APIs were originally used to supply custom plural rules to Lingui. Since v4, plural rules are automatically derived from Intl.PluralRules and these methods have had no effect.

Migration

Remove any calls to i18n.loadLocaleData() and any localeData property passed to setupI18n() or createI18n(). No replacement is needed - plural rules are handled automatically.

TypeScript Type Changes

Several Lingui packages now use TypeScript's strictNullChecks, which improves type safety but may require updates if you use Lingui's internal types in custom integrations.

What Changed

null vs undefined

The codebase now consistently uses undefined instead of null for optional values. This follows the TypeScript team's convention and simplifies type handling since undefined naturally occurs with optional properties and parameters.

If your code explicitly checks for null from Lingui APIs, update it to check for undefined instead.

ExtractedMessageType vs MessageType

The distinction between extracted messages and loaded catalog messages has been strengthened:

  • ExtractedMessageType / ExtractedCatalogType: Used for messages produced by the extractor. These always include placeholders and comments fields (even if empty).
  • MessageType / CatalogType: Used for messages loaded from catalogs on disk. These may include additional fields like translation, obsolete, and extra, but may omit placeholders and comments.

Migration

This change only affects users who have built custom integrations using Lingui's internal types (custom extractors, formatters, or tooling).

If you encounter TypeScript errors after upgrading:

  1. Update any null checks to use undefined
  2. Use the appropriate message type (ExtractedMessageType vs MessageType) based on your use case
  3. Update type annotations if properties that were previously | null are now | undefined