Aller au contenu principal

Guide de mise à jour (v2 -> v3)

Traduction Bêta Non Officielle

Cette page a été traduite par PageTurner AI (bêta). Non approuvée officiellement par le projet. Vous avez trouvé une erreur ? Signaler un problème →

Changements cassants de l'API

  • addLocaleData a été supprimé. Voir Migrer vers les APIs Intl natives pour plus de détails.

  • ReactIntlLocaleData a été supprimé. Voir Migrer vers les APIs Intl natives pour plus de détails.

  • intlShape a été supprimé. Voir Support TypeScript pour plus de détails.

  • La valeur par défaut de textComponent dans IntlProvider devient React.Fragment. Pour conserver l'ancien comportement, définissez explicitement textComponent sur span.

<IntlProvider textComponent="span" />
  • FormattedRelative a été renommé en FormattedRelativeTime et son API a significativement changé. Voir FormattedRelativeTime pour plus de détails.

  • formatRelative a été renommé en formatRelativeTime et son API a significativement changé. Voir FormattedRelativeTime pour plus de détails.

  • Changements de syntaxe du format de message. Voir Changements de syntaxe du format de message pour plus de détails.

  • IntlProvider n'hérite plus du IntlProvider parent.

Utiliser React 16.3 et supérieur

React Intl v3 prend en charge la nouvelle API de contexte, résolvant divers problèmes de mise à jour d'arbre 🎉 De plus, il utilise les nouveaux hooks de cycle de vie (et supprime ceux dépréciés). Il prend également en charge React.forwardRef(), permettant d'accéder directement aux refs via la prop standard ref (voir ci-dessous pour plus d'informations).

Migrer de withRef vers forwardRef

Avec la mise à jour vers React >= 16.3, nous pouvons utiliser React.forwardRef(). Par conséquent, l'option withRef du HOC injectIntl est dépréciée au profit de forwardRef. Lorsque forwardRef est défini sur true, vous pouvez désormais ignorer complètement la présence du HOC.

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} />
}
}

Nouveau hook useIntl comme alternative au HOC injectIntl

Cette version v3 prend également en charge la dernière API de hooks React pour React >= 16.8. Le hook useIntl constitue désormais une alternative au HOC injectIntl dans les composants fonctionnels. Les deux méthodes permettent d'accéder à l'instance intl, voici une comparaison rapide :

// 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
}

Pour conserver une API simple et épurée, nous fournissons uniquement le hook useIntl. Si préféré, les utilisateurs peuvent encapsuler ce hook pour créer des hooks personnalisés comme useFormatMessage. Consultez le site officiel de React pour une introduction générale aux hooks React.

Migrer vers les APIs Intl natives

React Intl v3 n'inclut plus de données CLDR et s'appuie désormais sur l'API Intl native. Plus précisément, les nouvelles API utilisées sont :

Ce changement vise à pérenniser React Intl car ces API sont stables et implémentées dans les navigateurs modernes. Cela signifie également que nous n'incluons plus les CLDR dans ce package.

Si vous utilisiez auparavant addLocaleData pour la prise en charge de navigateurs anciens, nous vous recommandons de procéder comme suit :

  1. Si vous prenez en charge des navigateurs sans Intl.PluralRules (ex. IE11 & Safari 12-), incluez ce polyfill dans votre build.

  2. Si vous prenez en charge des navigateurs sans Intl.RelativeTimeFormat (ex. IE11, Edge, Safari 13-), incluez ce polyfill dans votre build ainsi que les données CLDR individuelles pour chaque langue prise en charge.

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

Lorsque vous utilisez React Intl dans Node.js, votre binaire node doit soit :

OU

Prise en charge de TypeScript

react-intl a été réécrit en TypeScript et bénéficie donc d'une prise en charge native. Nous avons également supprimé la dépendance prop-types et exposons désormais IntlShape sous forme d'interface.

Tous les types doivent être accessibles depuis le fichier index racine sans import depuis des sous-fichiers spécifiques. Par exemple :

import {IntlShape} from 'react-intl' // Correct
import {IntlShape} from 'react-intl/lib/types' // Incorrect

Si une interface manque au niveau racine, merci de nous le signaler et/ou soumettre une PR serait grandement apprécié :)

Info

Vous devrez peut-être modifier votre code si vous utilisiez le package désormais déprécié @types/react-intl. L'exemple le plus courant est InjectedIntlProps qui doit être remplacé par WrappedComponentProps.

FormattedRelativeTime

Lors de l'introduction de FormattedRelative, la spécification Intl.RelativeTimeFormat était encore instable. Elle a maintenant atteint le stade 3 et plusieurs navigateurs l'implémentent. Son API différant de FormattedRelative, nous l'avons ajustée pour respecter la spécification, ce qui la rend non rétrocompatible.

  1. Toutes les units (ex. day-short) deviennent une combinaison de unit et style :
<FormattedRelative units="second-short"/>
// will be
<FormattedRelativeTime unit="second" style="short"/>
  1. style devient numeric (valeur par défaut) :
<FormattedRelative style="numeric"/>
// will be
<FormattedRelativeTime />

<FormattedRelative style="best fit"/>
// will be
<FormattedRelativeTime numeric="auto"/>
  1. Le type de value n'est plus Date mais un delta dans l'unit spécifiée :
<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" />
  1. updateInterval devient updateIntervalInSeconds et n'accepte que le delta temporel en secondes. Le comportement de mise à jour reste identique, ex :
<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`
  1. initialNow a été supprimé.

De même, la contrepartie fonctionnelle formatRelative a été renommée formatRelativeTime et ses paramètres ont été adaptés pour refléter les props de ce composant.

  1. Implémentation du comportement FormattedRelative

Vous pouvez utiliser @formatjs/intl-utils pour reproduire l'ancien comportement ainsi :

import {selectUnit} from '@formatjs/intl-utils'
const {value, unit} = selectUnit(Date.now() - 48 * 3600 * 1000)
// render
;<FormattedRelativeTime value={value} unit={unit} />

Formatage de texte enrichi amélioré pour FormattedMessage et formatMessage

Dans la v2, le formatage de texte enrichi (intégration d'un ReactElement) nécessitait :

<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>,
}}
/>

Désormais :

<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>,
}}
/>

Ce changement résout plusieurs problèmes :

  1. Perte d'informations contextuelles lors du style : dans l'exemple ci-dessus, link est une boîte noire pour le traducteur (personne, animal, horodatage). Transmettre le contexte via description et placeholder s'avère souvent insuffisant pour des variables complexes.

  2. Harmonisation avec d'autres bibliothèques comme fluent de Mozilla (via les Overlays).

Dans les cas où vous passiez précédemment un ReactElement comme placeholder, nous recommandons vivement de repenser la structure pour déclarer autant de texte que possible :

Avant

<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>
}}
/>

Après

<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>,
}}
/>

Build ESM

react-intl et ses bibliothèques sous-jacentes (intl-messageformat-parser, intl-messageformat, @formatjs/intl-relativetimeformat, intl-format-cache, intl-utils) exportent désormais des artefacts ESM. Vous devez donc configurer votre chaîne de build pour transpiler ces bibliothèques.

Jest

Ajoutez transformIgnorePatterns pour inclure systématiquement ces bibliothèques, par exemple :

{
transformIgnorePatterns: [
'/node_modules/(?!intl-messageformat|intl-messageformat-parser).+\\.js$',
],
}

webpack

Si vous utilisez babel-loader, ajoutez ces bibliothèques dans include, par exemple :

include: [
path.join(__dirname, "node_modules/react-intl"),
path.join(__dirname, "node_modules/intl-messageformat"),
path.join(__dirname, "node_modules/intl-messageformat-parser"),
],

Créer intl sans utiliser Provider

Nous avons ajouté une nouvelle API appelée createIntl qui permet de créer un objet IntlShape sans utiliser Provider. Cela vous permet de formater des éléments en dehors du cycle de vie React tout en réutilisant le même objet intl. Exemple :

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>

Ceci est particulièrement bénéfique en SSR où vous pouvez réutiliser le même objet intl entre les requêtes.

Changements de syntaxe du format de message

Nous avons réécrit notre parser pour mieux respecter le format ICU Message, notamment pour supporter potentiellement les squelettes. Les changements rétro-incompatibles sont :

Le caractère d'échappement est désormais l'apostrophe (').

Précédemment, nous utilisions le backslash (\) comme caractère d'échappement, ce qui posait des problèmes de compatibilité avec les outils de traduction stricts supportant d'autres implémentations comme ICU4J/ICU4C. Grâce à @pyrocat101, ce comportement est désormais conforme à la spécification. Cela signifie :

// Before
<FormattedMessage defaultMessage="\\{foo\\}" /> //prints out "{foo}"

// After
<FormattedMessage defaultMessage="'{foo}'" /> //prints out "{foo}"

Nous recommandons vivement de consulter la spécification sur les mécanismes de citation/échappement ici dans la section Quoting/Escaping.

Changement de syntaxe des arguments de placeholder

Les arguments de placeholder ne peuvent plus contenir de - (ex : this is a {placeholder-var} est invalide mais this is a {placeholder_var} est valide).

Tests

Nous avons supprimé IntlProvider.getChildContext pour les tests. Utilisez désormais createIntl pour créer un objet intl autonome en dehors de React à des fins de test. Voir Testing with React Intl pour plus de détails.