Определение необходимости полифилов

Роль Intl API и проблема совместимости

Intl — это встроенный набор API JavaScript для интернационализации, включающий форматирование дат, чисел, списков, множественного числа и других языковых конструкций. Несмотря на стандартизированность, поддержка отдельных частей Intl исторически внедрялась поэтапно, а в некоторых окружениях остаётся неполной.

Ключевая сложность заключается в том, что наличие объекта Intl само по себе не гарантирует доступность всех его возможностей. Разные браузеры и версии Node.js поддерживают разные наборы конструкторов и опций. Поэтому определение необходимости полифилов — это не бинарная проверка, а анализ конкретных возможностей среды выполнения.

Базовая проверка наличия Intl

Первичный уровень проверки сводится к определению существования глобального объекта:

if (typeof Intl === "undefined") {
  // требуется полифил всей библиотеки Intl
}

Такой сценарий встречается в крайне старых окружениях (например, устаревшие Android WebView или ранние версии Node.js). Однако современная практика показывает, что отсутствие Intl — редкий случай. Более значимой задачей становится проверка частичной поддержки.

Проверка поддержки конкретных конструкторов

Intl состоит из набора независимых модулей:

  • Intl.DateTimeFormat
  • Intl.NumberFormat
  • Intl.Collator
  • Intl.PluralRules
  • Intl.RelativeTimeFormat
  • Intl.ListFormat
  • Intl.Segmenter
  • Intl.DisplayNames

Наличие каждого конструктора должно проверяться отдельно:

const hasDateTimeFormat = typeof Intl.DateTimeFormat === "function";
const hasNumberFormat = typeof Intl.NumberFormat === "function";
const hasRelativeTimeFormat = typeof Intl.RelativeTimeFormat === "function";

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

Проверка поддержки локалей через supportedLocalesOf

Даже при наличии конструктора не гарантируется корректная поддержка локалей. Для этого используется метод supportedLocalesOf:

if (Intl.DateTimeFormat.supportedLocalesOf(["ru-RU"]).length === 0) {
  // локаль не поддерживается полноценно
}

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

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

Анализ поведения через resolvedOptions

Более точная диагностика возможна через метод resolvedOptions(), который показывает фактические параметры форматирования:

const formatter = new Intl.DateTimeFormat("ru-RU");
const options = formatter.resolvedOptions();

console.log(options.locale);
console.log(options.calendar);
console.log(options.numberingSystem);

Если результирующая локаль отличается от ожидаемой, это сигнал о неполной поддержке ICU-данных в среде.

Также этот метод позволяет выявить отсутствие определённых режимов форматирования, например:

  • отсутствие timeZone
  • ограниченные calendar
  • упрощённые правила числовых систем

Проверка новых модулей Intl

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

Intl.RelativeTimeFormat

const hasRTF = typeof Intl.RelativeTimeFormat === "function";

Отсутствие этого API характерно для старых браузеров и требует полифила при отображении «3 дня назад», «через 2 часа» и аналогичных конструкций.

Intl.PluralRules

const hasPluralRules = typeof Intl.PluralRules === "function";

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

Intl.ListFormat

const hasListFormat = typeof Intl.ListFormat === "function";

Используется для форматирования списков вида «яблоко, банан и груша» с учётом локали.

Intl.Segmenter

const hasSegmenter = typeof Intl.Segmenter === "function";

Отвечает за корректное разбиение текста на слова и предложения с учётом языка, включая сложные сценарии (например, китайский или японский текст).

Intl.DisplayNames

const hasDisplayNames = typeof Intl.DisplayNames === "function";

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

Стратегии определения необходимости полифилов

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

Runtime-проверка

Наиболее распространённый подход — проверка во время выполнения:

function needsIntlPolyfill() {
  return (
    typeof Intl === "undefined" ||
    typeof Intl.RelativeTimeFormat !== "function" ||
    typeof Intl.PluralRules !== "function"
  );
}

Этот подход обеспечивает максимальную точность, но увеличивает начальный объём логики.

Feature detection на уровне модулей

Более гранулярный вариант:

export const intlCapabilities = {
  dateTimeFormat: typeof Intl.DateTimeFormat === "function",
  numberFormat: typeof Intl.NumberFormat === "function",
  relativeTimeFormat: typeof Intl.RelativeTimeFormat === "function",
  listFormat: typeof Intl.ListFormat === "function"
};

Такой подход позволяет подключать полифилы точечно.

Build-time стратегия

В современных сборщиках (Webpack, Vite, Rollup) возможно определять поддержку через targets (browserslist), но это не всегда отражает реальную среду выполнения, особенно в embedded WebView.

Поэтому build-time стратегия часто комбинируется с runtime-проверками.

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

Полифилы для Intl обычно подключаются динамически:

async function ensureIntlSupport() {
  if (typeof Intl.RelativeTimeFormat === "undefined") {
    await import("@formatjs/intl-relativetimeformat/polyfill");
  }

  if (typeof Intl.PluralRules === "undefined") {
    await import("@formatjs/intl-pluralrules/polyfill");
  }
}

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

В некоторых случаях используется единый пакет:

  • @formatjs/intl
  • intl (устаревший, но всё ещё встречающийся)

Учет поведения Node.js

Node.js долгое время использовал собственную сборку ICU, что влияло на доступность локалей и API.

Проверка версии среды:

process.versions.node

Однако более надёжным остаётся проверка конкретных API, поскольку Node может быть собран с минимальным ICU (small-icu), где часть локалей отсутствует.

Типичные ошибки при определении необходимости полифилов

Проверка только Intl без детализации

if (!Intl) { /* плохо */ }

Такой подход игнорирует частичную поддержку.

Игнорирование supportedLocalesOf

Отсутствие проверки локалей приводит к неожиданным fallback-результатам.

Предположение полной поддержки ICU

Наличие конструктора не означает наличие всех локалей и правил форматирования.

Жёсткое подключение полифилов без проверки

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

Комбинированная модель оценки поддержки

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

const intlSupport = {
  base: typeof Intl !== "undefined",
  dateTimeFormat: typeof Intl.DateTimeFormat === "function",
  numberFormat: typeof Intl.NumberFormat === "function",
  rtf: typeof Intl.RelativeTimeFormat === "function",
  pluralRules: typeof Intl.PluralRules === "function",
  listFormat: typeof Intl.ListFormat === "function"
};

const needsPolyfills =
  !intlSupport.base ||
  !intlSupport.rtf ||
  !intlSupport.pluralRules;

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

Значение точной диагностики поддержки Intl

Корректное определение необходимости полифилов напрямую влияет на:

  • размер итогового JavaScript-бандла
  • скорость загрузки приложения
  • предсказуемость локализованного интерфейса
  • согласованность поведения между устройствами

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