Создание компонента обратного отсчета на нескольких языках

Несколько лет назад я создал компонент обратного отсчета с поддержкой нескольких языков. Редакторам приходилось заполнять такие метки, как «дни», «часы» и «минуты» для каждого языка — как в единственном, так и во множественном числе. К счастью, с тех пор ситуация значительно улучшилась!

В этом руководстве мы создадим ванильный компонент обратного отсчета, который будет работать со всеми языками — без необходимости заполнять какие-либо метки для каждого языка.

Мы будем использовать API Intl.RelativeTimeFormat и Intl.NumberFormat для создания такого компонента обратного отсчета:

Затем, просто изменив lang-атрибут, мы создадим такие варианты, как этот:

lang="fa-IR"
Вход в полноэкранный режим Выход из полноэкранного режима

lang="zh-Hans-CN-u-nu-hanidec"
Войти в полноэкранный режим Выход из полноэкранного режима

lang="fr-FR"
Войти в полноэкранный режим Выход из полноэкранного режима

Круто, правда? Давайте начнем!


HTML

Мы будем использовать тег <time> с конечной датой, заданной в свойстве datetime:

<time datetime="2023-01-01"></time>
Войти в полноэкранный режим Выход из полноэкранного режима

Если вы хотите, чтобы обратный отсчет заканчивался в определенное время, также добавьте это свойство в строку:

<time datetime="2022-08-12T09:30:00+02:00"></time> 
Ввести полноэкранный режим Выйти из полноэкранного режима

ПРИМЕЧАНИЕ: Не забудьте добавить смещение часового пояса того места, где заканчивается отсчет. Это не должно быть время сервера, так как ваше местоположение может находиться в центральной Европе, в то время как ваш сервер/CDN находится в США. В примере выше смещение равно +02:00.


JavaScript

Создадим новую функцию с одним аргументом, element, который является ссылкой на основной элемент (<time>):

function countDown(element) { ... }
Вход в полноэкранный режим Выход из полноэкранного режима

Далее мы установим некоторые константы и значения по умолчанию:

Локаль

Локаль — самая важная часть. Этот код будет искать lang-атрибут на главном элементе, затем — если он не найден — на самой странице, и, наконец, вернет обратный вариант, используя en-US:

const locale = element.lang || document.documentElement.getAttribute('lang') || 'en-US';
Вход в полноэкранный режим Выход из полноэкранного режима

Конечная дата/время

Конечное время — это datetime-атрибут основного элемента, преобразованный во время:

const endTime = new Date(element.getAttribute('datetime')).getTime();
Вход в полноэкранный режим Выход из полноэкранного режима

API

Наконец, мы создаем экземпляр Intl.RelativeTimeFormat — а также сохраняем const со значением locale равным нулю (подробнее об этом позже!):

const rtf = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });
const zero = new Intl.NumberFormat(locale).format(0);
Вход в полноэкранный режим Выход из полноэкранного режима

Отображение времени

Теперь о коде, который возвращает и форматирует время и его подчасти:

const showTime = () => {
  const remainingTime = getRemainingTime(endTime);
  element.innerHTML = 
    timePart(Math.floor(remainingTime / (24 * 60 * 60 * 1000)), 'day') +
    timePart(Math.floor((remainingTime / (60 * 60 * 1000)) % 24), 'hour') +
    timePart(Math.floor((remainingTime / (60 * 1000)) % 60), 'minute') +
    timePart(Math.floor((remainingTime / 1000) % 60), 'second');
}
Войти в полноэкранный режим Выход из полноэкранного режима

Функция вызывает вспомогательный метод, который вычитает текущее время из ‘end-time’:

const getRemainingTime = (endTime, currentTime = new Date().getTime()) => endTime - currentTime;
Войти в полноэкранный режим Выйти из полноэкранного режима

… а также timePart, самую важную функцию, которая возвращает время в локальном формате:

const timePart = (part, type) => {
  const parts = rtf.formatToParts(part === 0 ? 2 : part, type);
  if (parts && parts.length === 3) parts.shift();
  const [unit, label] = parts; 
  return `<span><strong>${part === 0 ? zero : unit.value}</strong><small>${label.value}</small></span>`
}
Войти в полноэкранный режим Выход из полноэкранного режима

formatToParts возвращает массив единиц времени и меток на локальном языке. Мы раскладываем их на unit и label, и выводим в strong> и <small>-тегах (не стесняйтесь заменить их на любые теги, которые вам нужны!).

requestAnimationFrame

Функция showTime должна вызывать себя постоянно, для чего мы используем requestAnimationFrame:

if (remainingTime >= 1000) requestAnimationFrame(showTime);
Вход в полноэкранный режим Выход из полноэкранного режима

Ноль — это множественное число

Теперь вы можете удивиться, почему я вызываю formatToParts с 2, если само значение 0 (ноль). Это потому, что Intl.RelativeTimeFormat вернет строку «now» (на локальном языке), если значение равно нулю — и никакой метки (по какой-то причине).

Мы не хотим этого, но мы также не хотим показывать английский ноль в языках, где есть свой собственный ноль!

Вот почему в начале мы объявили это:

const zero = new Intl.NumberFormat(locale).format(0);
Войти в полноэкранный режим Выйти из полноэкранного режима

Для метки нам нужно значение больше, чем 1. Если в качестве примера мы используем «секунды», то значение 1 вернет метку «секунда», а значение 2 вернет «секунды». Ноль — это множественное число (вы говорите «ноль секунд», а не «ноль секунд»), поэтому 2 😄.

Запутались? Я тоже!


Наконец, чтобы инициализировать и запустить его:

requestAnimationFrame(showTime);
Войти в полноэкранный режим Выйти из полноэкранного режима

Фух! Много кода, но всего около 400 байт при минимизации и сжатии!


CSS

CSS — это просто сетка, подробности смотрите в демонстрации ниже. Мне нравится использовать CSS Custom Props для частей компонента, которые могут иметь вариации. Я предпочитаю формат [component]-[part]-[emmet abbrevation of property], так:

.variant {
--countdown-bgc: hsl(0, 35%, 45%);
--countdown-time-bgc: hsl(0, 35%, 80%);
--countdown-time-lbl-c: hsl(0, 35%, 15%);
--countdown-time-val-c: hsl(0, 35%, 25%);
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Демонстрация

Ниже приведен Codepen. Не стесняйтесь использовать его и изменять локали:

Оцените статью
devanswers.ru
Добавить комментарий