В библиотеке date-fns локализация реализована через
набор JavaScript-объектов, описывающих правила форматирования, парсинга
и языковых преобразований для конкретного языка. В отличие от монолитных
систем интернационализации, локали здесь представляют собой модульные
структуры, подключаемые точечно и не влияющие на глобальное
состояние.
Локаль в date-fns определяет:
Ключевая особенность подхода — отсутствие скрытой магии: вся логика локализации явно выражена через функции.
Типичная структура локали в date-fns выглядит как набор
функций и словарей:
const locale = {
code: 'xx',
formatDistance,
formatLong,
formatRelative,
localize,
match,
options
}
Каждое поле отвечает за отдельный слой локализации:
formatDistance — формирование строк относительного
времениformatLong — правила длинных и коротких форматов
датformatRelative — форматирование дат относительно
текущего моментаlocalize — преобразование числовых значений в
текстmatch — обратное сопоставление строк с датамиМодуль formatLong отвечает за шаблоны представления дат
в разных контекстах: полная дата, время, комбинированные форматы.
Структура обычно включает функции:
datetimedateTimeПример реализации:
const formatLong = {
date: (options) => {
return options?.width === 'short'
? 'dd.MM.yyyy'
: 'd MMMM yyyy'
},
time: (options) => {
return options?.width === 'short'
? 'HH:mm'
: 'HH:mm:ss'
},
dateTime: () => {
return 'd MMMM yyyy, HH:mm'
}
}
Функции принимают объект options, позволяющий изменять
формат в зависимости от контекста (короткий, средний, длинный).
Функция formatDistance определяет, как будут выглядеть
выражения вроде:
Сигнатура обычно выглядит так:
function formatDistance(token, count, options) {
return string
}
token описывает тип интервала, например:
lessThanXMinutesxMinutesxHoursxDaysaboutXMonthsПример реализации:
const formatDistance = (token, count, options) => {
const result = {
lessThanXMinutes: 'менее минуты',
xMinutes: `${count} минут`,
xHours: `${count} часов`,
xDays: `${count} дней`,
aboutXMonths: `около ${count} месяцев`
}
return result[token]
}
Более сложные реализации учитывают:
formatRelative отвечает за выражения вида:
Функция получает:
lastWeek, yesterday,
today, tomorrow, nextWeek)Пример:
const formatRelative = (token, date, baseDate, options) => {
const formats = {
lastWeek: "'в прошлую неделю в' HH:mm",
yesterday: "'вчера в' HH:mm",
today: "'сегодня в' HH:mm",
tomorrow: "'завтра в' HH:mm",
nextWeek: "'на следующей неделе в' HH:mm"
}
return formats[token]
}
В реальных локалях часто учитываются:
localize — один из самых объёмных модулей локали. Он
отвечает за преобразование чисел и кодов в текстовые представления.
Обычно включает:
Структура:
const localize = {
month: (n, options) => string,
day: (n, options) => string,
ordinalNumber: (n, options) => string,
quarter: (n, options) => string,
dayPeriod: (period, options) => string
}
Пример:
const localize = {
month: (n) => {
const months = [
'январь', 'февраль', 'март', 'апрель',
'май', 'июнь', 'июль', 'август',
'сентябрь', 'октябрь', 'ноябрь', 'декабрь'
]
return months[n]
},
day: (n) => {
const days = [
'воскресенье', 'понедельник', 'вторник',
'среда', 'четверг', 'пятница', 'суббота'
]
return days[n]
},
ordinalNumber: (n) => `${n}-й`
}
Особое внимание уделяется порядковым числительным, поскольку в разных языках они формируются по-разному:
Модуль match используется при парсинге строковых дат. Он
определяет, как текстовые значения преобразуются обратно в числа.
Структура обычно зеркальна localize:
const match = {
month: (string) => number,
day: (string) => number,
ordinalNumber: (string) => number,
dayPeriod: (string) => value
}
Пример:
const match = {
month: (str) => {
const map = {
'январь': 0,
'февраль': 1,
'март': 2
}
return map[str.toLowerCase()]
}
}
Часто используется сопоставление через:
Полная кастомная локаль формируется путём реализации всех ключевых модулей.
Пример минимальной локали:
export const customLocale = {
code: 'custom',
formatLong: {
date: () => 'yyyy-MM-dd',
time: () => 'HH:mm',
dateTime: () => 'yyyy-MM-dd HH:mm'
},
formatDistance: (token, count) => {
const map = {
xMinutes: `${count}m`,
xHours: `${count}h`,
xDays: `${count}d`
}
return map[token]
},
formatRelative: (token) => {
const map = {
today: 'сегодня',
yesterday: 'вчера',
tomorrow: 'завтра'
}
return map[token]
},
localize: {
month: (n) => String(n + 1),
day: (n) => String(n),
ordinalNumber: (n) => `${n}`
},
match: {
month: (str) => Number(str) - 1,
day: (str) => Number(str)
}
}
Такая реализация минимальна и подходит только для контролируемых систем, где входные данные ограничены.
Внутри date-fns существует набор вспомогательных функций
для построения локалей:
buildFormatLongbuildLocalizebuildMatchPatternbuildMatchОни позволяют уменьшить дублирование кода и стандартизировать структуру.
Пример использования:
import buildLocalize from 'date-fns/locale/_lib/buildLocalize'
const localize = buildLocalize({
values: {
month: [...],
day: [...]
},
defaultWidth: 'wide'
})
Такой подход используется в официальных локалях, чтобы избежать ручной реализации повторяющейся логики.
Часто кастомная локаль строится поверх существующей, например поверх
enUS или ru.
Подход заключается в:
Пример:
import { enUS } from 'date-fns/locale'
export const extendedLocale = {
...enUS,
localize: {
...enUS.localize,
month: (n) => `Month ${n + 1}`
}
}
Такой способ позволяет сохранять совместимость с внутренними
механизмами date-fns.
Одной из частых проблем становится неполное покрытие токенов. При отсутствии нужного ключа поведение может становиться непредсказуемым.
Другие распространённые ошибки:
formatDistance;localize и
match;formatLong и реальными
форматами парсинга.Особенно критично расхождение между localize.month и
match.month, так как оно приводит к невозможности обратного
преобразования даты из строки.