Паттерн адаптера оборачивает существующий API в новый интерфейс, совместимый с требованиями системы. В контексте timeago.js он позволяет подключить библиотеку туда, где ожидается другой интерфейс форматирования, или сменить реализацию без изменения потребителей.
// Существующий компонент ожидает функцию такого типа
type FormatFn = (date: string, options: { locale: string; relative: boolean }) => string;
// timeago.js имеет другую сигнатуру
// format(date: DateInput, locale?: string): string
// Прямое использование невозможно без адаптера
import { format } from 'timeago.js';
type AppFormatFn = (date: string, options: { locale: string; relative: boolean }) => string;
function timeagoAdapter(): AppFormatFn {
return (date, options) => {
if (!options.relative) {
return new Date(date).toLocaleDateString(options.locale.replace('_', '-'));
}
return format(date, options.locale);
};
}
const appFormat = timeagoAdapter();
appFormat('2025-05-31', { locale: 'ru', relative: true }); // "день назад"
// Система A ожидает один интерфейс, система B предоставляет другой
interface SystemAFormatter {
getRelativeTime(timestamp: number, lang: string): string;
}
// timeago.js → SystemA интерфейс
class TimeagoToSystemAAdapter implements SystemAFormatter {
getRelativeTime(timestamp: number, lang: string): string {
return format(new Date(timestamp), lang);
}
}
// date-fns → SystemA интерфейс
class DateFnsToSystemAAdapter implements SystemAFormatter {
getRelativeTime(timestamp: number, lang: string): string {
const locale = { ru, de, fr }[lang];
return formatDistanceToNow(new Date(timestamp), {
locale,
addSuffix: true,
});
}
}
// i18next ожидает функцию форматирования такого вида
type I18nextFormatter = (
value: unknown,
format: string,
lng: string
) => string;
function createTimeagoI18nextAdapter(): I18nextFormatter {
return (value, formatType, lng) => {
if (formatType !== 'relative') {
return String(value);
}
if (
typeof value !== 'string' &&
typeof value !== 'number' &&
!(value instanceof Date)
) {
return '';
}
return format(value, lng.replace('-', '_'));
};
}
// Регистрация в i18next
i18n.services.formatter.add('relative', (value, lng) => {
return format(value, lng.replace('-', '_'));
});
// Vue I18n форматирование дат
const datetimeFormats = {
ru: {
short: { year: 'numeric', month: 'short', day: 'numeric' },
},
};
// Кастомный форматтер — адаптер timeago.js под Vue I18n
const vueI18nTimeagoPlugin = {
install(app) {
app.config.globalProperties.$timeago = (date: string, locale = 'ru') => {
return format(date, locale);
};
},
};
import { Pipe, PipeTransform } from '@angular/core';
import { format } from 'timeago.js';
@Pipe({ name: 'timeago' })
export class TimeagoPipe implements PipeTransform {
transform(value: string | Date | number, locale = 'ru'): string {
if (!value) return '';
try {
return format(value, locale);
} catch {
return '';
}
}
}
const Handlebars = require('handlebars');
const { format } = require('timeago.js');
Handlebars.registerHelper('timeago', function(date, options) {
const locale = options?.hash?.locale ?? 'ru';
return format(new Date(date), locale);
});
{{timeago post.createdAt locale="ru"}}
const { format } = require('timeago.js');
// Middleware, добавляющее helper в locals
app.use((req, res, next) => {
res.locals.timeago = (date, locale = 'ru') => format(date, locale);
next();
});
<time datetime="<%= post.createdAt %>">
<%= timeago(post.createdAt, 'ru') %>
</time>
При смене timeago.js на другую библиотеку адаптер позволяет изменить реализацию в одном месте:
// src/adapters/time-format.ts
// Версия 1: timeago.js
export function relativeFormat(date: Date | string, locale = 'ru'): string {
return format(date, locale);
}
// Версия 2: Intl.RTF (замена без изменения потребителей)
export function relativeFormat(date: Date | string, locale = 'ru'): string {
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
const diffSec = (new Date(date as any).getTime() - Date.now()) / 1000;
// ...
}
Все компоненты импортируют relativeFormat из адаптера —
при смене реализации менять ничего не нужно.
| Сценарий | Описание |
|---|---|
| Несовместимый API | Обернуть timeago.js под ожидаемый интерфейс |
| i18n интеграция | Подключить как форматтер дат в i18next/Vue I18n |
| Шаблонизатор | Handlebars helper, EJS locals |
| Смена библиотеки | Изолировать зависимость для безболезненной замены |
| Серверный рендеринг | Express middleware с helpers |