Форматирование с Intl
Число уроков и дата публикации должны храниться в удобной для обработки форме, но показываться понятным текстом. Ручная склейка цифр, запятых и окончаний быстро становится хрупкой. Объекты Intl предоставляют форматирование с учётом выбранного языка и параметров представления.
Продолжим самостоятельный снимок advanced/lesson-29. Момент публикации остаётся фиксированным значением прошлого урока, а количества 1,2,5 и 21 помогают увидеть формы русского слова «урок». Программа пока не запускалась. Ожидаемое содержание показано по смыслу, а точные пробельные символы и пунктуация могут зависеть от данных локали браузера.
Число как данные и строка как результат
Создадим форматтер числа один раз, затем будем использовать его для нескольких подписей:
const number = new Intl.NumberFormat('ru-RU');
console.log(number.format(12000));
По смыслу ожидается двенадцать тысяч с принятым локалью разделением групп. Это строка для человека, а не новое числовое значение для суммы. Если затем понадобится сложить количество уроков, используем исходное число, не пытаясь разобрать оформленную подпись.
Неразрывный пробел и обычный пробел визуально могут выглядеть похоже. Поэтому сравнение буквального текста с вручную записанным ASCII-пробелом не является лучшим способом понять результат. Для точного составного интерфейса есть formatToParts, который отделяет цифровые группы и разделители. В нашем маленьком каталоге готовой строки достаточно.
Локаль ru-RU выбирается явно. Она не обязана совпадать с языком меню браузера или системой читателя. Если язык сайта можно менять, решение о локали принадлежит настройке интерфейса. Не используйте случайное значение из внешних данных без политики допустимых языков и запасного варианта.
Основные возможности представлены в MDN об Intl. Форматтер помогает показать число, но не проверяет предметный диапазон, не округляет бизнес-правила самостоятельно и не знает, что величина означает именно уроки.
Категория множественного числа
Одно окончание не подходит для всех количеств. Intl.PluralRules выбирает категорию, а приложение связывает её с нужным словом. Категория не является готовым переводом существительного: словарь всё равно задаётся нами.
const plural = new Intl.PluralRules('ru-RU');
const words = {
one: 'урок',
few: 'урока',
many: 'уроков',
other: 'урока',
};
function lessonLabel(count) {
return `${number.format(count)} ${words[plural.select(count)]}`;
}
console.log([1, 2, 5, 21].map(lessonLabel));
Ожидаемые подписи имеют смысл «1 урок», «2 урока», «5 уроков», «21 урок». Сами числа передаются модели без готовых окончаний. Словарь соответствует выбранному русскому существительному; другая локаль и другое слово потребуют собственного набора форм.
Не проверяйте только последнюю цифру без исключений. Например, окончания для чисел 11–14 отличаются от привычных примеров 1–4. Правила языка включают больше случаев, чем короткая ветка if с остатком от деления. Механизм категорий описан в MDN о PluralRules.
Наше количество уроков является целым положительным числом. Если подпись используется для дробной величины, категории и смысл слова могут отличаться. Форматтер не отменяет контракт модели. Нельзя разрешить любую дробь только потому, что она получила грамматическую форму.
Язык и зона даты
Один момент прошлого урока покажем с явно выбранной зоной:
const date = new Intl.DateTimeFormat('ru-RU', {
timeZone: 'Europe/Minsk',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
});
console.log(date.format(new Date('2026-10-11T09:00:00Z')));
Язык определяет оформление, зона определяет локальные календарные компоненты момента. Эти настройки независимы: русский текст можно показать для UTC, а английский — для Минска. По выбранному договору ожидается дата 11 октября 2026 года и местное время момента в указанной зоне.
Без timeZone форматтер обычно использует зону среды. Тогда два читателя увидят разные часы, что может быть правильным продуктовым решением, но должно быть осознанным. В учебном опыте выбираем зону явно ради понятного сравнения. Мы не измеряли реальную настройку компьютера и не утверждаем её значение.
Зона задаётся идентификатором, а не произвольной подписью города. Неверное значение параметра может вызвать исключение при создании форматтера. Форматирование и поддерживаемые варианты описаны в MDN о DateTimeFormat.
Повторное использование и границы оформления
Создавать новый форматтер для каждой из тысячи карточек без причины не требуется. Настройки одинаковы, поэтому один экземпляр можно использовать многократно. Это ясная организация зависимости; мы не приводим выдуманного измерения ускорения и не объявляем её единственной оптимизацией сайта.
При смене локали или зоны нужно создать экземпляр с новой конфигурацией. Само изменение переменной с названием языка не перенастраивает уже созданный объект. Форматтер относится к определённому набору параметров, а не является живой связью с любым внешним состоянием.
Также не храните форматированную дату как единственный источник. После смены языка её пришлось бы разбирать обратно, причём исходный момент и зона могли бы потеряться. Храните ISO-момент или календарные компоненты по договору, а подпись вычисляйте у границы отображения.
Для цены понадобились бы другие параметры, включая код валюты и политику дробных разрядов. Красивое форматирование числа не превращает учебное количество в корректный денежный расчёт. Не смешиваем эти темы с подписью уроков; у финансовых данных собственный контракт и точность.
Ручная сверка результата
Ожидаемо значения 1,2,5 и 21 получают разные формы. Дополните опыт числами 11,12,14,22 и 25, сохранив исходную модель. Затем задайте UTC вместо Минска: текст останется русским, а часы представят тот же момент в другой зоне. Данные publishedAt не изменяются.
Наблюдение без обратного разбора подписи
Возьмите значения одиннадцать, двадцать один и двадцать пять. Первое должно получить форму «уроков», второе — «урок», третье — «уроков». Если сравнивать только числа из первого маленького массива, легко не заметить исключение для одиннадцати. Смысл ручного опыта состоит в проверке выбранной категории, а не только в том, что строка вообще появилась в консоли.
Поле lessons нашей модели всегда остаётся числом. Оно используется для суммы и сортировки, а lessonLabel вызывается на границе вывода. Если заменить число строкой с окончанием внутри данных, прежняя функция суммы начнёт получать другой тип. Поэтому красивое оформление не должно распространяться обратно в контракт каталога.
При смене языка создавайте согласованный набор зависимостей. Русский формат числа с английским словарём может быть намеренным дизайном, но чаще является пропущенным обновлением части интерфейса. Настройка локали должна управлять форматтером числа, правилами количества и словарём подписей совместно. В снимке язык фиксирован, поэтому отсутствует обещание полной системы переводов.
Для сравнения зон создайте второй форматтер с теми же языковыми настройками и timeZone: 'UTC'. Оба получают один и тот же Date; меняется представление, а не объект события. Ожидается различие часов и, для некоторых моментов, дня. Не меняйте исходную строку времени одновременно с зоной, иначе опыт не покажет, какая именно причина изменила подпись.
Параметры форматирования также требуют согласования. Если нужны только день, месяц и год, не добавляйте часы ради демонстрации API. Если нужна финансовая сумма, потребуется явная валюта и собственное правило округления данных. Наш пример ограничен количеством уроков и датой публикации. Повторное использование форматтера здесь сохраняет ясный договор и уменьшает дублирование настройки; измеренную экономию времени мы не заявляем.
Если точный формат строки важен для дальнейшего кода, это сигнал неправильной границы: вычисления должны работать с данными, а не с оформленным текстом. Строку передаём в textContent, не трактуем как HTML. Следующий урок рассмотрит сами строки: видимая буква, кодовая единица и составной символ не всегда совпадают.