Comment formater le temps relatif dans React Router v7

Formater les horodatages comme des expressions '2 jours auparavant'

Problème

L'affichage des horodatages sous forme de phrases temporelles relatives comme "il y a 2 jours" ou "dans 3 heures" rend le contenu immédiat et contextuel. Cependant, ces phrases suivent des règles grammaticales complexes qui varient considérablement selon les langues. L'anglais place "ago" après les quantités passées et "in" avant les futures, mais d'autres langues peuvent modifier la forme du mot désignant l'unité de temps, réorganiser les mots ou utiliser des structures grammaticales complètement différentes. Construire manuellement ces phrases par concaténation de chaînes produit des résultats incorrects dans toutes les langues sauf celle que vous avez codée en dur, ce qui détériore l'expérience utilisateur pour les audiences internationales.

Solution

Utilisez le formatage temporel relatif adapté à la locale pour convertir les différences d'horodatage en phrases grammaticalement correctes. Calculez la différence de temps entre un horodatage donné et le moment présent, puis formatez cette différence en utilisant les règles de la locale de l'utilisateur. Cela garantit que les expressions temporelles passées et futures suivent la grammaire, l'ordre des mots et les modèles d'inflexion corrects pour chaque langue sans manipulation manuelle de chaînes.

Étapes

1. Créer un assistant pour calculer les valeurs de temps relatives

Le composant FormattedRelativeTime nécessite une propriété numérique value et une propriété unit. Construisez une fonction d'assistance qui calcule la différence de temps et sélectionne une unité appropriée.

export function getRelativeTimeValue(date: Date | number) {
  const now = Date.now();
  const timestamp = typeof date === "number" ? date : date.getTime();
  const diffInSeconds = Math.round((timestamp - now) / 1000);

  const minute = 60;
  const hour = minute * 60;
  const day = hour * 24;
  const week = day * 7;
  const month = day * 30;
  const year = day * 365;

  const absDiff = Math.abs(diffInSeconds);

  if (absDiff < minute) {
    return { value: diffInSeconds, unit: "second" as const };
  } else if (absDiff < hour) {
    return {
      value: Math.round(diffInSeconds / minute),
      unit: "minute" as const,
    };
  } else if (absDiff < day) {
    return { value: Math.round(diffInSeconds / hour), unit: "hour" as const };
  } else if (absDiff < week) {
    return { value: Math.round(diffInSeconds / day), unit: "day" as const };
  } else if (absDiff < month) {
    return { value: Math.round(diffInSeconds / week), unit: "week" as const };
  } else if (absDiff < year) {
    return { value: Math.round(diffInSeconds / month), unit: "month" as const };
  } else {
    return { value: Math.round(diffInSeconds / year), unit: "year" as const };
  }
}

Cette fonction convertit un horodatage en une valeur numérique signée et l'unité de temps la plus appropriée en fonction de l'ampleur de la différence.

2. Créer un composant de temps relatif en utilisant FormattedRelativeTime

Utilisez le composant FormattedRelativeTime de react-intl, qui affiche le temps relatif formaté et peut éventuellement se mettre à jour à intervalles réguliers.

import { FormattedRelativeTime } from "react-intl";
import { getRelativeTimeValue } from "./getRelativeTimeValue";

interface RelativeTimeProps {
  date: Date | number;
  updateIntervalInSeconds?: number;
}

export function RelativeTime({
  date,
  updateIntervalInSeconds,
}: RelativeTimeProps) {
  const { value, unit } = getRelativeTimeValue(date);

  return (
    <FormattedRelativeTime
      value={value}
      unit={unit}
      numeric="auto"
      updateIntervalInSeconds={updateIntervalInSeconds}
    />
  );
}

L'option numeric="auto" permet au formateur d'utiliser des expressions comme "hier" au lieu de "il y a 1 jour" lorsque c'est approprié. La prop optionnelle updateIntervalInSeconds contrôle la fréquence à laquelle le composant se re-rend pour maintenir le temps relatif à jour.

3. Utiliser le composant dans une route React Router

Rendez le composant de temps relatif dans n'importe quel composant de route où vous devez afficher des horodatages.

import { RelativeTime } from "./RelativeTime";

interface Post {
  id: string;
  title: string;
  content: string;
  createdAt: number;
}

export function PostDetail({ post }: { post: Post }) {
  return (
    <article>
      <h1>{post.title}</h1>
      <time dateTime={new Date(post.createdAt).toISOString()}>
        <RelativeTime date={post.createdAt} />
      </time>
      <p>{post.content}</p>
    </article>
  );
}

Le composant formate automatiquement l'horodatage selon la locale de l'utilisateur, produisant des expressions comme "2 days ago" en anglais, "il y a 2 jours" en français, ou "hace 2 días" en espagnol.

4. Formater le temps relatif de manière impérative avec useIntl

Pour les cas où vous avez besoin de la chaîne formatée directement (comme pour définir des attributs d'élément), utilisez la fonction formatRelativeTime du hook useIntl.

import { useIntl } from "react-intl";
import { getRelativeTimeValue } from "./getRelativeTimeValue";

interface CommentProps {
  author: string;
  text: string;
  timestamp: number;
}

export function Comment({ author, text, timestamp }: CommentProps) {
  const intl = useIntl();
  const { value, unit } = getRelativeTimeValue(timestamp);
  const relativeTime = intl.formatRelativeTime(value, unit, {
    numeric: "auto",
  });

  return (
    <div aria-label={`Comment by ${author}, posted ${relativeTime}`}>
      <strong>{author}</strong>
      <p>{text}</p>
    </div>
  );
}

Cette approche vous donne une chaîne formatée que vous pouvez utiliser dans des attributs, concaténer avec d'autres textes, ou transmettre à des API non-React tout en maintenant une prise en charge complète des locales.