Zum Hauptinhalt springen

Upgrade-Leitfaden (v2 -> v3)

Inoffizielle Beta-Übersetzung

Diese Seite wurde von PageTurner AI übersetzt (Beta). Nicht offiziell vom Projekt unterstützt. Fehler gefunden? Problem melden →

Breaking API-Änderungen

<IntlProvider textComponent="span" />
  • FormattedRelative wurde in FormattedRelativeTime umbenannt und seine API hat sich erheblich geändert. Weitere Details finden Sie unter FormattedRelativeTime.

  • formatRelative wurde in formatRelativeTime umbenannt und seine API hat sich erheblich geändert. Weitere Details finden Sie unter FormattedRelativeTime.

  • Änderungen an der Message-Format-Syntax. Weitere Details finden Sie unter Message Format Syntax Changes.

  • IntlProvider erbt nicht mehr vom übergeordneten IntlProvider.

Verwenden Sie React 16.3 oder höher

React Intl v3 unterstützt die neue Context-API, wodurch verschiedene Probleme bei Baumaktualisierungen behoben werden 🎉 Zudem nutzt es die neuen Lifecycle-Hooks und entfernt die veralteten. Es unterstützt auch React.forwardRef(), wodurch Benutzer direkt über die Standard-ref-Prop auf Refs zugreifen können (weitere Informationen siehe unten).

Migration von withRef zu forwardRef

Mit dem Update auf React >= 16.3 können wir die neue React.forwardRef()-Funktion nutzen und haben daher die withRef-Option für das injectIntl-HOC zugunsten von forwardRef als veraltet markiert. Wenn forwardRef auf true gesetzt ist, können Sie das HOC jetzt einfach ignorieren.

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

Neuer useIntl-Hook als Alternative zum injectIntl-HOC

Dieses v3-Release unterstützt auch die neueste React-Hook-API für Benutzer mit React >= 16.8. Sie können jetzt den useIntl-Hook als Alternative zum injectIntl-HOC in Funktionskomponenten verwenden. Beide Methoden ermöglichen den Zugriff auf die intl-Instanz. Hier ist ein kurzer Vergleich:

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

Um die API-Oberfläche sauber und einfach zu halten, bieten wir nur den useIntl-Hook im Paket an. Bei Bedarf können Benutzer diesen integrierten Hook wrappen, um benutzerdefinierte Hooks wie useFormatMessage einfach zu erstellen. Weitere allgemeine Informationen finden Sie in der offiziellen Einführung in React-Hooks auf der React-Website.

Migration zur Verwendung nativer Intl-APIs

React Intl v3 enthält keine CLDR-Daten mehr und verlässt sich stattdessen auf die native Intl-API. Konkret sind die neuen APIs, auf die wir setzen:

Diese Änderung soll React Intl zukunftssicher machen, da diese APIs stabil sind und in modernen Browsern implementiert werden. Das bedeutet auch, dass wir keine CLDRs mehr in diesem Paket bündeln und verbrauchen.

Falls Sie zuvor addLocaleData zur Unterstützung älterer Browser verwendet haben, empfehlen wir folgende Schritte:

  1. Wenn Sie Browser unterstützen, die Intl.PluralRules nicht implementieren (z.B. IE11 & Safari 12-), binden Sie dieses Polyfill in Ihren Build ein.

  2. Für Browser ohne Intl.RelativeTimeFormat (z.B. IE11, Edge, Safari 13-) verwenden Sie dieses Polyfill inklusive individueller CLDR-Daten für jede unterstützte Locale.

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

Bei Nutzung von React Intl in Node.js muss Ihre node-Binary entweder:

  • Mit full-icu kompiliert werden, gemäß diesen Anleitungen

ODER

TypeScript-Unterstützung

react-intl wurde in TypeScript neu implementiert und bietet nun native TypeScript-Unterstützung. Daher haben wir die prop-types-Abhängigkeit entfernt und stellen IntlShape stattdessen als Interface bereit.

Alle Typen sollten über die Top-Level-index-Datei verfügbar sein, ohne Importe aus spezifischen Unterdateien. Beispiel:

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

Falls Ihnen Top-Level-Interfaces fehlen, freuen wir uns über Hinweise oder Pull Requests :)

Info

Codeanpassungen können nötig sein, wenn Sie das deprecated @types/react-intl-Paket verwendeten. Häufig muss InjectedIntlProps durch WrappedComponentProps ersetzt werden.

FormattedRelativeTime

FormattedRelative wurde eingeführt, als der Standard für Intl.RelativeTimeFormat noch instabil war. Nach Erreichen von Stage 3 und Browserimplementierungen haben wir die API an die Spezifikation angepasst, da sie sich von FormattedRelative unterscheidet – leider ohne Rückwärtskompatibilität.

  1. Alle units (wie day-short) werden zu Kombinationen aus unit & style:
<FormattedRelative units="second-short"/>
// will be
<FormattedRelativeTime unit="second" style="short"/>
  1. style wird zu numeric (Standardwert):
<FormattedRelative style="numeric"/>
// will be
<FormattedRelativeTime />

<FormattedRelative style="best fit"/>
// will be
<FormattedRelativeTime numeric="auto"/>
  1. Der value-Typ ist nicht länger Date, sondern das delta in der spezifizierten unit:
<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 wird zu updateIntervalInSeconds und akzeptiert nur Deltas in Sekunden. Das Update-Verhalten bleibt gleich, z.B:
<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 wurde entfernt.

Analog wurde die funktionale Entsprechung formatRelative in formatRelativeTime umbenannt, mit entsprechend angepassten Parametern.

  1. Nachbildung des FormattedRelative-Verhaltens

Mit @formatjs/intl-utils können Sie das bisherige Verhalten annähern:

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

Verbesserte Rich-Text-Formatierung in FormattedMessage & formatMessage

In v2 war für Rich-Text-Formatierungen (Einbetten von ReactElement) dieser Ansatz nötig:

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

Jetzt genügt:

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

Diese Änderung löst mehrere Probleme:

  1. Kontextverlust bei Textformatierungen: Im obigen Beispiel war link für Übersetzer ein Blackbox-Platzhalter ohne Kontext (Person, Tier, Zeitstempel). Kontextinformationen via description & placeholder reichen bei komplexen Variablen oft nicht aus.

  2. Feature-Parität mit Bibliotheken wie Mozillas fluent (mittels Overlays).

Falls Sie zuvor ein ReactElement an einen Platzhalter übergeben haben, empfehlen wir dringend, die Struktur zu überdenken, um möglichst viel Text deklarativ zu definieren:

Vorher

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

Nachher

<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 und seine zugrundeliegenden Bibliotheken (intl-messageformat-parser, intl-messageformat, @formatjs/intl-relativetimeformat, intl-format-cache, intl-utils) exportieren ESM-Artefakte. Das bedeutet, dass Sie Ihre Build-Toolchain so konfigurieren sollten, dass diese Bibliotheken transpiliert werden.

Jest

Fügen Sie transformIgnorePatterns hinzu, um diese Bibliotheken immer einzubeziehen, z.B:

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

webpack

Wenn Sie babel-loader verwenden, fügen Sie diese Bibliotheken in include hinzu, z.B:

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

Erstellen von intl ohne Provider

Wir haben eine neue API namens createIntl hinzugefügt, mit der Sie ein IntlShape-Objekt ohne Provider erstellen können. Dies ermöglicht die Formatierung außerhalb des React-Lebenszyklus unter Wiederverwendung desselben intl-Objekts. Beispiel:

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>

Dies ist besonders vorteilhaft bei SSR, wo dasselbe intl-Objekt über verschiedene Anfragen hinweg wiederverwendet werden kann.

Änderungen an der Message-Format-Syntax

Wir haben unseren Parser überarbeitet, um näher am ICU Message Format zu sein, um potenziell Skelette zu unterstützen. Die bisherigen rückwärtsinkompatiblen Änderungen sind:

Escape-Zeichen wurde auf Apostroph (') geändert.

Während wir zuvor die ICU-Nachrichtenformat-Syntax verwendeten, war unser Escape-Zeichen der Backslash (\). Dies verursachte jedoch Probleme mit strengen ICU-Übersetzungsanbietern, die andere Implementierungen wie ICU4J/ICU4C unterstützen. Dank @pyrocat101 haben wir dieses Verhalten an die Spezifikation angepasst. Das bedeutet:

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

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

Wir empfehlen dringend, die Spezifikation zu lesen, um mehr über die Funktionsweise von Anführungszeichen/Escaping zu erfahren: hier im Abschnitt Quoting/Escaping.

Syntaxänderung bei Platzhalterargumenten

Platzhalterargumente dürfen kein - enthalten (z.B. ist this is a {placeholder-var} ungültig, aber this is a {placeholder_var} gültig).

Tests

Wir haben IntlProvider.getChildContext für Tests entfernt. Stattdessen können Sie createIntl verwenden, um ein eigenständiges intl-Objekt außerhalb von React zu erstellen und für Testzwecke zu nutzen. Weitere Details finden Sie unter Tests mit React Intl.