Работа с устаревшим кодом при внедрении интернационализации через Globalize почти всегда связана с постепенной миграцией, сохранением обратной совместимости и минимизацией риска регрессий. В реальных проектах редко удаётся переписать весь слой форматирования дат, чисел и сообщений одновременно, поэтому ключевая задача — встроить Globalize поверх существующих механизмов так, чтобы система продолжала стабильно работать.
Типичный устаревший код в JavaScript-проектах содержит три основных категории локализационных решений:
toLocaleString() без централизованной
настройкиПример legacy-кода:
function formatPrice(price) {
return price.toFixed(2) + " $";
}
function formatDate(date) {
return date.getDate() + "." + (date.getMonth() + 1) + "." + date.getFullYear();
}
Подобный код плохо масштабируется, не поддерживает локали и усложняет переход на единый i18n-слой.
Основной принцип миграции — обёртка вместо переписывания. Вместо удаления старых функций вводится слой адаптеров, который делегирует форматирование Globalize.
import Globalize from "globalize";
Globalize.load(
require("cldr-data").entireSupplemental(),
require("cldr-data").entireMainFor("en", "ru")
);
Globalize.locale("ru");
На этом этапе важно не пытаться сразу заменить весь legacy-код, а подготовить инфраструктуру.
Legacy-функция:
function formatPrice(price) {
return price.toFixed(2) + " $";
}
Переходный адаптер:
const numberFormatter = Globalize.numberFormatter({
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
function formatPrice(price) {
return numberFormatter(price) + " $";
}
Ключевой момент: сохраняется интерфейс функции, но внутренность становится локализованной.
Legacy-код часто предполагает одну валюту. Globalize позволяет параметризовать форматирование:
function formatPrice(price, currency) {
const formatter = Globalize.currencyFormatter(currency, {
style: "symbol"
});
return formatter(price);
}
Такой подход устраняет жёсткую привязку к символу $ и
позволяет постепенно расширять поддержку локалей.
Legacy-реализация:
function formatDate(date) {
return date.getDate() + "." + (date.getMonth() + 1) + "." + date.getFullYear();
}
Адаптация через Globalize:
const dateFormatter = Globalize.dateFormatter({
date: "short"
});
function formatDate(date) {
return dateFormatter(date);
}
При этом сохраняется единая точка форматирования, что критично для постепенной миграции.
В крупных приложениях невозможно изменить все вызовы функций сразу. Поэтому применяется паттерн compatibility layer.
import * as Legacy from "./legacy";
export function formatPrice(price) {
return Legacy.formatPrice(price);
}
Позднее внутренняя реализация Legacy заменяется:
import Globalize from "globalize";
const numberFormatter = Globalize.numberFormatter({
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
export function formatPrice(price) {
return numberFormatter(price) + " $";
}
Таким образом внешний контракт сохраняется, а внутренняя реализация постепенно мигрирует.
Во многих проектах встречается ситуация, когда разные модули используют разные локали или вообще игнорируют их.
Типичный анти-паттерн:
// module A
date.toLocaleDateString("en-US");
// module B
date.toLocaleDateString("de-DE");
При переходе на Globalize вводится централизованный менеджер локали:
let currentLocale = "ru";
export function setLocale(locale) {
currentLocale = locale;
Globalize.locale(locale);
}
export function getLocale() {
return currentLocale;
}
Все форматтеры начинают зависеть от глобального состояния, но управляемого централизованно.
Чтобы минимизировать изменения по проекту, создаётся обёртка:
import Globalize from "globalize";
export const i18n = {
formatNumber(value) {
return Globalize.numberFormatter()(value);
},
formatDate(value) {
return Globalize.dateFormatter({ date: "medium" })(value);
},
formatCurrency(value, currency) {
return Globalize.currencyFormatter(currency)(value);
}
};
Legacy-код может продолжать использовать единый объект:
import { i18n } from "./i18n";
function render(price, date) {
return i18n.formatCurrency(price, "USD") + " " + i18n.formatDate(date);
}
Полная миграция часто невозможна, поэтому часть системы продолжает использовать встроенные API.
Подход — унификация через полифилл-слой:
function formatNumber(value) {
if (Globalize) {
return Globalize.numberFormatter()(value);
}
return value.toLocaleString();
}
Такой гибридный подход используется в переходных версиях.
Старые системы часто используют конкатенацию:
"Hello " + name + ", you have " + count + " messages"
Globalize не заменяет шаблонизацию напрямую, но позволяет стандартизировать данные:
const messageFormatter = (name, count) => {
const nf = Globalize.numberFormatter();
return `Hello ${name}, you have ${nf(count)} messages`;
};
Для более сложных случаев рекомендуется ввод промежуточного слоя сообщений.
На практике переход выполняется поэтапно:
Важно, что на каждом этапе система должна оставаться работоспособной без глобальных переключений.
function format(price) {
return Globalize.numberFormatter()(price);
}
Проблема — создание formatter на каждый вызов. В legacy-системах с высокой нагрузкой это приводит к деградации производительности.
Правильный подход — кэширование:
const formatter = Globalize.numberFormatter();
function format(price) {
return formatter(price);
}
Частая ошибка при модульной архитектуре:
Это приводит к несогласованности локалей. В legacy-системе должен быть один источник конфигурации.
Если часть функций использует Globalize, а часть — ручное форматирование, возникает фрагментация отображения данных. Это особенно критично для финансовых и аналитических интерфейсов.
В зрелых системах используется принцип двойного режима:
Переключение может быть feature-flag’ом:
function formatDate(date) {
if (FEATURES.i18n) {
return Globalize.dateFormatter({ date: "short" })(date);
}
return legacyFormat(date);
}
Это позволяет контролировать внедрение без риска полного отказа системы.
При переходе на Globalize тесты часто ломаются из-за изменения форматов.
Решение — нормализация:
expect(formatPrice(10)).toMatch(/10/);
Либо фиксация локали в тестах:
Globalize.locale("en");
Это устраняет нестабильность результатов.
В результате перехода система обычно приобретает трёхслойную структуру:
Такая модель позволяет интегрировать современную интернационализацию без разрушения существующей кодовой базы и обеспечивает постепенное вытеснение устаревших решений.