Как форматировать относительное время в Next.js (Pages Router) v16

Форматируйте временные метки как фразы вида «2 дня назад»

Проблема

Отображение временных меток в виде относительных временных фраз, таких как "2 дня назад" или "через 3 часа", делает контент более актуальным и контекстуальным. Однако эти фразы следуют сложным грамматическим правилам, которые значительно различаются в разных языках. В английском языке "ago" ставится после количества времени в прошлом, а "in" — перед количеством времени в будущем, но в других языках может использоваться другой порядок слов, изменяться форма единиц времени в зависимости от количества или применяться совершенно другие грамматические структуры. Ручное создание таких фраз с помощью конкатенации строк приводит к некорректному результату на всех языках, кроме того, для которого они были написаны, что нарушает пользовательский опыт для международной аудитории.

Решение

Используйте форматирование относительного времени из react-intl, чтобы преобразовывать числовые временные различия в грамматически правильные фразы для локали пользователя. Вычислите разницу во времени между временной меткой и текущим моментом, определите подходящую единицу (секунды, минуты, часы, дни) и передайте оба значения в API форматирования react-intl. Базовый API Intl.RelativeTimeFormat автоматически обрабатывает грамматику, порядок слов и правила изменения формы слов для конкретной локали.

Шаги

1. Создайте вспомогательную функцию для вычисления значений относительного времени

Разница во времени должна быть преобразована в числовое значение и подходящую единицу. Создайте функцию, которая сравнивает временную метку с текущим временем и выбирает наиболее подходящую единицу.

export function getRelativeTimeValue(date: Date | number) {
  const now = Date.now();
  const timestamp = typeof date === "number" ? date : date.getTime();
  const diffInSeconds = (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: Math.round(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 };
  }
}

Эта функция возвращает числовое значение с учетом знака (отрицательное для прошлого, положительное для будущего) и наиболее подходящую единицу в зависимости от величины разницы.

2. Форматирование относительного времени с использованием хука useIntl

Функция formatRelativeTime возвращает отформатированную строку относительного времени и ожидает числовое значение, единицу измерения и параметры, соответствующие Intl.RelativeTimeFormatOptions. Используйте вспомогательную функцию из шага 1, чтобы получить значение и единицу измерения, а затем отформатируйте их.

import { useIntl } from "react-intl";
import { getRelativeTimeValue } from "@/lib/relative-time";

export default function CommentTimestamp({ date }: { date: Date }) {
  const intl = useIntl();
  const { value, unit } = getRelativeTimeValue(date);

  return (
    <time dateTime={date.toISOString()}>
      {intl.formatRelativeTime(value, unit)}
    </time>
  );
}

Метод formatRelativeTime создает фразы, соответствующие локали, такие как "2 дня назад" на русском или "il y a 2 jours" на французском.

3. Использование FormattedRelativeTime для декларативного форматирования

FormattedRelativeTime реализует расширенные функции, такие как обновление со временем, и использует API formatRelativeTime с пропсами для значения, единицы измерения и параметров относительного форматирования. Для компонентов, которым требуется автоматическое обновление, используйте форму компонента.

import { FormattedRelativeTime } from "react-intl";
import { getRelativeTimeValue } from "@/lib/relative-time";

export default function LiveTimestamp({ date }: { date: Date }) {
  const { value, unit } = getRelativeTimeValue(date);

  return (
    <time dateTime={date.toISOString()}>
      <FormattedRelativeTime
        value={value}
        unit={unit}
        numeric="auto"
        updateIntervalInSeconds={10}
      />
    </time>
  );
}

Свойство updateIntervalInSeconds управляет частотой повторного рендеринга компонента, поддерживая актуальность отображаемой фразы по мере течения времени.

4. Настройка стиля форматирования

Компонент принимает RelativeTimeFormatOptions, включая свойства numeric и style. Настройте эти параметры, чтобы управлять форматом вывода.

import { FormattedRelativeTime } from "react-intl";
import { getRelativeTimeValue } from "@/lib/relative-time";

export default function CompactTimestamp({ date }: { date: Date }) {
  const { value, unit } = getRelativeTimeValue(date);

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

Установка numeric="auto" создает фразы, такие как "вчера" вместо "1 день назад", когда это уместно, а style="narrow" создает более короткие формы, такие как "2 мес. назад" вместо "2 месяца назад".