Repository navigation
Description
🐛 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:
Transpassesns: ns || t?.ns || …toTransWithoutContext(src/Trans.js), which callst(key, { …, ns: namespaces })(src/TransWithoutContext.js).t.nsholds the primary namespace only ('ns1').- In i18next's
getFixedT, an explicitnssetsexplicitCallNs, soselectorOpts.ns = scopeNsis skipped.keysFromSelectorthen sees['ns1'], does not treatns2as a namespace, and looks upns2.messageinns1.
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 <b>world</b></p><p>ns2.message</p>Expected behavior
Trans resolves the selector as t() does:
<p>Hello <b>world</b></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"withi18nKey={$ => $.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
adrai commentedon Oct 6, 2026
Thanks for the detailed report and analysis. Fixed in react-i18next v17.0.16: when
Transgets atthat carries its own namespace and nonsprop, it no longer passesnsto it, so the hook'stresolvesi18nKey={$ => $.ns2.message}with its full namespace list, the same ast(). An explicitnsprop behaves as before. The i18next-cli extractor will follow in a separate release.fix(extract): route a Trans selector key into the t prop's namespaces…
paour commentedon Oct 6, 2026
You're awesome!
adrai commentedon Oct 6, 2026
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 🙏
fix(Trans): resolve selector keys into useTranslation's secondary nam…