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

Локаль в Day.js определяет:

  • язык отображения дат;
  • формат месяцев и дней недели;
  • правила сокращений;
  • формат времени и даты;
  • относительное время (3 минуты назад);
  • правила календарного отображения;
  • начало недели;
  • правила склонений и множественного числа.

Стандартные локали покрывают большинство популярных языков, однако в крупных проектах часто требуется собственная локализация:

  • корпоративный формат дат;
  • нестандартные сокращения;
  • специализированная терминология;
  • локали для внутренних систем;
  • кастомные правила отображения времени;
  • псевдоязыки для тестирования интерфейсов.

Подключение локализации

Для работы с локалями используется модуль locale.

import dayjs from 'dayjs'

import 'dayjs/locale/ru'

dayjs.locale('ru')

console.log(dayjs().format('MMMM'))

Результат:

май

Структура локали

Локаль представляет собой объект конфигурации.

Минимальная структура:

const customLocale = {
    name: 'custom',
    months: [],
    weekdays: [],
    formats: {}
}

Чаще всего локаль содержит значительно больше настроек.

Полный пример:

const customLocale = {
    name: 'custom',

    weekdays: [
        'Воскресенье',
        'Понедельник',
        'Вторник',
        'Среда',
        'Четверг',
        'Пятница',
        'Суббота'
    ],

    weekdaysShort: [
        'Вс',
        'Пн',
        'Вт',
        'Ср',
        'Чт',
        'Пт',
        'Сб'
    ],

    weekdaysMin: [
        'Вс',
        'Пн',
        'Вт',
        'Ср',
        'Чт',
        'Пт',
        'Сб'
    ],

    months: [
        'Январь',
        'Февраль',
        'Март',
        'Апрель',
        'Май',
        'Июнь',
        'Июль',
        'Август',
        'Сентябрь',
        'Октябрь',
        'Ноябрь',
        'Декабрь'
    ],

    monthsShort: [
        'Янв',
        'Фев',
        'Мар',
        'Апр',
        'Май',
        'Июн',
        'Июл',
        'Авг',
        'Сен',
        'Окт',
        'Ноя',
        'Дек'
    ],

    weekStart: 1,

    yearStart: 4,

    formats: {
        LT: 'HH:mm',
        LTS: 'HH:mm:ss',
        L: 'DD.MM.YYYY',
        LL: 'D MMMM YYYY',
        LLL: 'D MMMM YYYY HH:mm',
        LLLL: 'dddd, D MMMM YYYY HH:mm'
    },

    relativeTime: {
        future: 'через %s',
        past: '%s назад',
        s: 'несколько секунд',
        m: 'минута',
        mm: '%d минут',
        h: 'час',
        hh: '%d часов',
        d: 'день',
        dd: '%d дней',
        M: 'месяц',
        MM: '%d месяцев',
        y: 'год',
        yy: '%d лет'
    }
}

Регистрация локали

Для регистрации используется метод locale.

dayjs.locale(customLocale)

После регистрации локаль становится доступной глобально.

Также можно сразу активировать её:

dayjs.locale(customLocale, null, true)

Использование имени локали

Поле name обязательно.

const customLocale = {
    name: 'my-locale'
}

После регистрации:

dayjs.locale('my-locale')

Локальное переключение языка

Глобальная локаль влияет на все экземпляры Day.js.

dayjs.locale('ru')

Иногда требуется локальное переключение.

const date = dayjs().locale('en')

console.log(date.format('MMMM'))

При этом глобальная локаль не изменяется.


Создание локали на основе существующей

Часто проще модифицировать готовую локаль.

import updateLocale from 'dayjs/plugin/updateLocale'

dayjs.extend(updateLocale)

Изменение русской локали:

dayjs.updateLocale('ru', {
    monthsShort: [
        'янв.',
        'фев.',
        'мар.',
        'апр.',
        'мая',
        'июн.',
        'июл.',
        'авг.',
        'сен.',
        'окт.',
        'ноя.',
        'дек.'
    ]
})

Настройка месяцев

Полные названия месяцев:

months: [
    'Январь',
    'Февраль',
    'Март'
]

Короткие названия:

monthsShort: [
    'Янв',
    'Фев',
    'Мар'
]

Использование:

console.log(dayjs().format('MMMM'))
console.log(dayjs().format('MMM'))

Динамические названия месяцев

В некоторых языках форма месяца зависит от контекста.

Day.js поддерживает функции.

months: (dayjsInstance, format) => {
    const months = [
        'января',
        'февраля',
        'марта'
    ]

    return months[dayjsInstance.month()]
}

Настройка дней недели

Полные названия:

weekdays: [
    'Воскресенье',
    'Понедельник',
    'Вторник'
]

Сокращённые:

weekdaysShort: [
    'Вс',
    'Пн',
    'Вт'
]

Минимальные:

weekdaysMin: [
    'Вс',
    'Пн',
    'Вт'
]

Начало недели

Параметр weekStart определяет первый день недели.

weekStart: 1

Значения:

Значение День
0 Воскресенье
1 Понедельник

Настройка форматов даты

Раздел formats задаёт шаблоны форматирования.

formats: {
    LT: 'HH:mm',
    LTS: 'HH:mm:ss',
    L: 'DD.MM.YYYY',
    LL: 'D MMMM YYYY'
}

Использование:

console.log(dayjs().format('L'))
console.log(dayjs().format('LL'))

Пользовательские форматы

Можно создавать собственные обозначения.

formats: {
    CUSTOM: 'DD/MM/YYYY HH:mm'
}

Однако встроенный format() не распознаёт произвольные ключи автоматически.

Поэтому обычно используют стандартные обозначения:

  • L
  • LL
  • LLL
  • LLLL

Относительное время

Для поддержки относительного времени нужен плагин.

import relativeTime from 'dayjs/plugin/relativeTime'

dayjs.extend(relativeTime)

Пример локали:

relativeTime: {
    future: 'через %s',
    past: '%s назад',
    s: 'несколько секунд',
    m: 'минута',
    mm: '%d минут',
    h: 'час',
    hh: '%d часов'
}

Использование:

dayjs().from(dayjs().subtract(3, 'hour'))

Результат:

3 часа назад

Функции в relativeTime

Для сложной грамматики можно использовать функции.

mm: function(number) {
    if (number === 1) {
        return '1 минута'
    }

    if (number < 5) {
        return `${number} минуты`
    }

    return `${number} минут`
}

Настройка календарного отображения

Используется плагин calendar.

import calendar from 'dayjs/plugin/calendar'

dayjs.extend(calendar)

Пример:

calendar: {
    sameDay: '[Сегодня в] HH:mm',
    nextDay: '[Завтра в] HH:mm',
    nextWeek: 'dddd [в] HH:mm',
    lastDay: '[Вчера в] HH:mm',
    lastWeek: '[В прошлый] dddd [в] HH:mm',
    sameElse: 'DD.MM.YYYY'
}

Использование:

dayjs().calendar()

Склонения и сложная грамматика

Для славянских языков часто требуются сложные правила.

Пример:

relativeTime: {
    mm(number) {
        const lastDigit = number % 10
        const lastTwoDigits = number % 100

        if (lastDigit === 1 && lastTwoDigits !== 11) {
            return `${number} минута`
        }

        if (
            lastDigit >= 2 &&
            lastDigit <= 4 &&
            !(lastTwoDigits >= 12 && lastTwoDigits <= 14)
        ) {
            return `${number} минуты`
        }

        return `${number} минут`
    }
}

Локаль с корпоративным форматом

Во внутренних системах часто используются специальные обозначения.

const enterpriseLocale = {
    name: 'enterprise',

    formats: {
        L: 'YYYY/MM/DD',
        LL: '[Отчёт от] DD.MM.YYYY',
        LLL: 'DD.MM.YYYY HH:mm:ss'
    }
}

Псевдолокали

Псевдолокали используются для тестирования интерфейсов.

Пример:

const pseudoLocale = {
    name: 'pseudo',

    months: [
        '[ЯНВАРЬ]',
        '[ФЕВРАЛЬ]',
        '[МАРТ]'
    ]
}

Это помогает:

  • проверять переполнение интерфейса;
  • тестировать длинные строки;
  • искать жёстко закодированный текст.

Изоляция локалей

Локаль может применяться только к конкретному экземпляру.

const ruDate = dayjs().locale('ru')
const enDate = dayjs().locale('en')

Такой подход особенно важен:

  • в SSR;
  • в Node.js;
  • в многопользовательских приложениях;
  • в API.

Глобальная проблема локалей в SSR

Глобальная локаль:

dayjs.locale('ru')

изменяет состояние библиотеки.

В SSR это может привести к конфликтам между запросами.

Безопаснее:

dayjs().locale('ru')

Создание локали для нескольких регионов

Часто язык один, но форматы разные.

Пример:

const ruKzLocale = {
    name: 'ru-kz',

    formats: {
        L: 'DD.MM.YYYY'
    }
}

const ruRuLocale = {
    name: 'ru-ru',

    formats: {
        L: 'YYYY-MM-DD'
    }
}

Наследование локалей

Удобный способ — копирование базовой локали.

import 'dayjs/locale/ru'

const newLocale = {
    ...dayjs.Ls.ru,

    name: 'ru-custom',

    weekStart: 1
}

Регистрация:

dayjs.locale(newLocale)

Внутреннее хранилище локалей

Day.js хранит локали в объекте Ls.

console.log(dayjs.Ls)

Получение локали:

console.log(dayjs.Ls.ru)

Динамическая загрузка локалей

Для оптимизации бандла локали часто загружаются динамически.

async function loadLocale(locale) {
    await import(`dayjs/locale/${locale}`)

    dayjs.locale(locale)
}

Локали и tree shaking

Импорт:

import 'dayjs/locale/ru'

включает локаль в бандл.

Если импортировать десятки локалей статически:

import 'dayjs/locale/ru'
import 'dayjs/locale/de'
import 'dayjs/locale/fr'

размер сборки значительно увеличится.


Создание полностью кастомной локали

Пример полноценной локали:

const customLocale = {
    name: 'project-x',

    weekdays: [
        'Sun-X',
        'Mon-X',
        'Tue-X',
        'Wed-X',
        'Thu-X',
        'Fri-X',
        'Sat-X'
    ],

    weekdaysShort: [
        'SU',
        'MO',
        'TU',
        'WE',
        'TH',
        'FR',
        'SA'
    ],

    weekdaysMin: [
        'S',
        'M',
        'T',
        'W',
        'T',
        'F',
        'S'
    ],

    months: [
        'Month-1',
        'Month-2',
        'Month-3',
        'Month-4',
        'Month-5',
        'Month-6',
        'Month-7',
        'Month-8',
        'Month-9',
        'Month-10',
        'Month-11',
        'Month-12'
    ],

    monthsShort: [
        'M1',
        'M2',
        'M3',
        'M4',
        'M5',
        'M6',
        'M7',
        'M8',
        'M9',
        'M10',
        'M11',
        'M12'
    ],

    formats: {
        LT: 'HH:mm',
        LTS: 'HH:mm:ss',
        L: 'YYYY-MM-DD',
        LL: 'D MMMM YYYY',
        LLL: 'D MMMM YYYY HH:mm',
        LLLL: 'dddd, D MMMM YYYY HH:mm'
    },

    relativeTime: {
        future: 'in %s',
        past: '%s ago',
        s: 'seconds',
        m: 'minute',
        mm: '%d minutes',
        h: 'hour',
        hh: '%d hours',
        d: 'day',
        dd: '%d days',
        M: 'month',
        MM: '%d months',
        y: 'year',
        yy: '%d years'
    },

    weekStart: 1,

    yearStart: 4
}

Регистрация:

dayjs.locale(customLocale)

Использование:

const date = dayjs().locale('project-x')

console.log(date.format('LLLL'))