Обработка различных разделителей

Форматирование чисел, валют, процентов и дат в разных странах существенно отличается. Одной из наиболее заметных особенностей локализации являются разделители:

  • разделители целой и дробной части;
  • разделители тысяч;
  • разделители элементов списка;
  • символы группировки чисел;
  • специальные символы валют.

В англоязычных странах число может выглядеть так:

1,234,567.89

Во многих европейских локалях используется другой формат:

1 234 567,89

В некоторых странах применяются нестандартные схемы группировки:

12,34,56,789

Библиотека Globalize позволяет корректно обрабатывать подобные различия на основе данных CLDR (Common Locale Data Repository), автоматически подставляя нужные символы и правила форматирования.


Подключение CLDR и Globalize

Для работы с локалями необходимы:

  • библиотека Globalize;
  • данные CLDR;
  • модуль cldr-data.

Установка:

npm install globalize cldr-data

Подключение:

const Globalize = require("globalize");

Globalize.load(
    require("cldr-data/main/en/numbers.json"),
    require("cldr-data/main/fr/numbers.json"),
    require("cldr-data/main/de/numbers.json"),
    require("cldr-data/supplemental/likelySubtags.json"),
    require("cldr-data/supplemental/numberingSystems.json")
);

Разделители дробной части

Английская локаль

В локали en дробная часть отделяется точкой.

const en = new Globalize("en");

console.log(en.formatNumber(1234.56));

Результат:

1,234.56

Здесь:

  • , — разделитель тысяч;
  • . — десятичный разделитель.

Французская локаль

Во французском формате десятичным разделителем является запятая.

const fr = new Globalize("fr");

console.log(fr.formatNumber(1234.56));

Результат:

1 234,56

Особенности:

  • дробная часть отделяется запятой;
  • между тысячами используется неразрывный пробел.

Немецкая локаль

В немецком языке также применяется запятая для дробной части.

const de = new Globalize("de");

console.log(de.formatNumber(1234.56));

Результат:

1.234,56

Здесь:

  • . используется как разделитель тысяч;
  • , — десятичный разделитель.

Автоматическое определение разделителей

Globalize извлекает правила из CLDR автоматически. Разработчику не требуется вручную задавать символы.

Пример:

function formatPrice(locale, value) {
    const g = new Globalize(locale);

    return g.formatNumber(value);
}

console.log(formatPrice("en", 12345.67));
console.log(formatPrice("fr", 12345.67));
console.log(formatPrice("de", 12345.67));

Результат:

12,345.67
12 345,67
12.345,67

Разделители групп разрядов

Стандартная группировка

Наиболее распространённая схема:

1 000 000

Globalize автоматически вставляет нужный символ.

const en = new Globalize("en");

console.log(en.formatNumber(1000000));

Результат:

1,000,000

Индийская система группировки

В Индии применяется особая схема:

12,34,56,789

Подключение локали:

Globalize.load(
    require("cldr-data/main/en-IN/numbers.json")
);

const india = new Globalize("en-IN");

console.log(india.formatNumber(123456789));

Результат:

12,34,56,789

Особенность:

  • первая группа содержит 3 цифры;
  • последующие группы — по 2 цифры.

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

Разделители особенно важны при отображении денежных значений.

Подключение данных:

Globalize.load(
    require("cldr-data/main/en/currencies.json"),
    require("cldr-data/main/fr/currencies.json"),
    require("cldr-data/main/de/currencies.json")
);

Валюта в английской локали

const en = new Globalize("en");

console.log(
    en.formatCurrency(1234567.89, "USD")
);

Результат:

$1,234,567.89

Валюта во французской локали

const fr = new Globalize("fr");

console.log(
    fr.formatCurrency(1234567.89, "EUR")
);

Результат:

1 234 567,89 €

Особенности:

  • пробелы между разрядами;
  • запятая как десятичный разделитель;
  • символ валюты расположен после числа.

Валюта в немецкой локали

const de = new Globalize("de");

console.log(
    de.formatCurrency(1234567.89, "EUR")
);

Результат:

1.234.567,89 €

Парсинг чисел с разными разделителями

Форматирование — только часть задачи. Важно также правильно интерпретировать пользовательский ввод.

Проблема стандартного parseFloat

parseFloat("1,234.56");

Результат:

1

Метод останавливается на запятой.

Для европейского формата ситуация ещё хуже:

parseFloat("1.234,56");

Результат:

1.234

Использование numberParser

Globalize предоставляет локализованный парсер.

Английский формат

const en = new Globalize("en");

const parser = en.numberParser();

console.log(parser("1,234.56"));

Результат:

1234.56

Немецкий формат

const de = new Globalize("de");

const parser = de.numberParser();

console.log(parser("1.234,56"));

Результат:

1234.56

Globalize автоматически:

  • удаляет разделители групп;
  • определяет десятичный символ;
  • преобразует строку в число JavaScript.

Парсинг валют

Парсер может работать и с денежными значениями.

const fr = new Globalize("fr");

const parser = fr.numberParser({
    style: "currency",
    currency: "EUR"
});

console.log(
    parser("1 234,56 €")
);

Результат:

1234.56

Нестандартные символы разделителей

Некоторые локали используют:

  • узкие пробелы;
  • неразрывные пробелы;
  • специальные Unicode-символы.

Пример французского разделителя:

1 234 567

Здесь используется символ:

U+202F

Это узкий неразрывный пробел.

Globalize корректно работает с такими символами благодаря данным CLDR.


Локализованные проценты

Разделители также влияют на формат процентов.

Английская локаль

const en = new Globalize("en");

console.log(
    en.formatNumber(0.256, {
        style: "percent"
    })
);

Результат:

26%

Французская локаль

const fr = new Globalize("fr");

console.log(
    fr.formatNumber(0.256, {
        style: "percent"
    })
);

Результат:

26 %

Во французском формате между числом и знаком процента добавляется пробел.


Пользовательский ввод и локали

Типичная ошибка — ожидание только одного формата числа.

Неправильно:

const value = parseFloat(userInput);

Проблемы:

  • неправильный разбор запятых;
  • потеря дробной части;
  • ошибки округления;
  • невозможность обработки локализованного ввода.

Корректный подход:

function parseLocalizedNumber(locale, value) {
    const g = new Globalize(locale);

    const parser = g.numberParser();

    return parser(value);
}

Использование:

console.log(
    parseLocalizedNumber("fr", "1 234,56")
);

console.log(
    parseLocalizedNumber("de", "1.234,56")
);

Разделители в компактных числах

Globalize поддерживает компактное представление:

const en = new Globalize("en");

const formatter = en.numberFormatter({
    compact: "short"
});

console.log(formatter(1200));

Результат:

1.2K

Во французской локали формат будет другим:

const fr = new Globalize("fr");

const formatter = fr.numberFormatter({
    compact: "short"
});

console.log(formatter(1200));

Результат:

1,2 k

Различия:

  • используется запятая;
  • присутствует пробел перед сокращением.

Форматирование чисел с фиксированной точностью

Разделители сохраняются независимо от количества знаков после запятой.

const de = new Globalize("de");

const formatter = de.numberFormatter({
    minimumFractionDigits: 2,
    maximumFractionDigits: 2
});

console.log(formatter(1234));

Результат:

1.234,00

Локализованные шаблоны отображения

Globalize позволяет создавать повторно используемые форматтеры.

const locale = new Globalize("fr");

const moneyFormatter =
    locale.currencyFormatter("EUR");

const percentFormatter =
    locale.numberFormatter({
        style: "percent"
    });

console.log(
    moneyFormatter(1234.5)
);

console.log(
    percentFormatter(0.45)
);

Результат:

1 234,50 €
45 %

Ошибки при ручной замене разделителей

Часто встречается попытка заменить символы вручную:

value.replace(",", ".");

Такой подход приводит к ошибкам.

Пример:

"1.234,56".replace(",", ".");

Результат:

1.234.56

Полученная строка уже не является корректным числом.

Дополнительные проблемы:

  • разные Unicode-пробелы;
  • нестандартные схемы группировки;
  • валютные символы;
  • локальные правила округления.

Globalize устраняет необходимость ручной обработки.


Комбинирование локалей

В приложении может использоваться несколько языков одновременно.

const locales = ["en", "fr", "de"];

const value = 1234567.89;

locales.forEach(locale => {
    const g = new Globalize(locale);

    console.log(
        locale,
        g.formatNumber(value)
    );
});

Результат:

en 1,234,567.89
fr 1 234 567,89
de 1.234.567,89

Работа с пользовательскими настройками

Локаль часто определяется:

  • языком браузера;
  • настройками профиля;
  • региональными параметрами ОС.

Пример:

const locale =
    navigator.language;

const g =
    new Globalize(locale);

console.log(
    g.formatNumber(12345.67)
);

Проверка корректности локализованных данных

После парсинга рекомендуется проверять результат.

const parser =
    new Globalize("fr")
        .numberParser();

const result =
    parser("1 234,56");

if (isNaN(result)) {
    console.log("Ошибка формата");
} else {
    console.log(result);
}

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

Создание форматтера — относительно дорогая операция.

Неэффективно:

function format(value) {
    return new Globalize("en")
        .formatNumber(value);
}

Лучше:

const formatter =
    new Globalize("en")
        .numberFormatter();

function format(value) {
    return formatter(value);
}

Особенно важно при:

  • больших таблицах;
  • финансовых отчётах;
  • графиках;
  • потоковой обработке данных.

Использование разделителей в интерфейсах

Корректная локализация критична для:

  • интернет-магазинов;
  • банковских систем;
  • ERP;
  • CRM;
  • аналитических панелей;
  • бухгалтерского ПО.

Неправильные разделители могут приводить к:

  • ошибкам ввода;
  • неверному пониманию сумм;
  • проблемам в финансовых расчётах;
  • потере доверия пользователей.

Globalize обеспечивает единообразную и стандартизированную обработку числовых форматов независимо от региона пользователя.