Полная и частичная загрузка

Библиотека Globalize использует модульный подход к работе с локализацией и интернационализацией. Основная особенность архитектуры заключается в разделении функциональности на отдельные части: форматирование чисел, дат, валют, относительного времени, сообщений и других компонентов подключаются независимо друг от друга. Такой подход позволяет существенно уменьшить размер итогового JavaScript-бандла и оптимизировать загрузку данных локализации.

В процессе разработки приложений возникает необходимость выбирать между полной загрузкой всех локализационных данных и частичной загрузкой только необходимых модулей и CLDR-данных. От выбранной стратегии зависит производительность, объём передаваемых данных и сложность поддержки проекта.


Архитектура загрузки в Globalize

Работа библиотеки строится на двух ключевых элементах:

  1. JavaScript-модули библиотеки.
  2. Данные стандарта CLDR.

CLDR (Common Locale Data Repository) содержит локализационные данные для разных языков и регионов.

Структура подключения выглядит следующим образом:

const Globalize = require("globalize");

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

const en = new Globalize("en");

console.log(en.formatNumber(12345.67));

В данном примере подключаются:

  • основной модуль Globalize;
  • данные числового форматирования;
  • supplemental-данные;
  • конкретная локаль en.

Полная загрузка библиотеки

Полная загрузка подразумевает подключение:

  • всех модулей Globalize;
  • полного набора CLDR-данных;
  • всех используемых локалей.

Особенности полной загрузки

При полном подключении приложение получает доступ ко всей функциональности библиотеки:

require("globalize");
require("globalize/number");
require("globalize/date");
require("globalize/currency");
require("globalize/message");
require("globalize/relative-time");
require("globalize/plural");
require("globalize/unit");

Загрузка CLDR может выглядеть следующим образом:

Globalize.load(
    require("cldr-data/main/en/ca-gregorian.json"),
    require("cldr-data/main/en/numbers.json"),
    require("cldr-data/main/en/currencies.json"),
    require("cldr-data/main/en/dateFields.json"),
    require("cldr-data/main/en/units.json"),
    require("cldr-data/supplemental/likelySubtags.json"),
    require("cldr-data/supplemental/timeData.json"),
    require("cldr-data/supplemental/weekData.json"),
    require("cldr-data/supplemental/currencyData.json"),
    require("cldr-data/supplemental/plurals.json")
);

Преимущества полной загрузки

Простота разработки

Не требуется отслеживать зависимости между модулями и CLDR-файлами.

Минимум ошибок конфигурации

Все необходимые данные уже находятся в памяти приложения.

Быстрое подключение новых функций

Форматирование валют, сообщений или единиц измерения можно использовать без дополнительной настройки.


Недостатки полной загрузки

Увеличенный размер бандла

CLDR содержит большой объём данных:

  • даты;
  • валюты;
  • единицы измерения;
  • правила множественного числа;
  • региональные настройки.

При подключении множества локалей размер приложения может увеличиться на несколько мегабайт.

Повышенное потребление памяти

Все локализационные данные хранятся в памяти независимо от фактического использования.

Замедление начальной загрузки

Особенно заметно в браузерных SPA-приложениях.


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

Частичная загрузка предполагает подключение только:

  • необходимых модулей;
  • конкретных CLDR-разделов;
  • используемых локалей.

Такой подход считается предпочтительным для production-приложений.


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

Если приложению требуется только форматирование чисел, подключается исключительно соответствующий модуль.

Пример

const Globalize = require("globalize");
require("globalize/number");

Теперь доступны только методы работы с числами:

const formatter = Globalize("en").numberFormatter();

console.log(formatter(12345.67));

Методы форматирования дат или сообщений будут отсутствовать.


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

Каждый модуль Globalize требует строго определённый набор CLDR-файлов.

Пример минимальной конфигурации для чисел

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

Для форматирования валют

Globalize.load(
    require("cldr-data/main/en/currencies.json"),
    require("cldr-data/supplemental/currencyData.json")
);

Для дат

Globalize.load(
    require("cldr-data/main/en/ca-gregorian.json"),
    require("cldr-data/main/en/timeZoneNames.json"),
    require("cldr-data/supplemental/timeData.json"),
    require("cldr-data/supplemental/weekData.json")
);

Зависимости между модулями и CLDR

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

Модуль Основные CLDR-файлы
number numbers.json
currency currencies.json, currencyData.json
date ca-gregorian.json, timeData.json
plural plurals.json
relative-time dateFields.json
unit units.json

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


Ошибки при неполной загрузке

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

E_MISSING_CLDR: Missing required CLDR content

Часто возникает при отсутствии:

  • likelySubtags.json;
  • plurals.json;
  • currencyData.json.

Подключён модуль без CLDR

require("globalize/date");

Но отсутствует:

ca-gregorian.json

В результате:

Globalize("en").formatDate(new Date());

вызовет исключение.


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

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

Пример lazy loading

async function loadLocale(locale) {
    const messages = await import(
        `cldr-data/main/${locale}/numbers.json`
    );

    Globalize.load(messages.default);

    return new Globalize(locale);
}

Такой подход позволяет:

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

Разделение локалей по чанкам

В сборщиках вроде Webpack или Vite локали обычно разделяются на отдельные чанки.

Пример Webpack

function loadLocale(locale) {
    return import(
        /* webpackChunkName: "i18n-[request]" */
        `./locales/${locale}.js`
    );
}

В результате:

  • i18n-en.js
  • i18n-fr.js
  • i18n-de.js

загружаются независимо.


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

Пакет globalize-compiler позволяет компилировать форматтеры заранее.

Основная идея

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

Пример

Исходный код:

const formatter = Globalize("en").numberFormatter();

После компиляции превращается в обычную JavaScript-функцию.


Преимущества компиляции

Существенное уменьшение размера

В production часто удаётся сократить объём библиотеки в несколько раз.

Ускорение работы

Не требуется строить форматтеры в рантайме.

Снижение зависимости от CLDR

Многие данные становятся встроенными в скомпилированный код.


Runtime + Compiler

На практике используются два основных режима.

Runtime-режим

Полная библиотека работает в браузере:

Globalize.formatNumber(1000);

Compiled-режим

Форматтер создаётся заранее:

compiledFormatter(1000);

Compiled-режим особенно эффективен для:

  • крупных SPA;
  • SSR-приложений;
  • мобильных web-приложений;
  • embedded-интерфейсов.

Tree Shaking

Модульная архитектура Globalize хорошо сочетается с tree shaking.

Пример

import Globalize from "globalize";
import "globalize/number";

Если не импортировать:

import "globalize/date";

сборщик исключит соответствующий код.


Проблемы tree shaking

Некоторые версии CommonJS-модулей плохо оптимизируются сборщиками.

Особенно это касается:

  • старых версий Webpack;
  • Babel-конфигураций;
  • mixed ESM/CommonJS-проектов.

Стратегия минимальной загрузки

Для production обычно придерживаются следующей схемы:

  1. Подключаются только нужные модули.
  2. Загружаются только используемые локали.
  3. Используется lazy loading.
  4. Форматтеры компилируются заранее.
  5. Supplemental-данные минимизируются.

Практический пример минимальной конфигурации

Только числа и валюты

const Globalize = require("globalize");

require("globalize/number");
require("globalize/currency");

Globalize.load(
    require("cldr-data/main/en/numbers.json"),
    require("cldr-data/main/en/currencies.json"),
    require("cldr-data/supplemental/likelySubtags.json"),
    require("cldr-data/supplemental/currencyData.json")
);

const en = new Globalize("en");

console.log(
    en.formatCurrency(100, "USD")
);

Загрузка нескольких локалей

Globalize.load(
    require("cldr-data/main/en/numbers.json"),
    require("cldr-data/main/ru/numbers.json"),
    require("cldr-data/main/de/numbers.json")
);

Создание экземпляров:

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

Ленивая инициализация

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

Пример

let formatter = null;

function formatPrice(value) {
    if (!formatter) {
        formatter = Globalize("en")
            .currencyFormatter("USD");
    }

    return formatter(value);
}

Такой подход уменьшает время первоначального запуска приложения.


Кэширование форматтеров

Создание форматтеров — дорогостоящая операция.

Неправильный вариант

function format(value) {
    return Globalize("en")
        .numberFormatter()(value);
}

Форматтер создаётся при каждом вызове.


Правильный вариант

const formatter =
    Globalize("en").numberFormatter();

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

Серверная загрузка данных

В SSR-приложениях локализационные данные могут загружаться на сервере.

Node.js-пример

const fs = require("fs");

const numbers = JSON.parse(
    fs.readFileSync(
        "./cldr/main/en/numbers.json"
    )
);

Globalize.load(numbers);

Предварительная загрузка локалей

Для популярных языков применяется preload.

Пример

<link
    rel="preload"
    href="/locales/en.js"
    as="script"
/>

Это уменьшает задержку при переключении языка интерфейса.


Комбинированная стратегия загрузки

Крупные приложения часто используют гибридный подход:

Компонент Стратегия
Основная локаль preload
Редкие локали lazy loading
Форматтеры compile-time
CLDR частичная загрузка

Оптимизация CLDR

Полный пакет cldr-data содержит огромный объём информации.

На практике приложение редко использует:

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

Поэтому часто создаются кастомные сборки CLDR.


Генерация минимального набора данных

Пример структуры

cldr/
 ├── en/
 │    ├── numbers.json
 │    └── currencies.json
 └── supplemental/
      ├── likelySubtags.json
      └── currencyData.json

Такой набор подходит для:

  • интернет-магазина;
  • панели администратора;
  • финансового интерфейса.

Сравнение полной и частичной загрузки

Характеристика Полная Частичная
Простота настройки высокая средняя
Размер бандла большой минимальный
Скорость запуска ниже выше
Гибкость низкая высокая
Контроль зависимостей минимальный полный
Подходит для production редко да

Типичная production-схема

Современное приложение обычно использует:

Core bundle
 ├── Globalize number
 ├── Основная локаль
 └── Compiled formatters

Async chunks
 ├── Дополнительные языки
 ├── Date formatting
 └── Message translations

Такая архитектура обеспечивает:

  • минимальный стартовый размер;
  • быструю загрузку интерфейса;
  • масштабируемость;
  • эффективное использование памяти;
  • высокую производительность локализации.