NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
npm · #2339 most downloaded on npm
i18next internationalization framework
Last release 1 months ago
03 Sep 2026
Ships fairly regularly
a new release about every 2 weeks
Nearly every release is documented
notes for 60 of the last 60 stable releases
Nothing withdrawn
no release was ever pulled
15 years old
608 releases · first in 2012
fix: $& , $ , $' and $$ inside a nested value ( $t(key) ) now stay literal. nest() handed the resolved value straight to String.replace as the replace
$&, $`, $' and $$ inside a nested value ($t(key)) now stay literal. nest() handed the resolved value straight to String.replace as the replacement argument, so those sequences were read as replacement patterns: $& re-inserted the $t(...) match, $` / $' inserted the text before / after it, and $$ collapsed to $. Through t() the $& case was worse than a wrong string: the nested lookup resets the shared nesting regexp, so the re-inserted $t(...) was matched again on every pass and t() never returned — also under the default escapeValue: true when the value arrives via a variable forwarded through nesting options ($t(key, { "name": "{{name}}" }) with a name containing $&). The value is now $-escaped at the String.replace call, the same guard interpolate() already has, and a non-string value returned by a formatter in the nesting chain ($t(key, myFormat)) is stringified before that. Nested values are still not HTML-escaped (#854). Thanks @mahirhir (#2447).One column per quarter.
fix(types): the selector-form keyPrefix overload of getFixedT() is now available under enableSelector: 'strict' . Its constraint was gated on true | '
keyPrefix overload of getFixedT() is now available under enableSelector: 'strict'. Its constraint was gated on true | 'optimize' only, so under 'strict' it collapsed to never, the overload dropped out, and the returned t silently lost its keyPrefix scope (t(($) => $.deep) failed with Property 'deep' does not exist on type '{}'). The same call already typechecked under true and 'optimize'. Thanks @hovelopin (#2446).…which resolves all open npm audit findings (0 vulnerabilities) and should close the dependabot alerts on the lockfile.
toResolveHierarchy results per (code, fallbackCode) pair. The hierarchy resolver runs on every t() call and calls Intl.getCanonicalLocales multiple times, which showed up prominently when profiling render-heavy UIs (e.g. virtualized data grids); with the cache the per-call cost drops from ~886 ns to ~41 ns. The cache is invalidated automatically when options.fallbackLng changes (reassignment or in-place array mutation); if you mutate other resolution-relevant options at runtime (load, lowerCaseLng, cleanCode, nonExplicitSupportedLngs), call i18next.services.languageUtils.clearCache() afterwards. Function-valued fallbackLng and per-call array/object fallbackLng options are never cached, so dynamic fallbacks keep working as before. Thanks @equaterina (#2444).@rollup/plugin-babel supports 8, eslint on 9.x for neostandard). Removed the unused coveralls package (CI uses the Coveralls GitHub Action) and replaced sinon with nise + vitest.spyOn in the v1 compatibility tests, which resolves all open npm audit findings (0 vulnerabilities) and should close the dependabot alerts on the lockfile.fix: allow TypeScript 7 in the optional typescript peer dependency range ( ^5 || ^6 || ^7 ). With typescript@7.0.2 in a project, npm install failed wi
typescript peer dependency range (^5 || ^6 || ^7). With typescript@7.0.2 in a project, npm install failed with an ERESOLVE peer conflict. The published types are TS7-compatible as-is: every test/typescript suite produces identical results under 6.0 and 7.0.2. Reported in react-i18next#1927, thanks @andikapradanaarif.fix: $t() nesting options blocks that span multiple lines are now parsed. nest() decided where the nested key ends by testing match[1] with /{.*}/ , w
$t() nesting options blocks that span multiple lines are now parsed. nest() decided where the nested key ends by testing match[1] with /{.*}/, whose dot does not cross line breaks — so a $t(key, { ... }) options object containing a newline was treated as having no options, mis-split as formatters, and the nested lookup ran without its options (placeholders stayed unresolved). The nesting regexp itself already matches newlines inside $t(...); adding the s (dotAll) flag makes multiline options behave like the single-line form. Thanks @spokodev (#2440).getUsedParamsDetails (the returnDetails: true path) no longer mutates the passed replace object. It wrote count straight onto options.replace so the returned usedParams would include it — a caller reusing one replace object across t() calls then carried a stale count into later interpolations (e.g. a previous call's count: 5 rendered instead of the current call's value). The details are now built from a copy; usedParams still includes count. Thanks @spokodev (#2441).skipOnVariables: true + escapeValue: true, a {{placeholder}} carried inside an interpolated value now stays literal even when the value contains escapable characters. The skip logic advanced the regex lastIndex by the raw value length, but the escaped text written into the string is longer, so lastIndex landed inside the inserted value and a trailing {{placeholder}} in it got interpolated — leaking another in-scope variable that should have stayed literal (values without escapable characters were already skipped correctly). The advance now uses the escaped length that is actually written, and the regex-safe $-doubling is applied only at the String.replace call so it can't distort the length arithmetic. Thanks @spokodev (#2442).…backend/integration does this. Distinct from CVE-2026-48713 / CVE-2026-48714 (different packages, setPath mechanism). Thanks to zx (Jace) for the resp…
deepExtend (used by addResourceBundle(..., deep, overwrite)) no longer recurses into inherited properties. It checked key existence with the in operator, which walks the prototype chain, so a source key matching an inherited built-in (e.g. hasOwnProperty, toString) caused recursion into the shared Object.prototype function and, with overwrite: true, could overwrite e.g. Object.prototype.hasOwnProperty.call with a non-callable value — corrupting a shared built-in process-wide (DoS). Existence is now checked with Object.prototype.hasOwnProperty.call, so such keys are copied as plain own data instead. This complements the existing __proto__/constructor guard and is also strictly more correct for an own-property merge. Only affects applications that pass attacker-controlled data with deep: true and overwrite: true; no standard backend/integration does this. Distinct from CVE-2026-48713 / CVE-2026-48714 (different packages, setPath mechanism). Thanks to zx (Jace) for the responsible disclosure.deepExtend (used by addResourceBundle(..., deep, overwrite)) no longer recurses into inherited properties. It checked key existence with the in operator, which walks the prototype chain, so a source key matching an inherited built-in (e.g. hasOwnProperty, toString) caused recursion into the shared Object.prototype function and, with overwrite: true, could overwrite e.g. Object.prototype.hasOwnProperty.call with a non-callable value — corrupting a shared built-in process-wide (DoS). Existence is now checked with Object.prototype.hasOwnProperty.call, so such keys are copied as plain own data instead. This complements the existing __proto__/constructor guard and is also strictly more correct for an own-property merge. Only affects applications that pass attacker-controlled data with deep: true and overwrite: true; no standard backend/integration does this. Distinct from CVE-2026-48713 / CVE-2026-48714 (different packages, setPath mechanism). See advisory GHSA-6jcc-5g8w-32mx, CVSS 5.9 (CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:H). Thanks to zx (Jace) @manus-use for the responsible disclosure.fix(types): selector t($ => $.arr, { returnObjects: true, context }) on a JSON array of heterogeneous objects now preserves each element's full shape
t($ => $.arr, { returnObjects: true, context }) on a JSON array of heterogeneous objects now preserves each element's full shape (e.g. { transKey1: string; transKey2: string }[]) instead of collapsing to a union of partial element types. Two type-level causes: (1) FilterKeys evaluated the whole array element type at once, so keyof (A | B) only saw the keys common to every element — it now distributes over the object union and filters each element independently; (2) when TypeScript merges mismatched array element types it injects phantom optional undefined keys (e.g. transKey1_withContext?: undefined on elements that don't define it), which the context-detection helpers mistook for real context variants — they now skip keys typed as undefined. Also adds a dedicated context + returnObjects: true selector overload using const Fn + ReturnType<Fn>, so Target is no longer collapsed to unknown via ApplyTarget. Resolves Problem 1 of #2398 (Problem 2 was already fixed on master). Thanks @sauravgupta-dotcom (#2438). Fixes #2398.fix: chained formatters with a parenthesised option that contains the format separator (e.g. join(separator: ', ') ) now work at any position in the c
join(separator: ', ')) now work at any position in the chain, not just first. Previously the comma-in-parens reassembly only repaired formats[0], so {{v, uppercase, join(separator: ', ')}} split the join(...) option on the inner comma and never rejoined it, producing corrupt output. Replaced the first-position-only repair with a position-independent pass that re-joins fragments until each open paren closes. Thanks @spokodev (#2437).fix(types): t() with a keyPrefix no longer pollutes its return type with sibling keys' values. A regression in 26.3.0 — the [Res] extends [never] guar
t() with a keyPrefix no longer pollutes its return type with sibling keys' values. A regression in 26.3.0 — the [Res] extends [never] guards added to KeysBuilderWithReturnObjects / KeysBuilderWithoutReturnObjects turned the builders into deferred conditional types, so KeyPrefix<Ns> stopped resolving to a literal union and keyPrefix inference widened to the whole namespace. Symptom: useTranslation(ns, { keyPrefix: 'a.b' }) then t('title') would resolve to '<a.b>.title' | '<other.path>.title' | ... instead of just the scoped value. Affected every react-i18next user using keyPrefix. Restored to the eager 26.2.0 form. The same-namespace conflict handling from #2434 still works via _DropConflictKeys at the merge layer (in options.d.ts). Thanks @aaronrosenthal (#2436).feat(types): introduce ResourceNamespaceMap — a separate mergeable augmentation surface for namespace resource types, designed for monorepos where mul
ResourceNamespaceMap — a separate mergeable augmentation surface for namespace resource types, designed for monorepos where multiple packages each want to contribute their own namespaces. Previously, every package had to coordinate on a single CustomTypeOptions.resources declaration (or fall back to typing dependency namespaces as any) because resources is a single property of an interface and TypeScript reports TS2717 when two declarations of the same property disagree. The new interface merges naturally across declare module 'i18next' blocks, so each package can ship its own i18next.d.ts independently. Per-property merge handles same-namespace contributions from multiple packages, and same-key/different-literal conflicts are silently dropped to avoid poisoning t() overload resolution. Fully backwards-compatible — existing CustomTypeOptions.resources augmentations continue to work, and both surfaces can coexist. Scalar options (defaultNS, returnNull, enableSelector, etc.) still belong on CustomTypeOptions. Thanks @sh3xu (#2434). Fixes #2409.feat(types): new parseInterpolation TypeOption (default true ). When set to false in CustomTypeOptions , the type-level extractor stops parsing transl
parseInterpolation TypeOption (default true). When set to false in CustomTypeOptions, the type-level extractor stops parsing translation strings for {{variable}} patterns. Required by i18next-icu users — the default extractor mistakes ICU MessageFormat nested-brace plurals like {count, plural, one {{count} row} other {{count} rows}} for an interpolation block and demands a phantom variable name. The flag is type-only; runtime interpolation is governed by InterpolationOptions and is unaffected. Fixes i18next-icu#85.enableSelector on InitOptions so i18next.init({ enableSelector: 'strict' }) typechecks without a module augmentation. The runtime already reads opts?.enableSelector from init options; this lands the matching type declaration next to the other selector-resolution knobs. Accepts false | true | 'optimize' | 'strict'. Thanks @Faithfinder (#2431)feat: enableSelector: 'strict' (TypeOptions + runtime option). Opt-in mode that drops the flattened-primary form from NsResource at the type level — e
enableSelector: 'strict' (TypeOptions + runtime option). Opt-in mode that drops the flattened-primary form from NsResource at the type level — every namespace (primary included) is exposed only under its own key on $, uniformly across single- and multi-ns hooks. At runtime, a leading selector path segment matching the scope's namespace list is always rewritten as a namespace prefix, including the primary. Eliminates the silent-miss surface area where t($ => $.primary.foo) typechecks but doesn't resolve under the default mode (see #2429). Backward-compatible: default enableSelector: false | true | 'optimize' behavior is unchanged. Note: strict mode is incompatible with the #2405 pattern (keys whose names match sibling namespaces) — those users should stay on default mode.feat: getFixedT accepts a fourth optional fixedOpts argument carrying scopeNs — the full namespace list the bound t was created for. The selector API
getFixedT accepts a fourth optional fixedOpts argument carrying scopeNs — the full namespace list the bound t was created for. The selector API uses scopeNs to detect when a path's first segment is a namespace prefix, without changing resolution scope. Resolution still uses the bound ns (a single primary string in the typical react-i18next setup), so plain t('key') lookups stay isolated to the primary namespace exactly as before — only t($ => $.secondaryNs.foo) selectors now route correctly under useTranslation([nsA, nsB]). Fixes the runtime side of #2429 for the react-i18next default-nsMode case. The 4th argument is opt-in: existing 3-arg getFixedT(lng, ns, keyPrefix) callers see no behavior change.fix(types): unformatted interpolation values are now typed as string | number (was string). i18next stringifies values at runtime, so requiring caller
string | number (was string). i18next stringifies values at runtime, so requiring callers to wrap numbers in String(...) for plain {{var}} placeholders was unnecessary friction — and could mask the real problem when a non-string value was passed alongside multiple interpolation slots (the t() overload resolution would fall through to the 3-arg form and report a confusing "not assignable to string" error against the options object). Typed format specifiers like {{x, number}}, {{x, currency}}, {{x, datetime}}, etc. keep their precise types; this only relaxes the no-format default. The count variable remains number-onlyfix(types): restore the pre-v25.10.4 ExistsFunction shape so plain arrow functions can again be assigned to ExistsFunction-typed variables (TypeScript
ExistsFunction shape so plain arrow functions can again be assigned to ExistsFunction-typed variables (TypeScript cannot infer type predicates through multi-overload assignment). Direct i18next.exists(key) calls still narrow key to SelectorKey — the predicate is now declared inline on i18n.exists. Custom wrappers that want the narrowing can type themselves as typeof i18next.exists 2425fix: when a plural lookup misses, the missingKey debug log now shows the actual plural-resolved key (e.g. foo.bar_many for Polish count: 14) instead o
missingKey debug log now shows the actual plural-resolved key (e.g. foo.bar_many for Polish count: 14) instead of the base key — making it obvious which plural category was expected and missing 2423@babel/runtime runtime dependency. The build no longer generates any @babel/runtime imports, so the package is unused by consumers. Rollup now uses babelHelpers: 'bundled' so any helpers that are ever needed in the future will be inlined rather than imported externally 2424dist/esm/i18next.bundled.js. It was byte-identical to dist/esm/i18next.js because no helpers were being imported 2424Security release — all issues found via an internal audit. GHSA advisory filed after release.
Security release — all issues found via an internal audit. GHSA advisory filed after release.
escapeValue: false with interpolated variables inside a $t(key, { ... "{{var}}" ... }) nesting-options block. In that narrow combination, attacker-controlled string values containing " can break out of the JSON options literal and inject additional nesting options (e.g. redirect lng/ns). The default escapeValue: true configuration is unaffected because HTML-escaping neutralises the quote before JSON.parse. See the security docs for mitigation guidance (GHSA-TBD)regexEscape to unescapePrefix / unescapeSuffix on par with the other interpolation delimiters. Prevents ReDoS (catastrophic-backtracking) when a misconfigured delimiter contains regex metacharacters, and fixes silent breakage of the {{- var}} syntax when the delimiter contains characters like (, [, ..env* and *.pem/*.key files in .gitignorefix: cloneInstance().changeLanguage() no longer fails to update language state when the target language is not yet loaded — a race between init()'s de
cloneInstance().changeLanguage() no longer fails to update language state when the target language is not yet loaded — a race between init()'s deferred load() and the user's changeLanguage() could overwrite isLanguageChangingTo, causing setLngProps to be skipped 2422fix(types): inline formatting options like {{price, currency(EUR)}} are now correctly resolved to their base format type (e.g. number for currency) in
{{price, currency(EUR)}} are now correctly resolved to their base format type (e.g. number for currency) instead of falling back to string 2378fix(types): addResourceBundle now accepts an optional 6th options parameter ({ silent?: boolean; skipCopy?: boolean }) matching the runtime API 2419
addResourceBundle now accepts an optional 6th options parameter ({ silent?: boolean; skipCopy?: boolean }) matching the runtime API 2419fix(types): t("key", {} as TOptions) no longer produces a type error — the context constraint now bypasses strict checking when context is unknown (e.
t("key", {} as TOptions) no longer produces a type error — the context constraint now bypasses strict checking when context is unknown (e.g. from TOptions) 2418fix: Formatter no longer crashes when alwaysFormat is true and no format specifier is present (format is undefined)
alwaysFormat is true and no format specifier is present (format is undefined)undefined/null values as-is instead of producing NaN when the value is missingRemove deprecated `initImmediate` option — the backward-compatibility mapping from initImmediate to initAsync (introduced in v24) has been removed. Us…
This is a major breaking release:
initImmediate option — the backward-compatibility mapping from initImmediate to initAsync (introduced in v24) has been removed. Use initAsync instead.interpolation.format function — the old monolithic format function (interpolation: { format: (value, format, lng) => ... }) is no longer supported. The built-in Formatter (or a custom Formatter module via .use()) is now always used. Migrate to the new formatting approach using i18next.services.formatter.add() or .addCached() for custom formatters.showSupportNotice option and all related internal suppression logic (globalThis.__i18next_supportNoticeShown, I18NEXT_NO_SUPPORT_NOTICE env var). See our blog post for the full story.simplifyPluralSuffix option — this option was unused by the core PluralResolver (which relies entirely on Intl.PluralRules). It only had an effect in the old v1/v2/v3 compatibility layer. The v4 test compatibility layer now defaults to true internally.@babel/polyfill from devDependencies.indexOf() > -1 / indexOf() < 0 with .includes() (~40+ occurrences)indexOf() === 0 with .startsWith() where appropriatevar with const, '' + object with String(object), .substring() with .slice().apply(observer, [event, ...args]) with direct call observer(event, ...args).call(this, ...) in BackendConnector retry logicarray-callback-return in LanguageUtils getBestMatchFromCodeseslint-disable comments from source filesonce() method for one-time event subscriptionscheckedLoadedFor cache to Translator instance, preventing cross-instance state leakageBackendModule generic parameter naming inconsistency between CJS and ESM type definitionsonce() method to i18n and ResourceStore type interfacestest.projects configfeat: suppress support notice automatically in production environments (NODE_ENV=production)
NODE_ENV=production)feat(types): export SelectorParam helper type — a stable, readable alternative to Parameters >[0] for typing selector function props 2414
SelectorParam<Ns, KPrefix> helper type — a stable, readable alternative to Parameters<TFunction<Ns>>[0] for typing selector function props 2414fix(types): reorder TFunctionSelector overloads so Parameters resolves to the general selector signature instead of the branded SelectorKey type 2412
TFunctionSelector overloads so Parameters<TFunction> resolves to the general selector signature instead of the branded SelectorKey type 2412feat(types): support typescript 6 to address https://github.com/i18next/react-i18next/issues/1910
feat(types): support typescript 6 to address https://github.com/i18next/react-i18next/issues/1910
feat(types): selector functions as keyPrefix in getFixedT now provide full type-safe key narrowing — the returned t function is scoped to the prefix s
keyPrefix in getFixedT now provide full type-safe key narrowing — the returned t function is scoped to the prefix subtree 2367feat(types): i18nKey of to accept a SelectorKey 2364
<Trans i18nKey={sk} /> to accept a SelectorKey 2364exists() is now a type guard that narrows the key to SelectorKey, so a validated key can be passed directly to t() 2364check also for I18NEXT_NO_SUPPORT_NOTICE env variable
feat(types): keyFromSelector is now type-safe — the selector callback is constrained against your resource definitions, catching invalid keys at compi
keyFromSelector is now type-safe — the selector callback is constrained against your resource definitions, catching invalid keys at compile time. Supports optional ns and keyPrefix options for non-default namespace/prefix contexts 2364fix(types): FilterKeys now correctly excludes base keys that have context variants when the provided context doesn't match any of them (e.g. key some
FilterKeys now correctly excludes base keys that have context variants when the provided context doesn't match any of them (e.g. key some with variant some_me is no longer accessible with context="one")feat(types): keyFromSelector now returns a branded SelectorKey type that t() accepts directly, enabling pre-computed and reusable translation keys 236
keyFromSelector now returns a branded SelectorKey type that t() accepts directly, enabling pre-computed and reusable translation keys 2364keyPrefix in getFixedT and per-call options 2367{{val, number}} requires number, {{val, datetime}} requires Date, {{name}} requires string, etc. Custom formatters can be typed via interpolationFormatTypeMap in CustomTypeOptions 2378FilterKeys in selector mode now preserves non-context, non-plural leaf keys when context is provided, fixing incorrect type narrowing when combining returnObjects: true with context 2398feat(types): selector API now enforces { count: number } when a key resolves to plural forms 2373
- fix: getFixedT() selector now resolves namespaces against the effective ns rather than the global init options #2406
getFixedT() selector now resolves namespaces against the effective ns rather than the global init options #2406fix: selector API namespace resolution regression for single-string ns and primary namespace in array #2405. Reverts the broad namespace-prefix rewrit
ns and primary namespace in array #2405. Reverts the broad namespace-prefix rewrite from v25.8.15 and replaces it with a targeted fix that only rewrites paths starting with a secondary namespace in a multi-namespace array, matching the type-level contract of GetSourceimprove selector api to accept array of selector functions, analogous to array of keys 2404
- update deps
fix(types): on() method now correctly returns this instead of void, matching the runtime behavior and enabling proper method chaining in TypeScript
on() method now correctly returns this instead of void, matching the runtime behavior and enabling proper method chaining in TypeScriptfix: Selector API unable to resolve namespaces #2402
fix: getCleanedCode now replaces all underscores
improve support notice shown logic
improve support notice shown logic
revert fix: compatibility with moduleResolution bundler (issue 2380) 2381
fix(interpolator): guard null matchedDoubleQuotes in nesting option parsing 2395
fix(interpolator): escape nestingOptionsSeparator in nesting option parsing 223943
types(i18n): add missing toJSON() declaration 2393
avoid crash due to ReferenceError without Intl API 2391
ts: address incomplete type definition for getFixedT() return value 2318
getFixedT() return value 2318fix: compatibility with moduleResolution bundler (issue 2380) 2381
fix: crashes when backend in backends array has no name property 2386
ts: document option to suppress the support message 2385
option to suppress the support message 2385
fix(types): Selector API - fix Namespace inference for selector ns option 2384
fix: TFunctionReturn fallback 2360
fix: Interpolation breaks when cloning an instance 2376
type definitions for new transDefaultProps option in react-i18next react-i18next: #1895
fix: Invalid overwrite of default value for overloadTranslationOptionHandler 2374.
TS: remove wrong signature 2372.
Your coding agent can read these notes before it upgrades. Set up the MCP server →