Международные веб-приложения редко работают только с одной валютой. Интернет-магазины, CRM-системы, SaaS-платформы, финансовые панели, системы аналитики и маркетплейсы одновременно отображают цены в долларах, евро, тенге, иенах, фунтах и десятках других валют. Простого форматирования чисел недостаточно: каждая локаль использует собственные правила отображения символов, разделителей, порядка записи и округления.
Библиотека Globalize строится поверх стандарта CLDR (Common Locale Data Repository) и предоставляет полноценную систему локализации чисел, валют, дат и сообщений.
Основная задача при работе с несколькими валютами — разделить:
На практике:
Пример данных:
{
"price": 1999.5,
"currency": "USD"
}
Globalize не занимается конвертацией валют. Библиотека отвечает именно за локализованное представление.
Для корректной работы необходимо подключить данные CLDR.
Установка:
npm install globalize cldr-data
Подключение:
const Globalize = require("globalize");
Globalize.load(
require("cldr-data/main/en/numbers.json"),
require("cldr-data/main/en/currencies.json"),
require("cldr-data/main/ru/numbers.json"),
require("cldr-data/main/ru/currencies.json"),
require("cldr-data/supplemental/likelySubtags.json"),
require("cldr-data/supplemental/numberingSystems.json"),
require("cldr-data/supplemental/plurals.json")
);
После загрузки можно переключать локали:
Globalize.locale("en");
Главный инструмент — currencyFormatter.
Базовый пример:
const formatter = Globalize.currencyFormatter("USD");
console.log(formatter(1999.5));
Результат для английской локали:
$1,999.50
Для русской:
Globalize.locale("ru");
const formatter = Globalize.currencyFormatter("USD");
console.log(formatter(1999.5));
Результат:
1 999,50 $
Разница включает:
В реальном приложении одна страница часто отображает несколько валют.
Пример:
const prices = [
{ amount: 1200, currency: "USD" },
{ amount: 950, currency: "EUR" },
{ amount: 560000, currency: "KZT" }
];
Globalize.locale("ru");
prices.forEach(item => {
const formatter = Globalize.currencyFormatter(item.currency);
console.log(formatter(item.amount));
});
Результат:
1 200,00 $
950,00 €
560 000,00 ₸
Создание форматтера — относительно дорогая операция. При большом количестве валют форматтеры необходимо кэшировать.
Плохой вариант:
items.forEach(item => {
const formatter = Globalize.currencyFormatter(item.currency);
render(formatter(item.price));
});
Оптимизированный подход:
const formatterCache = {};
function getFormatter(currency) {
if (!formatterCache[currency]) {
formatterCache[currency] =
Globalize.currencyFormatter(currency);
}
return formatterCache[currency];
}
Использование:
items.forEach(item => {
const formatter = getFormatter(item.currency);
render(formatter(item.price));
});
Такой подход особенно важен:
Валюты и локали — разные сущности.
Например:
| Локаль | Валюта |
|---|---|
| ru | USD |
| en | EUR |
| de | KZT |
Пользователь может находиться в Казахстане, но работать с долларами.
Корректная архитектура:
Globalize.locale(userLocale);
const formatter =
Globalize.currencyFormatter(currencyCode);
Нельзя жёстко связывать:
ru -> RUB
en -> USD
Такая модель быстро ломается в международных приложениях.
CLDR автоматически использует корректные обозначения валют.
Примеры:
Globalize.locale("en");
console.log(
Globalize.currencyFormatter("USD")(50)
);
console.log(
Globalize.currencyFormatter("EUR")(50)
);
console.log(
Globalize.currencyFormatter("JPY")(50)
);
Результат:
$50.00
€50.00
¥50
Обратить внимание необходимо на:
Некоторые валюты не используют десятичные значения.
Например:
Globalize автоматически применяет правила CLDR.
Globalize.locale("ja");
const formatter =
Globalize.currencyFormatter("JPY");
console.log(formatter(1200.75));
Результат:
¥1,201
Происходит автоматическое округление.
Иногда требуется переопределить стандартные правила.
Пример:
const formatter = Globalize.numberFormatter({
style: "currency",
currency: "USD",
minimumFractionDigits: 4,
maximumFractionDigits: 4
});
console.log(formatter(12.5));
Результат:
$12.5000
Полезно:
CLDR не содержит встроенных правил для криптовалют.
Для Bitcoin:
const formatter = Globalize.numberFormatter({
minimumFractionDigits: 8,
maximumFractionDigits: 8
});
console.log(formatter(0.12345678));
Результат:
0.12345678
Символ добавляется вручную:
const value = formatter(0.12345678);
console.log(`₿ ${value}`);
Современные приложения позволяют менять язык интерфейса без перезагрузки.
Пример:
function formatPrice(amount, currency, locale) {
Globalize.locale(locale);
const formatter =
Globalize.currencyFormatter(currency);
return formatter(amount);
}
Использование:
console.log(
formatPrice(1000, "USD", "en")
);
console.log(
formatPrice(1000, "USD", "fr")
);
console.log(
formatPrice(1000, "USD", "ru")
);
Результаты:
$1,000.00
1 000,00 $US
1 000,00 $
Метод:
Globalize.locale(...)
изменяет глобальное состояние.
В больших приложениях это создаёт проблемы:
Безопаснее использовать экземпляры:
const ru = new Globalize("ru");
const en = new Globalize("en");
Далее:
const ruFormatter =
ru.currencyFormatter("USD");
const enFormatter =
en.currencyFormatter("USD");
При серверном рендеринге особенно важно избегать глобального состояния.
Плохой вариант:
Globalize.locale(user.locale);
При одновременных запросах локали будут конфликтовать.
Правильный подход:
function createFormatter(locale, currency) {
const instance = new Globalize(locale);
return instance.currencyFormatter(currency);
}
Пример хука:
import Globalize from "globalize";
export function useCurrency(locale, currency) {
const globalize = new Globalize(locale);
return globalize.currencyFormatter(currency);
}
Компонент:
function Price({ value, locale, currency }) {
const format = useCurrency(locale, currency);
return <span>{format(value)}</span>;
}
Пример composable:
import Globalize from "globalize";
export function useCurrency(locale) {
function format(amount, currency) {
const globalize = new Globalize(locale);
return globalize
.currencyFormatter(currency)(amount);
}
return { format };
}
Типичная проблема — производительность.
Например:
| Товар | Валюта |
|---|---|
| Laptop | USD |
| Phone | EUR |
| Camera | GBP |
Если таблица содержит тысячи строк:
Эффективная архитектура:
const instances = {};
const formatters = {};
function getGlobalize(locale) {
if (!instances[locale]) {
instances[locale] = new Globalize(locale);
}
return instances[locale];
}
function getFormatter(locale, currency) {
const key = `${locale}-${currency}`;
if (!formatters[key]) {
formatters[key] =
getGlobalize(locale)
.currencyFormatter(currency);
}
return formatters[key];
}
Globalize не выполняет обменные операции.
Неверный подход:
Globalize.convertCurrency(...)
Такого API не существует.
Конвертация должна происходить отдельно:
function convert(amount, rate) {
return amount * rate;
}
После конвертации используется форматирование:
const converted = convert(100, 0.92);
const formatter =
Globalize.currencyFormatter("EUR");
console.log(formatter(converted));
Плавающая точка вызывает ошибки:
0.1 + 0.2
Результат:
0.30000000000000004
Для финансовых систем используют:
Пример хранения в центах:
{
amount: 199950,
currency: "USD"
}
Форматирование:
const dollars = amount / 100;
CLDR содержит локализованные названия.
Пример:
const globalize = new Globalize("ru");
console.log(
globalize.cldr.main([
"numbers",
"currencies",
"USD",
"displayName"
])
);
Результат:
доллар США
Для английской локали:
US Dollar
Иногда интерфейс требует сокращённых форматов.
Пример:
const formatter = Globalize.numberFormatter({
minimumFractionDigits: 1,
maximumFractionDigits: 1
});
console.log(formatter(1200000));
Для финансовых панелей дополнительно реализуют:
1.2M USD
или:
₸ 2.5 млн
Globalize предоставляет основу локализации, а бизнес-логика сокращений реализуется отдельно.
Арабские локали имеют особенности отображения:
Globalize.locale("ar");
const formatter =
Globalize.currencyFormatter("USD");
console.log(formatter(1500));
Формат включает:
Такие интерфейсы требуют дополнительного тестирования CSS.
Ошибка:
if (locale === "ru") {
currency = "RUB";
}
Локаль не определяет валюту.
Неверно:
"$100" * 0.9
Сначала вычисления, затем форматирование.
Ошибка:
{
total: "$1,200.00"
}
Необходимо хранить исходные значения:
{
total: 1200,
currency: "USD"
}
Для финансовых систем с высокой точностью Number недостаточен.
Крупные приложения обычно разделяют систему на уровни:
{
amountMinor: 150099,
currency: "USD"
}
convertCurrency(amount, from, to)
formatCurrency(amount, currency, locale)
<Price />
Такое разделение: