Skip to content

Trans ignores the secondary namespaces of useTranslation for a selector i18nKey #1933

Closed
zextras/carbonio-files-public-folder-ui
#368

Description

@paour

🐛 Bug Report

With useTranslation(['ns1', 'ns2']), t($ => $.ns2.message) resolves since 17.0.7. <Trans t={t} i18nKey={$ => $.ns2.message} /> with the same t renders the raw key ns2.message.

17.0.7 fixed i18next/i18next#2429 "for useTranslation-based callers": the hook passes its namespace list to getFixedT as scopeNs. Trans does not get that fix:

  • Trans passes ns: ns || t?.ns || … to TransWithoutContext (src/Trans.js), which calls t(key, { …, ns: namespaces }) (src/TransWithoutContext.js). t.ns holds the primary namespace only ('ns1').
  • In i18next's getFixedT, an explicit ns sets explicitCallNs, so selectorOpts.ns = scopeNs is skipped. keysFromSelector then sees ['ns1'], does not treat ns2 as a namespace, and looks up ns2.message in ns1.

To Reproduce

import i18next from 'i18next'
import { renderToStaticMarkup } from 'react-dom/server'
import { I18nextProvider, initReactI18next, Trans, useTranslation } from 'react-i18next'

const i18n = i18next.createInstance()
await i18n.use(initReactI18next).init({
  lng: 'en',
  resources: { en: { ns1: { title: 'Title' }, ns2: { message: 'Hello <b>world</b>' } } },
})

const App = () => {
  const { t } = useTranslation(['ns1', 'ns2'])
  return (
    <>
      <p>{t($ => $.ns2.message)}</p>
      <p><Trans t={t} i18nKey={$ => $.ns2.message} components={{ b: <b /> }} /></p>
    </>
  )
}

console.log(renderToStaticMarkup(<I18nextProvider i18n={i18n}><App /></I18nextProvider>))

Output:

<p>Hello &lt;b&gt;world&lt;/b&gt;</p><p>ns2.message</p>

Expected behavior

Trans resolves the selector as t() does:

<p>Hello &lt;b&gt;world&lt;/b&gt;</p><p>Hello <b>world</b></p>

Each of these renders the translation today, so only the selector form without an ns prop fails:

  • a string key: i18nKey="ns2:message"
  • the same selector with ns={['ns1', 'ns2']}
  • ns="ns2" with i18nKey={$ => $.message}

Possible fix

When Trans receives a t and no ns prop, leave ns out of the options it passes to that t, so that the fixed t applies its own ns and scopeNs. Both Trans.js (which fills ns from t.ns) and TransWithoutContext.js would need the change.

Related: i18next-cli

i18next-cli 1.74.4 follows the current Trans behavior, not t(). In the same component, it files the Trans key as ns1:ns2.message and the t() key as ns2:message. A fix here would need the extractor to follow.

Environment

  • react-i18next 17.0.15
  • i18next 26.4.2
  • i18next-cli 1.74.4
  • react / react-dom 19.3.0
  • The reproduction needs no TypeScript setup. Our project uses TypeScript 7.0.2 with enableSelector: 'optimize'.

Activity

  1. adrai commented on Oct 6, 2026

    @adrai
    Member

    Thanks for the detailed report and analysis. Fixed in react-i18next v17.0.16: when Trans gets a t that carries its own namespace and no ns prop, it no longer passes ns to it, so the hook's t resolves i18nKey={$ => $.ns2.message} with its full namespace list, the same as t(). An explicit ns prop behaves as before. The i18next-cli extractor will follow in a separate release.

  2. paour commented on Oct 6, 2026

    @paour
    Author

    You're awesome!

  3. adrai commented on Oct 6, 2026

    @adrai
    Member

    If you like this module don’t forget to star this repo. Make a tweet, share the word or have a look at our https://locize.com to support the devs of this project.

    There are many ways to help this project 🙏

  4. added a commit that references this issue on Oct 11, 2026
    51b7c26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions