Интернационализация в JavaScript опирается на встроенный стандарт
ECMAScript Internationalization API (ECMA-402), который реализует единый
слой форматирования дат, чисел, списков и текста с учётом локали. При
переходе к промышленным процессам разработки этот слой перестаёт быть
исключительно прикладным и становится частью CI/CD-инфраструктуры:
поведение Intl напрямую влияет на корректность интерфейсов,
тестов и релизов.
Ключевая особенность заключается в том, что Intl — это
не просто набор функций форматирования, а интерфейс к системным
ICU-данным (International Components for Unicode), которые могут
различаться между средами выполнения. Именно это делает
интернационализацию частью цепочки доставки, а не только логики
приложения.
Любая интеграция интернационализации в CI/CD начинается с понимания
детерминированности локалей. В Intl локаль задаётся строкой
BCP 47:
en-USru-RUar-EGzh-Hans-CNПоведение форматтеров зависит от:
Это приводит к потенциальной проблеме: одинаковый код может давать разные строки на разных агентах сборки.
Для устранения вариативности в CI принято фиксировать локаль:
const formatter = new Intl.DateTimeFormat('ru-RU', {
dateStyle: 'short',
timeStyle: 'short'
});
или принудительно использовать нейтральные локали в тестах:
const nf = new Intl.NumberFormat('en-US', { maximumFractionDigits: 2 });
В CI/CD форматирование становится контрактом, который должен быть стабилен.
const price = new Intl.NumberFormat('de-DE', {
style: 'currency',
currency: 'EUR'
}).format(1234.56);
Тесты фиксируют строковое представление:
expect(price).toBe('1.234,56 €');
Проблема возникает при смене ICU версии: даже пробелы и символы неразрывного пробела могут измениться.
const dt = new Intl.DateTimeFormat('en-GB', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
}).format(new Date('2024-05-01'));
В CI важно исключить флейки, связанные с временными зонами:
TZ=UTCnew Date() без параметров в тестахSnapshot-тесты часто используются для UI, где Intl
играет ключевую роль. Однако их стабильность зависит от:
Пример проблемы:
// ожидание
"1,234.50"
// фактический результат в другой версии ICU
"1.234,50"
Для уменьшения риска используют стратегию нормализации:
const normalize = (str) =>
str.replace(/\u00A0/g, ' ').trim();
или тестируют не строку, а структуру:
const parts = new Intl.NumberFormat('en-US').formatToParts(1234.5);
Метод formatToParts позволяет разложить форматированное
значение на атомарные элементы:
[
{ type: 'integer', value: '1' },
{ type: 'group', value: ',' },
{ type: 'integer', value: '234' },
{ type: 'decimal', value: '.' },
{ type: 'fraction', value: '50' }
]
В CI это снижает зависимость от локализованных символов:
Тесты начинают проверять структуру, а не строку.
Разные окружения могут поддерживать разный набор локалей. В Node.js это зависит от сборки ICU:
console.log(Intl.DateTimeFormat.supportedLocalesOf(['ru', 'fr', 'ja']));
В CI полезно вводить этап валидации:
Это предотвращает ситуацию, когда продакшн поддерживает
ru-RU, а тестовый раннер — нет.
Псевдолокализация используется для выявления проблем интерфейса без перевода:
Пример трансформации:
Settings → Šęťťïñğš
Это помогает обнаружить:
В CI это может быть отдельный шаг сборки UI.
Intl напрямую не управляет RTL, но локаль влияет на
выбор направления:
const locale = 'ar-EG';
В CI проверяется:
dir="rtl"Тесты часто используют headless браузер:
expect(document.documentElement.dir).toBe('rtl');
Intl.Collator определяет порядок сортировки строк:
const collator = new Intl.Collator('de', { sensitivity: 'base' });
В CI это критично для:
Проблема: разные ICU дают разный порядок сортировки.
Поэтому тесты часто фиксируют:
const sorted = ['ä', 'a', 'z'].sort(collator.compare);
В CI/CD интернационализация часто связана с этапом извлечения строк:
Пример:
{
"cart.items": "{count, plural, one {# item} other {# items}}"
}
На этапе CI проверяется:
Ошибки в ICU-строках часто проявляются только в рантайме. Поэтому добавляется линтинг:
i18n-lint translations/
Проверки включают:
one,
otherNode.js и браузеры используют разные версии ICU. Это влияет на:
В CI/CD это учитывается через:
process.versions.icuconsole.log(process.versions.icu);
Некоторые локализационные функции зависят от фич-флагов:
CI пайплайн часто разделяет:
Особое внимание уделяется краевым случаям:
new Intl.NumberFormat('en-US').format(Number.MAX_SAFE_INTEGER);
Создание Intl объектов дорогостоящее. В CI/CD
проверяется:
const nf = new Intl.NumberFormat('en-US');
Линтеры могут запрещать:
array.map(x => new Intl.NumberFormat().format(x));
Два подхода влияют на архитектуру пайплайна:
Runtime:
Build-time:
CI/CD pipeline должен явно разделять эти режимы, иначе возможны расхождения между staging и production.
Любое изменение UI может сломать локализацию:
Для этого применяются:
Типичный подход:
en, ruen, ru,
de, fr, ja, arКаждый билд прогоняется по матрице:
Это увеличивает стоимость CI, но снижает риск релизных дефектов.
В монорепозиториях интернационализация часто централизуется:
intl-utils пакетCI проверяет:
CI/CD должен учитывать, что форматирование логов тоже может зависеть от локали:
console.log(new Intl.DateTimeFormat().format(new Date()));
В продакшене это может усложнять:
Поэтому часто применяется правило: логирование — только в
en-US или ISO-форматах, а Intl используется
только для UI-слоя.