Международный API в JavaScript проектировался как слой стандартизированной локализации поверх различий платформ. Несмотря на спецификацию ECMAScript Internationalization API, реальная поддержка функций зависит от окружения: браузера, версии движка, Node.js и наличия ICU-данных. Поэтому ключевым архитектурным принципом становится graceful degradation — плавное снижение функциональности без поломки поведения приложения.
Основная идея заключается в том, что форматирование и локализация не должны быть бинарной функцией «работает / не работает». Вместо этого система должна деградировать по уровням:
en или системную;Перед использованием любой части Intl API критично учитывать, что не все классы и опции доступны в каждом окружении.
if (typeof Intl !== "undefined" && Intl.DateTimeFormat) {
// безопасное использование DateTimeFormat
}
Такой подход предотвращает падение кода в средах с урезанным ICU.
Разные движки могут поддерживать разные локали. Даже при наличии Intl результат может отличаться.
Intl.DateTimeFormat.supportedLocalesOf(["ru-RU", "en-US"]);
Возвращаемый массив показывает фактически доступные локали. При отсутствии поддержки конкретной локали необходимо переходить к fallback.
Наиболее частый сценарий graceful degradation — замена неподдерживаемой локали на дефолтную.
function getLocale(requested) {
const supported = Intl.DateTimeFormat.supportedLocalesOf(requested);
return supported.length ? supported[0] : "en-US";
}
Если ru-RU недоступен, система автоматически
переключается на en-US, не ломая форматирование.
Даже при наличии Intl.DateTimeFormat поведение может
различаться:
timeZone;const fmt = new Intl.DateTimeFormat("ru-RU", {
dateStyle: "full",
timeStyle: "short"
});
Если dateStyle/timeStyle не поддерживаются, необходимо
вручную перейти к классическим полям:
const fallback = new Intl.DateTimeFormat("ru-RU", {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit"
});
Метод formatToParts() обеспечивает контроль над
структурой результата, позволяя строить собственный форматтер при
неполной поддержке ICU.
const dtf = new Intl.DateTimeFormat("ru-RU");
const parts = dtf.formatToParts(new Date());
Если требуется fallback, можно использовать части как строительные блоки и игнорировать отсутствующие токены.
notation: "compact"unitDisplaycurrencyDisplaysignDisplayfunction formatNumber(value, locale) {
try {
return new Intl.NumberFormat(locale, {
notation: "compact"
}).format(value);
} catch {
return String(value);
}
}
Если compact notation не поддерживается, происходит
возврат к строковому представлению.
new Intl.NumberFormat("en-US", {
minimumFractionDigits: 0,
maximumFractionDigits: 2
});
Такой вариант обеспечивает стабильность вне зависимости от ICU возможностей.
Сортировка строк зависит от Unicode Collation Algorithm, но реализация может отличаться.
const collator = new Intl.Collator("ru-RU", {
sensitivity: "base"
});
Если Intl.Collator отсутствует или ограничен:
array.sort((a, b) => a.localeCompare(b));
При отсутствии полноценного ICU возможны расхождения в порядке сортировки, поэтому важна единая точка принятия решения.
Поддержка Intl.RelativeTimeFormat появилась позднее
других компонентов.
const rtf = new Intl.RelativeTimeFormat("ru", {
numeric: "auto"
});
function relativeDay(diff) {
if (diff === -1) return "вчера";
if (diff === 0) return "сегодня";
if (diff === 1) return "завтра";
return `${diff} дн.`;
}
Деградация здесь чаще семантическая, а не техническая: поведение сохраняется, но теряется локализация.
const lf = new Intl.ListFormat("ru", {
style: "long",
type: "conjunction"
});
function formatList(arr) {
return arr.length <= 1
? arr.join("")
: arr.slice(0, -1).join(", ") + " и " + arr[arr.length - 1];
}
Такой подход сохраняет читаемость даже без Intl.
Типичная схема:
function createFormatter(locale) {
if (typeof Intl === "undefined") {
return (x) => String(x);
}
if (Intl.NumberFormat) {
return new Intl.NumberFormat(locale).format;
}
return (x) => String(x);
}
В некоторых сборках Node.js ICU может быть минимальным. Это влияет на:
if (typeof process !== "undefined" && process.versions?.icu) {
// ICU доступен
}
const pr = new Intl.PluralRules("ru");
function pluralRu(n) {
const mod10 = n % 10;
const mod100 = n % 100;
if (mod10 === 1 && mod100 !== 11) return "one";
if (mod10 >= 2 && mod10 <= 4 && (mod100 < 10 || mod100 >= 20)) return "few";
return "many";
}
Intl снимает необходимость ручной реализации, но fallback остаётся необходимым при отсутствии поддержки.
Некоторые локали могут быть нераспознаны движком.
const locale = Intl.NumberFormat.supportedLocalesOf("ru-KZ")[0] || "ru";
Если региональная локаль отсутствует, используется базовая языковая метка.
В архитектуре приложений Intl часто инкапсулируется в слой форматирования:
const Format = {
number: (v) => new Intl.NumberFormat("en-US").format(v),
date: (d) => new Intl.DateTimeFormat("en-US").format(d),
list: (arr) => arr.join(", ")
};
Даже при деградации интерфейс остаётся стабильным, изменяется только реализация.
Intl может игнорировать неподдерживаемые опции без ошибок.
new Intl.NumberFormat("en-US", {
compactDisplay: "short",
signDisplay: "exceptZero"
});
В разных окружениях результат может отличаться, поэтому логика не должна зависеть от точного присутствия всех параметров.
Поведение Intl API можно представить как уровни:
Уровень 1: Полная поддержка
Уровень 2: Частичная поддержка
Уровень 3: Базовый Intl
Уровень 4: Fallback JS
Ключевой принцип заключается в том, что Intl не должен быть единственной точкой истины. Он является оптимизацией поверх уже существующего корректного поведения.
Любая функция форматирования должна: