如何格式化范围,例如 3-5 或 100-200
使用 JavaScript 以符合区域设置的格式显示数字范围
介绍
数字范围在用户界面中随处可见。价格范围显示为 $100-$200,页码显示为 1-10,数量估计显示为 3-5 个项目。这些范围表明一个值位于两个端点之间,为用户提供了一个范围内的信息,而不是单一的精确数字。
当您对范围值之间的分隔符进行硬编码时,您假设所有用户都遵循相同的排版惯例。英语使用者通常使用连字符或短横表示范围,但其他语言使用不同的符号或词语。德语在数字之间使用“bis”,而某些语言在分隔符周围添加空格。
JavaScript 提供了 Intl.NumberFormat 的 formatRange() 方法来自动处理范围格式化。此方法根据区域设置对数字和分隔符应用特定的惯例,确保范围能够正确显示给全球用户。
为什么数字范围需要特定于区域的格式化
不同的文化发展出了不同的表示范围的惯例。这些惯例涉及分隔符符号以及其周围的间距。
在美式英语中,范围通常使用无空格的短横: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 €。不同的区域设置对货币符号的位置和重复处理方式不同。
手动格式化范围需要了解这些惯例并实现特定于区域的逻辑。而 Intl API 封装了这些知识,根据区域设置应用适当的格式化。
使用 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()。如果您需要显示比较或差异,请根据上下文使用适当的格式,而不是范围格式。