Upgrade Guide (v2 -> v3)
Breaking API Changes
addLocaleData
has been removed. See Migrate to using native Intl APIs for more details.ReactIntlLocaleData
has been removed. See Migrate to using native Intl APIs for more details.intlShape
has been removed. See TypeScript Support for more details.- Change default
textComponent
inIntlProvider
toReact.Fragment
. In order to keep the old behavior, you can explicitly settextComponent
tospan
.
<IntlProvider textComponent="span" />
FormattedRelative
has been renamed toFormattedRelativeTime
and its API has changed significantly. See FormattedRelativeTime for more details.formatRelative
has been renamed toformatRelativeTime
and its API has changed significantly. See FormattedRelativeTime for more details.- Message Format syntax changes. See Message Format Syntax Changes for more details.
IntlProvider
no longer inherits from upstreamIntlProvider
.
Use React 16.3 and upwards
React Intl v3 supports the new context API, fixing all kinds of tree update problems 🎉
In addition it makes use of the new lifecycle hooks (and gets rid of the deprecated ones).
It also supports the new React.forwardRef()
enabling users to directly access refs using the standard ref
prop (see beneath for further information).
Migrate withRef to forwardRef
With the update to React >= 16.3
we got the option to use the new React.forwardRef()
feature and because of this deprecated the use of the withRef
option for the injectIntl
HOC in favour of forwardRef
.
When forwardRef
is set to true, you can now simply pretend the HOC wasn't there at all.
Intl v2:
import React from 'react'
import {injectIntl} from 'react-intl'
class MyComponent extends React.Component {
doSomething = () => console.log(this.state || null)
render() {
return <div>Hello World</div>
}
}
export default injectIntl(MyComponent, {withRef: true})
// somewhere else
class Parent extends React.Component {
componentDidMount() {
this.myComponentRef.getWrappedInstance().doSomething()
}
render() {
return (
<MyComponent
ref={ref => {
this.myComponentRef = ref
}}
/>
)
}
}
Intl v3:
import React from 'react'
import {injectIntl} from 'react-intl'
class MyComponent extends React.Component {
doSomething = () => console.log(this.state || null)
render() {
return <div>Hello World</div>
}
}
export default injectIntl(MyComponent, {forwardRef: true})
// somewhere else
class Parent extends React.Component {
myComponentRef = React.createRef()
componentDidMount() {
this.myComponentRef.doSomething() // no need to call getWrappedInstance()
}
render() {
return <MyComponent ref={this.myComponentRef} />
}
}
New useIntl hook as an alternative of injectIntl HOC
This v3 release also supports the latest React hook API for user with React >= 16.8
. You can now take useIntl
hook as an alternative to injectIntl
HOC on function components. Both methods allow you to access the intl
instance, here is a quick comparison:
// injectIntl
import {injectIntl} from 'react-intl'
const MyComponentWithHOC = injectIntl(({intl, ...props}) => {
// do something
})
// useIntl
import {useIntl} from 'react-intl'
const MyComponentWithHook = props => {
const intl = useIntl()
// do something
}
To keep the API surface clean and simple, we only provide useIntl
hook in the package. If preferable, user can wrap this built-in hook to make customized hook like useFormatMessage
easily. Please visit React's official website for more general introduction on React hooks.
Migrate to using native Intl APIs
React Intl v3 no longer comes with CLDR data and rely on native Intl API instead. Specifically the new APIs we're relying on are:
- Intl.PluralRules: This can be polyfilled using this package.
- Intl.RelativeTimeFormat: This can be polyfilled using this package.
This shift is meant to future-proof React Intl as these APIs are all stable and being implemented in modern browsers. This also means we no longer package and consume CLDRs in this package.
If you previously were using addLocaleData
to support older browsers, we recommend you do the following:
- If you're supporting browsers that do not have Intl.PluralRules (e.g IE11 & Safari 12-), include this polyfill in your build.
- If you're supporting browsers that do not have Intl.RelativeTimeFormat (e.g IE11, Edge, Safari 13-), include this polyfill in your build along with individual CLDR data for each locale you support.
require('@formatjs/intl-pluralrules/polyfill')
require('@formatjs/intl-pluralrules/locale-data/de') // Add locale data for de
require('@formatjs/intl-relativetimeformat/polyfill')
require('@formatjs/intl-relativetimeformat/locale-data/de') // Add locale data for de
When using React Intl in Node.js, your node
binary has to either:
- Get compiled with
full-icu
using these instructions
OR
- Uses
full-icu
npm package
TypeScript Support
react-intl
has been rewritten in TypeScript and thus has native TypeScript support. Therefore, we've also removed prop-types
dependency and expose IntlShape
as an interface instead.
All types should be available from top level index
file without importing from specific subfiles. For example:
import {IntlShape} from 'react-intl' // Correct
import {IntlShape} from 'react-intl/lib/types' // Incorrect
If we're missing any interface top level support, please let us know and/or submitting a PR is greatly appreciated :)
You might need to make a few changes to your code if you were relying on the now deprecated @types/react-intl package. The most common example is InjectedIntlProps
which must be replaced with WrappedComponentProps
.
FormattedRelativeTime
When we introduced FormattedRelative
, the spec for Intl.RelativeTimeFormat
was still unstable. It has now reached stage 3 and multiple browsers have implemented it. However, its API is different from FormattedRelative
so we've adjusted its API to match the spec which means it's not backwards compatible.
- All
units
(such asday-short
) becomes a combination ofunit
&style
:
<FormattedRelative units="second-short"/>
// will be
<FormattedRelativeTime unit="second" style="short"/>
style
becomesnumeric
(which is the default):
<FormattedRelative style="numeric"/>
// will be
<FormattedRelativeTime />
<FormattedRelative style="best fit"/>
// will be
<FormattedRelativeTime numeric="auto"/>
- Type of
value
is no longerDate
, but ratherdelta
in the specifiedunit
:
<FormattedRelative value={Date.now() - 1000} units="second-narrow"/>
// will be
<FormattedRelativeTime value={-1} unit="second" style="narrow" />
<FormattedRelative value={Date.now() + 2000} units="second-narrow"/>
// will be
<FormattedRelativeTime value={2} unit="second" style="narrow" />
updateInterval
becomesupdateIntervalInSeconds
and will only take the time delta in seconds. Update behavior remains the same, e.g:
<FormattedRelativeTime
value={2}
numeric="auto"
unit="second"
style="narrow"
updateIntervalInSeconds={1}
/>
// Initially prints: `in 2s`
// 1 second later: `in 1s`
// 1 second later: `now`
// 1 second later: `1s ago`
// 60 seconds later: `1m ago`
initialNow
has been removed.
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.
- Implementing
FormattedRelative
behavior
You can use @formatjs/intl-utils
to get close to the previous behavior like this:
import {selectUnit} from '@formatjs/intl-utils'
const {value, unit} = selectUnit(Date.now() - 48 * 3600 * 1000)
// render
;<FormattedRelativeTime value={value} unit={unit} />
Enhanced FormattedMessage
& formatMessage
rich text formatting
In v2, in order to do rich text formatting (embedding a ReactElement
), you had to do this:
<FormattedMessage
defaultMessage="To buy a shoe, { link } and { cta }"
values={{
link: (
<a class="external_link" target="_blank" href="https://www.shoe.com/">
visit our website
</a>
),
cta: <strong class="important">eat a shoe</strong>,
}}
/>
Now you can do:
<FormattedMessage
defaultMessage="To buy a shoe, <a>visit our website</a> and <cta>eat a shoe</cta>"
values={{
a: msg => (
<a class="external_link" target="_blank" href="https://www.shoe.com/">
{msg}
</a>
),
cta: msg => <strong class="important">{msg}</strong>,
}}
/>
The change solves several issues:
- Contextual information was lost when you need to style part of the string: In this example above,
link
effectively is a blackbox placeholder to a translator. It can be a person, an animal, or a timestamp. Conveying contextual information viadescription
&placeholder
variable is often not enough since the variable can get sufficiently complicated. - This brings feature-parity with other translation libs, such as fluent by Mozilla (using Overlays).
If previously in cases where you pass in a ReactElement
to a placeholder we highly recommend that you rethink the structure so that as much text is declared as possible:
Before
<FormattedMessage
defaultMessage="Hello, {name} is {awesome} and {fun}"
values={{
name: <b>John</b>,
awesome: <span style="font-weight: bold;">awesome</span>
fun: <span>fun and <FormattedTime value={Date.now()}/></span>
}}
/>
After
<FormattedMessage
defaultMessage="Hello, <b>John</b> is <custom>awesome</custom> and <more>fun and {ts, time}</more>"
values={{
b: name => <b>{name}</b>,
custom: str => <span style="font-weight: bold;">{str}</span>,
more: chunks => <span>{chunks}</span>,
}}
/>
ESM Build
react-intl
and its underlying libraries (intl-messageformat-parser
, intl-messageformat
, @formatjs/intl-relativetimeformat
, intl-format-cache
, intl-utils
) export ESM artifacts. This means you should configure your build toolchain to transpile those libraries.
Jest
Add transformIgnorePatterns
to always include those libraries, e.g:
{
transformIgnorePatterns: [
'/node_modules/(?!intl-messageformat|intl-messageformat-parser).+\\.js$',
],
}
webpack
If you're using babel-loader
, add those libraries in include
, e.g:
include: [
path.join(__dirname, "node_modules/react-intl"),
path.join(__dirname, "node_modules/intl-messageformat"),
path.join(__dirname, "node_modules/intl-messageformat-parser"),
],
Creating intl without using Provider
We've added a new API called createIntl
that allows you to create an IntlShape
object without using Provider
. This allows you to format things outside of React lifecycle while reusing the same intl
object. For example:
import {createIntl, createIntlCache, RawIntlProvider} from 'react-intl'
// This is optional but highly recommended
// since it prevents memory leak
const cache = createIntlCache()
const intl = createIntl({
locale: 'fr-FR',
messages: {}
}, cache)
// Call imperatively
intl.formatNumber(20)
// Pass it to IntlProvider
<RawIntlProvider value={intl}>{foo}</RawIntlProvider>
This is especially beneficial in SSR where you can reuse the same intl
object across requests.
Message Format Syntax Changes
We've rewritten our parser to be more faithful to ICU Message Format, in order to potentially support skeleton. So far the backwards-incompatible changes are:
Escape character has been changed to apostrophe ('
).
Previously while we were using ICU message format syntax, our escape char was backslash (\
). This however creates issues with strict ICU translation vendors that support other implementations like ICU4J/ICU4C. Thanks to @pyrocat101 we've changed this behavior to be spec-compliant. This means:
// Before
<FormattedMessage defaultMessage="\\{foo\\}" /> //prints out "{foo}"
// After
<FormattedMessage defaultMessage="'{foo}'" /> //prints out "{foo}"
We highly recommend reading the spec to learn more about how quote/escaping works here under Quoting/Escaping section.
Placeholder argument syntax change
Placeholder argument can no longer have -
(e.g: this is a {placeholder-var}
is invalid but this is a {placeholder_var}
is).
Testing
We've removed IntlProvider.getChildContext
for testing and now you can use createIntl
to create a standalone intl
object outside of React and use that for testing purposes. See Testing with React Intl for more details.