Как форматировать валюту с учётом локальных символов

Отображайте цены с правильным валютным символом, позицией и форматированием для любой локали

Введение

Валютные символы показывают, к какой валюте относится цена. Знак доллара обозначает доллары США, символ евро — евро, а знак фунта — британские фунты. Эти символы важны для международных приложений, потому что пользователи должны понимать, в какой валюте они видят или тратят деньги.

В разных странах суммы в валюте оформляются по-разному. В США пишут $1,234.56 — символ до суммы. В Германии — 1.234,56 €: символ после суммы и другие разделители. Во Франции — 1 234,56 € с пробелами между группами цифр. Если вы жёстко задаёте формат валюты, как в "$" + amount, вы предполагаете, что все пользователи используют одни и те же правила.

В JavaScript есть API Intl.NumberFormat для форматирования валюты с учётом локальных символов и стандартов. В этом уроке объясняется, как форматирование валюты меняется в зависимости от локали и как правильно отображать цены для любого языка или региона.

Валютные символы зависят от локали

У разных валют свои символы. Доллар США — $, евро — €, британский фунт — £, японская иена — ¥, швейцарский франк — Fr. или CHF в зависимости от контекста. Каждый символ помогает пользователям быстро понять, в какой валюте указана цена.

Некоторые символы используются для нескольких валют. Знак доллара $ применяют для долларов США, канадских, австралийских долларов, мексиканских песо и ряда других валют. Без дополнительного контекста пользователь не поймёт, о какой валюте идёт речь.

Положение валютного символа зависит от локали. В англоязычных странах символ обычно ставят перед суммой, например $100. Во многих европейских странах — после суммы, например 100 €. В некоторых странах между суммой и символом ставят пробел, в других — нет.

Эти различия означают, что нельзя просто склеить символ валюты и число. Нужна логика форматирования, которая учитывает как отображаемую валюту, так и локаль пользователя.

Форматирование валюты с помощью Intl.NumberFormat

Конструктор Intl.NumberFormat создаёт форматтер валюты, если передать style: 'currency' в опциях. Также обязательно указывается, какую валюту форматировать, с помощью опции currency с кодом валюты по ISO 4217.

const formatter = new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD'
});

console.log(formatter.format(1234.56));
// Output: "$1,234.56"

Это создаёт форматтер для американского английского, который отображает суммы в долларах США. Метод format() преобразует число в строку с символом доллара, разделителями тысяч и двумя знаками после запятой.

Опция currency требует трёхбуквенный код ISO 4217. Часто используемые коды: USD — доллар США, EUR — евро, GBP — британский фунт, JPY — японская иена, CAD — канадский доллар.

const usdFormatter = new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD'
});

const eurFormatter = new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'EUR'
});

const gbpFormatter = new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'GBP'
});

console.log(usdFormatter.format(100));
// Output: "$100.00"

console.log(eurFormatter.format(100));
// Output: "€100.00"

console.log(gbpFormatter.format(100));
// Output: "£100.00"

Каждый форматтер автоматически подставляет нужный символ валюты. Не нужно знать, какой символ соответствует коду валюты.

Локаль определяет позицию символа и форматирование

Параметр локали управляет тем, как форматируются суммы: где стоит символ, как группируются разряды и какой используется разделитель дробной части. Один и тот же код валюты может давать разный результат в зависимости от локали.

const usFormatter = new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'EUR'
});

const deFormatter = new Intl.NumberFormat('de-DE', {
  style: 'currency',
  currency: 'EUR'
});

const frFormatter = new Intl.NumberFormat('fr-FR', {
  style: 'currency',
  currency: 'EUR'
});

console.log(usFormatter.format(1234.56));
// Output: "€1,234.56"

console.log(deFormatter.format(1234.56));
// Output: "1.234,56 €"

console.log(frFormatter.format(1234.56));
// Output: "1 234,56 €"

Все три форматтера отображают евро, но используют разные правила. Форматтер для американского английского ставит символ перед суммой и использует точку как разделитель дробной части. Немецкий форматтер ставит символ после суммы с пробелом, использует точки для разделения тысяч и запятую для дробной части. Французский форматтер использует пробелы для разделения тысяч.

Intl API автоматически учитывает эти различия в зависимости от указанной локали. Вам не нужно знать правила форматирования для каждой локали.

Форматирование валюты включает десятичные знаки

Форматтеры валюты автоматически определяют нужное количество десятичных знаков в зависимости от валюты. В большинстве валют используются две десятичные цифры для центов или их эквивалентов.

const formatter = new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD'
});

console.log(formatter.format(100));
// Output: "$100.00"

console.log(formatter.format(100.5));
// Output: "$100.50"

console.log(formatter.format(100.567));
// Output: "$100.57"

Форматтер всегда отображает две десятичные цифры для долларов США, добавляя нули при необходимости и округляя, если введено больше знаков после запятой.

У некоторых валют нет десятичных знаков. Например, японская иена не имеет дробных единиц, поэтому суммы отображаются без десятичных знаков.

const formatter = new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'JPY'
});

console.log(formatter.format(1234.56));
// Output: "¥1,235"

Форматтер округляет до ближайшего целого числа, потому что в иене нет дробных единиц. Intl API знает точность для каждой валюты и применяет её автоматически.

Форматирование валюты для локали пользователя

Вместо жёстко заданной локали можно использовать языковые предпочтения браузера пользователя. Свойство navigator.language возвращает предпочитаемую локаль пользователя.

const userLocale = navigator.language;

const formatter = new Intl.NumberFormat(userLocale, {
  style: 'currency',
  currency: 'USD'
});

console.log(formatter.format(1234.56));
// Output varies by user's locale
// For en-US: "$1,234.56"
// For de-DE: "1.234,56 $"
// For fr-FR: "1 234,56 $US"

Этот подход отображает суммы в формате, привычном для каждого пользователя. Например, немецкий пользователь увидит символ после суммы с немецкими разделителями, а американский — символ до суммы с американскими разделителями.

Также можно передать весь массив navigator.languages, чтобы включить резервное поведение, если предпочтительная локаль пользователя недоступна.

const formatter = new Intl.NumberFormat(navigator.languages, {
  style: 'currency',
  currency: 'USD'
});

console.log(formatter.format(1234.56));

API использует первую поддерживаемую локаль из массива, автоматически обеспечивая резервное переключение.

Повторное использование форматтеров валюты

Создание нового экземпляра Intl.NumberFormat включает загрузку данных локали и обработку опций. Если нужно отформатировать несколько цен с одной локалью и валютой, создайте форматтер один раз и используйте его повторно.

const formatter = new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD'
});

const prices = [19.99, 29.99, 49.99, 99.99];

prices.forEach(price => {
  console.log(formatter.format(price));
});
// Output:
// "$19.99"
// "$29.99"
// "$49.99"
// "$99.99"

Этот подход эффективнее, чем создание нового форматтера для каждой цены. Разница в производительности становится заметной при форматировании массивов или списков с большим количеством значений.

Отображение цен в приложениях

Вы можете использовать форматтеры валют везде, где показываете цены пользователям: в списках товаров, корзинах, на страницах оформления заказа, в счетах и финансовых дашбордах.

const formatter = new Intl.NumberFormat(navigator.language, {
  style: 'currency',
  currency: 'USD'
});

const productPrice = 29.99;
const taxAmount = 2.40;
const totalPrice = productPrice + taxAmount;

document.getElementById('product-price').textContent = formatter.format(productPrice);
document.getElementById('tax-amount').textContent = formatter.format(taxAmount);
document.getElementById('total-price').textContent = formatter.format(totalPrice);

Отформатированные строки работают как любые другие строковые значения. Их можно вставлять в текст, атрибуты или любой другой контекст, где отображается информация для пользователей.

Работа с несколькими валютами

Приложения, поддерживающие несколько валют, должны создавать отдельные форматтеры для каждой валюты. Код валюты определяет, какой символ будет использоваться, а локаль — как будет форматироваться сумма.

const locale = navigator.language;

const usdFormatter = new Intl.NumberFormat(locale, {
  style: 'currency',
  currency: 'USD'
});

const eurFormatter = new Intl.NumberFormat(locale, {
  style: 'currency',
  currency: 'EUR'
});

const gbpFormatter = new Intl.NumberFormat(locale, {
  style: 'currency',
  currency: 'GBP'
});

console.log(usdFormatter.format(100));
console.log(eurFormatter.format(100));
console.log(gbpFormatter.format(100));

Каждый форматтер отображает нужный символ и следует конвенциям локали пользователя для этой валюты. Это гарантирует, что цены будут точными и понятными независимо от выбранной валюты и локали.