Как форматировать диапазоны, такие как 3–5 или 100–200
Используйте JavaScript для отображения числовых диапазонов с учетом локального форматирования
Введение
Числовые диапазоны встречаются повсеместно в пользовательских интерфейсах. Диапазоны цен отображаются как $100-$200, номера страниц как 1-10, а оценки количества как 3-5 предметов. Эти диапазоны показывают, что значение находится между двумя границами, предоставляя пользователям ограниченную информацию вместо одного точного числа.
Когда вы жестко задаете разделитель между значениями диапазона, вы предполагаете, что все пользователи следуют одним и тем же типографическим правилам. Англоговорящие обычно используют дефисы или короткие тире для обозначения диапазонов, но в других языках используются разные символы или слова. В немецком языке используется "bis" между числами, а в некоторых языках вокруг разделителей ставятся пробелы.
JavaScript предоставляет метод formatRange() в Intl.NumberFormat для автоматической обработки форматирования диапазонов. Этот метод применяет локализационные правила как к числам, так и к разделителю, гарантируя, что диапазоны отображаются корректно для пользователей по всему миру.
Почему числовые диапазоны нуждаются в локализованном форматировании
Разные культуры разработали разные правила для выражения диапазонов. Эти правила включают как символ разделителя, так и пробелы вокруг него.
В американском английском диапазоны обычно используют короткое тире без пробелов: 3-5, 100-200. В некоторых стилевых руководствах пробелы появляются вокруг тире: 3 - 5. Точные правила зависят от контекста и стандартов публикации.
В немецком языке диапазоны часто используют "bis" в качестве разделителя: 3 bis 5, 100 bis 200. Такой подход с использованием слов делает отношение диапазона явным, а не полагается на пунктуацию.
В испанском языке диапазоны могут использовать тире, как в английском, или слово "a": 3-5 или 3 a 5. Выбор зависит от конкретного испаноязычного региона и контекста.
При форматировании диапазонов, включающих валюту или единицы измерения, сложность возрастает. Диапазон цен может отображаться как $100-$200 в американском английском, но как 100 €-200 € в немецком или 100-200 € с символом, появляющимся только один раз. Разные локали по-разному размещают символы валют и обрабатывают их повторение.
Ручное форматирование диапазонов требует знания этих правил и реализации локализованной логики. API Intl инкапсулирует эти знания, применяя соответствующее форматирование в зависимости от локали.
Использование formatRange для форматирования диапазонов чисел
Метод formatRange() принимает два числа и возвращает отформатированную строку, представляющую диапазон. Создайте экземпляр Intl.NumberFormat с желаемой локалью и параметрами, затем вызовите formatRange() с начальным и конечным значениями.
const formatter = new Intl.NumberFormat("en-US");
console.log(formatter.formatRange(3, 5));
// Вывод: "3–5"
console.log(formatter.formatRange(100, 200));
// Вывод: "100–200"
console.log(formatter.formatRange(1000, 5000));
// Вывод: "1,000–5,000"
Форматировщик применяет разделители тысяч к обоим числам в диапазоне и использует соответствующий разделитель между ними. Для американского английского это длинное тире без пробелов.
Вы можете форматировать один и тот же диапазон для разных локалей, изменяя идентификатор локали.
const usFormatter = new Intl.NumberFormat("en-US");
console.log(usFormatter.formatRange(100, 200));
// Вывод: "100–200"
const deFormatter = new Intl.NumberFormat("de-DE");
console.log(deFormatter.formatRange(100, 200));
// Вывод: "100–200"
const esFormatter = new Intl.NumberFormat("es-ES");
console.log(esFormatter.formatRange(100, 200));
// Вывод: "100-200"
Каждая локаль применяет свои собственные правила для разделителей и пробелов. API автоматически обрабатывает эти детали на основе типографических стандартов локали.
Форматирование диапазонов валют
Форматирование диапазонов работает с любыми параметрами форматирования чисел, включая валюту. При форматировании диапазонов валют форматировщик обрабатывает как размещение символа валюты, так и разделитель диапазона.
const formatter = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
maximumFractionDigits: 0
});
console.log(formatter.formatRange(100, 200));
// Вывод: "$100 – $200"
console.log(formatter.formatRange(1000, 5000));
// Вывод: "$1,000 – $5,000"
Форматировщик размещает символ валюты перед каждым числом в диапазоне. Это делает очевидным, что оба значения представляют собой суммы в валюте.
Разные локали размещают символы валют по-разному.
const usFormatter = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
maximumFractionDigits: 0
});
console.log(usFormatter.formatRange(100, 200));
// Вывод: "$100 – $200"
const deFormatter = new Intl.NumberFormat("de-DE", {
style: "currency",
currency: "EUR",
maximumFractionDigits: 0
});
console.log(deFormatter.formatRange(100, 200));
// Вывод: "100–200 €"
Немецкий форматировщик размещает символ евро после диапазона, а не перед каждым числом. Это соответствует немецким типографическим стандартам для диапазонов валют.
Что происходит, когда значения диапазона примерно равны
Когда начальное и конечное значения округляются до одного и того же числа после форматирования, форматировщик объединяет диапазон и может добавить символ приближения.
const formatter = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
maximumFractionDigits: 0
});
console.log(formatter.formatRange(100, 200));
// Вывод: "$100 – $200"
console.log(formatter.formatRange(100, 120));
// Вывод: "$100 – $120"
console.log(formatter.formatRange(100.2, 100.8));
// Вывод: "~$100"
Третий пример показывает два значения, которые округляются до одного и того же целого числа. Вместо отображения "$100 – $100", что не передает информации о диапазоне, форматировщик выводит "~$100". Тильда указывает, что значение является приблизительным.
Это поведение применяется, когда параметры форматирования приводят к тому, что начальное и конечное значения выглядят одинаково.
const formatter = new Intl.NumberFormat("en-US", {
maximumFractionDigits: 1
});
console.log(formatter.formatRange(2.9, 3.1));
// Вывод: "~3"
console.log(formatter.formatRange(2.94, 2.96));
// Вывод: "~2.9"
Форматировщик вставляет символ приближения только при необходимости. Когда значения округляются до разных чисел, он отображает их как стандартный диапазон.
Форматирование диапазонов с десятичными знаками
Форматирование диапазонов сохраняет настройки десятичных знаков из параметров форматировщика. Вы можете контролировать точность для обоих значений в диапазоне.
const formatter = new Intl.NumberFormat("en-US", {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
console.log(formatter.formatRange(3.5, 5.7));
// Вывод: "3.50–5.70"
console.log(formatter.formatRange(100, 200));
// Вывод: "100.00–200.00"
Форматировщик применяет настройки десятичных знаков к обоим числам в диапазоне. Это обеспечивает единообразную точность для всего отображаемого диапазона.
Вы можете комбинировать десятичное форматирование с валютой или другими стилями.
const formatter = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
console.log(formatter.formatRange(99.99, 199.99));
// Вывод: "$99.99 – $199.99"
Форматирование числовых диапазонов на разных языках
Форматирование диапазонов адаптируется к правилам каждого языка для чисел, разделителей и пробелов.
const enFormatter = new Intl.NumberFormat("en-US");
console.log(enFormatter.formatRange(1000, 5000));
// Вывод: "1,000–5,000"
const deFormatter = new Intl.NumberFormat("de-DE");
console.log(deFormatter.formatRange(1000, 5000));
// Вывод: "1.000–5.000"
const frFormatter = new Intl.NumberFormat("fr-FR");
console.log(frFormatter.formatRange(1000, 5000));
// Вывод: "1 000–5 000"
const jaFormatter = new Intl.NumberFormat("ja-JP");
console.log(jaFormatter.formatRange(1000, 5000));
// Вывод: "1,000~5,000"
В английском языке используются запятые для разделения тысяч и короткое тире для диапазона. В немецком — точки для тысяч и короткое тире. Во французском — пробелы для тысяч и короткое тире. В японском — запятые для тысяч и волнистое тире (~) для диапазона.
Эти различия распространяются и на форматирование валют.
const enFormatter = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD"
});
console.log(enFormatter.formatRange(100, 200));
// Вывод: "$100.00 – $200.00"
const deFormatter = new Intl.NumberFormat("de-DE", {
style: "currency",
currency: "EUR"
});
console.log(deFormatter.formatRange(100, 200));
// Вывод: "100,00–200,00 €"
const jaFormatter = new Intl.NumberFormat("ja-JP", {
style: "currency",
currency: "JPY"
});
console.log(jaFormatter.formatRange(100, 200));
// Вывод: "¥100~¥200"
Каждый язык применяет свои правила для размещения символа валюты, разделителей десятичных знаков и разделителей диапазонов. API автоматически обрабатывает все эти вариации.
Комбинирование formatRange с компактной нотацией
Форматирование диапазонов работает с компактной нотацией, позволяя отображать диапазоны, такие как 1K-5K или 1M-5M.
const formatter = new Intl.NumberFormat("en-US", {
notation: "compact"
});
console.log(formatter.formatRange(1000, 5000));
// Вывод: "1K–5K"
console.log(formatter.formatRange(1000000, 5000000));
// Вывод: "1M–5M"
console.log(formatter.formatRange(1200, 4800));
// Вывод: "1.2K–4.8K"
Форматировщик применяет компактную нотацию к обоим значениям в диапазоне. Это позволяет сохранить краткость вывода, при этом передавая информацию о диапазоне.
Когда диапазон охватывает разные уровни величин, форматировщик обрабатывает каждое значение соответствующим образом.
const formatter = new Intl.NumberFormat("en-US", {
notation: "compact"
});
console.log(formatter.formatRange(500, 1500));
// Вывод: "500–1.5K"
console.log(formatter.formatRange(900000, 1200000));
// Вывод: "900K–1.2M"
Начальное значение может не использовать компактную нотацию, в то время как конечное значение использует, или они могут использовать разные индикаторы величин. Форматировщик принимает эти решения на основе размера каждого значения.
Использование formatRangeToParts для пользовательского оформления
Метод formatRangeToParts() возвращает массив объектов, представляющих части форматированного диапазона. Это позволяет стилизовать или изменять отдельные компоненты диапазона.
const formatter = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
maximumFractionDigits: 0
});
const parts = formatter.formatRangeToParts(100, 200);
console.log(parts);
Результатом будет массив объектов, каждый из которых содержит свойства type, value и source.
[
{ type: "currency", value: "$", source: "startRange" },
{ type: "integer", value: "100", source: "startRange" },
{ type: "literal", value: " – ", source: "shared" },
{ type: "currency", value: "$", source: "endRange" },
{ type: "integer", value: "200", source: "endRange" }
]
Свойство type указывает, что представляет часть: символ валюты, целое число, разделитель десятичных или текстовый литерал. Свойство value содержит отформатированный текст. Свойство source указывает, относится ли часть к начальному значению, конечному значению или является общей для обоих.
Вы можете использовать эти части для создания пользовательского HTML с разным оформлением для различных компонентов.
const formatter = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
maximumFractionDigits: 0
});
const parts = formatter.formatRangeToParts(100, 200);
let html = "";
parts.forEach(part => {
if (part.type === "currency") {
html += `<span class="currency-symbol">${part.value}</span>`;
} else if (part.type === "integer") {
html += `<span class="amount">${part.value}</span>`;
} else if (part.type === "literal") {
html += `<span class="separator">${part.value}</span>`;
} else {
html += part.value;
}
});
console.log(html);
// Результат: <span class="currency-symbol">$</span><span class="amount">100</span><span class="separator"> – </span><span class="currency-symbol">$</span><span class="amount">200</span>
Этот метод позволяет применять CSS-классы, добавлять всплывающие подсказки или реализовывать другие пользовательские функции, сохраняя при этом правильное форматирование, специфичное для локали.
Обработка крайних случаев с помощью formatRange
Метод formatRange() включает обработку ошибок для недопустимых входных данных. Если любой из параметров имеет значение undefined, будет выброшено исключение TypeError. Если любой из параметров имеет значение NaN или не может быть преобразован в число, будет выброшено исключение RangeError.
const formatter = new Intl.NumberFormat("en-US");
try {
console.log(formatter.formatRange(100, undefined));
} catch (error) {
console.log(error.name);
// Вывод: "TypeError"
}
try {
console.log(formatter.formatRange(NaN, 200));
} catch (error) {
console.log(error.name);
// Вывод: "RangeError"
}
При работе с пользовательским вводом или данными из внешних источников убедитесь, что значения являются допустимыми числами, прежде чем передавать их в formatRange().
Метод принимает числа, значения BigInt или строки, представляющие допустимые числа.
const formatter = new Intl.NumberFormat("en-US");
console.log(formatter.formatRange(100, 200));
// Вывод: "100–200"
console.log(formatter.formatRange(100n, 200n));
// Вывод: "100–200"
console.log(formatter.formatRange("100", "200"));
// Вывод: "100–200"
Строковые входные данные анализируются как числа, сохраняя точность без проблем преобразования с плавающей запятой.
Когда использовать formatRange вместо ручного форматирования
Используйте formatRange() при отображении диапазонов для пользователей. Это относится к диапазонам цен, количеств, измерений, номеров страниц или любых других ограниченных значений. Метод обеспечивает правильное форматирование, специфичное для локали, без необходимости реализовывать логику разделителей.
Избегайте использования formatRange(), если вам нужно отображать несколько отдельных значений, которые не связаны семантически как диапазон. Например, для отображения списка цен, таких как "$100, $150, $200", следует использовать обычные вызовы format() для каждого значения, а не рассматривать их как диапазоны.
Также избегайте использования formatRange(), если связь между значениями не является числовым диапазоном. Если вы показываете сравнение или разницу, используйте соответствующее форматирование для этого контекста, а не форматирование диапазона.