如何格式化 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));
// Output: "3–5"
console.log(formatter.formatRange(100, 200));
// Output: "100–200"
console.log(formatter.formatRange(1000, 5000));
// Output: "1,000–5,000"
格式化器会为区间内的两个数字都添加千位分隔符,并使用合适的分隔符连接它们。对于美式英语,区间分隔符是无空格的短横线(en dash)。
您可以通过更改本地化标识符,将同一个区间格式化为不同的本地化格式。
const usFormatter = new Intl.NumberFormat("en-US");
console.log(usFormatter.formatRange(100, 200));
// Output: "100–200"
const deFormatter = new Intl.NumberFormat("de-DE");
console.log(deFormatter.formatRange(100, 200));
// Output: "100–200"
const esFormatter = new Intl.NumberFormat("es-ES");
console.log(esFormatter.formatRange(100, 200));
// Output: "100-200"
每个本地化环境都有自己的区间分隔符和空格规范。API 会根据本地化的排版标准自动处理这些细节。
格式化货币区间
区间格式化支持所有数字格式化选项,包括货币。当您格式化货币区间时,格式化器会自动处理货币符号的位置和区间分隔符。
const formatter = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
maximumFractionDigits: 0
});
console.log(formatter.formatRange(100, 200));
// Output: "$100 – $200"
console.log(formatter.formatRange(1000, 5000));
// Output: "$1,000 – $5,000"
格式化器会在区间内每个数字前都加上货币符号,以明确表示两个值都是货币金额。
不同的本地化环境对货币符号的位置有不同的处理方式。
const usFormatter = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
maximumFractionDigits: 0
});
console.log(usFormatter.formatRange(100, 200));
// Output: "$100 – $200"
const deFormatter = new Intl.NumberFormat("de-DE", {
style: "currency",
currency: "EUR",
maximumFractionDigits: 0
});
console.log(deFormatter.formatRange(100, 200));
// Output: "100–200 €"
德语格式化器会将欧元符号放在区间末尾,而不是放在每个数字前。这符合德语在货币区间书写上的排版规范。
当区间值近似相等时会发生什么
当起始值和结束值在格式化后四舍五入为同一个数字时,格式化器会将区间合并,并可能添加近似符号。
const formatter = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
maximumFractionDigits: 0
});
console.log(formatter.formatRange(100, 200));
// Output: "$100 – $200"
console.log(formatter.formatRange(100, 120));
// Output: "$100 – $120"
console.log(formatter.formatRange(100.2, 100.8));
// Output: "~$100"
第三个示例展示了两个值在四舍五入后为同一个整数。与其显示“$100 – $100”(没有区间信息),格式化器会输出“~$100”。波浪号表示该值为近似值。
当格式化选项导致起始值和结束值显示相同,格式化器就会采用这种处理方式。
const formatter = new Intl.NumberFormat("en-US", {
maximumFractionDigits: 1
});
console.log(formatter.formatRange(2.9, 3.1));
// Output: "~3"
console.log(formatter.formatRange(2.94, 2.96));
// Output: "~2.9"
格式化器只在必要时插入近似符号。如果两个值四舍五入后不同,则会以标准区间形式显示。
带小数位的区间格式化
区间格式化会保留格式化选项中的小数位设置。你可以分别控制区间两端的精度。
const formatter = new Intl.NumberFormat("en-US", {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
console.log(formatter.formatRange(3.5, 5.7));
// Output: "3.50–5.70"
console.log(formatter.formatRange(100, 200));
// Output: "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));
// Output: "$99.99 – $199.99"
不同语言的数字区间格式化
区间格式化会根据每个语言环境的数字、分隔符和空格规范进行适配。
const enFormatter = new Intl.NumberFormat("en-US");
console.log(enFormatter.formatRange(1000, 5000));
// Output: "1,000–5,000"
const deFormatter = new Intl.NumberFormat("de-DE");
console.log(deFormatter.formatRange(1000, 5000));
// Output: "1.000–5.000"
const frFormatter = new Intl.NumberFormat("fr-FR");
console.log(frFormatter.formatRange(1000, 5000));
// Output: "1 000–5 000"
const jaFormatter = new Intl.NumberFormat("ja-JP");
console.log(jaFormatter.formatRange(1000, 5000));
// Output: "1,000~5,000"
英文用逗号作千位分隔符,区间用短横线(en dash)表示。德语用句点作千位分隔符,也用短横线。法语用空格作千位分隔符,也用短横线。日语用逗号作千位分隔符,区间用波浪线(~)表示。
这些差异还体现在货币格式化上。
const enFormatter = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD"
});
console.log(enFormatter.formatRange(100, 200));
// Output: "$100.00 – $200.00"
const deFormatter = new Intl.NumberFormat("de-DE", {
style: "currency",
currency: "EUR"
});
console.log(deFormatter.formatRange(100, 200));
// Output: "100,00–200,00 €"
const jaFormatter = new Intl.NumberFormat("ja-JP", {
style: "currency",
currency: "JPY"
});
console.log(jaFormatter.formatRange(100, 200));
// Output: "¥100~¥200"
每个本地化区域都有自己的货币符号位置、小数点分隔符和区间分隔符规则。API 会自动处理所有这些差异。
结合 formatRange 与紧凑记数法
区间格式化支持紧凑记数法,可以显示如 1K-5K 或 1M-5M 这样的区间。
const formatter = new Intl.NumberFormat("en-US", {
notation: "compact"
});
console.log(formatter.formatRange(1000, 5000));
// Output: "1K–5K"
console.log(formatter.formatRange(1000000, 5000000));
// Output: "1M–5M"
console.log(formatter.formatRange(1200, 4800));
// Output: "1.2K–4.8K"
格式化器会对区间的两个值都应用紧凑记数法。这样输出既简洁又能准确传达区间信息。
当区间跨越不同数量级时,格式化器会针对每个值做出相应处理。
const formatter = new Intl.NumberFormat("en-US", {
notation: "compact"
});
console.log(formatter.formatRange(500, 1500));
// Output: "500–1.5K"
console.log(formatter.formatRange(900000, 1200000));
// Output: "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);
// Output: <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);
// Output: "TypeError"
}
try {
console.log(formatter.formatRange(NaN, 200));
} catch (error) {
console.log(error.name);
// Output: "RangeError"
}
在处理用户输入或外部数据时,应在将值传递给 formatRange() 之前,验证其是否为有效数字。
该方法接受数字、BigInt 值或可表示为有效数字的字符串。
const formatter = new Intl.NumberFormat("en-US");
console.log(formatter.formatRange(100, 200));
// Output: "100–200"
console.log(formatter.formatRange(100n, 200n));
// Output: "100–200"
console.log(formatter.formatRange("100", "200"));
// Output: "100–200"
字符串输入会被解析为数字,能够在不产生浮点精度问题的情况下保留精度。
何时使用 formatRange 与手动格式化
在向用户展示区间时,建议使用 formatRange()。这适用于价格区间、数量区间、测量区间、页码或其他有界值。该方法可确保正确的本地化格式,无需自行实现分隔符逻辑。
当需要展示多个彼此无区间语义关联的独立值时,应避免使用 formatRange()。例如,显示价格列表如“$100, $150, $200”时,应对每个值分别调用 format(),而不是将其视为区间。
当值之间的关系不是数值区间时,也应避免使用 formatRange()。如果需要展示对比或差异,应根据具体场景采用合适的格式化方式,而不是区间格式化。