Как форматировать массивы с учётом локали в JavaScript?
Используйте Intl.ListFormat, чтобы автоматически применять правильные запятые, пробелы и знаки препинания для любого языка.
Введение
Когда вы преобразуете массив в читаемую строку, элементы нужно разделять запятыми или другими знаками препинания. В разных языках используются разные разделители. В английском — запятая и пробел, в японском — перечислительная запятая 、, а в арабском — свои знаки препинания и порядок слов.
API Intl.ListFormat преобразует массивы в строки с разделителями, подходящими для выбранной локали. Это гарантирует, что ваши списки будут выглядеть естественно для пользователей на любом языке.
Почему разделители в массивах зависят от локали
Можно подумать, что во всех языках для разделения элементов списка используется запятая. Это не так.
В английском элементы разделяются запятой и пробелом.
// English: "red, green, blue"
В японском используется перечислительная запятая 、 без пробелов.
// Japanese: "赤、緑、青"
В китайском используется тот же символ перечислительной запятой 、.
// Chinese: "红、绿、蓝"
В арабском используется другая запятая ، и текст читается справа налево.
// Arabic: "أحمر، أخضر، أزرق"
Эти различия обрабатываются автоматически в Intl.ListFormat. Вам не нужно знать правила пунктуации для каждого языка.
Проблема с join()
Метод Array.prototype.join() преобразует массивы в строки, используя указанный вами разделитель.
const colors = ["red", "green", "blue"];
console.log(colors.join(", "));
// "red, green, blue"
Это жёстко задаёт английскую пунктуацию. Разделитель «запятая и пробел» не подходит для других языков.
const colors = ["赤", "緑", "青"];
console.log(colors.join(", "));
// "赤, 緑, 青" (wrong - should use 、 instead of ,)
Вы не сможете вручную менять разделители в зависимости от локали, потому что для этого пришлось бы поддерживать карту всех языков и их правил пунктуации. Такая карта была бы неполной и сложной в поддержке.
Использование Intl.ListFormat для локализованных разделителей
Конструктор Intl.ListFormat создаёт форматтер, который применяет правильные разделители для любого языка.
const formatter = new Intl.ListFormat("en");
const colors = ["red", "green", "blue"];
console.log(formatter.format(colors));
// "red, green, and blue"
Форматтер автоматически использует нужную пунктуацию для выбранной локали. Код локали передаётся первым аргументом.
const enFormatter = new Intl.ListFormat("en");
const jaFormatter = new Intl.ListFormat("ja");
const arFormatter = new Intl.ListFormat("ar");
const colors = ["red", "green", "blue"];
console.log(enFormatter.format(colors));
// "red, green, and blue"
console.log(jaFormatter.format(["赤", "緑", "青"]));
// "赤、緑、青"
console.log(arFormatter.format(["أحمر", "أخضر", "أزرق"]));
// "أحمر، أخضر، أزرق"
Браузер предоставляет правила пунктуации. Вам не нужно поддерживать отдельный код для каждой локали.
Как меняются разделители в разных языках
Форматтер использует разные разделители в зависимости от локали. Эти примеры показывают, как один и тот же массив форматируется по-разному.
В английском используется запятая, пробел и слово «and».
const formatter = new Intl.ListFormat("en");
console.log(formatter.format(["apple", "orange", "banana"]));
// "apple, orange, and banana"
В испанском используется запятая, пробел и слово «y».
const formatter = new Intl.ListFormat("es");
console.log(formatter.format(["manzana", "naranja", "plátano"]));
// "manzana, naranja y plátano"
Во французском используется запятая, пробел и слово «et».
const formatter = new Intl.ListFormat("fr");
console.log(formatter.format(["pomme", "orange", "banane"]));
// "pomme, orange et banane"
В немецком используется запятая, пробел и слово «und».
const formatter = new Intl.ListFormat("de");
console.log(formatter.format(["Apfel", "Orange", "Banane"]));
// "Apfel, Orange und Banane"
В японском используется перечислительная запятая 、 и символ 、.
const formatter = new Intl.ListFormat("ja");
console.log(formatter.format(["りんご", "オレンジ", "バナナ"]));
// "りんご、オレンジ、バナナ"
В китайском используется перечислительная запятая 、 и слово 和.
const formatter = new Intl.ListFormat("zh");
console.log(formatter.format(["苹果", "橙子", "香蕉"]));
// "苹果、橙子和香蕉"
В корейском используется запятая и частица 및.
const formatter = new Intl.ListFormat("ko");
console.log(formatter.format(["사과", "오렌지", "바나나"]));
// "사과, 오렌지 및 바나나"
Форматтер автоматически учитывает все эти различия. Вам не нужно писать разный код для каждого языка.
Использование локали пользователя
Вы можете определить предпочитаемую локаль пользователя из настроек браузера и использовать её для форматирования списков.
const userLocale = navigator.language;
const formatter = new Intl.ListFormat(userLocale);
const items = ["first", "second", "third"];
console.log(formatter.format(items));
Это гарантирует, что список использует разделители, соответствующие ожиданиям пользователя. Пользователь с французскими настройками браузера увидит французскую пунктуацию. Пользователь с японскими настройками — японскую пунктуацию.
Форматирование массивов без союзов
По умолчанию Intl.ListFormat добавляет союз, например «и», перед последним элементом. Это можно отключить, используя тип unit.
const formatter = new Intl.ListFormat("en", { type: "unit" });
console.log(formatter.format(["5 km", "12 minutes", "100 calories"]));
// "5 km, 12 minutes, 100 calories"
Тип unit использует только разделители, не добавляя соединительных слов. Это удобно для технических списков, измерений или данных, где союзы не нужны.
const enFormatter = new Intl.ListFormat("en", { type: "unit" });
const jaFormatter = new Intl.ListFormat("ja", { type: "unit" });
console.log(enFormatter.format(["Item A", "Item B", "Item C"]));
// "Item A, Item B, Item C"
console.log(jaFormatter.format(["項目A", "項目B", "項目C"]));
// "項目A、項目B、項目C"
Даже без союзов пунктуация-разделитель всё равно соответствует правилам локали.
Создание переиспользуемых форматтеров
Вы можете создать форматтер один раз и использовать его для разных массивов. Это эффективнее, чем создавать новый форматтер для каждого массива.
const formatter = new Intl.ListFormat("en");
console.log(formatter.format(["red", "green"]));
// "red and green"
console.log(formatter.format(["a", "b", "c", "d"]));
// "a, b, c, and d"
console.log(formatter.format(["one"]));
// "one"
Один и тот же форматтер работает с массивами любой длины. Он применяет правильные разделители и союзы в зависимости от количества элементов в массиве.
Обработка пустых массивов
Если вы форматируете пустой массив, форматтер возвращает пустую строку.
const formatter = new Intl.ListFormat("en");
console.log(formatter.format([]));
// ""
Если вам нужно другое поведение, проверьте массив на пустоту до форматирования.
function formatList(items, locale) {
if (items.length === 0) {
return "No items";
}
const formatter = new Intl.ListFormat(locale);
return formatter.format(items);
}
console.log(formatList([], "en"));
// "No items"
console.log(formatList(["apple"], "en"));
// "apple"
Это даёт вам контроль над тем, как пустые массивы будут отображаться пользователям.
Поддержка браузеров
API Intl.ListFormat доступен во всех современных браузерах. С апреля 2021 года он поддерживается в Chrome, Firefox, Safari и Edge.
Вы можете проверить наличие API перед использованием.
if (typeof Intl.ListFormat !== "undefined") {
const formatter = new Intl.ListFormat("en");
console.log(formatter.format(["a", "b", "c"]));
} else {
console.log("Intl.ListFormat is not supported");
}
Для старых браузеров можно использовать метод join() в качестве запасного варианта. Он обеспечивает базовое форматирование без учёта локальных разделителей.
function formatList(items, locale) {
if (typeof Intl.ListFormat !== "undefined") {
const formatter = new Intl.ListFormat(locale);
return formatter.format(items);
}
return items.join(", ");
}
console.log(formatList(["red", "green", "blue"], "en"));
// "red, green, and blue" (or "red, green, blue" in older browsers)
Это гарантирует, что ваш код будет работать во всех браузерах и обеспечит лучший опыт в современных.