Множественные формы — одна из ключевых задач при локализации относительного времени. В русском, украинском, польском и других славянских языках существует три и более формы числительных, что требует особой логики в функции локали.
В английском языке две формы: 1 minute и
2 minutes. В русском — три:
1 минуту (одиночная: оканчивается на 1, кроме 11)
2 минуты (дробная: оканчивается на 2–4, кроме 12–14)
5 минут (множественная: всё остальное)
Без учёта этих правил будут появляться ошибки: “2 минут назад” вместо “2 минуты назад”.
function pluralRu(number, one, few, many) {
const n = Math.abs(number) % 100;
const n1 = n % 10;
if (n >= 11 && n <= 19) return many;
if (n1 === 1) return one;
if (n1 >= 2 && n1 <= 4) return few;
return many;
}
Примеры:
pluralRu(1, 'минуту', 'минуты', 'минут') // → "минуту"
pluralRu(2, 'минуту', 'минуты', 'минут') // → "минуты"
pluralRu(5, 'минуту', 'минуты', 'минут') // → "минут"
pluralRu(11, 'минуту', 'минуты', 'минут') // → "минут"
pluralRu(21, 'минуту', 'минуты', 'минут') // → "минуту"
pluralRu(22, 'минуту', 'минуты', 'минут') // → "минуты"
import { register } from 'timeago.js';
function pluralRu(n, one, few, many) {
const mod100 = 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_plural', (number, index) => {
const pairs = [
['только что', 'прямо сейчас'],
[
`${number} ${pluralRu(number, 'секунду', 'секунды', 'секунд')} назад`,
`через ${number} ${pluralRu(number, 'секунду', 'секунды', 'секунд')}`,
],
[
`${number} ${pluralRu(number, 'секунду', 'секунды', 'секунд')} назад`,
`через ${number} ${pluralRu(number, 'секунду', 'секунды', 'секунд')}`,
],
['1 минуту назад', 'через 1 минуту'],
[
`${number} ${pluralRu(number, 'минуту', 'минуты', 'минут')} назад`,
`через ${number} ${pluralRu(number, 'минуту', 'минуты', 'минут')}`,
],
['1 час назад', 'через 1 час'],
[
`${number} ${pluralRu(number, 'час', 'часа', 'часов')} назад`,
`через ${number} ${pluralRu(number, 'час', 'часа', 'часов')}`,
],
['1 день назад', 'через 1 день'],
[
`${number} ${pluralRu(number, 'день', 'дня', 'дней')} назад`,
`через ${number} ${pluralRu(number, 'день', 'дня', 'дней')}`,
],
['1 неделю назад', 'через 1 неделю'],
[
`${number} ${pluralRu(number, 'неделю', 'недели', 'недель')} назад`,
`через ${number} ${pluralRu(number, 'неделю', 'недели', 'недель')}`,
],
['1 месяц назад', 'через 1 месяц'],
[
`${number} ${pluralRu(number, 'месяц', 'месяца', 'месяцев')} назад`,
`через ${number} ${pluralRu(number, 'месяц', 'месяца', 'месяцев')}`,
],
['1 год назад', 'через 1 год'],
[
`${number} ${pluralRu(number, 'год', 'года', 'лет')} назад`,
`через ${number} ${pluralRu(number, 'год', 'года', 'лет')}`,
],
];
return pairs[index];
});
const pluralRules = new Intl.PluralRules('ru');
function getPluralForm(number, forms) {
const category = pluralRules.select(number); // 'one', 'few', 'many', 'other'
return forms[category] || forms.other;
}
const minuteForms = {
one: 'минуту',
few: 'минуты',
many: 'минут',
other: 'минуты',
};
getPluralForm(1, minuteForms); // → "минуту"
getPluralForm(3, minuteForms); // → "минуты"
getPluralForm(10, minuteForms); // → "минут"
Польский требует четырёх форм:
function pluralPl(n, one, few, many, other) {
if (n === 1) return one;
const mod100 = n % 100;
const mod10 = n % 10;
if (mod10 >= 2 && mod10 <= 4 && !(mod100 >= 12 && mod100 <= 14)) return few;
if (mod10 === 1 && mod100 !== 11) return one;
return mod10 >= 5 ? many : other;
}
const pluralAr = new Intl.PluralRules('ar');
function getArabicForm(n, forms) {
return forms[pluralAr.select(n)] || forms.other;
}
Арабский использует формы: zero, one,
two, few, many,
other.
function makePlural(locale, forms) {
const rules = new Intl.PluralRules(locale);
return (n) => forms[rules.select(n)] || forms.other;
}
const ruMinute = makePlural('ru', {
one: 'минуту',
few: 'минуты',
many: 'минут',
other: 'минуты',
});
ruMinute(1); // → "минуту"
ruMinute(2); // → "минуты"
ruMinute(11); // → "минут"
const testCases = [1, 2, 3, 4, 5, 10, 11, 12, 20, 21, 22, 100, 101];
testCases.forEach(n => {
console.log(`${n}: ${pluralRu(n, 'минуту', 'минуты', 'минут')}`);
});
Ожидаемый вывод:
1: минуту
2: минуты
3: минуты
4: минуты
5: минут
10: минут
11: минут
12: минут
20: минут
21: минуту
22: минуты
100: минут
101: минуту
Некоторые временны́е интервалы требуют согласования не только существительного, но и предлога или формы существительного с учётом падежа:
1 минуту назад (вин. падеж)
2 минуты назад (род. падеж)
5 минут назад (род. мн. ч.)
через 1 минуту (вин. падеж)
через 2 минуты (род. падеж)
через 5 минут (род. мн. ч.)
Правильная реализация учитывает падежные формы, а не только грамматическое число.