Skip to content

Commit

Permalink
self-closing components in translation strings should not attempt to …
Browse files Browse the repository at this point in the history
…replace the components children #1695
  • Loading branch information
adrai committed Nov 15, 2023
1 parent cfbbdf6 commit c9d3c87
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 1 deletion.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
### 13.5.0

- self-closing components in translation strings should not attempt to replace the component's children [1695](https://github.com/i18next/react-i18next/issues/1695)

### 13.4.1

- types: use CustomInstanceExtenstions to extend reportNamespaces
Expand Down
10 changes: 10 additions & 0 deletions react-i18next.js
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,16 @@
ns: namespaces
};
const translation = key ? t(key, combinedTOpts) : defaultValue;
if (components) {
Object.keys(components).forEach(c => {
const comp = components[c];
if (typeof comp.type === 'function' || !comp.props || !comp.props.children || translation.indexOf(`${c}/>`) < 0 && translation.indexOf(`${c} />`) < 0) return;
function Componentized() {
return React.createElement(React.Fragment, null, comp);
}
components[c] = React.createElement(Componentized, null);
});
}
const content = renderNodes(components || children, translation, i18n, reactI18nextOptions, combinedTOpts, shouldUnescape);
const useAsParent = parent !== undefined ? parent : reactI18nextOptions.defaultTransParent;
return useAsParent ? React.createElement(useAsParent, additionalProps, content) : content;
Expand Down
2 changes: 1 addition & 1 deletion react-i18next.min.js

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions src/TransWithoutContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,24 @@ export function Trans({
};
const translation = key ? t(key, combinedTOpts) : defaultValue;

if (components) {
Object.keys(components).forEach((c) => {
const comp = components[c];
if (
typeof comp.type === 'function' ||
!comp.props ||
!comp.props.children ||
(translation.indexOf(`${c}/>`) < 0 && translation.indexOf(`${c} />`) < 0)
)
return;
// eslint-disable-next-line react/no-unstable-nested-components, no-inner-declarations
function Componentized() {
return <>{comp}</>;
}
components[c] = <Componentized />;
});
}

const content = renderNodes(
components || children,
translation,
Expand Down
1 change: 1 addition & 0 deletions test/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ i18n.init({
deepPath: {
deepKey1: 'value1',
},
transTestWithSelfClosing: 'interpolated component: <component/>',
},
other: {
transTest1: 'Another go <1>there</1>.',
Expand Down
24 changes: 24 additions & 0 deletions test/trans.render.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,30 @@ describe('trans should work with uppercase elements in components', () => {
});
});

describe('trans should work with selfclosing elements in components', () => {
function TestComponent() {
return (
<Trans
i18nKey="transTestWithSelfClosing"
components={{
component: <div>These children will be preserved</div>,
}}
/>
);
}
it('should render translated string', () => {
const { container } = render(<TestComponent />);
expect(container.firstChild).toMatchInlineSnapshot(`
<div>
interpolated component:
<div>
These children will be preserved
</div>
</div>
`);
});
});

describe('trans with null child', () => {
function TestComponent() {
return (
Expand Down

1 comment on commit c9d3c87

@hongdeyuan
Copy link

@hongdeyuan hongdeyuan commented on c9d3c87 Apr 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why function components need to return directly? What is the reason? @adrai

Please sign in to comment.