Skip to content

Commit

Permalink
allow list maps in Trans
Browse files Browse the repository at this point in the history
  • Loading branch information
jamuhl committed Mar 14, 2019
1 parent 3814af2 commit e0e140c
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 29 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
@@ -1,3 +1,7 @@
### 10.5.0

- Adding support for nested component inside Trans that are a list.map like `<ul>{['a', 'b'].map(item => ( <li key={item}>{item}</li> ))}</ul>` [784](https://github.com/i18next/react-i18next/pull/784) (Adding `<ul i18nIsDynamicList>` will also create correct missing string)

### 10.4.2

- typescript: updated typescript definition of the UseTranslationOptions interface, added the useSuspense configuration property [778](https://github.com/i18next/react-i18next/pull/778)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -87,7 +87,7 @@ Head over to the **interactive playground** at [codesandbox](https://codesandbox

Want to learn more about how seamless your internationalization and translation process can be?

[![video](example/locize/video_sample.png)](https://www.youtube.com/watch?v=9NOzJhgmyQE)
[![video](example/v9.x.x/locize/video_sample.png)](https://www.youtube.com/watch?v=9NOzJhgmyQE)

[watch the video](https://www.youtube.com/watch?v=9NOzJhgmyQE)

Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -44,7 +44,7 @@
"babel-eslint": "10.0.1",
"babel-jest": "24.1.0",
"babel-plugin-macros": "^2.5.0",
"babel-plugin-tester": "^5.5.2",
"babel-plugin-tester": "^6.0.0",
"coveralls": "^3.0.2",
"dtslint": "^0.5.3",
"enzyme": "^3.8.0",
Expand Down
29 changes: 26 additions & 3 deletions react-i18next.js
Expand Up @@ -504,6 +504,11 @@
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__default.isValidElement(child));
}

function nodesToString(mem, children, index, i18nOptions) {
if (!children) return '';
if (Object.prototype.toString.call(children) !== '[object Array]') children = [children];
Expand All @@ -517,7 +522,15 @@
mem = `${mem}${child}`;
} else if (hasChildren(child)) {
const elementTag = keepArray.indexOf(child.type) > -1 && Object.keys(child.props).length === 1 && typeof hasChildren(child) === 'string' ? child.type : elementKey;
mem = `${mem}<${elementTag}>${nodesToString('', getChildren(child), i + 1, i18nOptions)}</${elementTag}>`;

if (child.props && child.props.i18nIsDynamicList) {
// we got a dynamic list like "<ul>{['a', 'b'].map(item => ( <li key={item}>{item}</li> ))}</ul>""
// the result should be "<0></0>" and not "<0><0>a</0><1>b</1></0>"
mem = `${mem}<${elementTag}></${elementTag}>`;
} else {
// regular case mapping the inner children
mem = `${mem}<${elementTag}>${nodesToString('', getChildren(child), i + 1, i18nOptions)}</${elementTag}>`;
}
} else if (React__default.isValidElement(child)) {
if (keepArray.indexOf(child.type) > -1 && Object.keys(child.props).length === 0) {
mem = `${mem}<${child.type}/>`;
Expand Down Expand Up @@ -570,14 +583,18 @@
if (Object.prototype.toString.call(reactNodes) !== '[object Array]') reactNodes = [reactNodes];
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__default.isValidElement(child);

if (typeof child === 'string') {
mem.push(child);
} else if (hasChildren(child)) {
const inner = mapAST(getChildren(child), node.children);
const childs = getChildren(child);
const mappedChildren = mapAST(childs, node.children);
const inner = hasValidReactChildren(childs) && mappedChildren.length === 0 ? childs : mappedChildren;
if (child.dummy) child.children = inner; // needed on preact!

mem.push(React__default.cloneElement(child, _objectSpread({}, child.props, {
Expand All @@ -597,12 +614,18 @@
}, inner));
}
} else if (typeof child === 'object' && !isElement) {
const content = node.children[0] ? node.children[0].content : null; // v1
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 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__default.cloneElement(child, _objectSpread({}, child.props, {
key: i
}), translationContent));
} else {
mem.push(child);
}
Expand Down
2 changes: 1 addition & 1 deletion react-i18next.min.js

Large diffs are not rendered by default.

47 changes: 24 additions & 23 deletions src/Trans.js
Expand Up @@ -36,12 +36,20 @@ export function nodesToString(mem, children, index, i18nOptions) {
typeof hasChildren(child) === 'string'
? child.type
: elementKey;
mem = `${mem}<${elementTag}>${nodesToString(
'',
getChildren(child),
i + 1,
i18nOptions,
)}</${elementTag}>`;

if (child.props && child.props.i18nIsDynamicList) {
// we got a dynamic list like "<ul>{['a', 'b'].map(item => ( <li key={item}>{item}</li> ))}</ul>""
// the result should be "<0></0>" and not "<0><0>a</0><1>b</1></0>"
mem = `${mem}<${elementTag}></${elementTag}>`;
} else {
// regular case mapping the inner children
mem = `${mem}<${elementTag}>${nodesToString(
'',
getChildren(child),
i + 1,
i18nOptions,
)}</${elementTag}>`;
}
} else if (React.isValidElement(child)) {
if (keepArray.indexOf(child.type) > -1 && Object.keys(child.props).length === 0) {
mem = `${mem}<${child.type}/>`;
Expand Down Expand Up @@ -111,16 +119,11 @@ function renderNodes(children, targetString, i18n, i18nOptions) {
if (typeof child === 'string') {
mem.push(child);
} else if (hasChildren(child)) {
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);
}
const childs = getChildren(child);
const mappedChildren = mapAST(childs, node.children);
const inner =
hasValidReactChildren(childs) && mappedChildren.length === 0 ? childs : mappedChildren;

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 @@ -138,14 +141,12 @@ function renderNodes(children, targetString, i18n, i18nOptions) {
// in the translation AST while having an object in reactNodes
// -> push the content no need to interpolate again
if (content) mem.push(content);
} else 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 {
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);
}
mem.push(child);
}
} else if (node.type === 'text') {
mem.push(node.content);
Expand Down
59 changes: 59 additions & 0 deletions test/trans.nodeToString.spec.js
Expand Up @@ -85,4 +85,63 @@ describe('trans nodeToString', () => {
expect(result).toEqual('lorem <1>bold</1> ipsum');
});
});

describe('having dynamic list maps', () => {
it('should create normal inner children if not set to ignore them', () => {
const children = [
'lorem ',
{
$$typeof: Symbol.for('react.element'),
type: 'ul',
props: {
children: [
{
$$typeof: Symbol.for('react.element'),
type: 'li',
props: { children: 'a' },
},
{
$$typeof: Symbol.for('react.element'),
type: 'li',
props: { children: 'b' },
},
],
},
},
' ipsum',
];

const result = nodesToString('', children, 0, {});
expect(result).toEqual('lorem <1><0>a</0><1>b</1></1> ipsum');
});

it('should omit inner children if set', () => {
const children = [
'lorem ',
{
$$typeof: Symbol.for('react.element'),
type: 'ul',
props: {
i18nIsDynamicList: true,
children: [
{
$$typeof: Symbol.for('react.element'),
type: 'li',
props: { children: 'a' },
},
{
$$typeof: Symbol.for('react.element'),
type: 'li',
props: { children: 'b' },
},
],
},
},
' ipsum',
];

const result = nodesToString('', children, 0, {});
expect(result).toEqual('lorem <1></1> ipsum');
});
});
});

0 comments on commit e0e140c

Please sign in to comment.