Releases · i18next/i18next · GitHub
Skip to content

Releases: i18next/i18next

v26.5.11

Choose a tag to compare

@adrai adrai released this 25 Oct 18:33
  • fix: a nested false, 0 or '' is now returned when a flat key with the same dotted name also exists. With { a: { b: false }, 'a.b': 'flat' }, getResource('en', 'translation', 'a.b') and t('a.b') returned 'flat': the nested result was checked for truthiness, so these present values fell through to the flat-key lookup. Without such a flat key they were already returned. A nested null, which is not a valid lookup by default (returnNull: false), still falls back to the flat key. Thanks @lprnmns (#2461).

v26.5.10

Choose a tag to compare

@adrai adrai released this 24 Oct 10:14
  • fix: when none of the detected languages is in supportedLngs, the best match no longer picks a different language whose code only starts with the same letters. The last fuzzy check took any supported code starting with the language part, so with supportedLngs: ['en', 'fil-PH'] a Finnish user (fi-FI) got Filipino instead of the fallback language, and ha (Hausa) picked haw-US (Hawaiian). Other regions of the same language still match as before (de-CH → de-DE, pt-PT → pt-BR, fil → fil-PH). Compatibility note: a two-letter code no longer matches a supported three-letter code that extends it, like ar → arz-EG, sw → swh-KE or no → nob-NO; list the two-letter code in supportedLngs (or map it with fallbackLng) if you relied on that. Thanks @Pharex38 (#2460).

v26.5.9

Choose a tag to compare

@adrai adrai released this 24 Oct 10:09
  • fix: nested lookups no longer resolve to methods inherited from the prototype, like constructor, toString or map. Only the top level was guarded, so {{constructor}} was treated as missing, but {{user.constructor}} rendered the function source (with a class instance, the full source code of the class), and {{user.toString}}, {{items.map}} or a class method like {{user.greet}} did the same. The same lookup is used for translation keys when ignoreJSONStructure is on (the default): a nested key named like an inherited method that is missing in the current language (t('roles.constructor') with roles.constructor only in the fallback language) returned function Object() { [native code] } instead of falling back, and exists('roles.valueOf') returned true. These keys are now missing, so the fallback language, missingInterpolationHandler, saveMissing and the missing key handler apply as for any other missing key. Getters defined on a class ({{user.fullName}} with get fullName()) still work. Nested lookups also no longer walk into the prototype via __proto__, so {{user.__proto__.constructor}} no longer prints the class source either. Thanks @Pharex38 (#2459).

v26.5.8

Choose a tag to compare

@adrai adrai released this 24 Oct 05:51
  • fix: arrays returned with joinArrays now get the same resolved details as strings. The joined value was extended with lastKey in the place of the resolved lookup, so with postProcessPassResolved a post processor got no usedLng, usedNS or exactUsedKey (t('steps', { joinArrays: ' / ' }) reported undefined/undefined:undefined). This affected the subliminal post processor used by the locize InContext editor, which could not map a joined array to its key. The nesting recursion guard did not get the parent key either, so arrays that nest each other (listA: ['a', '$t(listB)'], listB: ['b', '$t(listA)']) threw Maximum call stack size exceeded. They now stop at the guard, as strings do. An i18nFormat plugin with handleAsObject and parse also no longer throws Cannot read properties of undefined (reading 'usedNS') on joined arrays. Thanks @nk-batman21 (#2458).

v26.5.7

Choose a tag to compare

@adrai adrai released this 23 Oct 17:44
  • fix: with the default cacheInBuiltFormats: true, built-in formats now also work when the t() options contain a BigInt or a circular object. The cache key was built with JSON.stringify over all options, including the interpolation values, which throws on those, so every built-in format in that call returned the raw value: t('num', { val: 12345678901234567890n }) with 'Total: {{val, number}}' returned 'Total: 12345678901234567890' instead of 'Total: 12,345,678,901,234,567,890', and passing an object with a back-reference (like an ORM entity) left all numbers, dates and lists of that call unformatted. When the key cannot be built, the value is now formatted without the cache, as with cacheInBuiltFormats: false. This also applies to formatters added with addCached. Other calls still use the cache as before. Thanks @nk-batman21 (#2457).

v26.5.6

Choose a tag to compare

@adrai adrai released this 22 Oct 19:18
  • 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

Choose a tag to compare

@adrai adrai released this 22 Oct 09:05
  • 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

Choose a tag to compare

@adrai adrai released this 21 Oct 07:43
  • 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

Choose a tag to compare

@adrai adrai released this 19 Oct 17:20
  • 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

Choose a tag to compare

@adrai adrai released this 17 Oct 21:23
  • 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.