如何格式化带单位的测量列表?

使用 JavaScript 的 Intl API 以符合本地化的列表格式显示多个测量值,例如 5 公里、10 公里、15 公里。

介绍

显示测量值的应用程序通常需要同时显示多个值。例如,健身应用可能会显示分段时间为“5 公里、10 公里、15 公里”。天气应用可能会显示一周内的温度为“20°C、22°C、25°C、23°C”。食谱可能会列出配料的用量为“2 杯、1 汤匙、3 茶匙”。

这些列表结合了两个国际化挑战。首先,每个测量值需要符合目标语言的单位格式。其次,列表本身需要使用目标语言中适当的标点符号和分隔符。英语使用逗号,有时还会使用“and”这样的连词。其他语言使用不同的分隔符并遵循不同的语法规则。

JavaScript 提供了两个 API 来解决这个问题。Intl.NumberFormat 用于格式化单个测量值及其单位。Intl.ListFormat 用于将多个值组合成符合语法规则的列表。本课程将解释如何将这两个 API 结合使用,以格式化符合用户期望的测量值列表。

测量值列表需要两个格式化步骤

在格式化测量值列表时,不能跳过任何一个格式化步骤。如果只格式化列表而不格式化测量值,会得到符合目标语言的分隔符但单位显示不正确。如果只格式化测量值而不格式化列表,会得到正确的单位但分隔符不正确。

const distances = [5, 10, 15];

// 错误:列表已格式化但测量值未格式化
console.log(distances.join(', '));
// 输出:"5, 10, 15"(缺少单位)

// 错误:测量值已格式化但列表未格式化
const formatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'kilometer'
});
console.log(distances.map(d => formatter.format(d)).join(', '));
// 输出:"5 km, 10 km, 15 km"(硬编码的逗号可能不适用于某些语言)

正确的方法是先格式化测量值,然后将格式化后的字符串数组格式化为列表。

const distances = [5, 10, 15];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'kilometer'
});

const formattedMeasurements = distances.map(d => numberFormatter.format(d));
// 结果:["5 km", "10 km", "15 km"]

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedMeasurements));
// 输出:"5 km, 10 km, 15 km"

这种模式适用于任何测量类型和任何语言。您可以先格式化每个测量值及其单位,然后将格式化后的字符串数组格式化为列表。

使用类型单元格式化测量列表

Intl.ListFormat 构造函数接受一个 type 选项,用于控制列表项的组合方式。type: 'unit' 选项按照技术和科学数据的惯例格式化列表。

const measurements = ['5 km', '10 km', '15 km'];

const unitList = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(unitList.format(measurements));
// 输出: "5 km, 10 km, 15 km"

使用 type: 'unit' 的列表省略了 "and" 或 "or" 等连词。它们在项目之间使用简单的分隔符。这符合技术上下文中测量值的书写方式。

与此相比,type: 'conjunction' 会在最后一项前添加 "and"。

const measurements = ['5 km', '10 km', '15 km'];

const conjunctionList = new Intl.ListFormat('en-US', {
  type: 'conjunction'
});

console.log(conjunctionList.format(measurements));
// 输出: "5 km, 10 km, and 15 km"

连词形式在散文中读起来更自然,但在技术上下文中看起来不正确。当显示多个测量值时,使用 type: 'unit' 以遵循科学和技术写作的标准惯例。

格式化列表中的距离测量值

距离测量值使用诸如 kilometermetermilefoot 等单位标识符。在用单位格式化每个距离后,将它们组合成一个列表。

const distances = [5, 10, 15, 20];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'kilometer'
});

const formattedDistances = distances.map(d => numberFormatter.format(d));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedDistances));
// 输出: "5 km, 10 km, 15 km, 20 km"

同样的模式适用于英里。

const distances = [3, 6, 9];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'mile'
});

const formattedDistances = distances.map(d => numberFormatter.format(d));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedDistances));
// 输出: "3 mi, 6 mi, 9 mi"

通过设置数字格式选项,可以格式化带小数位的距离。

const distances = [5.2, 10.7, 15.3];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'kilometer',
  maximumFractionDigits: 1
});

const formattedDistances = distances.map(d => numberFormatter.format(d));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedDistances));
// 输出: "5.2 km, 10.7 km, 15.3 km"

数字格式化器在列表格式化器组合值之前处理四舍五入和小数位。

在列表中格式化重量测量值

重量测量值遵循相同的模式,使用诸如 kilogrampoundouncegram 之类的单位标识符。

const weights = [50, 75, 100];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'kilogram'
});

const formattedWeights = weights.map(w => numberFormatter.format(w));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedWeights));
// 输出: "50 kg, 75 kg, 100 kg"

您也可以将重量显示为磅。

const weights = [110, 165, 220];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'pound'
});

const formattedWeights = weights.map(w => numberFormatter.format(w));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedWeights));
// 输出: "110 lb, 165 lb, 220 lb"

数字格式化器会自动使用每个单位的正确缩写。

在列表中格式化温度测量值

温度测量值使用诸如 celsiusfahrenheit 之类的单位标识符。

const temperatures = [20, 22, 25, 23, 21];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'celsius'
});

const formattedTemperatures = temperatures.map(t => numberFormatter.format(t));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedTemperatures));
// 输出: "20°C, 22°C, 25°C, 23°C, 21°C"

温度格式化器会自动在输出中包含度数符号。

华氏温度的用法相同。

const temperatures = [68, 72, 77, 73, 70];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'fahrenheit'
});

const formattedTemperatures = temperatures.map(t => numberFormatter.format(t));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedTemperatures));
// 输出: "68°F, 72°F, 77°F, 73°F, 70°F"

该模式在不同的测量类型中保持一致。只有单位标识符会发生变化。

在列表中格式化体积测量值

体积测量值使用诸如 liter(升)、gallon(加仑)、milliliter(毫升)和 fluid-ounce(液体盎司)等单位标识符。

const volumes = [1, 2, 3];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'liter'
});

const formattedVolumes = volumes.map(v => numberFormatter.format(v));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedVolumes));
// 输出: "1 L, 2 L, 3 L"

体积测量值支持小数值。

const volumes = [0.5, 1.5, 2.5];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'liter',
  maximumFractionDigits: 1
});

const formattedVolumes = volumes.map(v => numberFormatter.format(v));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedVolumes));
// 输出: "0.5 L, 1.5 L, 2.5 L"

数字格式化器在列表格式化器处理值之前处理小数精度。

在列表中格式化速度测量值

速度测量值使用诸如 kilometer-per-hour(公里/小时)和 mile-per-hour(英里/小时)等复合单位。

const speeds = [50, 75, 100];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'kilometer-per-hour'
});

const formattedSpeeds = speeds.map(s => numberFormatter.format(s));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedSpeeds));
// 输出: "50 km/h, 75 km/h, 100 km/h"

英里/小时的处理方式相同。

const speeds = [30, 45, 60];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'mile-per-hour'
});

const formattedSpeeds = speeds.map(s => numberFormatter.format(s));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedSpeeds));
// 输出: "30 mph, 45 mph, 60 mph"

复合单位会自动使用正确的缩写和分隔符进行格式化。

语言环境决定列表分隔符格式

语言环境参数控制列表项的分隔和标点方式。不同语言对列表格式化有不同的约定。

const distances = [5, 10, 15];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'kilometer'
});

const formattedDistances = distances.map(d => numberFormatter.format(d));

const enList = new Intl.ListFormat('en-US', {
  type: 'unit'
});

const frList = new Intl.ListFormat('fr-FR', {
  type: 'unit'
});

const deList = new Intl.ListFormat('de-DE', {
  type: 'unit'
});

console.log(enList.format(formattedDistances));
// 输出: "5 km, 10 km, 15 km"

console.log(frList.format(formattedDistances));
// 输出: "5 km, 10 km, 15 km"

console.log(deList.format(formattedDistances));
// 输出: "5 km, 10 km, 15 km"

尽管公里的缩写在这些语言环境中相似,但空格和分隔符的约定可能会有所不同。Intl.ListFormat API 会自动处理这些特定语言环境的格式化规则。

某些语言对列表使用不同的分隔符或标点模式。该 API 确保您的列表遵循每种语言环境的正确约定,而无需您了解具体规则。

将数字的区域设置与列表的区域设置匹配

在格式化测量值列表时,请为数字格式化器和列表格式化器使用相同的区域设置。这可以确保输出中的格式一致。

const distances = [1000, 2000, 3000];

const locale = 'de-DE';

const numberFormatter = new Intl.NumberFormat(locale, {
  style: 'unit',
  unit: 'meter'
});

const formattedDistances = distances.map(d => numberFormatter.format(d));

const listFormatter = new Intl.ListFormat(locale, {
  type: 'unit'
});

console.log(listFormatter.format(formattedDistances));
// 输出: "1.000 m, 2.000 m, 3.000 m"

德语格式使用句点作为千位分隔符。由于数字格式化器和列表格式化器使用相同的区域设置,因此它们都遵循德语的格式规则。

为数字和列表格式化使用不同的区域设置会导致输出不一致。

const distances = [1000, 2000, 3000];

const numberFormatter = new Intl.NumberFormat('de-DE', {
  style: 'unit',
  unit: 'meter'
});

const formattedDistances = distances.map(d => numberFormatter.format(d));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedDistances));
// 输出: "1.000 m, 2.000 m, 3.000 m"

这会导致混合格式,其中数字使用德语规则,而列表使用英语规则。始终为两种格式化器使用相同的区域设置。

控制列表显示样式

style 选项控制列表格式的详细程度。该选项接受三个值:"long""short""narrow"

const measurements = ['5 km', '10 km', '15 km'];

const longList = new Intl.ListFormat('en-US', {
  type: 'unit',
  style: 'long'
});

const shortList = new Intl.ListFormat('en-US', {
  type: 'unit',
  style: 'short'
});

const narrowList = new Intl.ListFormat('en-US', {
  type: 'unit',
  style: 'narrow'
});

console.log(longList.format(measurements));
// 输出: "5 km, 10 km, 15 km"

console.log(shortList.format(measurements));
// 输出: "5 km, 10 km, 15 km"

console.log(narrowList.format(measurements));
// 输出: "5 km 10 km 15 km"

在英语中,longshort 样式为单位列表生成相似的输出。narrow 样式使用最少的间距并省略项目之间的分隔符。

不同的区域设置在样式之间的差异更大。具体的格式由区域设置决定。

与完整单位名称结合使用

您可以通过在数字格式化器中设置 unitDisplay: 'long',使用完整的单位名称而不是缩写来格式化测量值。

const distances = [5, 10, 15];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'kilometer',
  unitDisplay: 'long'
});

const formattedDistances = distances.map(d => numberFormatter.format(d));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedDistances));
// 输出: "5 kilometers, 10 kilometers, 15 kilometers"

数字格式化器会自动处理单数和复数形式。列表格式化器会将格式化后的字符串组合在一起,无论它们使用的是缩写还是完整名称。

const distances = [1, 5];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'kilometer',
  unitDisplay: 'long'
});

const formattedDistances = distances.map(d => numberFormatter.format(d));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedDistances));
// 输出: "1 kilometer, 5 kilometers"

数字格式化器对 1 使用 "kilometer",对 5 使用 "kilometers"。列表格式化器使用适当的分隔符将它们组合在一起。

重用格式化器以提高性能

创建 Intl.NumberFormatIntl.ListFormat 实例需要加载区域设置数据并处理选项。当您格式化多个测量值列表时,请创建格式化器一次并重复使用它们。

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'kilometer'
});

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

const distanceLists = [
  [5, 10, 15],
  [20, 25, 30],
  [35, 40, 45]
];

distanceLists.forEach(distances => {
  const formattedDistances = distances.map(d => numberFormatter.format(d));
  console.log(listFormatter.format(formattedDistances));
});
// 输出:
// "5 km, 10 km, 15 km"
// "20 km, 25 km, 30 km"
// "35 km, 40 km, 45 km"

此模式仅创建一次每个格式化器,并多次使用它们。当格式化多个列表时,性能差异会变得显著。

创建一个可复用的格式化函数

您可以将两步格式化模式封装到一个可复用的函数中。

function formatMeasurementList(values, locale, unit) {
  const numberFormatter = new Intl.NumberFormat(locale, {
    style: 'unit',
    unit: unit
  });

  const formattedValues = values.map(v => numberFormatter.format(v));

  const listFormatter = new Intl.ListFormat(locale, {
    type: 'unit'
  });

  return listFormatter.format(formattedValues);
}

console.log(formatMeasurementList([5, 10, 15], 'en-US', 'kilometer'));
// 输出: "5 km, 10 km, 15 km"

console.log(formatMeasurementList([50, 75, 100], 'en-US', 'kilogram'));
// 输出: "50 kg, 75 kg, 100 kg"

console.log(formatMeasurementList([20, 22, 25], 'en-US', 'celsius'));
// 输出: "20°C, 22°C, 25°C"

此函数可以处理任何测量类型和区域设置。您可以扩展它以接受其他格式化选项。

function formatMeasurementList(values, locale, unit, options = {}) {
  const numberFormatter = new Intl.NumberFormat(locale, {
    style: 'unit',
    unit: unit,
    ...options
  });

  const formattedValues = values.map(v => numberFormatter.format(v));

  const listFormatter = new Intl.ListFormat(locale, {
    type: 'unit'
  });

  return listFormatter.format(formattedValues);
}

console.log(formatMeasurementList(
  [5.123, 10.789, 15.456],
  'en-US',
  'kilometer',
  { maximumFractionDigits: 1 }
));
// 输出: "5.1 km, 10.8 km, 15.5 km"

console.log(formatMeasurementList(
  [1, 5, 10],
  'en-US',
  'kilometer',
  { unitDisplay: 'long' }
));
// 输出: "1 kilometer, 5 kilometers, 10 kilometers"

该函数将额外的选项传递给数字格式化器,从而可以控制小数位数、单位显示方式以及其他格式化设置。

根据用户的区域设置格式化列表

与硬编码特定区域设置不同,您可以使用用户的浏览器语言偏好。navigator.language 属性返回用户的首选区域设置。

const userLocale = navigator.language;

const distances = [5, 10, 15];

const numberFormatter = new Intl.NumberFormat(userLocale, {
  style: 'unit',
  unit: 'kilometer'
});

const formattedDistances = distances.map(d => numberFormatter.format(d));

const listFormatter = new Intl.ListFormat(userLocale, {
  type: 'unit'
});

console.log(listFormatter.format(formattedDistances));
// 输出因用户的区域设置而异

此方法根据每个用户的格式化期望显示测量列表。不同用户会根据其区域设置习惯看到相同数据的不同格式。

在应用程序中显示测量列表

您可以在任何向用户显示多个测量值的地方使用此模式。这包括显示分段时间的健身应用程序、显示温度预测的天气应用程序、显示成分用量的食谱应用程序以及显示实验数据的科学应用程序。

const splitTimes = [5, 10, 15, 20];

const numberFormatter = new Intl.NumberFormat(navigator.language, {
  style: 'unit',
  unit: 'kilometer',
  maximumFractionDigits: 1
});

const formattedTimes = splitTimes.map(t => numberFormatter.format(t));

const listFormatter = new Intl.ListFormat(navigator.language, {
  type: 'unit'
});

const result = listFormatter.format(formattedTimes);

document.getElementById('split-times').textContent = result;
// 显示: "5 公里, 10 公里, 15 公里, 20 公里"(或本地化等效值)

格式化后的字符串与任何其他字符串值一样工作。您可以将它们插入到文本内容、属性或任何向用户显示信息的上下文中。