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, it will continue to be available at lingui.dev until v6 is officially released.
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:
// No longer supported
export default {
locales: ["en", "cs"],
format: "po",
formatOptions: { lineNumbers: true },
};
Migration
Update your configuration to use the formatter function instead:
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:
- Replace
+with- - Replace
/with_ - 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.jsorlingui.config.ts(recommended).linguircin JSON formatlinguisection inpackage.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.
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-macrodirectly in your Babel config - SWC: Use
@lingui/swc-plugin
See Installation for configuration details.
Vue Extractor: vueExtractor Replaced by createVueExtractor()
The vueExtractor export from @lingui/extractor-vue is deprecated. Use the new createVueExtractor() factory function instead:
// Before
import { vueExtractor } from "@lingui/extractor-vue";
extractors: [babel, vueExtractor],
// After
import { createVueExtractor } from "@lingui/extractor-vue";
extractors: [babel, createVueExtractor()],
createVueExtractor() accepts an options object. If your project uses Vue's Reactivity Transform, enable reactivityTransform so message IDs match between extraction and runtime:
extractors: [babel, createVueExtractor({ reactivityTransform: true })],
The option is opt-in (reactivityTransform: false by default) to avoid breaking existing setups.
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 includeplaceholdersandcommentsfields (even if empty).MessageType/CatalogType: Used for messages loaded from catalogs on disk. These may include additional fields liketranslation,obsolete, andextra, but may omitplaceholdersandcomments.
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:
- Update any
nullchecks to useundefined - Use the appropriate message type (
ExtractedMessageTypevsMessageType) based on your use case - Update type annotations if properties that were previously
| nullare now| undefined