Skip to content

fix(deps): update dependency i18next to v26 - #813

Open
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/i18next-26.x
Open

renovate[bot] wants to merge 1 commit into
mainfrom
renovate/i18next-26.x

Conversation

@renovate

@renovate renovate Bot commented Apr 5, 2026 •

Copy link
Copy Markdown
Contributor

This PR contains the following updates:

Package Change Age Confidence
i18next (source) ^25.8.18 → ^26.5.6 age confidence

Release Notes

i18next/i18next (i18next)

v26.5.6

Compare Source

  • fix: cloneInstance({ forkResourceStore: true }) now copies the resources at every level. It copied them only down to the namespace objects, so nested keys and arrays stayed shared with the original instance: addResource() with a nested key (fork.addResource('en', 'translation', 'greeting.hello', 'Hi')) or a deep addResourceBundle() on the fork also changed the original instance and every other fork. Flat keys were already isolated. Forking a large store takes a bit longer: with 300k keys in nested groups, about 11 ms instead of 3 ms per fork. Clones without forkResourceStore still share the store as before. Thanks @​nk-batman21 (#​2456, follow-up to #​2270).

v26.5.5

Compare Source

  • fix: with the default skipOnVariables: true, a {{placeholder}} inside a value inserted with an unescaped {{- var}} now stays literal, as it already did for {{var}}. The escape pass ran after the unescaped values were written and scanned them again, so t('{{- a}}', { a: '{{b}}', b: 'c' }) returned 'c' instead of '{{b}}'. With pre-built HTML that carries user text, like t('You may contact {{name}} at {{- emailHTML}}.', { name, emailHTML }), that text could pull another variable of the same t() call into the output. Results with skipOnVariables: false, and values without placeholders, are unchanged. Compatibility note: a {{- var}} value whose placeholders were meant to be filled from the same t() call now keeps them literally; skipOnVariables: false restores the old behavior. Thanks @​nk-batman21 (#​2455, reported in #​1595).

v26.5.4

Compare Source

  • fix: with the default skipOnVariables: true, a {{placeholder}} inside an interpolated value now stays literal also when the same placeholder appears later in the translation. Each value was written with String.replace, which replaces the first copy of the placeholder text, and that could be the copy inside an earlier value: t('{{name}} has {{count}} items', { name: '{{count}}', count: 5 }) returned '5 has {{count}} items' instead of '{{count}} has 5 items'. The value is now written at the position of the matched placeholder. Results with skipOnVariables: false are unchanged. Thanks @​nk-batman21 (#​2454, reported in #​1595).
  • fix: with useRawValueToEscape: true, a non-string value in an unescaped {{- var}} placeholder or with escapeValue: false no longer throws val.replace is not a function; it is converted to a string like any other value.

v26.5.3

Compare Source

  • fix(types): with the selector API, a plural key now requires count also when the call passes an explicit ns. t(($) => $.item) already failed without count, but t(($) => $.item, { ns: 'items' }) compiled, although at runtime it looks up item instead of item_one / item_other and returns the key. It now fails the same way, also with ordinal: true, defaultValue, an ns array, a key prefix and i18n.t. Calls with count, context, returnObjects: true and arrays of selectors, as well as return types, are unchanged. Thanks @​nyarthan (#​2452).

v26.5.2

Compare Source

  • fix(types): on TypeScript 7, a t() call with a missing, misspelled or wrongly typed option now reports that option again (Property 'album' is missing, Type 'string' is not assignable to type 'number'), as TypeScript 5 and 6 do. TypeScript 7 elaborated the error against the default-value signature instead and ended in '{ count: 3; albm: "Summer"; }' is not assignable to type 'string': on a failed call TypeScript explains the last of the equally matching signatures, and TypeScript 7 sorts the 3-element [key, defaultValue, options?] last. It is now written as two signatures, [key, defaultValue] and [key, defaultValue, options]. This affects the default key checks with i18next {{name}} messages as well as parseInterpolation: 'icu' and 'mf2'. Which calls compile, their return types and the error positions are unchanged on TypeScript 5.9, 6 and 7.

v26.5.1

Compare Source

  • perf(types): type-checking t() calls against a large namespace is no longer quadratic in the number of keys. TypeScript does not cache keyof of an object type, and the types rebuilt the union of all keys of a namespace once per key while building the key union, and again in every t() call to look up the return type. Both now use a cached key union, and a literal key is looked up directly instead of being intersected with every key. With 5,000 keys in one namespace and one call per key, the check time went from 14.8s to 1.1s (TypeScript 6); with 1,000 keys from 0.75s to 0.27s. Keys, return types and errors are unchanged.
  • perf(types): the selector API (enableSelector: true / 'strict') with context is no longer quadratic in the size of an object. To decide which keys a context keeps, the types scanned all keys of the object once for every key, also when TypeScript checks the selector with context widened to string. With 1,000 keys plus one context variant each, a namespace took 18.7s and failed with "Type instantiation is excessively deep and possibly infinite" (TS2589); 2,500 and more ran out of memory. The context variants of an object are now collected once, in one pass over its keys: 1,000 keys check in 0.69s, 5,000 in 3.1s. Long keys (whole sentences with keySeparator: false) work as before. Selected values and errors are unchanged.

v26.5.0

Compare Source

  • feat(types): parseInterpolation: 'icu' and parseInterpolation: 'mf2' type t() options from the message's ICU MessageFormat arguments (i18next-icu) or MessageFormat 2 variables (i18next-mf2), so ICU and MF2 users no longer need parseInterpolation: false and lose argument checking. Missing, misspelled and wrongly typed arguments fail, and so does leaving out the options for a message with arguments (t('greeting')); that error names the missing arguments. Opt-in, default true is unchanged.
    • ICU: {name} takes string | number, {n, number}, plural and selectordinal take number, {d, date} and {t, time} take Date, select takes its case keys or any string (for other), and arguments nested in cases are required too. # and tags are not arguments, apostrophe quoting is read like intl-messageformat does.
    • MF2: {$name} takes string | number, a variable annotated with :number, :integer, :currency, :percent or :unit (inline or through .input / .local) takes number, :datetime, :date and :time take Date, :string takes string, option and markup variables (minimumFractionDigits=$digits, {#link href=$url}) are required too, .local variables are not arguments, and |quoted literals| and \{ escapes are skipped.
    • interpolationFormatTypeMap overrides a type by ICU format or MF2 function name, e.g. date: Date | number or a custom uppercase: string.
    • Applies to t(), getFixedT(), i18n.t, key prefixes, namespaces, the selector API and react-i18next's Trans values. Messages need literal types, as the resources file from i18next-cli's types command has them.
    • Type-check cost with 1,000 messages and one call each: 1.35s instead of 0.86s with false (TypeScript 6), 0.5s instead of 0.26s with TypeScript 7. Messages that no call uses cost nothing.
  • fix(types): with the default (non-strict) key checks, an options object with a missing or misspelled interpolation variable now reports the missing variable (Property 'name' is missing) instead of '{ nmae: string }' is not assignable to type 'string' from the default-value overload.

v26.4.7

Compare Source

  • perf: cloneInstance() no longer inherits preload. loadResources() adds every preload language to each load, and clones copied preload from the parent, so a clone's init() and every changeLanguage() walked all preloaded languages × all namespaces through queueLoad() / hasResourceBundle(), although the parent had already loaded them into the shared store. With i18next-http-middleware (one clone + changeLanguage() per request), 25 preloaded languages and 20 namespaces, that was most of the per-request CPU: the reproduction from the issue went from ~90 µs to ~10 µs per request, the same as with a single language. A clone now only loads what it uses itself: its changeLanguage() no longer waits for parent preload languages that are still loading (the parent's own init() still does), and clone.loadNamespaces() loads the new namespace for the clone's language and fallbacks instead of every preloaded language. clone.loadLanguages() works as before, the parent instance is unchanged, and passing preload to cloneInstance() keeps the old behavior. Thanks @​theMostCuriousHomunculus for the detailed report (#​2451).

v26.4.6

Compare Source

  • fix: a fallback language whose formatted code is already in the hierarchy is no longer added a second time. toResolveHierarchy() checked the raw fallbackLng value against the list, but the list holds formatted codes, so any fallback written differently from its formatted form slipped through: lng: 'de-CH' with fallbackLng: 'de-ch' gave languages ['de-CH', 'de', 'de-CH'], and the same happened with fallbackLng: 'EN' + cleanCode, lowerCaseLng, and Intl aliases (iw-IL → he-IL). Missing keys were looked up in the same language twice. Only exact duplicates are removed; the order is unchanged. Thanks @​lprnmns (#​2450).

v26.4.5

Compare Source

  • fix: hasLoadedNamespace(ns, { lng }) no longer stays false forever when the passed in lng is loaded under another code, or not at all. It checked the load state of the raw lng, but loadLanguages() stores a language under its resolved code (de-ch → de-CH, de-CH → de with load: 'languageOnly') and skips a lng outside supportedLngs entirely (only the fallback gets loaded), so that state never got set. react-i18next's useTranslation(ns, { lng }) then never became ready: with useSuspense it kept throwing an already resolved promise and React retried forever (100% CPU, SSR never finished), without it the load effect re-rendered in a loop. It now checks the code the language is actually loaded under, and for a lng outside supportedLngs only waits for the fallback, which is also what t() resolves to. Calls without lng and the precheck (react-i18next's bindI18n: 'languageChanging') are unchanged (react-i18next#1934).

v26.4.4

Compare Source

  • fix(security): the nesting regexp no longer backtracks exponentially (ReDoS) on an unclosed $t(. Its repeated group had overlapping alternatives ([^()"']+ inside (?:...)*?, and inside parentheses [^()] also matched quotes), so a string containing $t( without a closing ) made the engine try every way to split the text: about 2x the runtime per extra character, and a ~25 character value hung t() for minutes. With the default config this was reachable through any interpolated value (t('greet', { name: '$t(aaaaaaaaaaaaaaaaaaaaaaaa' })): escaping does not touch $, t or (, and the skipOnVariables check runs the regexp on the interpolated result, so nest: false did not help either. Every alternative now starts with a different character (plain char, ", ' or (), so there is only one way to match and the cost is linear. Quoted strings and one level of parentheses in nesting options keep working (#​2336). The only difference: a lone " or ' inside unquoted parentheses within $t(...) is no longer matched. Affects 25.3.3 to 26.4.3. See advisory GHSA-5jr9-983r-c8x4, CVSS 7.5 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H). Thanks to Anisetti Chaitanya Eshwar Prasad for the responsible disclosure.
  • fix(security): nest() now resolves $t(...) in a single pass and no longer nests text that came out of a resolved value. Every nested lookup reset the shared nesting regexp, so the loop started over from the beginning of the string after each match. That had two effects. First, a $t(...) inside a variable that the nested lookup deliberately kept literal (default skipOnVariables: true) was resolved anyway by the outer loop: with outer: 'A $t(inner) B' and inner: 'Hi {{name}}', t('outer', { name: '$t(secret)' }) rendered the secret translation. It now stays A Hi $t(secret) B, the same as without the outer nesting; with skipOnVariables: false it is still resolved. Second, the cost was quadratic when an unclosed $t( preceded many nestings, which matters when an app passes untrusted input as the key itself (t(userInput)): 20,000 nestings took ~1.4 s, now ~25 ms. The resolved value is now sliced into the result instead of going through String.replace, so $&, $`, $' and $$ stay literal as before (26.4.2).

v26.4.3

Compare Source

  • perf: ResourceStore.hasLanguageSomeTranslations() no longer enumerates every key of a large bundle on each call. It ran Object.keys() on the whole bundle just to learn whether it had at least one key, and init() / changeLanguage() reach it three times per instance (once while picking the language, twice via setResolvedLanguage()). With large flat bundles and one i18next instance per SSR request reusing the same resources, that showed up as measurable CPU work before any t() call. Bundle objects already found non-empty are now remembered in a WeakSet shared across instances, so later checks are O(1): 2,000 inits with a 6,800-key bundle went from ~750 ms to ~20 ms, the same as a one-key bundle. Only the non-empty result is cached. No API removes single keys from a bundle, so a non-empty bundle stays non-empty, and empty bundles are re-checked every time, so keys added later (backend loads, addResource, addResourceBundle, direct or silent changes) count right away. The only edge case: a bundle emptied by hand with delete on every key while it is still in the store keeps counting as having translations; use removeResourceBundle() for that. Thanks @​dmchoi77 for the detailed report (#​2449).

v26.4.2

Compare Source

  • fix: $&, $`, $' 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).

v26.4.1

Compare Source

  • fix(types): the selector-form 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).

v26.4.0

Compare Source

  • perf: cache 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).
  • chore: update all devDependencies (Babel stays on 7.x until @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.

v26.3.6

Compare Source

  • 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 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.

v26.3.5

Compare Source

  • fix: $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).
  • fix: 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).
  • fix: with the default 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).

v26.3.4

Compare Source

  • fix(security): 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.

v26.3.3

Compare Source

  • fix(types): selector 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.

v26.3.2

Compare Source

  • fix: chained formatters with a parenthesised option that contains the format separator (e.g. 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).

v26.3.1

Compare Source

  • 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] 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).

v26.3.0

Compare Source

  • feat(types): introduce 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.

v26.2.0

Compare Source

  • feat(types): new 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.
  • fix(types): expose 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)

v26.1.0

Compare Source

  • feat: 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.

v26.0.10

Compare Source

  • feat: 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.

v26.0.9

Compare Source

  • fix(types): unformatted interpolation values are now typed as 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-only

v26.0.8

Compare Source

  • fix(types): restore the pre-v25.10.4 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 2425

v26.0.7

Compare Source

  • fix: 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 of the base key — making it obvious which plural category was expected and missing 2423
  • chore: drop @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 2424
  • chore: stop emitting dist/esm/i18next.bundled.js. It was byte-identical to dist/esm/i18next.js because no helpers were being imported 2424

v26.0.6

Compare Source

Security release — all issues found via an internal audit.

  • security: warn when a translation string combines 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 note in the Nesting docs for the full pattern and mitigations
  • security: apply 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 (, [, .
  • security: strip CR/LF/NUL and other C0/C1 control characters from string log arguments to prevent log forging via user-controlled translation keys, language codes, namespaces, or interpolation variable names (CWE-117)
  • chore: ignore .env* and *.pem/*.key files in .gitignore

v26.0.5

Compare Source

  • fix: 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 2422

v26.0.4

Compare Source

  • fix(types): inline formatting options like {{price, currency(EUR)}} are now correctly resolved to their base format type (e.g. number for currency) instead of falling back to string 2378

v26.0.3

Compare Source

  • fix(types): addResourceBundle now accepts an optional 6th options parameter ({ silent?: boolean; skipCopy?: boolean }) matching the runtime API 2419

v26.0.2

Compare Source

  • fix(types): 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) 2418

v26.0.1

Compare Source

  • feat: 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.

v26.0.0

Compare Source

This is a major breaking release:

Breaking Changes
  • Remove deprecated initImmediate option — the backward-compatibility mapping from initImmediate to initAsync (introduced in v24) has been removed. Use initAsync instead.
  • Remove legacy 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.
  • Remove console support notice — the console support notice introduced in v25.8.0 has been removed, along with the 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.
  • Remove 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.
  • Remove deprecated @babel/polyfill from devDependencies.
Improvements
  • Code modernization across all source files:
    • Replace indexOf() > -1 / indexOf() < 0 with .includes() (~40+ occurrences)
    • Replace indexOf() === 0 with .startsWith() where appropriate
    • Replace var with const, '' + object with String(object), .substring() with .slice()
    • Replace .apply(observer, [event, ...args]) with direct call observer(event, ...args)
    • Remove unnecessary .call(this, ...) in BackendConnector retry logic
    • Fix array-callback-return in LanguageUtils getBestMatchFromCodes
    • Clean up all stale eslint-disable comments from source files
  • EventEmitter: add once() method for one-time event subscriptions
  • Memory leak fix: move module-level checkedLoadedFor cache to Translator instance, preventing cross-instance state leakage
  • TypeScript: fix BackendModule generic parameter naming inconsistency between CJS and ESM type definitions
  • TypeScript: add once() method to i18n and ResourceStore type interfaces
  • ESLint 9: migrate from ESLint 8 (airbnb-base) to ESLint 9 flat config with neostandard
  • Vitest 4: upgrade from vitest 3 to vitest 4, migrate workspace files to test.projects config

v25.10.10

Compare Source

  • feat: suppress support notice automatically in production environments (NODE_ENV=production)

v25.10.9

Compare Source

  • feat(types): export SelectorParam<Ns, KPrefix> helper type — a stable, readable alternative to Parameters<TFunction<Ns>>[0] for typing selector function props 2414

v25.10.8

Compare Source

  • fix(types): reorder TFunctionSelector overloads so Parameters<TFunction> resolves to the general selector signature instead of the branded SelectorKey type 2412

v25.10.7

Compare Source

v25.10.6

Compare Source

v25.10.5

Compare Source

  • feat(types): selector functions as keyPrefix in getFixedT now provide full type-safe key narrowing — the returned t function is scoped to the prefix subtree 2367

v25.10.4

Compare Source

  • feat(types): exists() is now a type guard that narrows the key to SelectorKey, so a validated key can be passed directly to t() 2364

v25.10.3

Compare Source

  • check also for I18NEXT_NO_SUPPORT_NOTICE env variable

v25.10.2

Compare Source

  • feat(types): 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 2364

v25.10.1

Compare Source

  • feat: suppress support notice automatically in production environments (NODE_ENV=production)

v25.10.0

Compare Source

  • feat(types): keyFromSelector now returns a branded SelectorKey type that t() accepts directly, enabling pre-computed and reusable translation keys 2364
  • feat: support selector syntax for keyPrefix in getFixedT and per-call options 2367
  • feat(types): interpolation values are now automatically typed based on built-in format specifiers — {{val, number}} requires number, {{val, datetime}} requires Date, {{name}} requires string, etc. Custom formatters can be typed via interpolationFormatTypeMap in CustomTypeOptions 2378
  • fix(types): FilterKeys 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 2398

v25.9.0

Compare Source

  • feat(types): selector API now enforces { count: number } when a key resolves to plural forms 2373
  • fix(types): string unions with invalid members are now correctly detected as type errors when used as context option 2172

v25.8.20

Compare Source

  • fix: getFixedT() selector now resolves namespaces against the effective ns rather than the global init options 2406

v25.8.19

Compare Source

  • fix: selector API namespace resolution regression for single-string 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 GetSource

Configuration

📅 Schedule: (UTC)

  • Branch creation
    • "before 12pm on Sunday"
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate renovate Bot added the dependencies label Apr 5, 2026
@renovate
renovate Bot force-pushed the renovate/i18next-26.x branch from b1a92ba to 7c0f123 Compare April 8, 2026 10:34
@renovate
renovate Bot force-pushed the renovate/i18next-26.x branch 2 times, most recently from 8bc524f to 81c5527 Compare April 18, 2026 18:44
@renovate
renovate Bot force-pushed the renovate/i18next-26.x branch 2 times, most recently from 3137aa8 to a39ca4e Compare April 24, 2026 19:33
@renovate
renovate Bot force-pushed the renovate/i18next-26.x branch 2 times, most recently from 8eba733 to c2608d9 Compare May 7, 2026 09:58
@sonarqubecloud

sonarqubecloud Bot commented May 7, 2026

Copy link
Copy Markdown

@renovate
renovate Bot force-pushed the renovate/i18next-26.x branch 2 times, most recently from fe9f5ad to 20bc08e Compare May 14, 2026 20:32
@renovate
renovate Bot force-pushed the renovate/i18next-26.x branch 2 times, most recently from 7a48c10 to 4818be2 Compare June 1, 2026 21:43
@renovate
renovate Bot force-pushed the renovate/i18next-26.x branch from 4818be2 to 0464497 Compare June 3, 2026 16:48
@sonarqubecloud

sonarqubecloud Bot commented Jun 3, 2026

Copy link
Copy Markdown

@renovate
renovate Bot force-pushed the renovate/i18next-26.x branch 3 times, most recently from 25dee2c to c69f156 Compare June 30, 2026 11:50
@renovate
renovate Bot force-pushed the renovate/i18next-26.x branch 2 times, most recently from 0f2bb63 to c9021a9 Compare July 9, 2026 14:14
@sonarqubecloud

sonarqubecloud Bot commented Jul 9, 2026

Copy link
Copy Markdown

@renovate
renovate Bot force-pushed the renovate/i18next-26.x branch from c9021a9 to c5c01b0 Compare August 11, 2026 23:37
@renovate
renovate Bot force-pushed the renovate/i18next-26.x branch from c5c01b0 to 12c9b67 Compare August 20, 2026 11:04
@renovate
renovate Bot force-pushed the renovate/i18next-26.x branch 2 times, most recently from 1c873b3 to cc48b38 Compare September 3, 2026 22:31
@renovate
renovate Bot force-pushed the renovate/i18next-26.x branch from cc48b38 to a8dfd3c Compare September 15, 2026 17:00
@renovate
renovate Bot force-pushed the renovate/i18next-26.x branch from a8dfd3c to c4ee1ed Compare October 1, 2026 03:58
@renovate
renovate Bot force-pushed the renovate/i18next-26.x branch 3 times, most recently from 66e9022 to 5a17361 Compare October 14, 2026 16:00
@renovate
renovate Bot force-pushed the renovate/i18next-26.x branch 6 times, most recently from 898e856 to 157370b Compare October 22, 2026 09:53
@renovate
renovate Bot force-pushed the renovate/i18next-26.x branch from 157370b to 406c964 Compare October 22, 2026 21:08
@sonarqubecloud

Copy link
Copy Markdown

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants