Skip to content

Commit 15ed625

Browse files
authored
feat: make formatMessage take in ReactElement (#1367)
Allow using `formatMessage` with `ReactElement` imperatively, e.g: ```tsx formatMessage('hello {world}', {world: <b>world</b>) // returns ['hello ', <b>world</b>] ``` New signature will be: ```ts formatMessage(msg: string, values: Record<string, string | number | null | undefined | React.ReactNode>): string[] | React.ReactNodeArray ``` Use case: - Right now the only way to format rich text is to use `FormattedMessage`, this brings feature-parity to the imperative version `formatMessage` as well. - `formatMessage` is also faster and doesn't create extra React Node. - Also remove the need to regen UUID for every rich text token in `FormattedMessage`, which improves perf as well Before: ``` 100 x <FormattedMessage> with placeholder x 76.83 ops/sec ±9.83% (69 runs sampled) 100 x <FormattedMessage> with placeholder in AST form x 206 ops/sec ±1.12% (79 runs sampled) 100 x <FormattedMessage> with placeholder, cached x 573 ops/sec ±1.07% (84 runs sampled) 100 x <FormattedMessage> with placeholder, cached in AST form x 392 ops/sec ±4.28% (82 runs sampled) ``` After: ``` 100 x <FormattedMessage> with placeholder x 85.51 ops/sec ±3.66% (66 runs sampled) 100 x <FormattedMessage> with placeholder in AST form x 210 ops/sec ±2.27% (76 runs sampled) 100 x <FormattedMessage> with placeholder, cached x 619 ops/sec ±2.00% (87 runs sampled) 100 x <FormattedMessage> with placeholder, cached in AST form x 426 ops/sec ±1.61% (84 runs sampled) ```
1 parent 20d39e6 commit 15ed625

File tree

10 files changed

+228
-170
lines changed

10 files changed

+228
-170
lines changed

docs/API.md

+22-3
Original file line numberDiff line numberDiff line change
@@ -416,11 +416,16 @@ Above, "source" refers to using the template as is, without any substitutions ma
416416

417417
#### `formatMessage`
418418

419-
```js
419+
```ts
420+
type MessageFormatPrimitiveValue = string | number | boolean | null | undefined;
420421
function formatMessage(
421-
messageDescriptor: MessageDescriptor,
422-
values?: object
422+
descriptor: MessageDescriptor,
423+
values?: Record<string, MessageFormatPrimitiveValue>
423424
): string;
425+
function formatMessage(
426+
descriptor: MessageDescriptor,
427+
values?: Record<string, MessageFormatPrimitiveValue | React.ReactElement>
428+
): string | React.ReactNodeArray;
424429
```
425430

426431
This function will return a formatted message string. It expects a `MessageDescriptor` with at least an `id` property, and accepts a shallow `values` object which are used to fill placeholders in the message.
@@ -439,6 +444,20 @@ const messages = defineMessages({
439444
formatMessage(messages.greeting, {name: 'Eric'}); // "Hello, Eric!"
440445
```
441446

447+
with `ReactElement`
448+
449+
```tsx
450+
const messages = defineMessages({
451+
greeting: {
452+
id: 'app.greeting',
453+
defaultMessage: 'Hello, {name}!',
454+
description: 'Greeting to welcome the user to the app',
455+
},
456+
});
457+
458+
formatMessage(messages.greeting, {name: <b>Eric</b>}); // ['Hello, ', <b>Eric</b>, '!']
459+
```
460+
442461
The message we defined using [`defineMessages`](#definemessages) to support extraction via `babel-plugin-react-intl`, but it doesn't have to be if you're not using the Babel plugin.
443462

444463
**Note:** Messages can be simple strings _without_ placeholders, and that's the most common type of message.

docs/Upgrade-Guide.md

+29
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
- [Migrate to using native Intl APIs](#migrate-to-using-native-intl-apis)
1111
- [TypeScript Support](#typescript-support)
1212
- [FormattedRelativeTime](#formattedrelativetime)
13+
- [`formatMessage` now supports `ReactElement`](#formatmessage-now-supports-reactelement)
1314
- [2.0.0](#200)
1415
- [Use React 0.14 or 15](#use-react-014-or-15)
1516
- [Update How Locale Data is Added](#update-how-locale-data-is-added)
@@ -240,6 +241,34 @@ When we introduced `FormattedRelative`, the spec for [`Intl.RelativeTimeFormat`]
240241

241242
Similarly, the functional counterpart of this component which is `formatRelative` has been renamed to `formatRelativeTime` and its parameters have been changed to reflect this component's props accordingly.
242243

244+
### `formatMessage` now supports `ReactElement`
245+
246+
The imperative API `formatMessage` now supports `ReactElement` in values and will resolve type correctly. This change should be backwards-compatible since for regular non-`ReactElement` values it will still return a `string`, but for rich text like the example down below, it will return a `Array<string, React.ReactElement>`:
247+
248+
```ts
249+
const messages = defineMessages({
250+
greeting: {
251+
id: 'app.greeting',
252+
defaultMessage: 'Hello, {name}!',
253+
description: 'Greeting to welcome the user to the app',
254+
},
255+
});
256+
257+
formatMessage(messages.greeting, {name: 'Eric'}); // "Hello, Eric!"
258+
```
259+
260+
```tsx
261+
const messages = defineMessages({
262+
greeting: {
263+
id: 'app.greeting',
264+
defaultMessage: 'Hello, {name}!',
265+
description: 'Greeting to welcome the user to the app',
266+
},
267+
});
268+
269+
formatMessage(messages.greeting, {name: <b>Eric</b>}); // ['Hello, ', <b>Eric</b>, '!']
270+
```
271+
243272
## 2.0.0
244273

245274
- [Use React 0.14 or 15](#use-react-014-or-15)

package-lock.json

+63-63
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+12-12
Original file line numberDiff line numberDiff line change
@@ -36,38 +36,38 @@
3636
"react": "global:React"
3737
},
3838
"dependencies": {
39-
"@formatjs/intl-relativetimeformat": "^2.3.4",
39+
"@formatjs/intl-relativetimeformat": "^2.4.0",
4040
"@types/hoist-non-react-statics": "^3.3.1",
4141
"@types/invariant": "^2.2.30",
4242
"@types/react": "^16.8.23",
4343
"hoist-non-react-statics": "^3.3.0",
44-
"intl-format-cache": "^4.0.1",
45-
"intl-locales-supported": "^1.3.4",
46-
"intl-messageformat": "^5.0.1",
47-
"intl-messageformat-parser": "^2.0.1",
44+
"intl-format-cache": "^4.1.0",
45+
"intl-locales-supported": "^1.4.0",
46+
"intl-messageformat": "^5.1.0",
47+
"intl-messageformat-parser": "^2.1.0",
4848
"invariant": "^2.1.1",
4949
"react": "^16.3.0",
5050
"shallow-equal": "^1.1.0"
5151
},
5252
"devDependencies": {
5353
"@babel/cli": "^7.5.0",
54-
"@babel/core": "^7.5.0",
54+
"@babel/core": "^7.5.4",
5555
"@babel/node": "^7.5.0",
5656
"@babel/plugin-proposal-class-properties": "^7.5.0",
57-
"@babel/plugin-proposal-object-rest-spread": "^7.5.3",
57+
"@babel/plugin-proposal-object-rest-spread": "^7.5.4",
5858
"@babel/plugin-transform-async-to-generator": "^7.5.0",
5959
"@babel/plugin-transform-modules-commonjs": "^7.5.0",
60-
"@babel/preset-env": "^7.5.3",
60+
"@babel/preset-env": "^7.5.4",
6161
"@babel/preset-react": "^7.0.0",
6262
"@types/benchmark": "^1.0.31",
63-
"@types/enzyme": "^3.10.1",
63+
"@types/enzyme": "^3.10.2",
6464
"@types/jest": "^24.0.13",
6565
"@types/prop-types": "^15.7.1",
6666
"@types/react-dom": "^16.8.4",
67-
"@typescript-eslint/eslint-plugin": "^1.10.2",
68-
"@typescript-eslint/parser": "^1.10.2",
67+
"@typescript-eslint/eslint-plugin": "^1.12.0",
68+
"@typescript-eslint/parser": "^1.12.0",
6969
"babel-jest": "^24.8.0",
70-
"babel-plugin-react-intl": "^4.0.1",
70+
"babel-plugin-react-intl": "^4.1.0",
7171
"babel-plugin-transform-member-expression-literals": "^6.9.4",
7272
"babel-plugin-transform-property-literals": "^6.9.4",
7373
"babel-plugin-transform-react-remove-prop-types": "^0.4.18",

0 commit comments

Comments
 (0)