Кастомизация форматирования выходит за рамки изменения строк локали. Иногда требуется изменить саму логику — как число вычисляется, как выбирается интервал, что происходит в граничных случаях. Библиотека предоставляет несколько точек расширения.
| Уровень | Механизм | Сложность |
|---|---|---|
| Строки локали | register(key, fn) |
Низкая |
| Логика выбора формы | Логика внутри функции локали | Средняя |
| Опорная дата | relativeDate в опциях |
Низкая |
| Пороги интервалов | Обёртка над format |
Средняя |
| Полная замена | Собственная функция вместо format |
Высокая |
Функция локали может содержать произвольную логику. Это позволяет полностью переопределить поведение для конкретного индекса:
import { register, format } fr om 'timeago.js';
register('ru_smart', (number, index) => {
// Специальное поведение для "только что"
if (index === 0) return ['только что', 'прямо сейчас'];
// Специальное поведение для 1 дня
if (index === 7) return ['вчера', 'завтра'];
// Специальное поведение для 2 дней
if (index === 8 && number === 2) return ['позавчера', 'послезавтра'];
// Стандартное поведение для остального
return standardRu(number, index);
});
Вместо изменения локали создаётся обёртка, меняющая вход или выход:
import { format } fr om 'timeago.js';
function smartFormat(date, locale = 'ru') {
const d = new Date(date);
const diff = Math.abs(Date.now() - d.getTime());
const DAY = 86400000;
const YEAR = 365 * DAY;
// Для очень старых дат — абсолютный формат
if (diff > YEAR * 2) {
return new Intl.DateTimeFormat('ru', {
year: 'numeric', month: 'long',
}).format(d);
}
return format(date, locale);
}
По умолчанию %s заменяется числом. Можно перехватить это
поведение:
register('ru_ordinal', (number, index) => {
const ordinal = number + '-й';
return [
[`${ordinal} день назад`, `через ${ordinal} день`],
][0];
});
Функция локали получает два аргумента: number и
index. Дополнительный контекст может передаваться через
замыкание:
function createContextLocale(context = 'default') {
return function(number, index) {
if (context === 'formal') {
return formalStrings(number, index);
}
return informalStrings(number, index);
};
}
import { register } fr om 'timeago.js';
register('ru_formal', createContextLocale('formal'));
register('ru_informal', createContextLocale('informal'));
register('ru_rounded', (number, index) => {
// Округлять до ближайшего удобного числа
let displayNumber = number;
if (index === 4 && number > 10) {
displayNumber = Math.round(number / 5) * 5; // 13 → 15, 27 → 25
}
return [
`${displayNumber} мин. назад`,
`через ${displayNumber} мин.`,
];
});
Если нужна принципиально иная логика расчёта:
function customRelativeTime(date, locale = 'ru') {
const now = Date.now();
const then = new Date(date).getTime();
const diff = Math.abs(now - then);
const isPast = then < now;
const steps = [
{ lim it: 30000, label: 'только что' },
{ lim it: 60000, label: `${Math.round(diff / 1000)} сек.` },
{ lim it: 3600000, label: `${Math.round(diff / 60000)} мин.` },
{ limit: 86400000, label: `${Math.round(diff / 3600000)} ч.` },
{ limit: 604800000, label: `${Math.round(diff / 86400000)} дн.` },
{ limit: Infinity, label: `${Math.round(diff / 604800000)} нед.` },
];
const step = steps.find(s => diff < s.limit);
const label = step ? step.label : 'давно';
if (label === 'только что') return label;
return isPast ? `${label} назад` : `через ${label}`;
}
Собственный рендер, который не только обновляет текст, но и меняет классы:
import { format } from 'timeago.js';
function semanticRender(elements, locale = 'ru') {
elements.forEach(el => {
const date = el.getAttribute('datetime');
function update() {
const diff = Date.now() - new Date(date).getTime();
el.textContent = format(date, locale);
el.classList.remove('fresh', 'recent', 'old');
if (diff < 300000) el.classList.add('fresh'); // < 5 мин
else if (diff < 86400000) el.classList.add('recent'); // < 1 дня
else el.classList.add('old');
}
update();
setInterval(update, 60000);
});
}
const formatMiddlewares = [];
function use(fn) {
formatMiddlewares.push(fn);
}
function applyFormat(date, locale) {
let result = format(date, locale);
for (const middleware of formatMiddlewares) {
result = middleware(result, date, locale);
}
return result;
}
// Добавить middleware
use((result) => result.toUpperCase());
use((result, date) => {
if (new Date(date).getTime() < Date.now()) return result;
return `⏳ ${result}`;
});