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

Библиотека Globalize предоставляет гибкий механизм форматирования данных на основе стандарта CLDR (Common Locale Data Repository). Помимо встроенных методов форматирования дат, чисел и валют, библиотека позволяет создавать собственные паттерны отображения значений. Это особенно важно в крупных приложениях, где стандартных локализационных форматов недостаточно.

Пользовательские паттерны используются для:

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

Архитектура форматирования в Globalize

Внутри Globalize форматирование строится поверх данных CLDR. Библиотека не содержит жёстко закодированных шаблонов — она использует локализационные данные конкретной культуры.

Основные механизмы:

Компонент Назначение
CLDR Источник локализованных данных
Globalize API форматирования
Formatter Скомпилированная функция форматирования
Skeleton / Pattern Описание формата

Подключение модулей форматирования

Для работы с пользовательскими паттернами требуется подключение соответствующих модулей.

const Globalize = require("globalize");

require("cldr-data").entireSupplemental();
require("cldr-data").entireMainFor("ru");

Globalize.load(
    require("cldr-data/supplemental/likelySubtags.json"),
    require("cldr-data/main/ru/numbers.json"),
    require("cldr-data/main/ru/ca-gregorian.json")
);

Globalize.locale("ru");

Форматирование чисел с пользовательскими паттернами

Базовый принцип

Метод:

Globalize.numberFormatter(options)

создаёт функцию форматирования с заданными параметрами.

Пример:

const formatter = Globalize.numberFormatter({
    minimumFractionDigits: 2,
    maximumFractionDigits: 2
});

console.log(formatter(15));
// 15,00

Пользовательское количество знаков после запятой

const formatter = Globalize.numberFormatter({
    minimumFractionDigits: 3,
    maximumFractionDigits: 5
});

console.log(formatter(12.3));
// 12,300

Форматирование с группировкой разрядов

const formatter = Globalize.numberFormatter({
    useGrouping: true
});

console.log(formatter(1000000));
// 1 000 000

Отключение группировки:

const formatter = Globalize.numberFormatter({
    useGrouping: false
});

console.log(formatter(1000000));
// 1000000

Пользовательские паттерны процентов

const formatter = Globalize.numberFormatter({
    style: "percent",
    minimumFractionDigits: 1
});

console.log(formatter(0.256));
// 25,6 %

Форматирование валют

const formatter = Globalize.currencyFormatter("USD", {
    minimumFractionDigits: 2
});

console.log(formatter(1500));
// 1 500,00 $

Контроль отображения валюты

const formatter = Globalize.currencyFormatter("EUR", {
    style: "symbol"
});

Возможные значения:

Значение Описание
symbol Символ валюты
code Код валюты
name Полное название

Форматирование отрицательных чисел

Во многих системах требуется нестандартное отображение отрицательных значений.

Например:

const formatter = Globalize.numberFormatter({
    minimumFractionDigits: 2
});

console.log(formatter(-1500.5));
// -1 500,50

Однако иногда требуется бухгалтерский формат:

(1 500,50)

Стандартный API Globalize напрямую не поддерживает произвольные маски отрицательных значений, поэтому обычно применяется дополнительная обёртка:

const formatter = Globalize.numberFormatter({
    minimumFractionDigits: 2
});

function accountingFormat(value) {
    if (value < 0) {
        return `(${formatter(Math.abs(value))})`;
    }

    return formatter(value);
}

console.log(accountingFormat(-1500.5));
// (1 500,50)

Пользовательские паттерны дат

Форматирование через dateFormatter

const formatter = Globalize.dateFormatter();

console.log(formatter(new Date()));

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

const formatter = Globalize.dateFormatter({
    datetime: "medium"
});

Возможные варианты:

Значение Описание
short Краткий формат
medium Средний
long Расширенный
full Полный

Использование skeleton-паттернов

Skeleton — это декларативное описание структуры даты.

Пример:

const formatter = Globalize.dateFormatter({
    skeleton: "yMMMd"
});

console.log(formatter(new Date()));

Результат для русской локали:

5 июл. 2025 г.

Наиболее распространённые skeleton-паттерны

Skeleton Формат
yMd дата
yMMMd сокращённый месяц
yMMMMd полный месяц
Hm часы и минуты
Hms часы, минуты, секунды
EEEE день недели

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

const formatter = Globalize.dateFormatter({
    skeleton: "Hm"
});

console.log(formatter(new Date()));
// 14:35

Форматирование даты и времени

const formatter = Globalize.dateFormatter({
    skeleton: "yMMMMdHm"
});

console.log(formatter(new Date()));

Форматирование только месяца и года

const formatter = Globalize.dateFormatter({
    skeleton: "yMMMM"
});

console.log(formatter(new Date()));

Компиляция formatter-функций

Причина использования compile-подхода

Globalize создаёт formatter-функции заранее. Это снижает нагрузку при многократном форматировании.

const numberFormatter = Globalize.numberFormatter({
    minimumFractionDigits: 2
});

const dateFormatter = Globalize.dateFormatter({
    skeleton: "yMMMd"
});

Далее функции переиспользуются:

items.forEach(item => {
    console.log(numberFormatter(item.price));
});

Кастомные обёртки форматирования

Централизованный сервис форматирования

В крупных проектах обычно создаётся единый слой форматирования.

class FormatterService {

    constructor(locale) {
        this.globalize = new Globalize(locale);

        this.currency = this.globalize.currencyFormatter("USD");

        this.date = this.globalize.dateFormatter({
            skeleton: "yMMMd"
        });

        this.number = this.globalize.numberFormatter({
            minimumFractionDigits: 2
        });
    }

    formatCurrency(value) {
        return this.currency(value);
    }

    formatDate(value) {
        return this.date(value);
    }

    formatNumber(value) {
        return this.number(value);
    }
}

Создание собственных паттернов поверх Globalize

Пример корпоративного денежного формата

class MoneyFormatter {

    constructor(locale, currencyCode) {

        this.formatter =
            new Globalize(locale)
                .currencyFormatter(currencyCode, {
                    minimumFractionDigits: 2,
                    maximumFractionDigits: 2
                });
    }

    format(value) {

        const result = this.formatter(Math.abs(value));

        if (value < 0) {
            return `[${result}]`;
        }

        return result;
    }
}

Форматирование единиц измерения

Сам Globalize ограниченно работает с unit formatting, поэтому часто используются пользовательские паттерны.

const formatter = Globalize.numberFormatter({
    minimumFractionDigits: 1
});

function formatWeight(value) {
    return `${formatter(value)} кг`;
}

console.log(formatWeight(15.2));
// 15,2 кг

Комбинированные паттерны

Сложное форматирование интерфейсов

const dateFormatter = Globalize.dateFormatter({
    skeleton: "yMMMd"
});

const numberFormatter = Globalize.numberFormatter({
    minimumFractionDigits: 2
});

function buildReport(date, amount) {

    return `
Дата: ${dateFormatter(date)}
Сумма: ${numberFormatter(amount)}
`;
}

Динамическое изменение паттернов

Выбор формата в runtime

function createFormatter(type) {

    switch(type) {

        case "money":
            return Globalize.numberFormatter({
                minimumFractionDigits: 2
            });

        case "integer":
            return Globalize.numberFormatter({
                maximumFractionDigits: 0
            });

        case "percent":
            return Globalize.numberFormatter({
                style: "percent"
            });

        default:
            return Globalize.numberFormatter();
    }
}

Использование разных локалей

Форматирование в нескольких культурах

const ru = new Globalize("ru");
const en = new Globalize("en");

const ruFormatter = ru.numberFormatter();
const enFormatter = en.numberFormatter();

console.log(ruFormatter(123456.78));
// 123 456,78

console.log(enFormatter(123456.78));
// 123,456.78

Кэширование formatter-объектов

Причина кэширования

Создание formatter-функций является сравнительно дорогой операцией.

Неправильно:

function render(value) {

    const formatter =
        Globalize.numberFormatter({
            minimumFractionDigits: 2
        });

    return formatter(value);
}

Правильно:

const formatter =
    Globalize.numberFormatter({
        minimumFractionDigits: 2
    });

function render(value) {
    return formatter(value);
}

Пользовательские паттерны для логирования

Форматирование диагностических сообщений

const dateFormatter = Globalize.dateFormatter({
    skeleton: "yMdHms"
});

function log(message) {

    const timestamp =
        dateFormatter(new Date());

    console.log(
        `[${timestamp}] ${message}`
    );
}

Работа с parseFormatter

Парсинг пользовательских форматов

Globalize поддерживает обратное преобразование строк.

const parser =
    Globalize.numberParser();

console.log(
    parser("1 500,25")
);

// 1500.25

Парсинг дат

const parser =
    Globalize.dateParser({
        skeleton: "yMd"
    });

console.log(
    parser("10.07.2025")
);

Ограничения пользовательских паттернов

Отсутствие прямой поддержки mask-based форматов

В отличие от некоторых библиотек:

  • Moment.js
  • Numeral.js
  • date-fns

Globalize не использует строковые маски вида:

"DD.MM.YYYY"
"#,###.00"

Вместо этого применяется декларативный API CLDR.


Причины такого подхода

Основные преимущества CLDR-модели:

Особенность Преимущество
Локализация Автоматическая адаптация
Стандартизация Совместимость ICU
Масштабируемость Поддержка множества языков
Предсказуемость Единое поведение

Практический пример системы форматирования

Универсальный форматтер

class I18nFormatter {

    constructor(locale) {

        this.globalize =
            new Globalize(locale);

        this.formatters = {
            money:
                this.globalize.currencyFormatter("USD"),

            percent:
                this.globalize.numberFormatter({
                    style: "percent"
                }),

            integer:
                this.globalize.numberFormatter({
                    maximumFractionDigits: 0
                }),

            date:
                this.globalize.dateFormatter({
                    skeleton: "yMMMd"
                })
        };
    }

    format(type, value) {

        const formatter =
            this.formatters[type];

        if (!formatter) {
            throw new Error(
                `Unknown formatter: ${type}`
            );
        }

        return formatter(value);
    }
}

Производительность пользовательских паттернов

Основные рекомендации

Повторно использовать formatter-функции

const formatter =
    Globalize.numberFormatter();

Избегать создания formatter внутри циклов

Плохо:

items.forEach(item => {

    const formatter =
        Globalize.numberFormatter();

    console.log(
        formatter(item.price)
    );
});

Хорошо:

const formatter =
    Globalize.numberFormatter();

items.forEach(item => {
    console.log(
        formatter(item.price)
    );
});

Кэшировать formatter по locale

const cache = new Map();

function getFormatter(locale) {

    if (!cache.has(locale)) {

        cache.set(
            locale,
            new Globalize(locale)
                .numberFormatter()
        );
    }

    return cache.get(locale);
}

Частые ошибки

Отсутствие CLDR-данных

E_MISSING_CLDR

Причина:

Globalize.load(...)

не был вызван.


Использование неподдерживаемого skeleton

Globalize.dateFormatter({
    skeleton: "INVALID"
});

Создание formatter при каждом рендере

Особенно критично для:

  • React;
  • Vue;
  • Angular;
  • серверного SSR.

Интеграция с UI-компонентами

Пример для React

const formatter =
    Globalize.numberFormatter({
        minimumFractionDigits: 2
    });

function Price({ value }) {

    return (
        <span>
            {formatter(value)}
        </span>
    );
}

Сравнение skeleton и стандартных preset

datetime preset

Globalize.dateFormatter({
    datetime: "short"
});

Простой способ быстрого форматирования.


skeleton

Globalize.dateFormatter({
    skeleton: "yMMMMd"
});

Более гибкий и точный подход.


Когда использовать skeleton

Сценарий Решение
Простое приложение datetime
Точный контроль skeleton
Корпоративные системы skeleton
Много локалей skeleton

Формирование пользовательских DSL-форматов

Иногда создаётся собственный язык шаблонов поверх Globalize.

Пример:

const patterns = {

    MONEY: {
        minimumFractionDigits: 2
    },

    INTEGER: {
        maximumFractionDigits: 0
    },

    PERCENT: {
        style: "percent"
    }
};

function createPattern(name) {

    return Globalize.numberFormatter(
        patterns[name]
    );
}

Такой подход часто используется:

  • в CMS;
  • ERP-системах;
  • BI-панелях;
  • конструкторах отчётов;
  • enterprise-интерфейсах.