Независимо от используемого фреймворка, интеграция timeago.js следует нескольким устоявшимся паттернам. Знание этих паттернов позволяет быстро применять библиотеку в любом контексте.
Создать единую точку форматирования для приложения:
// src/lib/time.js
import { format } from 'timeago.js';
const DEFAULT_LOCALE = 'ru';
export function ago(date, locale = DEFAULT_LOCALE) {
if (!date) return '';
const d = new Date(date);
if (isNaN(d.getTime())) return '';
return format(d, locale);
}
Все компоненты используют ago() вместо прямых вызовов
format.
// src/lib/timeago-setup.js
import { register } from 'timeago.js';
const LOCALES = import.meta.glob('./locales/*.js');
export async function setupTimeago(localeKey = 'ru') {
const path = `./locales/${localeKey}.js`;
if (LOCALES[path]) {
const mod = await LOCALES[path]();
register(localeKey, mod.default);
}
}
// src/lib/time-adapter.js
import { format } from 'timeago.js';
export function fromAPI(apiDate, locale = 'ru') {
// API возвращает строку без Z
const normalized = apiDate.includes('Z') ? apiDate : apiDate + 'Z';
return format(normalized, locale);
}
export function fromDB(dbDate, locale = 'ru') {
// MySQL: "2025-05-26 10:00:00"
const isoDate = dbDate.replace(' ', 'T') + 'Z';
return format(isoDate, locale);
}
export function fromUnix(seconds, locale = 'ru') {
return format(seconds * 1000, locale);
}
import { format } from 'timeago.js';
export function smartAgo(date, { locale = 'ru', maxDays = 30 } = {}) {
const d = new Date(date);
const age = (Date.now() - d.getTime()) / 86400000;
if (age > maxDays) {
return new Intl.DateTimeFormat(locale, {
day: 'numeric', month: 'long', year: 'numeric',
}).format(d);
}
return format(d, locale);
}
Для ванильного JS — класс, управляющий монтированием/размонтированием:
import { render, cancel } from 'timeago.js';
export class TimeagoManager {
#locale;
#elements = new WeakSet();
constructor(locale = 'ru') {
this.#locale = locale;
}
mount(container, selector = '[datetime]') {
const nodes = container.querySelectorAll(selector);
render(nodes, this.#locale);
}
unmount(container, selector = '[datetime]') {
const nodes = container.querySelectorAll(selector);
cancel(nodes);
}
refresh(container, selector = '[datetime]') {
this.unmount(container, selector);
this.mount(container, selector);
}
setLocale(locale) {
this.#locale = locale;
}
}
import { format, register } from 'timeago.js';
const loadedLocales = new Set(['en_US']);
export async function formatLazy(date, locale = 'ru') {
if (!loadedLocales.has(locale)) {
try {
const mod = await import(`timeago.js/esm/lang/${locale}.js`);
register(locale, mod.default);
loadedLocales.add(locale);
} catch {
return format(date, 'en_US');
}
}
return format(date, locale);
}
Добавлять timeAgo при маппинге данных из API:
import { format } from 'timeago.js';
function enrichPosts(posts, locale = 'ru') {
return posts.map(post => ({
...post,
timeAgo: format(post.createdAt, locale),
}));
}
const posts = enrichPosts(await fetchPosts(), 'ru');
На сервере генерируется абсолютная дата (стабильная), на клиенте заменяется относительной:
// Сервер
const html = `<time data-timeago datetime="${post.createdAt}">
${new Intl.DateTimeFormat('ru').format(new Date(post.createdAt))}
</time>`;
// Клиент
import { render } from 'timeago.js';
document.addEventListener('DOMContentLoaded', () => {
const nodes = document.querySelectorAll('[data-timeago]');
render(nodes, 'ru');
});
import { cancel, render } from 'timeago.js';
const SELECTOR = '[data-timeago]';
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
const nodes = document.querySelectorAll(SELECTOR);
cancel(nodes);
render(nodes, 'ru');
}
});
register вызывается один раз при старте
приложения.cancel вызывается при каждом размонтировании.cancel вызывается перед повторным
render.format, не
render.render.