Диапазоны чисел в локализации представляют собой отдельный класс задач, возникающих при отображении интервалов значений, где важно сохранить корректность форматирования для конкретной культуры: разделители тысяч, десятичные знаки, пробелы, символы валют, а также правила сокращения повторяющихся частей. В контексте Globalize, работающей поверх CLDR-данных, диапазоны чисел строятся не как примитивная конкатенация строк, а как результат применения локализованных правил форматирования отдельных чисел и правил их объединения в интервал.
CLDR (Common Locale Data Repository) определяет отдельные шаблоны для диапазонов, включая числовые, временные и валютные. Для чисел это выражается в виде правил, позволяющих корректно отображать диапазоны вроде «1–5», «1 000–2 000» или «1,5–3,75» в зависимости от локали. В некоторых языках используется короткое тире, в других — длинное тире с пробелами, а в третьих — полное повторение единиц измерения для устранения неоднозначности.
Globalize, опираясь на CLDR, разделяет задачу на два уровня:
форматирование отдельных чисел и построение диапазонного представления.
Первый уровень реализуется через Globalize.formatNumber,
второй — через пользовательскую композицию или использование API,
совместимого с Intl (если окружение его поддерживает), либо
через шаблонные механизмы Globalize.
Любой диапазон в локализации начинается с корректного форматирования его границ. Globalize предоставляет API для форматирования чисел с учётом локали:
const Globalize = require("globalize");
Globalize.locale("de");
Globalize.formatNumber(1234567.89);
// "1.234.567,89"
При построении диапазона оба значения должны быть приведены к одному и тому же формату, иначе визуальная целостность интервала нарушается.
Пример подготовки значений:
const start = Globalize.formatNumber(1000);
const end = Globalize.formatNumber(5000);
console.log(start + " – " + end);
// "1.000 – 5.000"
Здесь уже проявляется важный аспект: диапазон — это не просто две строки, а структурированная композиция, где каждый элемент уже локализован.
В разных языковых системах диапазоны воспринимаются неодинаково. Основные различия:
CLDR описывает эти правила через шаблоны диапазонов, которые условно можно представить как:
{0}–{1}
или более сложные варианты, где часть данных из первого числа опускается, если она совпадает со вторым.
Globalize не всегда напрямую экспонирует готовый
formatRange для чисел, поэтому диапазон часто формируется
вручную с учётом локализованных компонентов.
Наиболее универсальный подход заключается в отдельном форматировании границ диапазона с последующей их композицией.
function formatNumberRange(globalize, from, to) {
const formattedFrom = globalize.formatNumber(from);
const formattedTo = globalize.formatNumber(to);
return `${formattedFrom} – ${formattedTo}`;
}
Globalize.locale("fr");
formatNumberRange(Globalize, 1200, 5600);
// "1 200 – 5 600"
В этом примере учитываются пробелы как разделители тысяч, характерные для французской локали.
Однако подобная реализация не учитывает более сложные правила сокращения, где диапазон может быть сжат, если верхняя и нижняя границы имеют общие префиксы.
CLDR допускает оптимизацию отображения диапазонов, при которой повторяющиеся элементы не дублируются.
Например:
1,000–1,9991,000–999Такая форма возможна только при наличии контекста, в котором однозначность сохраняется.
Реализация подобного поведения требует анализа строкового представления чисел:
function compactRange(fromStr, toStr) {
let i = 0;
while (i < fromStr.length && i < toStr.length && fromStr[i] === toStr[i]) {
i++;
}
const commonPrefix = fromStr.slice(0, i);
const fromSuffix = fromStr.slice(i);
const toSuffix = toStr.slice(i);
return `${commonPrefix}${fromSuffix}–${toSuffix}`;
}
Применение этого подхода зависит от языка: в некоторых локалях сокращение недопустимо, поскольку нарушает читаемость чисел.
Особую сложность представляют диапазоны дробных чисел, где важно сохранить единообразие точности.
Globalize.locale("en");
const a = Globalize.formatNumber(1.5);
const b = Globalize.formatNumber(3.75);
console.log(`${a} – ${b}`);
// "1.5 – 3.75"
Проблема возникает при разной точности:
const a = Globalize.formatNumber(1.5);
const b = Globalize.formatNumber(3.7);
В некоторых локалях требуется выравнивание количества знаков после запятой, чтобы диапазон выглядел симметрично. Это достигается через настройки форматтера чисел.
Globalize позволяет управлять форматированием через параметры числа:
const formatter = Globalize.numberFormatter({
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
formatter(1.5); // "1.50"
formatter(3.7); // "3.70"
После этого диапазон становится структурно стабильным:
const from = formatter(1.5);
const to = formatter(3.7);
`${from} – ${to}`;
// "1.50 – 3.70"
Такая унификация особенно важна для финансовых и технических данных, где несоответствие точности может быть интерпретировано как ошибка измерения.
Диапазоны денежных значений требуют отдельного подхода, поскольку символ валюты может:
Globalize предоставляет formatCurrency:
Globalize.locale("en");
Globalize.formatCurrency(100, "USD");
// "$100.00"
Для диапазонов валют применяется та же композиционная модель:
function formatCurrencyRange(globalize, from, to, currency) {
return `${globalize.formatCurrency(from, currency)} – ${globalize.formatCurrency(to, currency)}`;
}
formatCurrencyRange(Globalize, 100, 500, "USD");
// "$100.00 – $500.00"
В некоторых локалях символ валюты может дублироваться или сокращаться в зависимости от CLDR-правил, однако базовая реализация через Globalize остаётся композицией двух форматированных значений.
Символ, разделяющий границы диапазона, не является универсальным. В зависимости от локали используются:
CLDR хранит это как часть pattern-структуры диапазона.
Пример различий:
1,000–2,0001 000 – 2 0001.000–2.000При отсутствии специализированного API разработчики часто вводят единый символ вручную, однако это приводит к потере локализационной точности.
Globalize поддерживает message-форматирование через CLDR plural rules и ICU-подобные шаблоны. Диапазоны могут быть встроены в сообщения:
const message = Globalize.messageFormatter("range-template");
message({ start: 10, end: 20 });
При этом шаблон может выглядеть так:
"{start} – {end}"
Более сложные сценарии допускают интеграцию с plural rules:
"{count, plural, one {{start}–{end} item} other {{start}–{end} items}}"
Такой подход позволяет учитывать грамматические особенности языка при отображении диапазонов значений.
Перед форматированием диапазона числа должны быть нормализованы:
function normalizeRange(a, b) {
const from = Math.min(a, b);
const to = Math.max(a, b);
return { from, to };
}
Это предотвращает неконсистентное отображение, например:
5000 – 1000
которое в локализованном контексте может восприниматься неоднозначно.
При обработке больших массивов данных (например, аналитические таблицы или финансовые отчёты) создание диапазонов может стать затратным из-за повторного вызова форматтеров.
Оптимизация заключается в переиспользовании экземпляров форматтеров:
const numberFormatter = Globalize.numberFormatter();
const currencyFormatter = Globalize.currencyFormatter("USD");
function formatRangeFast(from, to) {
return `${numberFormatter(from)} – ${numberFormatter(to)}`;
}
Снижение накладных расходов особенно заметно при рендеринге списков диапазонов в интерфейсах.
При работе с физическими величинами диапазон часто включает единицу измерения:
10–20 км
или
10 km – 20 km
CLDR в некоторых локалях допускает сокращённую форму, где единица выводится один раз. Реализация этого поведения требует дополнительной логики:
function formatUnitRange(from, to, unit) {
const f = Globalize.formatNumber(from);
const t = Globalize.formatNumber(to);
return `${f}–${t} ${unit}`;
}
Более строгая локализация может требовать повторения единицы у каждой границы, что зависит от языковых правил.
Особые случаи включают:
5–5)-10–5)0–0)Каждый из этих случаев требует согласованного форматирования через Globalize, чтобы избежать смешения форматов:
Globalize.formatNumber(-1000);
// "-1.000" (в зависимости от локали)
Диапазон с отрицательными значениями требует сохранения знака у каждой границы без дополнительной интерпретации.
На уровне архитектуры диапазон чисел обычно представляется не строкой, а объектом:
{
from: number,
to: number,
format: "number" | "currency" | "unit"
}
Финальный строковый вид формируется на уровне представления. Такой подход позволяет:
Globalize выступает в роли слоя преобразования, а не хранения данных.