Double-escaping of interpolated values in component props
🐛 Bug Report
When using Trans component with escapeValue: true and passing interpolated user input as component props (e.g., title attribute), the values are double-escaped. HTML entities like " and ' render as " and ' instead of their actual characters, making the output unreadable.
Additionally, without explicit escapeValue: true in tOptions, the Trans component fails to parse and replace componenttags entirely, rendering them as literal text.
To Reproduce
minimal reproducible example: stackblitz
{
"hello": "Hello <Item title=\"{{ name }}\" />!"
}
Code:
import { Trans } from 'react-i18next';
import type { FC } from 'react';
function App() {
const value1 = 'World " \'" Test';
const value2 = "World \" '\" Test";
return (
<>
<div>
case 1:{' '}
<Trans
i18nKey="hello"
values={{ name: value1 }}
components={{ Item: <Item /> }}
shouldUnescape
/>
</div>
<div>
case 2:{' '}
<Trans
i18nKey="hello"
values={{ name: value2 }}
components={{ Item: <Item /> }}
tOptions={{
interpolation: { escapeValue: true },
}}
shouldUnescape
/>
</div>
</>
);
}
const Item: FC<{ title?: string }> = ({ title }) => (
<span id="span15" title={title}>
{title}
</span>
);
export default App;
i18n initialization:
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
await i18n.use(initReactI18next).init({
lng: 'en',
fallbackLng: 'en',
interpolation: {
escapeValue: false, // Global setting
},
react: {
useSuspense: false,
transSupportBasicHtmlNodes: false,
transKeepBasicHtmlNodesFor: [],
},
})
Actual output:
Case 1: Hello <Item title="World " '" Test" />!
- Component tag not parsed - rendered as literal text
Case 2:
Hello <span id="span15" title="World &quot; &#39;&quot; Test"> World &quot; &#39;&quot; Test </span>
- Component parsed but values are double-escaped
Expected behavior
Both cases should properly parse the component AND render values with correct escaping (once, not twice).
Hello <span id="span15" title="World " '" Test">World " '" Test</span>
The component should:
- Parse and replace <Item /> tags correctly
- Escape user input once for XSS protection
- NOT double-escape when rendering as HTML attributes
- Work consistently regardless of global escapeValue setting
Current Workarounds (all have drawbacks)
Manually decode in each component - Not scalable for large projects
import he from 'he';
const Item: FC<{ title?: string }> = ({ title }) => {
const decoded = title ? he.decode(title) : undefined;
return (
<span id="span15" title={decoded}>
{decoded}
</span>
);
};
Environment
- runtime version: node v22.19.0
- i18next version: 25.6.3
- os: Windows
- Package manager: pnpm
Thank you for maintaining this excellent library! Any guidance would be greatly appreciated.
as far as I know - interpolation.escapeValue can safely be set to false as react already does escape (-> therefore also the double escaping if set true -> once by i18next, once by react)
v16.4.1 should fix this... Can you try and let me know?
Thank you so much for fixing the issue I reported. I really appreciate your quick response and the effort you put into maintaining this library. We can close this issue