Отслеживание использования локалей

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

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


В Globalize локаль представляет собой строковый идентификатор вида en, en-US, ru, fr-FR. Она влияет на:

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

Ключевая точка управления локалью — глобальное состояние:

Globalize.locale("ru");

Именно изменение этого состояния становится первичной точкой отслеживания использования локалей. Любое решение о наблюдении строится вокруг перехвата или обёртки этой операции.


Перехват установки локали

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

import Globalize from "globalize";

const localeUsageLog = [];

const originalLocale = Globalize.locale;

Globalize.locale = function (locale) {
  const previous = originalLocale.call(Globalize);

  if (locale !== undefined) {
    localeUsageLog.push({
      from: previous,
      to: locale,
      timestamp: Date.now()
    });
  }

  return originalLocale.call(Globalize, locale);
};

В данной схеме фиксируются:

  • предыдущая локаль;
  • новая локаль;
  • момент переключения.

Такой подход формирует базовую телеметрию переключений без изменения внутренней логики библиотеки.


Отслеживание активной локали в рантайме

Во многих приложениях локаль не только устанавливается, но и запрашивается:

const current = Globalize.locale();

Для фиксации фактического использования вводится прокси-слой:

let lastAccessLog = null;

const originalLocaleGetter = Globalize.locale;

Globalize.locale = function (locale) {
  if (locale === undefined) {
    lastAccessLog = {
      locale: originalLocale.call(Globalize),
      timestamp: Date.now(),
      type: "read"
    };
  }
  return originalLocale.call(Globalize, locale);
};

Это позволяет различать:

  • установку локали;
  • чтение текущего значения;
  • повторные обращения без изменений состояния.

Фиксация использования локали в форматировании

Локаль фактически проявляется не только при установке, но и при вызовах форматеров:

  • Globalize.dateFormatter
  • Globalize.numberFormatter
  • Globalize.currencyFormatter
  • Globalize.messageFormatter

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

const formatUsage = [];

function trackFormatter(methodName, originalMethod) {
  return function (...args) {
    const formatter = originalMethod.apply(this, args);

    return function (value) {
      const result = formatter(value);

      formatUsage.push({
        method: methodName,
        locale: Globalize.locale(),
        timestamp: Date.now()
      });

      return result;
    };
  };
}

Globalize.numberFormatter = trackFormatter(
  "numberFormatter",
  Globalize.numberFormatter
);

Globalize.dateFormatter = trackFormatter(
  "dateFormatter",
  Globalize.dateFormatter
);

Такая модель позволяет фиксировать:

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

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

Собранные данные формируют структуру наблюдений:

const localeStats = {};

function registerLocale(locale, feature) {
  if (!localeStats[locale]) {
    localeStats[locale] = {
      number: 0,
      date: 0,
      currency: 0,
      message: 0
    };
  }

  localeStats[locale][feature]++;
}

Интеграция с форматерами:

function trackFormatter(methodName, originalMethod, featureKey) {
  return function (...args) {
    const formatter = originalMethod.apply(this, args);

    return function (value) {
      const result = formatter(value);

      registerLocale(Globalize.locale(), featureKey);

      return result;
    };
  };
}

Результатом становится распределение:

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

Отслеживание загрузки CLDR-данных

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

const cldrLoadLog = [];

function trackCldrLoad(fn) {
  return async function (data) {
    cldrLoadLog.push({
      size: JSON.stringify(data).length,
      timestamp: Date.now()
    });

    return fn(data);
  };
}

Применение:

Globalize.load = trackCldrLoad(Globalize.load);

Фиксируются:

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

Анализ переходов между локалями

Для оценки поведения интерфейса используется граф переходов:

const transitions = {};

function recordTransition(from, to) {
  const key = `${from} -> ${to}`;

  if (!transitions[key]) {
    transitions[key] = 0;
  }

  transitions[key]++;
}

Интеграция с перехватом Globalize.locale:

Globalize.locale = function (locale) {
  const current = originalLocale.call(Globalize);

  if (locale) {
    recordTransition(current, locale);
  }

  return originalLocale.call(Globalize, locale);
};

Это позволяет выявлять:

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

Сегментация локалей по функциональным зонам

При сложной архитектуре приложения локали могут различаться по модулям:

  • платежи;
  • аналитика;
  • интерфейс;
  • отчёты.

Для этого вводится контекст:

let currentContext = "global";

function setLocaleContext(context) {
  currentContext = context;
}

И расширение логирования:

function registerLocale(locale, feature) {
  const key = `${currentContext}:${locale}`;

  if (!localeStats[key]) {
    localeStats[key] = {};
  }

  localeStats[key][feature] =
    (localeStats[key][feature] || 0) + 1;
}

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


Метрики стабильности локали

Для оценки качества интернационализации вводятся метрики:

  • коэффициент переключений локали;
  • средняя продолжительность активной локали;
  • доля дефолтной локали;
  • доля редких локалей.

Пример расчёта средней продолжительности:

function calculateAverageDuration(log) {
  let total = 0;

  for (let i = 1; i < log.length; i++) {
    total += log[i].timestamp - log[i - 1].timestamp;
  }

  return total / (log.length - 1);
}

Использование локалей в диагностике

Отслеживание локалей позволяет выявлять:

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

Такая диагностика строится на агрегированных логах, собранных из всех точек перехвата.


Интеграция с системой логирования

Собранные данные обычно направляются в единый лог-слой:

function flushLocaleLogs() {
  sendToAnalytics({
    transitions,
    localeUsageLog,
    formatUsage,
    localeStats
  });
}

Передача может выполняться:

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

Кэширование и локали

Отслеживание локалей тесно связано с кэшированием форматеров:

const formatterCache = new Map();

function getNumberFormatter(locale) {
  if (!formatterCache.has(locale)) {
    Globalize.locale(locale);
    formatterCache.set(locale, Globalize.numberFormatter());
  }

  return formatterCache.get(locale);
}

Логирование позволяет контролировать:

  • эффективность кэша;
  • количество уникальных локалей;
  • повторное создание форматеров.

Корреляция локалей и пользовательских сценариев

Собранные данные могут быть связаны с действиями:

  • регистрация;
  • оформление заказа;
  • просмотр отчётов;
  • изменение настроек.

Это достигается через расширение контекста событий:

function trackAction(action, locale) {
  localeEventLog.push({
    action,
    locale,
    timestamp: Date.now()
  });
}

Поведенческие паттерны локалей

Анализ логов позволяет выделить типовые паттерны:

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

Каждый паттерн отражается в графе переходов и распределении частот использования.


Оптимизация на основе наблюдений

Результаты отслеживания используются для:

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

Такая оптимизация опирается не на конфигурацию, а на реальные данные использования локалей в Globalize.