Кастомизация данных

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

  • переопределить формат дат;
  • добавить собственные единицы измерения;
  • внедрить нестандартные валютные обозначения;
  • изменить plural-правила;
  • сократить объём подключаемых данных;
  • добавить корпоративные или отраслевые обозначения;
  • адаптировать локализацию под бизнес-логику.

Кастомизация в Globalize строится вокруг модификации и расширения CLDR-структур.


Архитектура данных CLDR

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

Основная схема выглядит так:

const Globalize = require("globalize");
const Cldr = require("cldrjs");

Cldr.load(...cldrData);

Globalize.locale("ru");

После загрузки данные попадают в глобальное хранилище CLDR, откуда Globalize извлекает информацию о:

  • форматировании;
  • календарях;
  • правилах склонения;
  • валютах;
  • часовых поясах;
  • единицах измерения;
  • системах счисления.

Структура CLDR-данных

Большинство данных организовано в древовидной JSON-структуре.

Пример фрагмента локали:

{
  "main": {
    "ru": {
      "numbers": {
        "symbols-numberSystem-latn": {
          "decimal": ",",
          "group": " "
        }
      }
    }
  }
}

Основные секции:

Раздел Назначение
numbers Форматирование чисел
currencies Валюты
ca-gregorian Календарь
dates Форматы дат
units Единицы измерения
messages Сообщения
supplemental Глобальные правила

Загрузка собственных данных

Добавление пользовательского JSON

Globalize позволяет загружать произвольные структуры через Cldr.load().

Cldr.load({
  customData: {
    projectName: "Enterprise CRM",
    timezoneLabel: "МСК"
  }
});

Получение данных:

const cldr = new Cldr("ru");

console.log(
  cldr.get("customData/projectName")
);

Результат:

Enterprise CRM

Переопределение существующих значений

Изменение символов форматирования

Можно заменить разделители чисел.

Исходное значение:

1 234,56

Кастомизация:

Cldr.load({
  main: {
    ru: {
      numbers: {
        "symbols-numberSystem-latn": {
          decimal: ".",
          group: "_"
        }
      }
    }
  }
});

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

Globalize.locale("ru");

const formatter =
  Globalize.numberFormatter();

console.log(formatter(1234.56));

Результат:

1_234.56

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

Изменение шаблонов

Стандартные шаблоны CLDR можно переопределять.

Cldr.load({
  main: {
    ru: {
      dates: {
        calendars: {
          gregorian: {
            dateFormats: {
              short: "dd.MM.yyyy",
              medium: "d MMM yyyy",
              long: "d MMMM yyyy 'года'"
            }
          }
        }
      }
    }
  }
});

Форматирование:

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

console.log(
  formatter(new Date(2026, 4, 12))
);

Кастомизация валют

Изменение отображения валюты

CLDR хранит названия валют в секции currencies.

Cldr.load({
  main: {
    ru: {
      numbers: {
        currencies: {
          USD: {
            displayName: "доллар США",
            symbol: "US$"
          }
        }
      }
    }
  }
});

Пример:

const formatter =
  Globalize.currencyFormatter("USD");

console.log(formatter(150));

Результат:

US$150.00

Добавление новой валюты

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

Cldr.load({
  main: {
    ru: {
      numbers: {
        currencies: {
          COIN: {
            displayName: "Coin",
            symbol: "ⓒ"
          }
        }
      }
    }
  }
});

Форматирование:

const formatter =
  Globalize.currencyFormatter("COIN");

console.log(formatter(500));

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

Расширение units

Cldr.load({
  main: {
    ru: {
      units: {
        long: {
          "digital-packet": {
            displayName: "пакет",
            unitPattern_count_one: "{0} пакет",
            unitPattern_count_few: "{0} пакета",
            unitPattern_count_many: "{0} пакетов"
          }
        }
      }
    }
  }
});

Получение данных:

const cldr = new Cldr("ru");

console.log(
  cldr.get(
    "main/ru/units/long/digital-packet"
  )
);

Работа с plural-формами

Plural-правила используются при склонении слов.

Globalize опирается на CLDR plural categories:

  • zero
  • one
  • two
  • few
  • many
  • other

Для русского языка:

Число Категория
1 one
2 few
5 many

Проверка:

const pluralGenerator =
  Globalize.pluralGenerator();

console.log(pluralGenerator(1));
console.log(pluralGenerator(2));
console.log(pluralGenerator(5));

Переопределение plural-правил

CLDR позволяет внедрять собственные правила.

Cldr.load({
  supplemental: {
    "plurals-type-cardinal": {
      ru: {
        pluralRuleCountOne:
          "i = 1 and v = 0"
      }
    }
  }
});

Подобная кастомизация применяется редко, поскольку может нарушить корректность локализации.


Пользовательские сообщения

Интеграция с MessageFormatter

Globalize поддерживает ICU MessageFormat.

const formatter =
  Globalize.messageFormatter(
    "{count, plural, " +
    "one {# файл} " +
    "few {# файла} " +
    "many {# файлов} " +
    "other {# файла}}"
  );

console.log(formatter({ count: 5 }));

Кастомные message bundles

Сообщения можно хранить централизованно.

Globalize.loadMessages({
  ru: {
    greetings: {
      morning: "Доброе утро",
      evening: "Добрый вечер"
    }
  }
});

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

const formatter =
  Globalize.messageFormatter(
    "greetings/morning"
  );

console.log(formatter());

Переопределение календаря

Изменение названий месяцев

Cldr.load({
  main: {
    ru: {
      dates: {
        calendars: {
          gregorian: {
            months: {
              format: {
                wide: {
                  1: "Январь",
                  2: "Февраль"
                }
              }
            }
          }
        }
      }
    }
  }
});

Кастомизация часовых поясов

CLDR содержит данные о time zone names.

Cldr.load({
  main: {
    ru: {
      dates: {
        timeZoneNames: {
          hourFormat: "+HH:mm;-HH:mm",
          gmtFormat: "GMT{0}",
          gmtZeroFormat: "GMT"
        }
      }
    }
  }
});

Добавление нестандартных локалей

Создание корпоративной локали

Например:

Globalize.locale("ru-CORP");

Загрузка:

Cldr.load({
  main: {
    "ru-CORP": {
      identity: {
        language: "ru",
        territory: "CORP"
      },

      numbers: {
        defaultNumberingSystem: "latn"
      }
    }
  }
});

Теперь локаль становится полноценной частью системы.


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

CLDR поддерживает fallback-механизм.

Пример:

ru-CORP → ru → root

Если значение отсутствует в ru-CORP, оно берётся из ru.


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

Раздел supplemental содержит общие правила:

  • pluralization;
  • weekData;
  • currencyData;
  • numberingSystems.

Пример:

Cldr.load({
  supplemental: {
    weekData: {
      firstDay: {
        ru: "mon"
      }
    }
  }
});

Частичная загрузка данных

Минимизация размера bundle

Полный CLDR может занимать несколько мегабайт.

Практика production-приложений:

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

Загружаются только необходимые части.


Генерация собственных наборов CLDR

Часто используется build-этап.

Пример:

node scripts/build-cldr.js

Скрипт:

const fs = require("fs");

const result = {
  main: {
    ru: {
      numbers: {
        defaultNumberingSystem: "latn"
      }
    }
  }
};

fs.writeFileSync(
  "./dist/cldr.json",
  JSON.stringify(result)
);

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

Загрузка данных во время выполнения

async function loadLocale(locale) {
  const data =
    await fetch(`/cldr/${locale}.json`)
      .then(r => r.json());

  Cldr.load(data);

  Globalize.locale(locale);
}

Модификация данных после загрузки

CLDR-данные можно изменять напрямую.

const cldr = new Cldr("ru");

cldr.attributes.maxLanguageId = "ru";

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


Кэширование кастомных данных

Форматтеры Globalize создаются дорого.

Рекомендуется:

const cache = {};

function getFormatter(locale) {
  if (!cache[locale]) {
    cache[locale] =
      new Globalize(locale)
        .numberFormatter();
  }

  return cache[locale];
}

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

Globalize поддерживает precompilation.

const formatter =
  Globalize.compileNumberFormatter();

Это уменьшает runtime-издержки и ускоряет работу приложения.


Разделение кастомных пакетов

Крупные приложения разделяют данные:

cldr/
 ├── base/
 ├── currencies/
 ├── calendars/
 ├── enterprise/

Такой подход:

  • уменьшает initial bundle;
  • ускоряет lazy loading;
  • упрощает поддержку.

Проверка корректности кастомизации

Валидация структуры

Типичная ошибка:

{
  number: {}
}

Вместо:

{
  numbers: {}
}

Для проверки используется:

const cldr = new Cldr("ru");

console.log(
  cldr.get("main/ru/numbers")
);

Отладка путей CLDR

Полезный инструмент:

cldr.main([
  "numbers",
  "currencies",
  "USD"
]);

Конфликты при объединении данных

Если загрузить одинаковые секции несколько раз:

Cldr.load(dataA);
Cldr.load(dataB);

последняя загрузка перезапишет предыдущую.

Это важно при:

  • lazy loading;
  • hot reload;
  • plugin architecture.

Изоляция кастомных данных

Практика enterprise-приложений:

{
  enterprise: {
    branding: {},
    units: {},
    labels: {}
  }
}

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


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

Для объединения конфигураций:

const deepmerge =
  require("deepmerge");

const result =
  deepmerge(baseData, customData);

Версионирование кастомных локалей

При обновлении CLDR структура может меняться.

Рекомендуется хранить:

/locales/v1/
/locales/v2/

или:

{
  "version": "44.0"
}

Типичные ошибки кастомизации

Перезапись системных узлов

Ошибка:

numbers: {}

Без необходимых полей.

В результате Globalize теряет доступ к стандартным форматам.


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

Для pluralization и formatting нужны:

supplemental/plurals.json
supplemental/likelySubtags.json

Несовместимость версий

Версии:

  • Globalize;
  • Cldr.js;
  • cldr-data

должны быть совместимы между собой.


Организация структуры проекта

Пример production-структуры:

src/
 ├── i18n/
 │    ├── cldr/
 │    ├── locales/
 │    ├── messages/
 │    ├── custom/
 │    └── loaders/

Подходы к кастомизации

Локальное расширение

Изменяется только конкретная локаль.

ru-RU

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


Глобальное расширение

Модифицируются supplemental rules.

Используется при:

  • SaaS-платформах;
  • мультинациональных системах;
  • enterprise-решениях.

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

Большие JSON-структуры:

  • увеличивают bundle size;
  • замедляют парсинг;
  • повышают потребление памяти.

Практики оптимизации:

  • tree shaking;
  • lazy loading;
  • precompiled formatters;
  • разделение namespaces;
  • CDN-кэширование.

Интеграция с Webpack

Пример динамической загрузки:

async function setLocale(locale) {
  const data =
    await import(
      `./cldr/${locale}.json`
    );

  Cldr.load(data.default);

  Globalize.locale(locale);
}

Интеграция с Vite

const locales =
  import.meta.glob("./cldr/*.json");

async function load(locale) {
  const loader =
    locales[`./cldr/${locale}.json`];

  const module = await loader();

  Cldr.load(module.default);
}

Интеграция с Node.js

Серверная локализация:

const Globalize =
  require("globalize");

Globalize.locale("ru");

module.exports = {
  money(value) {
    return Globalize
      .currencyFormatter("RUB")(value);
  }
};

Практика enterprise-локализации

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

  • собственные plural forms;
  • брендированные единицы;
  • внутренние валюты;
  • отраслевые сокращения;
  • локальные календарные правила;
  • специализированные форматы отчётов;
  • кастомные message catalogs.

Рекомендации по поддержке

Наиболее устойчивый подход:

  1. Не изменять core CLDR напрямую.
  2. Хранить расширения отдельно.
  3. Использовать namespaced custom sections.
  4. Версионировать данные.
  5. Автоматизировать генерацию JSON.
  6. Проводить snapshot-тестирование форматтеров.
  7. Избегать runtime mutation.
  8. Использовать precompiled formatters в production.