Регистрация расширений — это механизм, при котором расширение объявляет себя доступным и получает доступ к API. В контексте timeago.js это касается как локалей, так и кастомных модулей поведения.
import { register } from 'timeago.js';
// Простейшая регистрация
register('my_locale', (number, index) => {
const forms: [string, string][] = [
['только что', 'сейчас'],
['%s с назад', 'через %s с'],
// ... индексы 2-14
];
return forms[index] ?? ['давно', 'потом'];
});
Зарегистрировать локаль нужно один раз — до первого вызова
format с этой локалью.
import { register, format as baseFormat, render as baseRender } from 'timeago.js';
import type { LocaleFunc } from 'timeago.js';
interface Extension {
name: string;
version: string;
install: (registry: ExtensionRegistry) => void;
}
class ExtensionRegistry {
private installed = new Map<string, string>(); // name → version
private locales = new Set<string>();
use(extension: Extension): this {
if (this.installed.has(extension.name)) {
console.warn(`Extension "${extension.name}" already installed`);
return this;
}
extension.install(this);
this.installed.set(extension.name, extension.version);
return this;
}
registerLocale(code: string, fn: LocaleFunc): void {
if (this.locales.has(code)) {
console.warn(`Locale "${code}" already registered`);
return;
}
register(code, fn);
this.locales.add(code);
}
hasLocale(code: string): boolean {
return this.locales.has(code);
}
getInstalled(): Map<string, string> {
return new Map(this.installed);
}
}
export const registry = new ExtensionRegistry();
// extensions/ru-formal.ts
import type { LocaleFunc } from 'timeago.js';
const fn: LocaleFunc = (n, i) => {
const forms: [string, string][] = [
['только что', 'в ближайшее время'],
['%s секунду назад', 'через %s секунду'],
['%s секунды назад', 'через %s секунды'],
['%s секунд назад', 'через %s секунд'],
['минуту назад', 'через минуту'],
['%s минуты назад', 'через %s минуты'],
['%s минут назад', 'через %s минут'],
['час назад', 'через час'],
['%s часа назад', 'через %s часа'],
['%s часов назад', 'через %s часов'],
['день назад', 'через день'],
['%s дня назад', 'через %s дня'],
['%s дней назад', 'через %s дней'],
['год назад', 'через год'],
['%s лет назад', 'через %s лет'],
];
return forms[i] ?? ['давно', 'скоро'];
};
export const RuFormalExtension = {
name: 'ru-formal',
version: '1.0.0',
install: (reg: ExtensionRegistry) => {
reg.registerLocale('ru_formal', fn);
},
};
interface CacheExtensionOptions {
ttl?: number;
maxSize?: number;
}
function createCacheExtension(opts: CacheExtensionOptions = {}) {
const { ttl = 30_000, maxSize = 500 } = opts;
return {
name: 'cache',
version: '1.0.0',
install(reg: ExtensionRegistry) {
// Регистрация поведения через monkey-patch
const cache = new Map<string, { value: string; ts: number }>();
reg.wrapFormat((original) => (date, locale, options) => {
if (cache.size >= maxSize) {
const firstKey = cache.keys().next().value;
cache.delete(firstKey);
}
const key = `${date}:${locale}`;
const cached = cache.get(key);
const now = Date.now();
if (cached && now - cached.ts < ttl) {
return cached.value;
}
const value = original(date, locale, options);
cache.set(key, { value, ts: now });
return value;
});
},
};
}
Порядок имеет значение, если расширения оборачивают один и тот же метод:
registry
.use(ValidationExtension) // Первый — проверяет входные данные
.use(CacheExtension) // Второй — кеширует результат
.use(LogExtension); // Третий — логирует каждый вызов
Стек выполнения (снаружи внутрь):
LogExtension → CacheExtension → ValidationExtension → baseFormat
// Расширение регистрирует себя при импорте
// extensions/auto-register-ru.ts
import { register } from 'timeago.js';
import ruLocale from 'timeago.js/esm/lang/ru';
// Выполняется при импорте файла
register('ru', ruLocale);
export {}; // Пустой экспорт для ESM совместимости
// Использование
import './extensions/auto-register-ru';
import { format } from 'timeago.js';
format(new Date(), 'ru'); // ru уже зарегистрирована
const REGISTERED_LOCALES = new Set<string>();
const originalRegister = register;
const trackedRegister = (locale: string, fn: LocaleFunc): void => {
REGISTERED_LOCALES.add(locale);
originalRegister(locale, fn);
};
function isLocaleRegistered(code: string): boolean {
return REGISTERED_LOCALES.has(code);
}
function requireLocale(code: string): void {
if (!isLocaleRegistered(code)) {
throw new Error(`Locale "${code}" is not registered. Call register("${code}", fn) first.`);
}
}
// src/lib/timeago-setup.ts
import { registry } from './registry';
import { RuFormalExtension } from '../extensions/ru-formal';
import { createCacheExtension } from '../extensions/cache';
import { LogExtension } from '../extensions/log';
export function setupTimeago(): void {
registry
.use(RuFormalExtension)
.use(createCacheExtension({ ttl: 60_000 }))
.use(LogExtension);
}
// В точке входа приложения
setupTimeago();