Skip to content

Commit

Permalink
Merge pull request #784 from brumeregan/master
Browse files Browse the repository at this point in the history
Adding support for nested component inside Trans
  • Loading branch information
jamuhl authored Mar 14, 2019
2 parents 849bae7 + 76d608e commit 3814af2
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 4 deletions.
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);
});
});

0 comments on commit 3814af2

Please sign in to comment.