Comment formater le temps relatif dans TanStack Start v1

Formater les horodatages en expressions du type 'il y a 2 jours'

Problème

Afficher les horodatages sous forme de phrases temporelles relatives comme "il y a 2 jours" ou "dans 3 heures" rend l'information temporelle plus intuitive pour les utilisateurs. Cependant, ces phrases suivent des règles grammaticales complexes qui varient selon la langue. L'anglais place "ago" après les quantités passées et "in" avant les futures, mais d'autres langues peuvent utiliser des ordres de mots différents, infliger des unités de temps ou employer des structures grammaticales entièrement différentes. Construire manuellement ces phrases avec une concaténation de chaînes produit des résultats incorrects dans toutes les langues sauf celle que vous avez codée en dur.

L'API de formatage sous-jacente nécessite une valeur numérique et une unité de temps, mais les horodatages arrivent sous forme d'objets Date ou de valeurs en millisecondes. Convertir un horodatage en unité et valeur appropriées nécessite de calculer la différence par rapport au moment présent et de sélectionner l'unité la plus naturelle pour exprimer cette différence.

Solution

Calculer la différence de temps entre un horodatage et le moment présent, sélectionner l'unité la plus appropriée pour cette différence, puis formater le résultat en utilisant un formatage temporel relatif spécifique à la locale. Cela transforme les horodatages bruts en phrases grammaticalement correctes qui s'adaptent à la langue de l'utilisateur.

Utiliser le formatage de temps relatif de react-intl avec une fonction auxiliaire qui détermine la meilleure unité. L'assistant compare la différence de temps avec des seuils pour chaque unité et renvoie à la fois la valeur numérique et le nom de l'unité. Le formateur applique ensuite les règles grammaticales correctes pour la locale active.

Étapes

1. Créer un assistant de sélection d'unité

Construire une fonction qui calcule la différence de temps et sélectionne l'unité la plus naturelle pour l'exprimer.

type RelativeTimeUnit =
  | "second"
  | "minute"
  | "hour"
  | "day"
  | "week"
  | "month"
  | "year";

interface RelativeTimeValue {
  value: number;
  unit: RelativeTimeUnit;
}

export function selectRelativeTimeUnit(
  timestamp: Date | number,
  baseTime: Date | number = Date.now(),
): RelativeTimeValue {
  const date = typeof timestamp === "number" ? timestamp : timestamp.getTime();
  const base = typeof baseTime === "number" ? baseTime : baseTime.getTime();
  const diffMs = date - base;
  const absDiff = Math.abs(diffMs);

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

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

Cette fonction convertit un horodatage en une valeur relative et une unité en comparant la différence de temps avec des seuils fixes. Elle renvoie des valeurs négatives pour les temps passés et des valeurs positives pour les temps futurs, que le formateur interprète correctement.

2. Créer un composant de temps relatif

Construisez un composant qui combine l'assistant de sélection d'unité avec le formatage de react-intl.

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

interface RelativeTimeProps {
  date: Date | number;
  numeric?: "always" | "auto";
  style?: "long" | "short" | "narrow";
}

export function RelativeTime({
  date,
  numeric = "auto",
  style = "long",
}: RelativeTimeProps) {
  const { value, unit } = selectRelativeTimeUnit(date);

  return (
    <FormattedRelativeTime
      value={value}
      unit={unit}
      numeric={numeric}
      style={style}
    />
  );
}

Ce composant accepte un horodatage et des options de formatage, calcule la valeur relative et l'unité, puis délègue au composant de react-intl pour un rendu adapté à la locale. L'option numeric="auto" produit des phrases comme "hier" au lieu de "il y a 1 jour" lorsque c'est approprié.

3. Utiliser le composant dans vos routes

Importez et rendez le composant partout où vous avez besoin d'afficher des horodatages relatifs.

import { createFileRoute } from "@tanstack/react-router";
import { RelativeTime } from "../components/RelativeTime";

export const Route = createFileRoute("/posts/$postId")({
  component: PostPage,
});

function PostPage() {
  const post = {
    title: "Understanding Relative Time",
    publishedAt: new Date("2024-11-15T10:30:00Z"),
    updatedAt: new Date(Date.now() - 2 * 60 * 60 * 1000),
  };

  return (
    <article>
      <h1>{post.title}</h1>
      <p>
        Publié <RelativeTime date={post.publishedAt} />
      </p>
      <p>
        Mis à jour <RelativeTime date={post.updatedAt} style="short" />
      </p>
    </article>
  );
}

Le composant fonctionne à la fois dans les contextes rendus côté serveur et côté client. Sur le serveur, il produit la phrase initiale de temps relatif, et sur le client, il affiche la même phrase en utilisant la locale de l'utilisateur depuis l'IntlProvider.

4. Ajouter une option de formatage impérative

Pour les cas où vous avez besoin de la chaîne formatée directement, créez un assistant basé sur un hook.

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

export function useRelativeTime() {
  const intl = useIntl();

  return (
    date: Date | number,
    options?: {
      numeric?: "always" | "auto";
      style?: "long" | "short" | "narrow";
    },
  ) => {
    const { value, unit } = selectRelativeTimeUnit(date);
    return intl.formatRelativeTime(value, unit, options);
  };
}

Ce hook renvoie une fonction qui formate les horodatages de manière impérative, utile pour définir des attributs de texte ou calculer des valeurs en dehors de JSX.

5. Utiliser le hook pour les contextes non-composants

Appelez le hook dans les composants où vous avez besoin de chaînes formatées pour les attributs ou la logique.

import { createFileRoute } from "@tanstack/react-router";
import { useRelativeTime } from "../hooks/useRelativeTime";

export const Route = createFileRoute("/events/$eventId")({
  component: EventPage,
});

function EventPage() {
  const formatRelativeTime = useRelativeTime();
  const event = {
    name: "Product Launch",
    startTime: new Date(Date.now() + 3 * 24 * 60 * 60 * 1000),
  };

  const timeUntil = formatRelativeTime(event.startTime);

  return (
    <div>
      <h1>{event.name}</h1>
      <time dateTime={event.startTime.toISOString()} title={timeUntil}>
        {timeUntil}
      </time>
    </div>
  );
}

Le hook fournit la même logique de formatage sous forme fonctionnelle, vous permettant d'utiliser des chaînes de temps relatif dans les attributs, les valeurs calculées, ou partout où un composant ne peut pas être rendu.