Создание собственных локалей

Создание собственной локали позволяет полностью контролировать формат строк, грамматику, стиль обращения и набор используемых единиц времени. Это основной механизм кастомизации в timeago.js.


Структура функции локали

Локаль — это функция следующей сигнатуры:

function myLocale(number, index) {
  return [pastString, futureString];
}
  • number — количество единиц времени (число, используемое в %s)
  • index — индекс интервала от 0 до 13
  • Возвращает массив из двух строк: первая — для прошлого, вторая — для будущего

Минимальная рабочая локаль

import { register, format } from 'timeago.js';

register('simple', (number, index) => {
  return [
    ['только что',     'прямо сейчас'],      // 0
    ['%s сек. назад',  'через %s сек.'],      // 1
    ['%s сек. назад',  'через %s сек.'],      // 2
    ['1 мин. назад',   'через 1 мин.'],       // 3
    ['%s мин. назад',  'через %s мин.'],      // 4
    ['1 ч. назад',     'через 1 ч.'],         // 5
    ['%s ч. назад',    'через %s ч.'],        // 6
    ['1 д. назад',     'через 1 д.'],         // 7
    ['%s д. назад',    'через %s д.'],        // 8
    ['1 нед. назад',   'через 1 нед.'],       // 9
    ['%s нед. назад',  'через %s нед.'],      // 10
    ['1 мес. назад',   'через 1 мес.'],       // 11
    ['%s мес. назад',  'через %s мес.'],      // 12
    ['1 г. назад',     'через 1 г.'],         // 13
    ['%s л. назад',    'через %s л.'],        // 14
  ][index];
});

format(Date.now() - 1000 * 60 * 30, 'simple');
// → "30 мин. назад"

Локаль с правилами множественного числа (русский)

Русский язык имеет три формы:

  • 1, 21, 31 → “минута”
  • 2–4, 22–24 → “минуты”
  • 5–20, 25–30 → “минут”
import { register } from 'timeago.js';

function plural(n, one, few, many) {
  const mod100 = Math.abs(n) % 100;
  const mod10  = mod100 % 10;

  if (mod100 >= 11 && mod100 <= 14) return many;
  if (mod10 === 1) return one;
  if (mod10 >= 2 && mod10 <= 4) return few;
  return many;
}

register('ru_full', (number, index) => {
  const ago  = 'назад';
  const next = 'через';

  return [
    ['только что', 'прямо сейчас'],
    [`${number} ${plural(number, 'секунду', 'секунды', 'секунд')} ${ago}`,
     `${next} ${number} ${plural(number, 'секунду', 'секунды', 'секунд')}`],
    [`${number} ${plural(number, 'секунду', 'секунды', 'секунд')} ${ago}`,
     `${next} ${number} ${plural(number, 'секунду', 'секунды', 'секунд')}`],
    [`1 минуту ${ago}`, `${next} 1 минуту`],
    [`${number} ${plural(number, 'минуту', 'минуты', 'минут')} ${ago}`,
     `${next} ${number} ${plural(number, 'минуту', 'минуты', 'минут')}`],
    [`1 час ${ago}`, `${next} 1 час`],
    [`${number} ${plural(number, 'час', 'часа', 'часов')} ${ago}`,
     `${next} ${number} ${plural(number, 'час', 'часа', 'часов')}`],
    [`1 день ${ago}`, `${next} 1 день`],
    [`${number} ${plural(number, 'день', 'дня', 'дней')} ${ago}`,
     `${next} ${number} ${plural(number, 'день', 'дня', 'дней')}`],
    [`1 неделю ${ago}`, `${next} 1 неделю`],
    [`${number} ${plural(number, 'неделю', 'недели', 'недель')} ${ago}`,
     `${next} ${number} ${plural(number, 'неделю', 'недели', 'недель')}`],
    [`1 месяц ${ago}`, `${next} 1 месяц`],
    [`${number} ${plural(number, 'месяц', 'месяца', 'месяцев')} ${ago}`,
     `${next} ${number} ${plural(number, 'месяц', 'месяца', 'месяцев')}`],
    [`1 год ${ago}`, `${next} 1 год`],
    [`${number} ${plural(number, 'год', 'года', 'лет')} ${ago}`,
     `${next} ${number} ${plural(number, 'год', 'года', 'лет')}`],
  ][index];
});

Локаль с использованием Intl.PluralRules

Современный способ через встроенный API браузера:

import { register } from 'timeago.js';

const rules = new Intl.PluralRules('ru');

const forms = {
  second: { one: 'секунду', few: 'секунды', many: 'секунд', other: 'секунды' },
  minute: { one: 'минуту', few: 'минуты', many: 'минут', other: 'минуты' },
  hour:   { one: 'час',    few: 'часа',   many: 'часов', other: 'часа' },
  day:    { one: 'день',   few: 'дня',    many: 'дней',  other: 'дня' },
};

function plural(n, unit) {
  const rule = rules.select(n);
  return forms[unit][rule] || forms[unit].other;
}

register('ru_intl', (number, index) => {
  if (index === 0) return ['только что', 'прямо сейчас'];
  if (index <= 2) return [`${number} ${plural(number, 'second')} назад`, `через ${number} ${plural(number, 'second')}`];
  if (index <= 4) return [`${number} ${plural(number, 'minute')} назад`, `через ${number} ${plural(number, 'minute')}`];
  if (index <= 6) return [`${number} ${plural(number, 'hour')} назад`, `через ${number} ${plural(number, 'hour')}`];
  if (index <= 8) return [`${number} ${plural(number, 'day')} назад`, `через ${number} ${plural(number, 'day')}`];
  return [`${number} ед. назад`, `через ${number} ед.`];
});

Описательная локаль (без чисел)

register('ru_human', (number, index) => {
  return [
    ['только что', 'скоро'],
    ['несколько секунд назад', 'через несколько секунд'],
    ['менее минуты назад', 'менее чем через минуту'],
    ['около минуты назад', 'примерно через минуту'],
    ['несколько минут назад', 'через несколько минут'],
    ['около часа назад', 'примерно через час'],
    ['несколько часов назад', 'через несколько часов'],
    ['вчера', 'завтра'],
    ['несколько дней назад', 'через несколько дней'],
    ['на прошлой неделе', 'на следующей неделе'],
    ['несколько недель назад', 'через несколько недель'],
    ['в прошлом месяце', 'в следующем месяце'],
    ['несколько месяцев назад', 'через несколько месяцев'],
    ['в прошлом году', 'в следующем году'],
    ['давно', 'нескоро'],
  ][index];
});

Организация файла локали

Для больших проектов локаль выносится в отдельный файл:

// src/locales/ru.js

function pluralRu(n, one, few, many) {
  const m100 = Math.abs(n) % 100;
  const m10  = m100 % 10;
  if (m100 > 10 && m100 < 20) return many;
  if (m10 === 1) return one;
  if (m10 > 1 && m10 < 5) return few;
  return many;
}

export default function ru(number, index) {
  // ... строки
}
// src/main.js
import { register } from 'timeago.js';
import ru from './locales/ru.js';

register('ru', ru);

Тестирование локали

import { register, format } from 'timeago.js';
import myLocale from './locales/my_locale.js';

register('my', myLocale);

const cases = [
  [Date.now() - 1000 * 30,       'только что'],
  [Date.now() - 1000 * 60 * 5,   '5 мин. назад'],
  [Date.now() - 1000 * 3600 * 3, '3 ч. назад'],
  [Date.now() + 1000 * 60 * 10,  'через 10 мин.'],
];

cases.forEach(([date, expected]) => {
  const result = format(date, 'my');
  console.assert(result === expected, `Expected "${expected}", got "${result}"`);
});