Aller au contenu principal

API Impérative

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 →

React Intl repose sur plusieurs couches d'API. Lors de son utilisation, vous interagirez avec son API impérative (documentée ici) et ses composants React.

Pourquoi une API Impérative ?

Bien que nos composants offrent une intégration transparente avec React, l'API impérative est recommandée (parfois indispensable) dans plusieurs cas d'usage :

  • Définir des attributs textuels comme title, aria-label dans des contextes où un composant React ne peut être utilisé (ex: <img title/>)

  • Formater du texte/dates-heures... dans des environnements non-React comme Node, API serveur, store Redux, tests...

  • Scénarios haute performance où le nombre de composants React devient un goulot d'étranglement (ex: rendu de portefeuilles boursiers, tables virtuelles avec nombreuses cellules...)

L'objet intl

Le cœur de react-intl est l'objet intl (de type IntlShape), qui stocke le cache des APIs Intl.*, configurations, messages compilés, etc. Le cycle de vie de l'objet intl est lié à la locale et à la liste de messages qu'il contient, ce qui signifie que lorsque vous changez de locale, cet objet doit être recréé.

Conseil

L'objet intl doit être réutilisé autant que possible pour des raisons de performance.

Plusieurs méthodes permettent d'accéder à l'objet intl :

  • Hook useIntl : Après avoir déclaré votre IntlProvider, accédez à intl en appelant ce hook dans vos composants fonctionnels React

  • HOC injectIntl : Pour les composants React basés sur des class, encapsulez-les avec le HOC injectIntl pour accéder à intl via une prop

  • createIntl : Dans des environnements non-React (Node, Vue, Angular, tests...), créez directement un objet intl avec la même configuration que IntlProvider

Le hook useIntl

Pour les composants fonctionnels, le hook useIntl est pratique. Ce hook useIntl ne requiert aucun argument lors de son appel. Voici un exemple d'utilisation typique :

import React from 'react'
import {useIntl, FormattedDate} from 'react-intl'

const FunctionComponent: React.FC<{date: number | Date}> = ({date}) => {
const intl = useIntl()
return (
<span title={intl.formatDate(date)}>
<FormattedDate value={date} />
</span>
)
}

export default FunctionComponent

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.

Le HOC injectIntl

type WrappedComponentProps<IntlPropName extends string = 'intl'> = {
[k in IntlPropName]: IntlShape
}

type WithIntlProps<P> = Omit<P, keyof WrappedComponentProps> & {
forwardedRef?: React.Ref<any>
}

function injectIntl<
IntlPropName extends string = 'intl',
P extends WrappedComponentProps<IntlPropName> = WrappedComponentProps<any>,
>(
WrappedComponent: React.ComponentType<P>,
options?: Opts<IntlPropName>
): React.ComponentType<WithIntlProps<P>> & {
WrappedComponent: typeof WrappedComponent
}

Exporté par react-intl, ce composant d'ordre supérieur (HOC) encapsule vos composants React pour injecter l'API de formatage via leurs props. (Similaire au pattern connect-to-stores des implémentations Flux.)

Par défaut, l'API est injectée via props.intl, mais ce nom peut être modifié via options.intlPropName. La prop sera de type IntlShape, défini dans la section suivante.

import React from 'react'
import {injectIntl, FormattedDate} from 'react-intl'

interface Props {
date: Date | number
}

const FunctionalComponent: React.FC<Props> = props => {
const {
date,
intl, // Injected by `injectIntl`
} = props
return (
<span title={intl.formatDate(date)}>
<FormattedDate value={date} />
</span>
)
}

export default injectIntl(FunctionalComponent)

createIntl

Permet de créer un objet IntlShape sans Provider, pour formater 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>

createIntlCache

Crée une instance de cache utilisable globalement pour toutes les locales. Cela mémorise les constructeurs Intl.* précédemment créés pour des performances optimales et ne constitue qu'un cache en mémoire.

IntlShape

interface IntlConfig {
locale: string
timeZone?: string
formats: CustomFormats
textComponent?: React.ComponentType | keyof React.JSX.IntrinsicElements
messages: Record<string, string> | Record<string, MessageFormatElement[]>
defaultLocale: string
defaultFormats: CustomFormats
onError(err: string): void
onWarn(warning: string): void
}

interface IntlFormatters {
formatDate(value: number | Date | string, opts?: FormatDateOptions): string
formatTime(value: number | Date | string, opts?: FormatDateOptions): string
formatDateToParts(
value: number | Date | string,
opts?: FormatDateOptions
): Intl.DateTimeFormatPart[]
formatTimeToParts(
value: number | Date | string,
opts?: FormatDateOptions
): Intl.DateTimeFormatPart[]
formatRelativeTime(
value: number,
unit?: FormattableUnit,
opts?: FormatRelativeTimeOptions
): string
formatNumber(value: number, opts?: FormatNumberOptions): string
formatNumberToParts(
value: number,
opts?: FormatNumberOptions
): Intl.NumberFormatPart[]
formatPlural(
value: number | string,
opts?: FormatPluralOptions
): ReturnType<Intl.PluralRules['select']>
formatMessage(
descriptor: MessageDescriptor,
values?: Record<string, PrimitiveType | FormatXMLElementFn<string, string>>
): string
formatMessage(
descriptor: MessageDescriptor,
values?: Record<string, PrimitiveType | T | FormatXMLElementFn<T, R>>
): R
formatList(values: Iterable<string>, opts?: FormatListOptions): string
formatList(
values: Iterable<string | T>,
opts?: FormatListOptions
): T | string | Array<string | T>
formatListToParts(
values: Iterable<string | T>,
opts?: FormatListOptions
): Part[]
formatDisplayName(
value: string,
opts?: FormatDisplayNameOptions
): string | undefined
}

type IntlShape = IntlConfig & IntlFormatters

Cette interface, exportée par react-intl, est utilisée avec la fabrique de HOC injectIntl.

La définition ci-dessus montre la structure de props.intl injecté via injectIntl, composé de deux parties :

  • IntlConfig : Les métadonnées intl passées comme props au parent <IntlProvider>.

  • IntlFormatters : L'API de formatage impératif décrite ci-dessous.

locale, formats et messages

La locale actuelle de l'utilisateur et celle dans laquelle l'application doit être rendue. Tandis que defaultLocale et defaultFormats servent de fallbacks ou durant le développement pour représenter les valeurs par défaut de l'application. Notez l'absence de defaultMessages car chaque Message Descriptor fournit déjà un defaultMessage.

defaultLocale et defaultFormats

Locale et formats par défaut utilisés lorsqu'un message n'est pas traduit (absent de messages). defaultLocale doit correspondre à la locale dans laquelle les defaultMessage sont déclarés pour garantir la cohérence des phrases dans une seule locale. Sans defaultLocale ou si celui-ci est mal configuré, vous pourriez obtenir des phrases en anglais avec des dates/heures intégrées en espagnol.

textComponent

Configure le wrapper par défaut des composants <Formatted*> de React Intl. Par défaut, <React.Fragment> est utilisé. Avant la version 3, span était employé ; consultez le guide de migration pour plus de détails.

onError

Permet à l'utilisateur de fournir un gestionnaire d'erreurs personnalisé. Par défaut, les erreurs sont journalisées via console.error si NODE_ENV n'est pas défini sur production.

wrapRichTextChunksInFragment

Lors du formatage de texte enrichi, la sortie est de type Array<string | React.ReactElement>, ce qui génère une erreur de clé. Cette option englobe le résultat dans un React.Fragment unique pour résoudre ce problème.

defaultRichTextElements

Une correspondance entre balises et fonctions de formatage de texte enrichi. Conçu pour centraliser le formatage de balises courantes comme <b>, <p>... ou imposer un Design System spécifique dans la base de code (ex: <a> ou <button> standardisés). Voir https://github.com/formatjs/formatjs/issues/1752 pour plus de contexte.

formatDate

function formatDate(
value: number | Date | string,
options?: Intl.DateTimeFormatOptions & {format?: string}
): string

Cette fonction retourne une chaîne de caractères représentant une date formatée. Elle attend une value interprétable comme date (c'est-à-dire isFinite(new Date(value))), et accepte des options conformes à DateTimeFormatOptions.

Live Editor
intl.formatDate(Date.now(), {
  year: 'numeric',
  month: 'numeric',
  day: 'numeric',
})
Result

formatTime

function formatTime(
value: number | Date | string,
options?: Intl.DateTimeFormatOptions & {format?: string}
): string

Cette fonction retourne également une chaîne de caractères de date formatée, mais diffère de formatDate par ses options par défaut :

{
hour: 'numeric',
minute: 'numeric',
}

Elle attend une value interprétable comme date (c'est-à-dire isFinite(new Date(value))), et accepte des options conformes à DateTimeFormatOptions.

Live Editor
intl.formatTime(Date.now()) /* "4:03 PM" */
Result

formatDateTimeRange

Support navigateur

Cela nécessite Intl.DateTimeFormat.prototype.formatRange qui a un support navigateur limité. Utilisez notre polyfill si vous prévoyez de les supporter.

function formatDateTimeRange(
from: number | Date | string,
to: number | Date | string,
options?: Intl.DateTimeFormatOptions & {format?: string}
): string

Cette fonction retourne une chaîne de caractères représentant une plage de dates/heures formatée. from et to doivent être des valeurs interprétables comme dates (c'est-à-dire isFinite(new Date(value))).

Elle attend 2 valeurs (une date from et une date to) et accepte des options conformes à DateTimeFormatOptions.

Live Editor
intl.formatDateTimeRange(new Date('2020-1-1'), new Date('2020-1-15'))
Result

formatRelativeTime

compatibilité navigateur

Cette fonctionnalité nécessite Intl.RelativeTimeFormat qui a un support navigateur limité. Utilisez notre polyfill si vous devez la supporter.

type Unit =
| 'second'
| 'minute'
| 'hour'
| 'day'
| 'week'
| 'month'
| 'quarter'
| 'year'

type RelativeTimeFormatOptions = {
numeric?: 'always' | 'auto'
style?: 'long' | 'short' | 'narrow'
}

function formatRelativeTime(
value: number,
unit: Unit,
options?: Intl.RelativeTimeFormatOptions & {
format?: string
}
): string

Cette fonction retourne une chaîne de temps relatif formatée (ex : "il y a 1 heure"). Elle attend une value numérique, une unit et des options conformes à Intl.RelativeTimeFormatOptions.

Live Editor
intl.formatRelativeTime(0)
Result
Live Editor
intl.formatRelativeTime(-24, 'hour', {style: 'narrow'})
Result

formatNumber

Cette fonction utilise les options d'Intl.NumberFormat.

function formatNumber(
value: number,
options?: Intl.NumberFormatOptions & {format?: string}
): string

Cette fonction retourne une chaîne de nombre formatée. Elle attend une value interprétable comme nombre, et accepte des options conformes à NumberFormatOptions.

Live Editor
intl.formatNumber(1000, {style: 'currency', currency: 'USD'})
Result

Formatage numérique avec unit

Actuellement, cette fonctionnalité fait partie d'ES2020 NumberFormat. Nous fournissons un polyfill ici et les types de react-intl permettent d'utiliser une unité sanctionnée :

Live Editor
intl.formatNumber(1000, {
  style: 'unit',
  unit: 'kilobyte',
  unitDisplay: 'narrow',
})
Result
Live Editor
intl.formatNumber(1000, {
  unit: 'fahrenheit',
  unitDisplay: 'long',
  style: 'unit',
})
Result

formatPlural

type PluralFormatOptions = {
type?: 'cardinal' | 'ordinal' = 'cardinal'
}

function formatPlural(
value: number,
options?: Intl.PluralFormatOptions
): 'zero' | 'one' | 'two' | 'few' | 'many' | 'other'

Cette fonction retourne une catégorie de pluriel : "zero", "one", "two", "few", "many", ou "other". Elle attend une value interprétable comme nombre, et accepte des options conformes à PluralFormatOptions.

C'est une utilitaire bas niveau dont le résultat peut être utilisé dans une instruction switch pour sélectionner une chaîne spécifique à afficher.

Live Editor
intl.formatPlural(1)
Result
Live Editor
intl.formatPlural(3, {style: 'ordinal'})
Result
Live Editor
intl.formatPlural(4, {style: 'ordinal'})
Result
Support multilingue

Cette fonction ne doit être utilisée que pour des applications supportant une seule langue. Pour des applications multilingues, utilisez plutôt formatMessage.

formatList

Compatibilité navigateur

Cette fonctionnalité nécessite Intl.ListFormat dont le support navigateur est limité. Utilisez notre polyfill si vous prévoyez de les prendre en charge.

type ListFormatOptions = {
type?: 'disjunction' | 'conjunction' | 'unit'
style?: 'long' | 'short' | 'narrow'
}

function formatList(
elements: (string | React.ReactNode)[],
options?: Intl.ListFormatOptions
): string | React.ReactNode[]

Cette fonction permet de joindre des listes d'éléments de manière compatible i18n. Par exemple, lorsque la locale est en :

Live Editor
intl.formatList(['Me', 'myself', 'I'], {type: 'conjunction'})
Result
Live Editor
intl.formatList(['5 hours', '3 minutes'], {type: 'unit'})
Result

formatDisplayName

Compatibilité navigateur

Cette fonctionnalité nécessite Intl.DisplayNames dont le support navigateur est limité. Utilisez notre polyfill si vous prévoyez de les prendre en charge.

type FormatDisplayNameOptions = {
style?: 'narrow' | 'short' | 'long'
type?: 'language' | 'region' | 'script' | 'currency'
fallback?: 'code' | 'none'
}

function formatDisplayName(
value: string | number | Record<string, unknown>,
options?: FormatDisplayNameOptions
): string | undefined

Exemples d'utilisation :

Live Editor
intl.formatDisplayName('zh-Hans-SG', {type: 'language'})
Result
Live Editor
// ISO-15924 four letters script code to localized display name
intl.formatDisplayName('Deva', {type: 'script'})
Result
Live Editor
// ISO-4217 currency code to localized display name
intl.formatDisplayName('CNY', {type: 'currency'})
Result
Live Editor
// ISO-3166 two letters region code to localized display name
intl.formatDisplayName('UN', {type: 'region'})
Result

formatMessage

Syntaxe des messages

Le formatage des chaînes/messages est une fonctionnalité essentielle de React Intl, basée sur le formatage de messages ICU via la syntaxe ICU. Cette syntaxe permet de définir des messages simples à complexes, de les traduire, puis de les formater à l'exécution.

Message simple :

Hello, {name}

Message complexe :

Hello, {name}, you have {itemCount, plural,
=0 {no items}
one {# item}
other {# items}
}.

Voir : Le guide de syntaxe des messages.

Message Descriptor

React Intl utilise le concept de Descripteur de message pour définir les messages/chaînes par défaut de votre application, qui est passé à formatMessage. Les Descripteurs de message sont optimaux pour fournir les données nécessaires à la traduction des chaînes/messages et contiennent les propriétés suivantes :

  • id : Identifiant unique et stable pour le message

  • description : Contexte pour le traducteur sur l'utilisation dans l'UI

  • defaultMessage : Le message par défaut (probablement en anglais)

type MessageDescriptor = {
id: string
defaultMessage?: string
description?: string | object
}
Extraction du Descripteur de message

Vous pouvez extraire les messages déclarés en ligne des fichiers sources en utilisant notre CLI.

Fallbacks de formatage des messages

Les API de formatage de messages vont au-delà pour fournir des solutions de repli dans les situations courantes où le formatage échoue ; au minimum, une chaîne non vide devrait toujours être retournée. Voici l'algorithme de repli pour le formatage des messages :

  1. Rechercher et formater le message traduit à l'id, passé à <IntlProvider>.

  2. Retomber sur le formatage du defaultMessage.

  3. Retomber sur la source du message traduit à l'id.

  4. Retomber sur la source du defaultMessage.

  5. Retomber sur l'id littéral du message.

Ci-dessus, "source" fait référence à l'utilisation du modèle tel quel, sans aucune substitution.

Utilisation

type MessageFormatPrimitiveValue = string | number | boolean | null | undefined
function formatMessage(
descriptor: MessageDescriptor,
values?: Record<string, MessageFormatPrimitiveValue>
): string
function formatMessage(
descriptor: MessageDescriptor,
values?: Record<
string,
MessageFormatPrimitiveValue | React.ReactElement | FormatXMLElementFn
>
): string | React.ReactNode[]

Cette fonction retourne une chaîne de message formatée. Elle attend un MessageDescriptor avec au moins une propriété id, et accepte un objet values peu profond qui est utilisé pour remplir les espaces réservés dans le message.

Si un message traduit avec l'id a été passé au <IntlProvider> via sa prop messages, il sera formaté ; sinon, il retombera sur le formatage du defaultMessage. Voir : Fallbacks de formatage de message pour plus de détails.

Live Editor
function () {
  const messages = defineMessages({
    greeting: {
      id: 'app.greeting',
      defaultMessage: 'Hello, {name}!',
      description: 'Greeting to welcome the user to the app',
    },
  })

  return intl.formatMessage(messages.greeting, {name: 'Eric'})
}
Result

avec ReactElement

Live Editor
function () {
  const messages = defineMessages({
    greeting: {
      id: 'app.greeting',
      defaultMessage: 'Hello, {name}!',
      description: 'Greeting to welcome the user to the app',
    },
  })

  return intl.formatMessage(messages.greeting, {name: <b>Eric</b>})
}
Result

avec formatage de texte enrichi

Live Editor
function () {
  const messages = defineMessages({
    greeting: {
      id: 'app.greeting',
      defaultMessage: 'Hello, <bold>{name}</bold>!',
      description: 'Greeting to welcome the user to the app',
    },
  })

  return intl.formatMessage(messages.greeting, {
    name: 'Eric',
    bold: str => <b>{str}</b>,
  })
}
Result

Le message que nous avons défini en utilisant defineMessages pour supporter l'extraction via babel-plugin-formatjs, mais ce n'est pas obligatoire si vous n'utilisez pas le plugin Babel.

message simple

Les messages peuvent être des chaînes simples sans espaces réservés, et c'est le type de message le plus courant.

defineMessages/defineMessage

interface MessageDescriptor {
id?: string
description?: string | object
defaultMessage?: string
}

function defineMessages(
messageDescriptors: Record<string, MessageDescriptor>
): Record<string, MessageDescriptor>

function defineMessage(messageDescriptor: MessageDescriptor): MessageDescriptor

Ces fonctions sont exportées par le package react-intl et constituent simplement un hook pour notre CLI et les plugins babel/TS à utiliser lors de la compilation des messages par défaut définis dans les fichiers source JavaScript. Cette fonction retourne simplement l'objet map de Descripteur de message qui lui est passé.

import {defineMessages, defineMessage} from 'react-intl'

const messages = defineMessages({
greeting: {
id: 'app.home.greeting',
description: 'Message to greet the user.',
defaultMessage: 'Hello, {name}!',
},
})

const msg = defineMessage({
id: 'single',
defaultMessage: 'single message',
description: 'header',
})