API интернационализации в JavaScript построен поверх ICU
(International Components for Unicode) и тесно зависит от реализации
движка. Это означает, что поведение Intl в браузерах и
Node.js не всегда одинаково и не всегда полнофункционально. В старых
браузерах встречаются три основных типа проблем:
Intl полностью;RelativeTimeFormat, ListFormat,
PluralRules);Ключевая сложность заключается в том, что Intl
невозможно “дополнить” стандартным полифиллом без учёта объёма
ICU-данных и архитектуры окружения.
Базовая стратегия начинается не с подключения полифиллов, а с определения доступного функционала.
if (typeof Intl === "undefined") {
// Полное отсутствие Intl
}
Это редкий случай для современных браузеров, но он важен для устаревших встроенных WebView или сильно ограниченных окружений.
if (!Intl.NumberFormat) {
// нет поддержки числового форматирования
}
if (!Intl.RelativeTimeFormat) {
// нет относительного времени
}
Intl.DateTimeFormat.supportedLocalesOf(["ru-RU"]);
Если возвращается пустой массив, движок игнорирует локаль и использует fallback.
В старых браузерах часто выгоднее не пытаться полностью восстановить поведение Intl, а деградировать функциональность предсказуемым образом.
function formatNumber(value, locale) {
if (typeof Intl !== "undefined" && Intl.NumberFormat) {
return new Intl.NumberFormat(locale).format(value);
}
return String(value);
}
Вместо полного набора локалей можно ограничиться:
"en" вместо "en-US",
"en-GB""ru" вместо "ru-RU"Это снижает зависимость от конкретной реализации ICU.
Наиболее распространённый подход — подключение пакета
intl:
npm install intl
import "intl";
import "intl/locale-data/jsonp/ru";
Особенности:
Экосистема @formatjs предоставляет модульные
полифиллы:
@formatjs/intl-datetimeformat@formatjs/intl-numberformat@formatjs/intl-relativetimeformat@formatjs/intl-pluralrulesПример подключения:
import "@formatjs/intl-numberformat/polyfill";
import "@formatjs/intl-numberformat/locale-data/ru";
Преимущество подхода — гранулярность: подключаются только нужные части API.
Стратегия lazy-loading позволяет не включать Intl-полифиллы в основной бандл.
async function ensureIntl() {
if (!Intl.NumberFormat) {
await import("@formatjs/intl-numberformat/polyfill");
await import("@formatjs/intl-numberformat/locale-data/ru");
}
}
Использование:
Для браузеров основная стратегия:
core-js не решает Intl напрямую,
требуется отдельный слой.Node.js зависит от сборки ICU:
full-icu — полная локализация;small-icu — ограниченный набор локалей;intl полифиллы почти не нужны в современных
версиях.Проверка:
console.log(Intl.DateTimeFormat.supportedLocalesOf(["ru-RU"]));
Babel сам по себе не полифиллит Intl, но может помочь с
инфраструктурой загрузки.
{
"presets": [
["@babel/preset-env", {
"targets": {
"ie": "11"
}
}]
]
}
IE11 требует явного подключения Intl polyfill.
В крупных приложениях используется слой абстракции:
const formatters = {
number: (value, locale) => {
try {
return new Intl.NumberFormat(locale).format(value);
} catch {
return String(value);
}
},
date: (value, locale) => {
try {
return new Intl.DateTimeFormat(locale).format(value);
} catch {
return new Date(value).toISOString();
}
}
};
Этот слой:
Старые реализации часто:
options;timeZone;Стратегия компенсации:
Не всегда требуется отключать весь Intl. Можно деградировать только отдельные возможности.
function formatRelativeTime(value, unit, locale) {
if (Intl.RelativeTimeFormat) {
const rtf = new Intl.RelativeTimeFormat(locale);
return rtf.format(value, unit);
}
return `${value} ${unit}`;
}
В старых браузерах важно снижать стоимость повторных вызовов:
const cache = new Map();
function getFormatter(locale) {
if (!cache.has(locale)) {
cache.set(locale, new Intl.NumberFormat(locale));
}
return cache.get(locale);
}
При отсутствии Intl:
const fallback = {
format: (v) => String(v)
};
Стратегия заключается в загрузке полифиллов только при необходимости:
if (!Intl.RelativeTimeFormat) {
import("@formatjs/intl-relativetimeformat");
}
Это особенно важно для:
Intl-полифиллы часто содержат:
Это может увеличивать размер бандла в несколько мегабайт.
Стратегии оптимизации:
Модель построения приложения:
Intl.NumberFormat.Intl.DateTimeFormat.Каждый уровень не ломает предыдущий, а только расширяет возможности.
Тестирование в старых браузерах включает:
Особое внимание уделяется:
Архитектурно Intl лучше не использовать напрямую:
class LocaleService {
constructor(locale) {
this.locale = locale;
}
formatNumber(value) {
return Intl.NumberFormat
? new Intl.NumberFormat(this.locale).format(value)
: String(value);
}
}
Это упрощает замену реализации в будущем без рефакторинга всей кодовой базы.
Некоторые Intl возможности не имеют стабильной поддержки в старых окружениях:
Intl.ListFormat;Intl.Segmenter;Intl.DisplayNames.Подход:
Когда клиентская поддержка ограничена:
Это особенно эффективно в SSR приложениях: