Vai al contenuto principale

Guida all'aggiornamento (v2 -> v3)

Traduzione Beta Non Ufficiale

Questa pagina è stata tradotta da PageTurner AI (beta). Non ufficialmente approvata dal progetto. Hai trovato un errore? Segnala problema →

Modifiche di Rottura nelle API

  • addLocaleData è stato rimosso. Consulta Migrazione alle API Intl native per maggiori dettagli.

  • ReactIntlLocaleData è stato rimosso. Consulta Migrazione alle API Intl native per maggiori dettagli.

  • intlShape è stato rimosso. Consulta Supporto TypeScript per maggiori dettagli.

  • Il componente predefinito textComponent in IntlProvider è ora React.Fragment. Per mantenere il comportamento precedente, imposta esplicitamente textComponent su span.

<IntlProvider textComponent="span" />
  • FormattedRelative è stato rinominato FormattedRelativeTime con modifiche significative all'API. Consulta FormattedRelativeTime per dettagli.

  • formatRelative è stato rinominato formatRelativeTime con modifiche significative all'API. Consulta FormattedRelativeTime per dettagli.

  • Modifiche alla sintassi del Message Format. Consulta Modifiche alla sintassi del Message Format per dettagli.

  • IntlProvider non eredita più dal IntlProvider superiore.

Utilizza React 16.3 e versioni successive

React Intl v3 supporta la nuova Context API, risolvendo vari problemi di aggiornamento dell'albero 🎉 Inoltre utilizza i nuovi lifecycle hook (rimuovendo quelli deprecati). Supporta anche React.forwardRef(), consentendo l'accesso diretto ai ref tramite la prop standard ref (vedi sotto per dettagli).

Migra da withRef a forwardRef

Con l'aggiornamento a React >= 16.3 abbiamo introdotto React.forwardRef(), deprecando l'opzione withRef per l'HOC injectIntl in favore di forwardRef. Quando forwardRef è impostato su true, puoi ora ignorare completamente la presenza dell'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} />
}
}

Nuovo hook useIntl come alternativa a injectIntl HOC

Questa versione v3 supporta le React hook API per React >= 16.8. Puoi utilizzare l'hook useIntl come alternativa all'HOC injectIntl nei componenti funzionali. Entrambi i metodi consentono l'accesso all'istanza intl. Ecco un confronto rapido:

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

Per mantenere l'API semplice e pulita, forniamo solo l'hook useIntl. Se preferito, puoi estenderlo per creare hook personalizzati come useFormatMessage. Consulta il sito ufficiale di React per un'introduzione generale agli hook.

Migrazione alle API Intl native

React Intl v3 non include più dati CLDR, basandosi invece sulle API Intl native. Nello specifico:

Questo cambiamento rende React Intl più futuro-proof, poiché queste API sono stabili e implementate nei browser moderni. Ciò significa anche che non includiamo più dati CLDR nel pacchetto.

Se in precedenza utilizzavi addLocaleData per supportare browser obsoleti, ti consigliamo di procedere come segue:

  1. Se supporti browser privi di Intl.PluralRules (es. IE11 e Safari 12-), includi questo polyfill nel tuo build.

  2. Se supporti browser privi di Intl.RelativeTimeFormat (es. IE11, Edge, Safari 13-), includi questo polyfill nel tuo build insieme ai dati CLDR specifici per ogni lingua supportata.

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

Quando utilizzi React Intl in Node.js, il tuo binario node deve:

  • Essere compilato con full-icu seguendo queste istruzioni

OPPURE

Supporto TypeScript

react-intl è stato riscritto in TypeScript e offre quindi supporto nativo. Abbiamo inoltre rimosso la dipendenza prop-types ed esposto IntlShape come interfaccia.

Tutti i tipi dovrebbero essere disponibili dal file index principale senza importare da sottocartelle specifiche. Esempio:

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

Se manca il supporto per qualche interfaccia a livello principale, segnalacelo o invia una PR: sarà molto apprezzato :)

informazioni

Potrebbero servire modifiche al codice se facevi affidamento sul deprecato pacchetto @types/react-intl. L'esempio più comune è InjectedIntlProps, da sostituire con WrappedComponentProps.

FormattedRelativeTime

Quando introdussimo FormattedRelative, la specifica di Intl.RelativeTimeFormat era instabile. Ora è allo stage 3 e implementata in vari browser. L'API differisce però da FormattedRelative, quindi l'abbiamo adeguata alla specifica rompendo la retrocompatibilità.

  1. Le units (es. day-short) diventano combinazioni di unit e style:
<FormattedRelative units="second-short"/>
// will be
<FormattedRelativeTime unit="second" style="short"/>
  1. Lo style diventa numeric (valore predefinito):
<FormattedRelative style="numeric"/>
// will be
<FormattedRelativeTime />

<FormattedRelative style="best fit"/>
// will be
<FormattedRelativeTime numeric="auto"/>
  1. Il tipo di value non è più Date, ma il delta nell'unit specificata:
<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 diventa updateIntervalInSeconds accettando solo delta temporali in secondi. Il comportamento d'aggiornamento resta invariato, es:
<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 è stato rimosso.

Analogamente, la controparte funzionale formatRelative è stata rinominata formatRelativeTime con parametri allineati alle props del componente.

  1. Implementare il comportamento di FormattedRelative

Puoi usare @formatjs/intl-utils per replicare parzialmente il vecchio comportamento:

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

Formattazione avanzata per FormattedMessage & formatMessage

Nella v2, per la formattazione avanzata (incorporare un ReactElement) occorreva:

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

Ora puoi fare:

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

Questa modifica risolve diversi problemi:

  1. Perdita di informazioni contestuali durante lo styling di parti della stringa: nell'esempio precedente, link è di fatto un placeholder a scatola nera per il traduttore. Può rappresentare una persona, un animale o un timestamp. Trasmettere informazioni contestuali tramite le variabili description e placeholder spesso non è sufficiente poiché la variabile può diventare sufficientemente complessa.

  2. Allinea le funzionalità a librerie come fluent di Mozilla (usando Overlays).

Nei casi in cui in precedenza veniva passato un ReactElement come segnaposto, consigliamo vivamente di ripensare la struttura in modo che la maggior parte possibile del testo sia dichiarato esplicitamente:

Prima

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

Dopo

<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 e le librerie sottostanti (intl-messageformat-parser, intl-messageformat, @formatjs/intl-relativetimeformat, intl-format-cache, intl-utils) esportano artefatti ESM. Ciò significa che dovresti configurare la tua toolchain di build per transpilare queste librerie.

Jest

Aggiungi transformIgnorePatterns per includere sempre queste librerie, ad esempio:

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

webpack

Se utilizzi babel-loader, aggiungi queste librerie in include, ad esempio:

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

Creazione di intl senza utilizzare Provider

Abbiamo aggiunto una nuova API chiamata createIntl che consente di creare un oggetto IntlShape senza utilizzare Provider. Ciò permette di formattare contenuti al di fuori del ciclo di vita di React riutilizzando lo stesso oggetto intl. Esempio:

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>

Questo è particolarmente vantaggioso in SSR dove puoi riutilizzare lo stesso oggetto intl tra diverse richieste.

Modifiche alla sintassi del formato messaggio

Abbiamo riscritto il nostro parser per essere più fedele allo ICU Message Format, per supportare potenzialmente gli skeleton. Le modifiche non retrocompatibili finora sono:

Il carattere di escape è stato cambiato in apostrofo (')

In precedenza utilizzavamo il backslash (\) come carattere di escape nella sintassi ICU, ma ciò creava problemi con i sistemi di traduzione che supportano altre implementazioni come ICU4J/ICU4C. Grazie a @pyrocat101 abbiamo modificato questo comportamento per essere conforme alle specifiche. Ciò significa:

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

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

Consigliamo vivamente di leggere le specifiche per approfondire il funzionamento di quoting/escaping qui nella sezione Quoting/Escaping.

Modifica della sintassi degli argomenti segnaposto

Gli argomenti segnaposto non possono più contenere - (es: this is a {placeholder-var} è invalido mentre this is a {placeholder_var} è valido).

Test

Abbiamo rimosso IntlProvider.getChildContext per i test. Ora puoi usare createIntl per creare un oggetto intl autonomo al di fuori di React e utilizzarlo per scopi di testing. Consulta Test con React Intl per maggiori dettagli.