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:
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.
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, or8)
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.
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 onlyidin production"all": keepsid,message,context, andcomment"id-only": keeps onlyid"message": keepsid,message, andcontext
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:
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:
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:
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:
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:
LocaleDatatypeAllLocaleDatatypeI18nProps.localeDatapropertyi18n.localeDatagetteri18n.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 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