react-i18next
Version:
Internationalization for react done right. Using the i18next i18n ecosystem.
240 lines (200 loc) • 10.7 kB
JavaScript
;
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.nodesToString = nodesToString;
exports.Trans = Trans;
var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
var _objectSpread2 = _interopRequireDefault(require("@babel/runtime/helpers/objectSpread"));
var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
var _react = _interopRequireWildcard(require("react"));
var _htmlParseStringify = _interopRequireDefault(require("html-parse-stringify2"));
var _context = require("./context");
var _utils = require("./utils");
function hasChildren(node) {
return node && (node.children || node.props && node.props.children);
}
function getChildren(node) {
if (!node) return [];
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(function (child) {
return _react.default.isValidElement(child);
});
}
function nodesToString(mem, children, index, i18nOptions) {
if (!children) return '';
if (Object.prototype.toString.call(children) !== '[object Array]') children = [children];
var keepArray = i18nOptions.transKeepBasicHtmlNodesFor || [];
children.forEach(function (child, i) {
// const isElement = React.isValidElement(child);
// const elementKey = `${index !== 0 ? index + '-' : ''}${i}:${typeof child.type === 'function' ? child.type.name : child.type || 'var'}`;
var elementKey = "".concat(i);
if (typeof child === 'string') {
mem = "".concat(mem).concat(child);
} else if (hasChildren(child)) {
var elementTag = keepArray.indexOf(child.type) > -1 && Object.keys(child.props).length === 1 && typeof hasChildren(child) === 'string' ? child.type : elementKey;
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 = "".concat(mem, "<").concat(elementTag, "></").concat(elementTag, ">");
} else {
// regular case mapping the inner children
mem = "".concat(mem, "<").concat(elementTag, ">").concat(nodesToString('', getChildren(child), i + 1, i18nOptions), "</").concat(elementTag, ">");
}
} else if (_react.default.isValidElement(child)) {
if (keepArray.indexOf(child.type) > -1 && Object.keys(child.props).length === 0) {
mem = "".concat(mem, "<").concat(child.type, "/>");
} else {
mem = "".concat(mem, "<").concat(elementKey, "></").concat(elementKey, ">");
}
} else if ((0, _typeof2.default)(child) === 'object') {
var clone = (0, _objectSpread2.default)({}, child);
var format = clone.format;
delete clone.format;
var keys = Object.keys(clone);
if (format && keys.length === 1) {
mem = "".concat(mem, "{{").concat(keys[0], ", ").concat(format, "}}");
} else if (keys.length === 1) {
mem = "".concat(mem, "{{").concat(keys[0], "}}");
} else {
// not a valid interpolation object (can only contain one value plus format)
(0, _utils.warn)("react-i18next: the passed in object contained more than one variable - the object should look like {{ value, format }} where format is optional.", child);
}
} else {
(0, _utils.warn)("Trans: the passed in value is invalid - seems you passed in a variable like {number} - please pass in variables for interpolation as full objects like {{number}}.", child);
}
});
return mem;
}
function renderNodes(children, targetString, i18n, i18nOptions) {
if (targetString === '') return []; // check if contains tags we need to replace from html string to react nodes
var keepArray = i18nOptions.transKeepBasicHtmlNodesFor || [];
var emptyChildrenButNeedsHandling = targetString && new RegExp(keepArray.join('|')).test(targetString); // no need to replace tags in the targetstring
if (!children && !emptyChildrenButNeedsHandling) return [targetString]; // v2 -> interpolates upfront no need for "some <0>{{var}}</0>"" -> will be just "some {{var}}" in translation file
var data = {};
function getData(childs) {
if (Object.prototype.toString.call(childs) !== '[object Array]') childs = [childs];
childs.forEach(function (child) {
if (typeof child === 'string') return;
if (hasChildren(child)) getData(getChildren(child));else if ((0, _typeof2.default)(child) === 'object' && !_react.default.isValidElement(child)) Object.assign(data, child);
});
}
getData(children);
targetString = i18n.services.interpolator.interpolate(targetString, data, i18n.language); // parse ast from string with additional wrapper tag
// -> avoids issues in parser removing prepending text nodes
var ast = _htmlParseStringify.default.parse("<0>".concat(targetString, "</0>"));
function mapAST(reactNodes, astNodes) {
if (Object.prototype.toString.call(reactNodes) !== '[object Array]') reactNodes = [reactNodes];
if (Object.prototype.toString.call(astNodes) !== '[object Array]') astNodes = [astNodes];
return astNodes.reduce(function (mem, node, i) {
var translationContent = node.children && node.children[0] && node.children[0].content;
if (node.type === 'tag') {
var child = reactNodes[parseInt(node.name, 10)] || {};
var isElement = _react.default.isValidElement(child);
if (typeof child === 'string') {
mem.push(child);
} else if (hasChildren(child)) {
var childs = getChildren(child);
var mappedChildren = mapAST(childs, node.children);
var inner = hasValidReactChildren(childs) && mappedChildren.length === 0 ? childs : mappedChildren;
if (child.dummy) child.children = inner; // needed on preact!
mem.push(_react.default.cloneElement(child, (0, _objectSpread2.default)({}, child.props, {
key: i
}), inner));
} else if (emptyChildrenButNeedsHandling && (0, _typeof2.default)(child) === 'object' && child.dummy && !isElement) {
// we have a empty Trans node (the dummy element) with a targetstring that contains html tags needing
// conversion to react nodes
// so we just need to map the inner stuff
var _inner = mapAST(reactNodes
/* wrong but we need something */
, node.children);
mem.push(_react.default.cloneElement(child, (0, _objectSpread2.default)({}, child.props, {
key: i
}), _inner));
} else if (isNaN(node.name) && i18nOptions.transSupportBasicHtmlNodes) {
if (node.voidElement) {
mem.push(_react.default.createElement(node.name, {
key: "".concat(node.name, "-").concat(i)
}));
} else {
var _inner2 = mapAST(reactNodes
/* wrong but we need something */
, node.children);
mem.push(_react.default.createElement(node.name, {
key: "".concat(node.name, "-").concat(i)
}, _inner2));
}
} else if ((0, _typeof2.default)(child) === 'object' && !isElement) {
var 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, (0, _objectSpread2.default)({}, child.props, {
key: i
}), translationContent));
} else {
mem.push(child);
}
} else if (node.type === 'text') {
mem.push(node.content);
}
return mem;
}, []);
} // call mapAST with having react nodes nested into additional node like
// we did for the string ast from translation
// return the children of that extra node to get expected result
var result = mapAST([{
dummy: true,
children: children
}], ast);
return getChildren(result[0]);
}
function Trans(_ref) {
var children = _ref.children,
count = _ref.count,
parent = _ref.parent,
i18nKey = _ref.i18nKey,
tOptions = _ref.tOptions,
values = _ref.values,
defaults = _ref.defaults,
components = _ref.components,
ns = _ref.ns,
i18nFromProps = _ref.i18n,
tFromProps = _ref.t,
additionalProps = (0, _objectWithoutProperties2.default)(_ref, ["children", "count", "parent", "i18nKey", "tOptions", "values", "defaults", "components", "ns", "i18n", "t"]);
var _ref2 = (0, _context.getHasUsedI18nextProvider)() ? (0, _react.useContext)(_context.I18nContext) : {},
i18nFromContext = _ref2.i18n;
var i18n = i18nFromProps || i18nFromContext || (0, _context.getI18n)();
if (!i18n) {
(0, _utils.warnOnce)('You will need pass in an i18next instance by using i18nextReactModule');
return children;
}
var t = tFromProps || i18n.t.bind(i18n);
var reactI18nextOptions = i18n.options && i18n.options.react || {};
var useAsParent = parent !== undefined ? parent : reactI18nextOptions.defaultTransParent;
var defaultValue = defaults || nodesToString('', children, 0, reactI18nextOptions) || reactI18nextOptions.transEmptyNodeValue;
var hashTransKey = reactI18nextOptions.hashTransKey;
var key = i18nKey || (hashTransKey ? hashTransKey(defaultValue) : defaultValue);
var interpolationOverride = values ? {} : {
interpolation: {
prefix: '#$?',
suffix: '?$#'
}
};
var translation = key ? t(key, (0, _objectSpread2.default)({}, tOptions, values, interpolationOverride, {
defaultValue: defaultValue,
count: count,
ns: ns
})) : defaultValue;
if (!useAsParent) return renderNodes(components || children, translation, i18n, reactI18nextOptions);
return _react.default.createElement(useAsParent, additionalProps, renderNodes(components || children, translation, i18n, reactI18nextOptions));
}