// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import React, {createElement, isValidElement} from 'react'; import PropTypes from 'prop-types'; import {Text} from 'react-native'; import {intlShape} from 'react-intl'; export default class FormattedText extends React.PureComponent { static propTypes = { id: PropTypes.string.isRequired, defaultMessage: PropTypes.string, values: PropTypes.object, }; static defaultProps = { defaultMessage: '', }; static contextTypes = { intl: intlShape.isRequired, }; render() { const { id, defaultMessage, values, ...props } = this.props; const {formatMessage} = this.context.intl; let tokenDelimiter; let tokenizedValues; let elements; const hasValues = values && Object.keys(values).length > 0; if (hasValues) { // Creates a token with a random UID that should not be guessable or // conflict with other parts of the `message` string. const uid = Math.floor(Math.random() * 0x10000000000).toString(16); const generateToken = (() => { let counter = 0; return () => { const elementId = `ELEMENT-${uid}-${counter += 1}`; return elementId; }; })(); // Splitting with a delimiter to support IE8. When using a regex // with a capture group IE8 does not include the capture group in // the resulting array. tokenDelimiter = `@__${uid}__@`; tokenizedValues = {}; elements = {}; // Iterates over the `props` to keep track of any React Element // values so they can be represented by the `token` as a placeholder // when the `message` is formatted. This allows the formatted // message to then be broken-up into parts with references to the // React Elements inserted back in. Object.keys(values).forEach((name) => { const value = values[name]; if (isValidElement(value)) { const token = generateToken(); tokenizedValues[name] = tokenDelimiter + token + tokenDelimiter; elements[token] = value; } else { tokenizedValues[name] = value; } }); } const descriptor = {id, defaultMessage}; const formattedMessage = formatMessage(descriptor, tokenizedValues || values); const hasElements = elements && Object.keys(elements).length > 0; let nodes; if (hasElements) { // Split the message into parts so the React Element values captured // above can be inserted back into the rendered message. This // approach allows messages to render with React Elements while // keeping React's virtual diffing working properly. nodes = formattedMessage. split(tokenDelimiter). filter((part) => Boolean(part)). map((part) => elements[part] || part); } else { nodes = [formattedMessage]; } return createElement(Text, props, ...nodes); } }