Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding support for nested component inside Trans #784

Merged
merged 3 commits into from
Mar 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 24 additions & 4 deletions src/Trans.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ function getChildren(node) {
return node && node.children ? node.children : node.props && node.props.children;
}

function hasValidReactChildren(children) {
if (Object.prototype.toString.call(children) !== '[object Array]') return false;
return children.every(child => React.isValidElement(child));
}

export function nodesToString(mem, children, index, i18nOptions) {
if (!children) return '';
if (Object.prototype.toString.call(children) !== '[object Array]') children = [children];
Expand Down Expand Up @@ -98,14 +103,24 @@ function renderNodes(children, targetString, i18n, i18nOptions) {
if (Object.prototype.toString.call(astNodes) !== '[object Array]') astNodes = [astNodes];

return astNodes.reduce((mem, node, i) => {
const translationContent = node.children && node.children[0] && node.children[0].content;
if (node.type === 'tag') {
const child = reactNodes[parseInt(node.name, 10)] || {};
const isElement = React.isValidElement(child);

if (typeof child === 'string') {
mem.push(child);
} else if (hasChildren(child)) {
const inner = mapAST(getChildren(child), node.children);
let inner;
if (
hasValidReactChildren(getChildren(child)) &&
mapAST(getChildren(child), node.children).length === 0
) {
// In a case Trans have nested components without translation values
inner = getChildren(child);
} else {
inner = mapAST(getChildren(child), node.children);
}
if (child.dummy) child.children = inner; // needed on preact!
mem.push(React.cloneElement(child, { ...child.props, key: i }, inner));
} else if (isNaN(node.name) && i18nOptions.transSupportBasicHtmlNodes) {
Expand All @@ -117,15 +132,20 @@ function renderNodes(children, targetString, i18n, i18nOptions) {
mem.push(React.createElement(node.name, { key: `${node.name}-${i}` }, inner));
}
} else if (typeof child === 'object' && !isElement) {
const content = node.children[0] ? node.children[0].content : null;

const content = node.children[0] ? translationContent : null;
// v1
// as interpolation was done already we just have a regular content node
// in the translation AST while having an object in reactNodes
// -> push the content no need to interpolate again
if (content) mem.push(content);
} else {
mem.push(child);
if (node.children.length === 1 && translationContent) {
// If component does not have children, but translation - has
// with this in component could be components={[<span class='make-beautiful'/>]} and in translation - 'some text <0>some highlighted message</0>'
mem.push(React.cloneElement(child, { ...child.props, key: i }, translationContent));
} else {
mem.push(child);
}
}
} else if (node.type === 'text') {
mem.push(node.content);
Expand Down
3 changes: 3 additions & 0 deletions test/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ i18n.init({
testTransKey3_plural: 'Result: <1><0>{{numOfItems}}</0></1> items matched.',
testInvalidHtml: '<hello',
testInvalidHtml2: '<hello>',
testTrans4KeyWithNestedComponent: 'Result should be a list: <0></0>',
testTrans5KeyWithNestedComponent: 'Result should be a list: <1></1>',
testTrans5KeyWithValue: 'Result should be rendered within tag <0>{{testValue}}</0>',
},
other: {
transTest1: 'Another go <1>there</1>.',
Expand Down
85 changes: 85 additions & 0 deletions test/trans.render.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -365,3 +365,88 @@ describe('trans should not break on invalid node from translations - part2', ()
expect(wrapper.contains(<div>&lt;hello&gt;</div>)).toBe(true);
});
});

describe('Trans should render nested components', () => {
it('should render dynamic ul as components property', () => {
const list = ['li1', 'li2'];

const TestElement = () => (
<Trans
i18nKey="testTrans4KeyWithNestedComponent"
components={[
<ul>
{list.map(item => (
<li key={item}>{item}</li>
))}
</ul>,
]}
/>
);
const wrapper = mount(<TestElement />);

expect(
wrapper.contains(
<ul>
<li>li1</li>
<li>li2</li>
</ul>,
),
).toBe(true);
});

it('should render dynamic ul as components property when pass as a children', () => {
const list = ['li1', 'li2'];

const TestElement = () => (
<Trans i18nKey="testTrans5KeyWithNestedComponent">
My list:
<ul>
{list.map(item => (
<li key={item}>{item}</li>
))}
</ul>
</Trans>
);
const wrapper = mount(<TestElement />);
expect(
wrapper.contains(
<ul>
<li>li1</li>
<li>li2</li>
</ul>,
),
).toBe(true);
});
});

describe('Trans should use value from translation', () => {
it('should use value from translation if no data provided in component', () => {
const TestElement = () => (
<Trans
i18nKey="testTrans5KeyWithValue"
values={{
testValue: 'dragonfly',
}}
components={[<span className="awesome-styles" />]}
/>
);

const wrapper = mount(<TestElement />);
expect(wrapper.contains(<span className="awesome-styles">dragonfly</span>)).toBe(true);
});

it('should use value from translation if dummy data provided in component', () => {
const TestElement = () => (
<Trans
i18nKey="testTrans5KeyWithValue"
values={{
testValue: 'dragonfly',
}}
components={[<span className="awesome-styles">test string</span>]}
/>
);

const wrapper = mount(<TestElement />);
expect(wrapper.contains(<span className="awesome-styles">dragonfly</span>)).toBe(true);
});
});