diff --git a/README.md b/README.md
index 3e9d12af..97657f55 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,7 @@
- Display numbers, currency, dates and times for different locales.
- Pluralize labels in strings.
- Support variables in message.
+- Support React Elements as variables in message.
- Support HTML in message.
- Automatically load [Common Locale Data Repository (CLDR)](http://cldr.unicode.org/) locale data on demand. It's used for displaying numbers, currency, dates and times accordingly.
- Support for 150+ languages.
@@ -134,10 +135,10 @@ class App extends Component {
}
```
-
### HTML Message
-As shown in above example, the `get` method returns string message. For HTML message, use `getHTML` instead. For example,
-
+The `get` method returns string message. For HTML message, use `getHTML` instead.
+ Avoid using this in favor of React Element embedding, if at all possible. For example,
+
Locale data:
```json
{ "TIP": "This is HTML" }
@@ -178,6 +179,18 @@ JS code:
intl.get('HELLO', {name:'Tony', where:'Alibaba'}) // "Hello, Tony. Welcome to Alibaba!"
```
+### React Element in Message
+When you want to add rich text as a variable to a message, use the following method:
+
+Locale data:
+```json
+{ "hello": "Hello, {name}" }
+```
+
+JS Code:
+```js
+intl.get('hello', { name: Tony }); // ["Hello ", React.createElement('b', null, Tony)]
+```
### Plural Form and Number Thousands Separators
diff --git a/src/index.js b/src/index.js
index dbd50ffc..27c67945 100644
--- a/src/index.js
+++ b/src/index.js
@@ -29,7 +29,6 @@ const COMMON_LOCALE_DATA_URLS = {
tr: "https://g.alicdn.com/react-intl-universal/locale-data/1.0.0/tr.js",
};
-
const isBrowser = !isElectron() && !!(typeof window !== 'undefined' &&
window.document &&
window.document.createElement);
@@ -38,6 +37,8 @@ String.prototype.defaultMessage = String.prototype.d = function (msg) {
return this || msg || "";
};
+Array.prototype.d = Array.prototype.defaultMessage = String.prototype.defaultMessage;
+
class ReactIntlUniversal {
constructor() {
this.options = {
@@ -56,7 +57,7 @@ class ReactIntlUniversal {
* Get the formatted message by key
* @param {string} key The string representing key in locale data file
* @param {Object} variables Variables in message
- * @returns {string} message
+ * @returns {(string | T)[]} message
*/
get(key, variables) {
invariant(key, "key is required");
@@ -69,6 +70,7 @@ class ReactIntlUniversal {
return "";
}
let msg = this.getDescendantProp(locales[currentLocale], key);
+
if (msg == null) {
if (this.options.fallbackLocale) {
msg = this.getDescendantProp(locales[this.options.fallbackLocale], key);
@@ -85,8 +87,33 @@ class ReactIntlUniversal {
return "";
}
}
+
+ let tokenDelimiter;
+ let tokenizedValues;
+ let elements;
+
if (variables) {
variables = Object.assign({}, variables);
+ const uid = Math.floor(Math.random() * 0x10000000000).toString(16);
+ tokenDelimiter = `@__${uid}__@`;
+ tokenizedValues = {};
+ elements = {};
+
+ const generateToken = (() => {
+ let counter = 0;
+ return () => `ELEMENT-${uid}-${(counter += 1)}`;
+ })();
+
+ Object.keys(variables).forEach(name => {
+ const value = variables[name];
+ if (React.isValidElement(value)) {
+ const token = generateToken();
+ tokenizedValues[name] = tokenDelimiter + token + tokenDelimiter;
+ elements[token] = value;
+ } else {
+ tokenizedValues[name] = value;
+ }
+ });
// HTML message with variables. Escape it to avoid XSS attack.
for (let i in variables) {
let value = variables[i];
@@ -101,10 +128,17 @@ class ReactIntlUniversal {
variables[i] = value;
}
}
-
+ let hasElements = elements && Object.keys(elements).length > 0;
try {
const msgFormatter = new IntlMessageFormat(msg, currentLocale, formats);
- return msgFormatter.format(variables);
+ const finalMessage = msgFormatter.format(tokenizedValues || variables);
+ if (hasElements) {
+ return finalMessage
+ .split(tokenDelimiter)
+ .filter(part => !!part)
+ .map(part => elements[part] || part);
+ }
+ return finalMessage;
} catch (err) {
this.options.warningHandler(
`react-intl-universal format message failed for key='${key}'.`,
diff --git a/test/index.js b/test/index.js
index 41f46772..87eda849 100644
--- a/test/index.js
+++ b/test/index.js
@@ -45,6 +45,17 @@ test("react-intl mirror API formatMessage:variables", () => {
).toBe(intl.get("HELLO", { name }));
});
+test("react-intl mirror API formatMessage:variables with React Elements", () => {
+ intl.init({ locales, currentLocale: "en-US" });
+ const name = React.createElement('b', null, "Tony");
+ const answer = intl.formatMessage(
+ { id: "HELLO", defaultMessage: `Hello, {name}` },
+ { name }
+ );
+ expect(answer[0]).toBe("Hello, ");
+ expect(answer[1].props.children).toBe('Tony');
+});
+
test("react-intl mirror API formatMessage:defaultMessage", () => {
intl.init({ locales, currentLocale: "en-US" });
expect(intl.formatMessage({ id: "not-exist-key" })).toBe(
@@ -305,3 +316,20 @@ test("Uses default message if key not found in fallbackLocale", () => {
expect(intl.get("not-exist-key").defaultMessage("this is default msg")).toBe("this is default msg");
});
+test("get with React Element as variable", () => {
+ intl.init({ locales, currentLocale: "en-US" });
+ const name = React.createElement('b', null, "Tony");
+ const answer = intl.get("HELLO",{ name });
+ expect(answer[0]).toBe("Hello, ");
+ expect(answer[1].props.children).toBe('Tony');
+});
+
+test("get with React element and normal variable", () => {
+ intl.init({ locales, currentLocale: "en-US" });
+ const name = React.createElement('b', null, "Tony");
+ const year = new Date().getFullYear();
+ const answer = intl.get("HELLO_ADV",{ name, year });
+ expect(answer[0]).toBe("Hello, ");
+ expect(answer[1].props.children).toBe('Tony');
+ expect(answer[2]).toBe(`. The year is ${year}.`);
+});
\ No newline at end of file
diff --git a/test/locales/en-US.js b/test/locales/en-US.js
index 623beee6..a8dab7ed 100644
--- a/test/locales/en-US.js
+++ b/test/locales/en-US.js
@@ -1,6 +1,7 @@
module.exports = ({
"SIMPLE": "Simple",
"HELLO": "Hello, {name}",
+ "HELLO_ADV": "Hello, {name}. The year is {year}.",
"TIP": "This is HTML",
"TIP_VAR": "This is{message}",
"SALE_START": "Sale begins {start, date}",
diff --git a/typings/index.d.ts b/typings/index.d.ts
index b244d061..262f5f31 100644
--- a/typings/index.d.ts
+++ b/typings/index.d.ts
@@ -11,22 +11,22 @@ declare module "react-intl-universal" {
/**
* Provide React-Intl compatibility, same as getHTML(...) API.
*/
- export function formatHTMLMessage(messageDescriptor: ReactIntlUniversalMessageDescriptor): string;
+ export function formatHTMLMessage(messageDescriptor: ReactIntlUniversalMessageDescriptor): string | [string | JSX.Element];
/**
* Provide React-Intl compatibility, same as getHTML(...) API.
*/
- export function formatHTMLMessage(messageDescriptor: ReactIntlUniversalMessageDescriptor, variables: any): string;
+ export function formatHTMLMessage(messageDescriptor: ReactIntlUniversalMessageDescriptor, variables: any): string | [string | JSX.Element];
/**
* Provide React-Intl compatibility, same as get(...) API.
*/
- export function formatMessage(messageDescriptor: ReactIntlUniversalMessageDescriptor): string;
+ export function formatMessage(messageDescriptor: ReactIntlUniversalMessageDescriptor): string | [string | JSX.Element];
/**
* Provide React-Intl compatibility, same as get(...) API.
*/
- export function formatMessage(messageDescriptor: ReactIntlUniversalMessageDescriptor, variables: any): string;
+ export function formatMessage(messageDescriptor: ReactIntlUniversalMessageDescriptor, variables: any): string | [string | JSX.Element];
/**
* Get the formatted message by key
@@ -41,7 +41,7 @@ declare module "react-intl-universal" {
* @param {Object} variables Variables in message
* @returns {string} message
*/
- export function get(key: string, value: any): string;
+ export function get(key: string, value: any): string | [string | JSX.Element];
/**
* Get the formatted html message by key.
@@ -99,6 +99,11 @@ declare module "react-intl-universal" {
}
declare interface String {
- defaultMessage(msg: string | JSX.Element): string;
- d(msg: string | JSX.Element): string;
+ defaultMessage(msg: string | JSX.Element): string;
+ d(msg: string | JSX.Element): string;
+}
+
+declare interface Array {
+ defaultMessage(msg: string | JSX.Element): string;
+ d(msg: string | JSX.Element): string;
}