Библиотека Globalize предоставляет мощный механизм интернационализации на основе стандарта CLDR и библиотеки ICU. Однако высокая гибкость сопровождается дополнительными вычислительными затратами. Основные проблемы производительности возникают в следующих ситуациях:
Оптимизация в проектах с Globalize обычно сводится к уменьшению:
Globalize использует данные Unicode CLDR. Полный набор локализационных файлов может занимать несколько мегабайт.
Пример неэффективной загрузки:
const Globalize = require("globalize");
Globalize.load(
require("cldr-data"),
);
Подобный подход загружает:
В браузере это критически увеличивает:
Наиболее важная оптимизация — загрузка только необходимых данных.
const Globalize = require("globalize");
Globalize.load(
require("cldr-data/main/en/numbers.json"),
require("cldr-data/supplemental/numberingSystems.json")
);
Globalize.locale("en");
Globalize.load(
require("cldr-data/main/en/ca-gregorian.json"),
require("cldr-data/main/en/timeZoneNames.json"),
require("cldr-data/supplemental/timeData.json"),
require("cldr-data/supplemental/weekData.json")
);
Globalize состоит из отдельных модулей:
Полный импорт:
const Globalize = require("globalize");
Подключает значительную часть библиотеки.
Оптимизированный вариант:
const Globalize = require("globalize/dist/globalize/number");
Или:
import Globalize from "globalize/dist/globalize/number";
Tree shaking начинает работать эффективнее:
Создание formatter-функций — дорогая операция.
Неэффективный код:
function formatPrice(value) {
return Globalize
.numberFormatter({
style: "currency",
currency: "USD"
})(value);
}
Здесь formatter создаётся при каждом вызове.
const currencyFormatter = Globalize.numberFormatter({
style: "currency",
currency: "USD"
});
function formatPrice(value) {
return currencyFormatter(value);
}
Особенно важно в:
Плохой пример внутри React-компонента:
function Price({ value }) {
const formatter = Globalize.numberFormatter();
return <span>{formatter(value)}</span>;
}
Formatter будет создаваться при каждом render.
Оптимизированный вариант:
const formatter = Globalize.numberFormatter();
function Price({ value }) {
return <span>{formatter(value)}</span>;
}
Иногда formatter нужен не сразу.
let formatter = null;
function getFormatter() {
if (!formatter) {
formatter = Globalize.numberFormatter();
}
return formatter;
}
Globalize поддерживает компиляцию через пакет:
globalize-compiler
Это один из наиболее мощных механизмов оптимизации.
Во время выполнения:
В production formatter генерируется заранее.
const GlobalizeCompiler = require("globalize-compiler");
Результатом становится готовый JS-код:
function format(value) {
return "$" + value.toFixed(2);
}
Наиболее эффективная архитектура:
| Этап | Выполнение |
|---|---|
| Компиляция formatter | build-time |
| Загрузка CLDR | build-time |
| Генерация функций | build-time |
| Форматирование | runtime |
Ошибка многих проектов — загрузка всех языков.
Плохой подход:
Globalize.load(
enData,
frData,
deData,
ruData,
esData,
zhData,
jaData
);
Если пользователь использует только одну локаль, остальные данные загружаются бессмысленно.
async function loadLocale(locale) {
const data = await import(`./locales/${locale}.json`);
Globalize.load(data.default);
Globalize.locale(locale);
}
Globalize хорошо сочетается с:
const localeLoaders = {
en: () => import("./i18n/en"),
ru: () => import("./i18n/ru"),
de: () => import("./i18n/de")
};
Иногда приложение несколько раз вызывает:
Globalize.load(...)
Это приводит к:
Создание единого bootstrap-модуля:
// i18n.js
Globalize.load(...);
Globalize.locale("en");
export default Globalize;
Форматирование дат — одна из самых дорогих операций.
Неэффективный вариант:
items.map(item => {
return Globalize.formatDate(item.date, {
datetime: "medium"
});
});
Каждый вызов создаёт внутренний formatter.
const dateFormatter = Globalize.dateFormatter({
datetime: "medium"
});
items.map(item => {
return dateFormatter(item.date);
});
При обработке тысяч значений эффективность становится критической.
for (let i = 0; i < values.length; i++) {
console.log(
Globalize.formatNumber(values[i])
);
}
const formatter = Globalize.numberFormatter();
for (let i = 0; i < values.length; i++) {
console.log(
formatter(values[i])
);
}
Проблема UI-фреймворков — дорогое форматирование при перерисовке.
render() {
return users.map(user => (
<div>
{Globalize.formatDate(user.created)}
</div>
));
}
const formatter = Globalize.dateFormatter({
datetime: "short"
});
const formatted = useMemo(() => {
return formatter(date);
}, [date]);
Pluralization тоже создаёт внутренние структуры.
Неэффективно:
function getPlural(count) {
return Globalize.plural(count);
}
Оптимизированный вариант:
const pluralGenerator = Globalize.pluralGenerator();
function getPlural(count) {
return pluralGenerator(count);
}
Globalize хранит:
При большом количестве локалей память может быстро расти.
Globalize.locale(currentLocale);
Разделение:
Крупные JSON-файлы локализации можно вынести отдельно.
fetch("/cldr/ru.json")
В SSR каждый request может создавать formatter заново.
app.get("/", (req, res) => {
const formatter = Globalize.numberFormatter();
res.send(formatter(1000));
});
const formatter = Globalize.numberFormatter();
app.get("/", (req, res) => {
res.send(formatter(1000));
});
class I18nService {
constructor() {
this.currencyFormatter =
Globalize.numberFormatter({
style: "currency",
currency: "USD"
});
}
formatPrice(value) {
return this.currencyFormatter(value);
}
}
export default new I18nService();
Без измерений оптимизация теряет смысл.
console.time("format");
for (let i = 0; i < 100000; i++) {
formatter(i);
}
console.timeEnd("format");
Типичный результат:
| Операция | Время |
|---|---|
| Создание formatter 100000 раз | очень медленно |
| Повторное использование | в десятки раз быстрее |
При сложной локализации:
форматирование можно переносить в worker.
worker.postMessage(data);
Внутри worker:
const formatter =
Globalize.numberFormatter();
Главный поток UI остаётся свободным.
Globalize может существенно увеличивать размер приложения.
import Globalize from
"globalize/dist/globalize/number";
await import("./locales/ru");
Не подключать:
если они не используются.
Полезные инструменты:
Они помогают выявить:
Сообщения ICU-format могут быть дорогими.
Неэффективно:
Globalize.messageFormatter("hello");
при каждом вызове.
const helloFormatter =
Globalize.messageFormatter("hello");
helloFormatter({
name: "John"
});
Особенно важна для:
При оптимизации Globalize важно анализировать:
Эффективная схема обычно выглядит так:
Build Stage
├── CLDR extraction
├── Message compilation
├── Formatter compilation
└── Locale splitting
Runtime
├── Lazy locale loading
├── Cached formatter usage
├── Singleton i18n service
└── Minimal allocations
| Оптимизация | Эффект |
|---|---|
| Кэширование formatter | очень высокий |
| Минимизация CLDR | очень высокий |
| Предкомпиляция | очень высокий |
| Dynamic import | высокий |
| Code splitting | высокий |
| Lazy initialization | средний |
| Web Workers | зависит от нагрузки |
for (...) {
Globalize.numberFormatter();
}
require("cldr-data");
render() {
Globalize.formatDate(...);
}
Globalize.locale("en");
Globalize.locale("en");
Globalize.locale("en");
На слабых устройствах проблемы проявляются сильнее:
Особенно важно:
В серверных приложениях важны:
| Подход | Скорость |
|---|---|
| Runtime Globalize | ниже |
| Compiled Globalize | очень высокая |
class FormatterRegistry {
constructor() {
this.cache = new Map();
}
getCurrencyFormatter(currency) {
if (!this.cache.has(currency)) {
this.cache.set(
currency,
Globalize.numberFormatter({
style: "currency",
currency
})
);
}
return this.cache.get(currency);
}
}