API Intl.RelativeTimeFormat предназначен для
локализованного форматирования относительного времени:
Поддержка встроенного Intl.RelativeTimeFormat
присутствует не во всех средах исполнения. Особенно это касается:
Для решения проблемы используется полифилл из экосистемы FormatJS.
@formatjs/intl-relativetimeformatОсновной полифилл поставляется в пакете:
npm install @formatjs/intl-relativetimeformat
Дополнительно требуются данные локализации:
npm install @formatjs/intl-relativetimeformat
После установки становятся доступны:
Intl.RelativeTimeFormat;import '@formatjs/intl-relativetimeformat/polyfill';
import '@formatjs/intl-relativetimeformat/locale-data/en';
import '@formatjs/intl-relativetimeformat/locale-data/ru';
После импорта API становится доступным глобально:
const rtf = new Intl.RelativeTimeFormat('ru');
console.log(rtf.format(-5, 'minute'));
Результат:
5 минут назад
FormatJS предоставляет безопасную проверку:
import {shouldPolyfill} from '@formatjs/intl-relativetimeformat/should-polyfill';
async function setupRelativeTime(locale) {
const unsupportedLocale = shouldPolyfill(locale);
if (!unsupportedLocale) {
return;
}
await import('@formatjs/intl-relativetimeformat/polyfill-force');
await import(
`@formatjs/intl-relativetimeformat/locale-data/${locale}`
);
}
Такой подход:
polyfill
и polyfill-forcepolyfillПодключает реализацию только при отсутствии поддержки:
import '@formatjs/intl-relativetimeformat/polyfill';
Используется чаще всего.
polyfill-forceПринудительно заменяет реализацию:
import '@formatjs/intl-relativetimeformat/polyfill-force';
Полезно:
Без locale data форматирование работать не будет.
import '@formatjs/intl-relativetimeformat/locale-data/ru';
import '@formatjs/intl-relativetimeformat/locale-data/en';
import '@formatjs/intl-relativetimeformat/locale-data/de';
import '@formatjs/intl-relativetimeformat/locale-data/fr';
import '@formatjs/intl-relativetimeformat/locale-data/ru';
async function loadLocale(locale) {
await import(
`@formatjs/intl-relativetimeformat/locale-data/${locale}`
);
}
const formatter = new Intl.RelativeTimeFormat('ru');
console.log(formatter.format(-1, 'day'));
console.log(formatter.format(3, 'month'));
Результат:
вчера
через 3 месяца
Допустимые значения:
'year'
'quarter'
'month'
'week'
'day'
'hour'
'minute'
'second'
Пример:
formatter.format(-2, 'week');
Результат:
2 недели назад
new Intl.RelativeTimeFormat(locale, options)
numericУправляет тем, использовать ли специальные слова:
или строго числовой формат.
numeric: 'auto'const formatter = new Intl.RelativeTimeFormat('ru', {
numeric: 'auto'
});
console.log(formatter.format(-1, 'day'));
Результат:
вчера
numeric: 'always'const formatter = new Intl.RelativeTimeFormat('ru', {
numeric: 'always'
});
console.log(formatter.format(-1, 'day'));
Результат:
1 день назад
styleПоддерживаются стили:
longshortnarrowlongconst formatter = new Intl.RelativeTimeFormat('ru', {
style: 'long'
});
console.log(formatter.format(-3, 'month'));
Результат:
3 месяца назад
shortconst formatter = new Intl.RelativeTimeFormat('ru', {
style: 'short'
});
console.log(formatter.format(-3, 'month'));
Результат:
3 мес. назад
narrowconst formatter = new Intl.RelativeTimeFormat('ru', {
style: 'narrow'
});
console.log(formatter.format(-3, 'month'));
Результат:
-3 мес.
formatОсновной метод форматирования.
formatter.format(value, unit)
formatter.format(-10, 'second');
formatter.format(5, 'minute');
formatter.format(2, 'year');
Результаты:
10 секунд назад
через 5 минут
через 2 года
formatToPartsПозволяет разбить результат на токены.
const formatter = new Intl.RelativeTimeFormat('ru');
console.log(
formatter.formatToParts(-5, 'day')
);
Результат:
[
{ type: 'integer', value: '5', unit: 'day' },
{ type: 'literal', value: ' дней назад' }
]
formatToPartsМетод полезен:
const formatter = new Intl.RelativeTimeFormat('ru');
const parts = formatter.formatToParts(-5, 'minute');
const html = parts.map(part => {
if (part.type === 'integer') {
return `<strong>${part.value}</strong>`;
}
return part.value;
}).join('');
console.log(html);
Результат:
<strong>5</strong> минут назад
import React from 'react';
function RelativeDate({minutes}) {
const formatter = new Intl.RelativeTimeFormat('ru', {
numeric: 'auto'
});
return (
<span>
{formatter.format(-minutes, 'minute')}
</span>
);
}
FormatJS тесно интегрируется с react-intl.
FormattedRelativeTimeimport {FormattedRelativeTime} from 'react-intl';
function App() {
return (
<FormattedRelativeTime
value={-5}
unit="minute"
/>
);
}
Результат:
5 minutes ago
FormattedRelativeTime умеет автоматически
обновляться.
<FormattedRelativeTime
value={-30}
unit="second"
updateIntervalInSeconds={1}
/>
Компонент будет пересчитывать время каждую секунду.
import '@formatjs/intl-relativetimeformat/polyfill';
import '@formatjs/intl-relativetimeformat/locale-data/ru';
const formatter = new Intl.RelativeTimeFormat('ru');
console.log(
formatter.format(-7, 'day')
);
При большом количестве языков рекомендуется загружать локали динамически.
const loadedLocales = new Set();
async function ensureLocale(locale) {
if (loadedLocales.has(locale)) {
return;
}
await import(
`@formatjs/intl-relativetimeformat/locale-data/${locale}`
);
loadedLocales.add(locale);
}
Полифилл полностью поддерживает TypeScript.
const formatter = new Intl.RelativeTimeFormat('ru', {
numeric: 'auto'
});
const result: string =
formatter.format(-2, 'hour');
Intl.RelativeTimeFormat зависит от
Intl.PluralRules.
В старых браузерах может потребоваться дополнительный полифилл:
npm install @formatjs/intl-pluralrules
import '@formatjs/intl-pluralrules/polyfill';
import '@formatjs/intl-pluralrules/locale-data/ru';
После этого подключается RelativeTimeFormat:
import '@formatjs/intl-relativetimeformat/polyfill';
import '@formatjs/intl-relativetimeformat/locale-data/ru';
Корректная последовательность:
import '@formatjs/intl-pluralrules/polyfill';
import '@formatjs/intl-pluralrules/locale-data/ru';
import '@formatjs/intl-relativetimeformat/polyfill';
import '@formatjs/intl-relativetimeformat/locale-data/ru';
Типичная ошибка:
Missing locale data for locale: "ru"
Причина:
await import(
'@formatjs/intl-relativetimeformat/locale-data/ru'
);
Качество форматирования зависит от ICU-данных среды выполнения.
Особенно важно для:
При SSR необходимо:
await Promise.all([
import('@formatjs/intl-pluralrules/polyfill'),
import('@formatjs/intl-pluralrules/locale-data/ru'),
import('@formatjs/intl-relativetimeformat/polyfill'),
import('@formatjs/intl-relativetimeformat/locale-data/ru')
]);
Полифилл особенно актуален для:
Плохо:
import '@formatjs/intl-relativetimeformat/locale-data/*';
Хорошо:
import '@formatjs/intl-relativetimeformat/locale-data/ru';
import '@formatjs/intl-relativetimeformat/locale-data/en';
async function loadI18n(locale) {
await Promise.all([
import(
`@formatjs/intl-relativetimeformat/locale-data/${locale}`
)
]);
}
Ручной подход:
function format(minutes) {
return `${minutes} минут назад`;
}
Проблемы:
Intl.RelativeTimeFormatnew Intl.RelativeTimeFormat('fr')
il y a 5 minutes
1 минута
2 минуты
5 минут
Одинаковое поведение:
import '@formatjs/intl-relativetimeformat/polyfill';
import '@formatjs/intl-relativetimeformat/locale-data/ru';
const cache = new Map();
function getFormatter(locale) {
if (!cache.has(locale)) {
cache.set(
locale,
new Intl.RelativeTimeFormat(locale, {
numeric: 'auto',
style: 'long'
})
);
}
return cache.get(locale);
}
export function formatRelativeDate(value, unit, locale = 'ru') {
return getFormatter(locale)
.format(value, unit);
}
Создание экземпляров Intl.RelativeTimeFormat —
сравнительно дорогая операция.
Рекомендуется:
function render() {
const formatter =
new Intl.RelativeTimeFormat('ru');
return formatter.format(-5, 'minute');
}
const formatter =
new Intl.RelativeTimeFormat('ru');
function render() {
return formatter.format(-5, 'minute');
}
expect(
formatter.format(-1, 'day')
).toBe('вчера');
const locales = ['ru', 'en', 'fr'];
locales.forEach(locale => {
const formatter =
new Intl.RelativeTimeFormat(locale);
console.log(
formatter.format(-1, 'day')
);
});
Полифилл:
Хорошая практика — выносить подключение полифиллов в отдельный модуль:
// intl-setup.js
import '@formatjs/intl-pluralrules/polyfill';
import '@formatjs/intl-pluralrules/locale-data/ru';
import '@formatjs/intl-relativetimeformat/polyfill';
import '@formatjs/intl-relativetimeformat/locale-data/ru';
class RelativeTimeService {
constructor(locale) {
this.formatter =
new Intl.RelativeTimeFormat(locale, {
numeric: 'auto'
});
}
minutes(value) {
return this.formatter.format(
value,
'minute'
);
}
hours(value) {
return this.formatter.format(
value,
'hour'
);
}
days(value) {
return this.formatter.format(
value,
'day'
);
}
}
Часто используется вместе с:
@formatjs/intl-numberformat@formatjs/intl-datetimeformat@formatjs/intl-pluralrules@formatjs/intl-listformat@formatjs/intl-displaynamesimport '@formatjs/intl-pluralrules/polyfill';
import '@formatjs/intl-pluralrules/locale-data/ru';
import '@formatjs/intl-relativetimeformat/polyfill';
import '@formatjs/intl-relativetimeformat/locale-data/ru';
import '@formatjs/intl-numberformat/polyfill';
import '@formatjs/intl-numberformat/locale-data/ru';