Локализация в timeago.js построена на функциональной модели: каждая локаль представлена функцией, которая на основе числа и индекса интервала возвращает строку. Этот подход позволяет реализовать любую грамматику — от простых языков с одной формой множественного числа до языков со сложными падежными и числовыми системами.
Каждая локаль — это функция следующей сигнатуры:
type LocaleFunc = (number: number, index: number, totalSeconds?: number) => [string, string];
Аргументы:
number — количество единиц времени (например,
5 для “5 минут”)index — индекс временного интервала (0–13)totalSeconds — общая разница в секундах (доступна в
некоторых версиях)Возвращаемое значение — массив из двух строк:
[прошлое, будущее].
| index | Прошлое (пример) | Будущее (пример) |
|---|---|---|
| 0 | только что | прямо сейчас |
| 1 | 1 секунду назад | через 1 секунду |
| 2 | X секунд назад | через X секунд |
| 3 | 1 минуту назад | через 1 минуту |
| 4 | X минут назад | через X минут |
| 5 | 1 час назад | через 1 час |
| 6 | X часов назад | через X часов |
| 7 | 1 день назад | через 1 день |
| 8 | X дней назад | через X дней |
| 9 | 1 неделю назад | через 1 неделю |
| 10 | X недель назад | через X недель |
| 11 | 1 месяц назад | через 1 месяц |
| 12 | X месяцев назад | через X месяцев |
| 13 | 1 год назад | через 1 год |
| 14 | X лет назад | через X лет |
В строках локали %s заменяется числом при формировании
результата:
"%s минут назад" → "5 минут назад"
"через %s часов" → "через 3 часов"
Для индексов с единственным числом (индексы 3, 5, 7, 9, 11, 13)
плейсхолдер %s обычно не нужен, так как число там
фиксировано (1).
import { register } from 'timeago.js';
register('de', (number, index) => {
return [
['gerade eben', 'gleich'],
['vor %s Sekunden', 'in %s Sekunden'],
['vor 1 Minute', 'in 1 Minute'],
['vor %s Minuten', 'in %s Minuten'],
['vor 1 Stunde', 'in 1 Stunde'],
['vor %s Stunden', 'in %s Stunden'],
['vor 1 Tag', 'in 1 Tag'],
['vor %s Tagen', 'in %s Tagen'],
['vor 1 Woche', 'in 1 Woche'],
['vor %s Wochen', 'in %s Wochen'],
['vor 1 Monat', 'in 1 Monat'],
['vor %s Monaten', 'in %s Monaten'],
['vor 1 Jahr', 'in 1 Jahr'],
['vor %s Jahren', 'in %s Jahren'],
][index];
});
Для языков типа турецкого или японского, где множественное число не изменяется:
register('tr', (number, index) => {
return [
['az önce', 'az sonra'],
['%s saniye önce', '%s saniye sonra'],
['1 dakika önce', '1 dakika sonra'],
['%s dakika önce', '%s dakika sonra'],
['1 saat önce', '1 saat sonra'],
['%s saat önce', '%s saat sonra'],
['1 gün önce', '1 gün sonra'],
['%s gün önce', '%s gün sonra'],
['1 hafta önce', '1 hafta sonra'],
['%s hafta önce', '%s hafta sonra'],
['1 ay önce', '1 ay sonra'],
['%s ay önce', '%s ay sonra'],
['1 yıl önce', '1 yıl sonra'],
['%s yıl önce', '%s yıl sonra'],
][index];
});
Русский язык требует трёх форм в зависимости от окончания числа:
function pluralRu(number, one, few, many) {
const n = Math.abs(number) % 100;
const n1 = n % 10;
if (n > 10 && n < 20) return many;
if (n1 > 1 && n1 < 5) return few;
if (n1 === 1) return one;
return many;
}
register('ru', (number, index) => {
return [
['только что', 'прямо сейчас'],
[`${number} секунду назад`, `через ${number} секунду`],
[`${number} ${pluralRu(number, 'секунду', 'секунды', 'секунд')} назад`,
`через ${number} ${pluralRu(number, 'секунду', 'секунды', 'секунд')}`],
['1 минуту назад', 'через 1 минуту'],
[`${number} ${pluralRu(number, 'минуту', 'минуты', 'минут')} назад`,
`через ${number} ${pluralRu(number, 'минуту', 'минуты', 'минут')}`],
// ... и так далее
][index];
});
timeago.js поставляется с набором готовых локалей. Путь к ним:
timeago.js/esm/lang/ru.js
timeago.js/esm/lang/zh_CN.js
timeago.js/esm/lang/de.js
timeago.js/esm/lang/fr.js
timeago.js/esm/lang/ja.js
timeago.js/esm/lang/ko.js
timeago.js/esm/lang/es.js
import { register, format } from 'timeago.js';
import ru from 'timeago.js/esm/lang/ru';
register('ru', ru);
format(Date.now() - 3600000, 'ru');
// → "1 час назад"
Каждая локаль обязана предоставить оба варианта строки — для прошлого и для будущего. Библиотека автоматически выбирает нужный:
const [past, future] = locale(number, index);
return diff < 0 ? past : future;
Функция локали может быть динамической — принимать решение в runtime:
register('ru_dynamic', (number, index) => {
const base = getBaseStrings(index); // загружается из словаря
return [
base.past.replace('%n', number),
base.future.replace('%n', number),
];
});