Легаси-системы, построенные до появления современного стандарта ECMAScript Internationalization API, часто содержат самодельные решения для форматирования дат, чисел, валют, сортировки и локализации текста. Эти решения обычно основаны на строковых шаблонах, регулярных выражениях и ручной поддержке локалей, что приводит к накоплению технического долга и росту числа скрытых ошибок при масштабировании системы на новые регионы.
Классические реализации локализации в старых кодовых базах чаще всего обладают рядом характерных ограничений:
DD.MM.YYYY или
MM/DD/YYYY без учета региона;Подобные системы становятся особенно уязвимыми при переходе на новые рынки, где требования к локализации выходят за рамки простого перевода интерфейса.
ECMAScript Internationalization API (Intl) предоставляет стандартизированный набор инструментов для работы с локалями непосредственно в языке JavaScript. Он опирается на ICU (International Components for Unicode), что обеспечивает согласованное поведение между средами выполнения.
Ключевые компоненты API:
Intl.DateTimeFormat — форматирование дат и времениIntl.NumberFormat — форматирование чисел и валютIntl.Collator — лексикографическая сортировка
строкIntl.PluralRules — работа с множественными формамиIntl.RelativeTimeFormat — относительное время («2 дня
назад»)Intl.ListFormat — форматирование списковВ legacy-коде часто встречается подобная логика:
function formatDate(date) {
return date.getDate() + "." +
(date.getMonth() + 1) + "." +
date.getFullYear();
}
Подобный подход игнорирует локальные различия, порядок компонентов и часовые пояса.
Современная замена:
const formatter = new Intl.DateTimeFormat('de-DE', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
});
formatter.format(new Date());
Использование Intl.DateTimeFormat устраняет
необходимость ручного управления форматами и снижает риск ошибок при
переходе между регионами.
Особое значение имеет работа с часовыми поясами:
const formatter = new Intl.DateTimeFormat('en-GB', {
timeZone: 'Europe/London',
dateStyle: 'full',
timeStyle: 'short'
});
Подобная конфигурация критична для распределённых систем, где сервер и клиент могут находиться в разных временных зонах.
Legacy-решения часто используют примитивную замену точек и запятых:
function formatNumber(n) {
return n.toString().replace(".", ",");
}
Подобная логика не учитывает группировку разрядов, локальные правила округления и валютные обозначения.
Intl.NumberFormat решает эти проблемы:
const formatter = new Intl.NumberFormat('ru-RU', {
style: 'currency',
currency: 'RUB'
});
formatter.format(1234567.89);
Также возможно форматирование процентов и единиц измерения:
new Intl.NumberFormat('en-US', {
style: 'percent'
}).format(0.42);
Ключевой аспект миграции — централизованное создание форматтеров.
Повторное создание экземпляров Intl.NumberFormat в горячих
путях выполнения может приводить к избыточным затратам, поэтому часто
применяется кэширование:
const cache = new Map();
function getNumberFormatter(locale, options) {
const key = locale + JSON.stringify(options);
if (!cache.has(key)) {
cache.set(key, new Intl.NumberFormat(locale, options));
}
return cache.get(key);
}
Legacy-системы часто используют простое сравнение:
items.sort((a, b) => a.name > b.name ? 1 : -1);
Такой подход не учитывает особенности алфавитов и диакритических знаков.
Intl.Collator обеспечивает корректную локализованную
сортировку:
const collator = new Intl.Collator('tr', {
sensitivity: 'base'
});
items.sort((a, b) => collator.compare(a.name, b.name));
Особенно критично это для языков с особыми правилами сортировки, где простое сравнение Unicode-кодов приводит к некорректному порядку.
Legacy-код часто содержит цепочки условий:
function formatItems(count) {
if (count === 1) return count + " item";
if (count < 5) return count + " items";
return count + " items";
}
Подобные конструкции не масштабируются на языки с более сложной грамматикой.
Intl.PluralRules позволяет выделить категорию
множественности:
const rules = new Intl.PluralRules('ru-RU');
rules.select(1); // "one"
rules.select(2); // "few"
rules.select(5); // "many"
На основе результата строится таблица переводов:
const messages = {
one: '1 файл',
few: 'несколько файлов',
many: 'много файлов'
};
const rule = rules.select(count);
const message = messages[rule];
Такой подход отделяет грамматическую логику от бизнес-логики.
Ручные реализации вида:
function timeAgo(ms) {
const diff = Date.now() - ms;
return Math.floor(diff / 1000 / 60) + " minutes ago";
}
не учитывают локализацию и грамматику.
Intl.RelativeTimeFormat стандартизирует этот
процесс:
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
rtf.format(-1, 'day'); // "yesterday"
rtf.format(-5, 'day'); // "5 days ago"
Это особенно важно в интерфейсах с высокой динамикой данных: ленты новостей, уведомления, журналы активности.
Legacy-подход:
function formatList(items) {
return items.slice(0, -1).join(", ") + " and " + items.at(-1);
}
Такой код не учитывает языковые различия.
Intl.ListFormat:
const lf = new Intl.ListFormat('en', {
style: 'long',
type: 'conjunction'
});
lf.format(['apples', 'bananas', 'oranges']);
Результат автоматически адаптируется под локаль, включая пунктуацию и союзы.
Переход на Intl API в больших кодовых базах требует постепенного подхода:
Форматирование выносится в отдельный слой:
formatters/date.jsformatters/number.jsformatters/text.jsЭто снижает связность и упрощает замену реализации.
Старые функции сохраняются как оболочки:
function formatCurrencyLegacy(value) {
return getNumberFormatter('en-US', {
style: 'currency',
currency: 'USD'
}).format(value);
}
Постепенно внутренняя реализация полностью заменяется Intl.
Некоторые окружения могут не поддерживать полный набор API:
if (typeof Intl !== "undefined" && Intl.DateTimeFormat) {
// использование Intl
} else {
// fallback на legacy-реализацию
}
В legacy-системах локаль часто определяется хаотично: часть через backend, часть через frontend.
Intl требует централизованного определения:
Accept-LanguageХотя Intl API встроен в движки JavaScript, существуют нюансы:
Оптимизация обычно строится вокруг следующих принципов:
В серверных приложениях Intl используется для подготовки данных до отправки клиенту:
При этом важно избегать зависимости от локали окружения сервера:
process.env.LANG = "en-US";
или явная передача локали как параметра.
После замены legacy-решений на Intl API структура системы обычно становится более предсказуемой:
Переход на стандартизированный API приводит к унификации поведения между браузером и сервером, снижая количество edge-case сценариев, связанных с форматированием данных в разных окружениях.