Offline поддержка

Офлайн-режим в Globalize строится вокруг принципа полного отсутствия зависимости от сети во время выполнения приложения. Библиотека не выполняет запросов к CDN или внешним API для получения локализационных данных. Все необходимые ресурсы — CLDR-данные, форматы, сообщения — должны быть заранее включены в сборку или загружены локально.

Ключевой элемент модели — CLDR (Unicode Common Locale Data Repository). Globalize использует его как единственный источник локализационной информации: числовые форматы, правила плюрализации, календарные данные, валюты, единицы измерения.

Основной принцип офлайн-режима:

Все данные локализации должны быть доступны в момент инициализации приложения


CLDR как основа офлайн-режима

Globalize не содержит встроённых локалей. Вместо этого используется внешняя поставка CLDR JSON-файлов.

Минимальный набор данных для офлайн-работы включает:

  • likelySubtags
  • numbers
  • currencies
  • calendar
  • timeZoneNames
  • pluralRules

CLDR-файлы подключаются вручную:

import likelySubtags from "cldr-core/supplemental/likelySubtags.json";
import numberingSystems from "cldr-core/supplemental/numberingSystems.json";
import plurals from "cldr-core/supplemental/plurals.json";

import enNumbers from "cldr-numbers-full/main/en/numbers.json";
import enCurrencies from "cldr-numbers-full/main/en/currencies.json";
import enCaGregorian from "cldr-dates-full/main/en/ca-gregorian.json";

Далее данные передаются в Globalize:

import Globalize from "globalize";

Globalize.load(
  likelySubtags,
  numberingSystems,
  plurals,
  enNumbers,
  enCurrencies,
  enCaGregorian
);

После этого библиотека полностью работает без сетевых запросов.


Инициализация Globalize в офлайн-среде

Инициализация требует явного определения локали:

const globalize = new Globalize("en");

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


Сборка локалей в приложении

Офлайн-режим требует выбора стратегии упаковки локалей:

Полная сборка всех локалей

Используется в приложениях с множеством языков:

import * as en from "cldr-dates-full/main/en/ca-gregorian.json";
import * as ru from "cldr-dates-full/main/ru/ca-gregorian.json";

Недостаток — увеличение размера бандла.


Подключаются только используемые языки:

const locales = ["en", "ru", "de"];

locales.forEach(locale => {
  require(`cldr-dates-full/main/${locale}/ca-gregorian.json`);
});

Такой подход оптимизирует размер и ускоряет загрузку.


Webpack и Vite интеграция

Webpack

Офлайн-режим требует статической поставки JSON:

module.exports = {
  module: {
    rules: [
      {
        test: /\.json$/,
        type: "json"
      }
    ]
  }
};

Дополнительно используется ContextReplacementPlugin для ограничения локалей:

new webpack.ContextReplacementPlugin(
  /cldr-dates-full/,
  /(en|ru|de)\.json/
);

Vite

Vite позволяет использовать динамические импорты:

const loadLocale = async (locale) => {
  const data = await import(`cldr-dates-full/main/${locale}/ca-gregorian.json`);
  Globalize.load(data);
};

В офлайн-сценарии динамические импорты должны быть предсобраны во время build step.


Кэширование локализационных данных

При работе в браузере офлайн-режим может дополняться кешированием через Service Worker.

Пример стратегии кеширования

self.addEventListener("install", (event) => {
  event.waitUntil(
    caches.open("globalize-cache").then((cache) => {
      return cache.addAll([
        "/locales/en.json",
        "/locales/ru.json"
      ]);
    })
  );
});

При последующих загрузках данные берутся из кеша, исключая сеть.


Офлайн-форматирование чисел

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

const formatNumber = new Globalize("en").numberFormatter();

formatNumber(12345.67);
// "12,345.67"

Все правила берутся из локально загруженного CLDR.


Офлайн-форматирование валют

Для валют требуется дополнительный CLDR-модуль:

const formatCurrency = new Globalize("en").currencyFormatter("USD");

formatCurrency(1999.99);
// "$1,999.99"

При отсутствии currencies.json форматирование становится невозможным, что делает его обязательным элементом офлайн-сборки.


Офлайн-работа с датами

CLDR календарные данные обеспечивают корректную локализацию дат:

const formatDate = new Globalize("ru").dateFormatter({
  datetime: "medium"
});

formatDate(new Date());
// "28 мая 2026 г., 12:34:56"

Зависимые данные:

  • ca-gregorian.json
  • timeZoneNames.json
  • dateFields.json

Разделение бандлов по локалям

Офлайн-архитектура часто требует разделения сборки:

Основной бандл

  • логика приложения
  • Globalize core

Локальные чанки

  • locale-en.js
  • locale-ru.js
  • locale-de.js

Пример загрузки:

import Globalize from "globalize";

async function init(locale) {
  const data = await import(`./locales/${locale}.js`);
  Globalize.load(data);
  return new Globalize(locale);
}

Tree-shaking и минимизация CLDR

CLDR имеет большой объём данных, поэтому офлайн-режим требует оптимизации:

  • удаление неиспользуемых локалей
  • исключение ненужных календарей
  • включение только нужных валют
  • ограничение типов форматов (например, только numbers без timeZoneNames)

Проблемы офлайн-режима

Увеличение размера сборки

CLDR может занимать десятки мегабайт при полной локализации множества языков.

Сложность управления версиями

Несоответствие версий CLDR и Globalize приводит к ошибкам форматирования.

Жёсткая зависимость от сборщика

Без webpack/Vite корректная сборка офлайн-пакета становится нетривиальной задачей.


Паттерны проектирования офлайн-локализации

Lazy loading локалей

const locales = {};

async function getLocale(locale) {
  if (!locales[locale]) {
    const data = await import(`./cldr/${locale}.json`);
    Globalize.load(data);
    locales[locale] = new Globalize(locale);
  }
  return locales[locale];
}

Preload критических локалей

const criticalLocales = ["en", "ru"];

Promise.all(
  criticalLocales.map(l => import(`./cldr/${l}.json`))
).then(modules => {
  modules.forEach(m => Globalize.load(m));
});

Изоляция CLDR слоя

Создание отдельного слоя данных:

class LocaleStore {
  constructor() {
    this.cache = new Map();
  }

  async load(locale) {
    if (!this.cache.has(locale)) {
      const data = await import(`./cldr/${locale}.json`);
      Globalize.load(data);
      this.cache.set(locale, new Globalize(locale));
    }
    return this.cache.get(locale);
  }
}

Проверка полноты офлайн-данных

Перед запуском приложения выполняется валидация:

  • наличие numbers
  • наличие currencies
  • наличие calendar
  • соответствие версии CLDR

Пример простого чекера:

function validateCLDR(data) {
  return (
    data.numbers &&
    data.numbers.defaultNumberingSystem &&
    data.currencies &&
    data["ca-gregorian"]
  );
}

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

В прогрессивных веб-приложениях офлайн-режим Globalize становится частью сервисной архитектуры:

  • локали кешируются через Service Worker
  • CLDR загружается один раз
  • дальнейшая работа происходит полностью локально
  • обновление локалей выполняется через версионирование кеша

Изоляция окружений Node.js и браузера

В Node.js офлайн-режим проще:

const Globalize = require("globalize");
const cldrData = require("./cldr/ru.json");

Globalize.load(cldrData);

const g = new Globalize("ru");

В браузере требуется упаковка через сборщик, но логика остаётся идентичной.


Оптимизация производительности офлайн-режима

Ключевые факторы производительности:

  • предзагрузка CLDR
  • уменьшение количества локалей
  • кеширование форматтеров
  • повторное использование экземпляров Globalize

Пример кеширования форматтера:

const cache = new Map();

function getFormatter(locale) {
  if (!cache.has(locale)) {
    cache.set(locale, new Globalize(locale).numberFormatter());
  }
  return cache.get(locale);
}