Intl.ListFormat API

Arrays in sprachspezifische lesbare Listen formatieren

Einführung

Bei der Anzeige mehrerer Elemente für Benutzer verbinden Entwickler häufig Arrays mit Kommas und fügen vor dem letzten Element ein "und" ein:

const users = ["Alice", "Bob", "Charlie"];
const message = users.slice(0, -1).join(", ") + ", and " + users[users.length - 1];
// "Alice, Bob, and Charlie"

Dieser Ansatz codiert die englischen Interpunktionsregeln fest ein und funktioniert in anderen Sprachen nicht. Japanisch verwendet andere Partikel, Deutsch hat andere Abstandsregeln und Chinesisch verwendet andere Trennzeichen. Die Intl.ListFormat API löst dieses Problem, indem sie Listen gemäß den Konventionen jeder Sprache formatiert.

Was Intl.ListFormat macht

Intl.ListFormat wandelt Arrays in menschenlesbare Listen um, die den grammatikalischen und Interpunktionsregeln jeder Sprache folgen. Es behandelt drei Arten von Listen, die in allen Sprachen vorkommen:

  • Konjunktionslisten verwenden "und" zur Verbindung von Elementen ("A, B und C")
  • Disjunktionslisten verwenden "oder" zur Darstellung von Alternativen ("A, B oder C")
  • Einheitenlisten formatieren Messungen ohne Konjunktionen ("5 ft, 2 in")

Die API kennt die Formatierungsregeln jeder Sprache für diese Listentypen, von der Interpunktion über die Wortwahl bis hin zu Abständen.

Grundlegende Verwendung

Erstellen Sie einen Formatierer mit einer Sprache und Optionen, und rufen Sie dann format() mit einem Array auf:

const formatter = new Intl.ListFormat("en", {
  type: "conjunction",
  style: "long"
});

const items = ["bread", "milk", "eggs"];
console.log(formatter.format(items));
// "bread, milk, and eggs"

Der Formatierer behandelt Arrays jeder Länge, einschließlich Sonderfälle:

formatter.format([]);              // ""
formatter.format(["bread"]);       // "bread"
formatter.format(["bread", "milk"]); // "bread and milk"

Listentypen steuern Konjunktionen

Die Option type bestimmt, welche Konjunktion in der formatierten Liste erscheint.

Konjunktionslisten

Verwenden Sie type: "conjunction" für Listen, bei denen alle Elemente zusammen gelten. Dies ist der Standardtyp:

const formatter = new Intl.ListFormat("en", { type: "conjunction" });

console.log(formatter.format(["HTML", "CSS", "JavaScript"]));
// "HTML, CSS, and JavaScript"

Gängige Anwendungen umfassen die Anzeige ausgewählter Elemente, die Auflistung von Funktionen und die Darstellung mehrerer Werte, die alle zutreffen.

Disjunktionslisten

Verwenden Sie type: "disjunction" für Listen, die Alternativen oder Auswahlmöglichkeiten darstellen:

const formatter = new Intl.ListFormat("en", { type: "disjunction" });

console.log(formatter.format(["credit card", "debit card", "PayPal"]));
// "credit card, debit card, or PayPal"

Dies erscheint in Optionslisten, Fehlermeldungen mit mehreren Lösungen und in jedem Kontext, in dem Benutzer ein Element auswählen.

Einheitenlisten

Verwenden Sie type: "unit" für Messungen und technische Werte, die ohne Konjunktionen erscheinen sollten:

const formatter = new Intl.ListFormat("en", { type: "unit" });

console.log(formatter.format(["5 feet", "2 inches"]));
// "5 feet, 2 inches"

Einheitenlisten eignen sich für Messungen, technische Spezifikationen und zusammengesetzte Werte.

Listenstile steuern die Ausführlichkeit

Die Option style passt an, wie ausführlich die Formatierung erscheint. Es gibt drei Stile: long, short und narrow.

const items = ["Monday", "Wednesday", "Friday"];

const long = new Intl.ListFormat("en", { style: "long" });
console.log(long.format(items));
// "Monday, Wednesday, and Friday"

const short = new Intl.ListFormat("en", { style: "short" });
console.log(short.format(items));
// "Monday, Wednesday, and Friday"

const narrow = new Intl.ListFormat("en", { style: "narrow" });
console.log(narrow.format(items));
// "Monday, Wednesday, Friday"

Im Englischen erzeugen long und short für die meisten Listen identische Ausgaben. Der Stil narrow lässt die Konjunktion weg. Andere Sprachen zeigen mehr Variation zwischen den Stilen, besonders bei Disjunktionslisten.

Wie verschiedene Sprachen Listen formatieren

Jede Sprache hat eigene Regeln zur Listenformatierung. Intl.ListFormat behandelt diese Unterschiede automatisch.

Englisch verwendet Kommas, Leerzeichen und Konjunktionen:

const en = new Intl.ListFormat("en");
console.log(en.format(["Tokyo", "Paris", "London"]));
// "Tokyo, Paris, and London"

Deutsch verwendet die gleiche Kommastruktur, aber andere Konjunktionen:

const de = new Intl.ListFormat("de");
console.log(de.format(["Tokyo", "Paris", "London"]));
// "Tokyo, Paris und London"

Japanisch verwendet andere Trennzeichen und Partikel:

const ja = new Intl.ListFormat("ja");
console.log(ja.format(["東京", "パリ", "ロンドン"]));
// "東京、パリ、ロンドン"

Chinesisch verwendet völlig andere Interpunktion:

const zh = new Intl.ListFormat("zh");
console.log(zh.format(["东京", "巴黎", "伦敦"]));
// "东京、巴黎和伦敦"

Diese Unterschiede gehen über die Interpunktion hinaus und betreffen auch Abstandsregeln, Konjunktionsplatzierung und grammatikalische Partikel. Die Hardcodierung eines einzelnen Ansatzes funktioniert nicht für andere Sprachen.

Verwendung von formatToParts für benutzerdefiniertes Rendering

Die Methode formatToParts() gibt ein Array von Objekten anstelle eines Strings zurück. Jedes Objekt repräsentiert einen Teil der formatierten Liste:

const formatter = new Intl.ListFormat("en");
const parts = formatter.formatToParts(["red", "green", "blue"]);

console.log(parts);
// [
//   { type: "element", value: "red" },
//   { type: "literal", value: ", " },
//   { type: "element", value: "green" },
//   { type: "literal", value: ", and " },
//   { type: "element", value: "blue" }
// ]

Jeder Teil hat einen type und einen value. Der type ist entweder "element" für Listenelemente oder "literal" für Formatierungszeichen und Konjunktionen.

Diese Struktur ermöglicht benutzerdefiniertes Rendering, bei dem Elemente und Literale unterschiedliche Stile benötigen:

const formatter = new Intl.ListFormat("en");
const items = ["Alice", "Bob", "Charlie"];

const html = formatter.formatToParts(items)
  .map(part => {
    if (part.type === "element") {
      return `<strong>${part.value}</strong>`;
    }
    return part.value;
  })
  .join("");

console.log(html);
// "<strong>Alice</strong>, <strong>Bob</strong>, and <strong>Charlie</strong>"

Dieser Ansatz behält die sprachlich korrekte Interpunktion bei und wendet gleichzeitig eine benutzerdefinierte Darstellung auf die eigentlichen Listenelemente an.

Wiederverwendung von Formatierern für bessere Leistung

Das Erstellen von Intl.ListFormat-Instanzen verursacht Overhead. Erstellen Sie Formatierer einmal und verwenden Sie sie wieder:

// Einmal erstellen
const listFormatter = new Intl.ListFormat("en", { type: "conjunction" });

// Mehrfach wiederverwenden
function displayUsers(users) {
  return listFormatter.format(users.map(u => u.name));
}

function displayTags(tags) {
  return listFormatter.format(tags);
}

Für Anwendungen mit mehreren Sprachen speichern Sie Formatierer in einer Map:

const formatters = new Map();

function getListFormatter(locale, options) {
  const key = `${locale}-${options.type}-${options.style}`;
  if (!formatters.has(key)) {
    formatters.set(key, new Intl.ListFormat(locale, options));
  }
  return formatters.get(key);
}

const formatter = getListFormatter("en", { type: "conjunction", style: "long" });
console.log(formatter.format(["a", "b", "c"]));

Dieses Muster reduziert wiederholte Initialisierungskosten und unterstützt gleichzeitig mehrere Sprachen und Konfigurationen.

Formatierung von Fehlermeldungen

Bei der Formularvalidierung treten oft mehrere Fehler auf. Formatieren Sie diese mit Disjunktionslisten, um Optionen darzustellen:

const formatter = new Intl.ListFormat("en", { type: "disjunction" });

function validatePassword(password) {
  const errors = [];

  if (password.length < 8) {
    errors.push("mindestens 8 Zeichen");
  }
  if (!/[A-Z]/.test(password)) {
    errors.push("einen Großbuchstaben");
  }
  if (!/[0-9]/.test(password)) {
    errors.push("eine Zahl");
  }

  if (errors.length > 0) {
    return `Passwort muss ${formatter.format(errors)} enthalten.`;
  }

  return null;
}

console.log(validatePassword("weak"));
// "Passwort muss mindestens 8 Zeichen, einen Großbuchstaben oder eine Zahl enthalten."

Die Disjunktionsliste verdeutlicht, dass Benutzer eines dieser Probleme beheben müssen, und die Formatierung passt sich an die Konventionen jeder Sprache an.

Anzeige ausgewählter Elemente

Wenn Benutzer mehrere Elemente auswählen, formatieren Sie die Auswahl mit Konjunktionslisten:

const formatter = new Intl.ListFormat("en", { type: "conjunction" });

function getSelectionMessage(selectedFiles) {
  if (selectedFiles.length === 0) {
    return "Keine Dateien ausgewählt";
  }

  if (selectedFiles.length === 1) {
    return `${selectedFiles[0]} ausgewählt`;
  }

  return `${formatter.format(selectedFiles)} ausgewählt`;
}

console.log(getSelectionMessage(["report.pdf", "data.csv", "notes.txt"]));
// "report.pdf, data.csv und notes.txt ausgewählt"

Dieses Muster funktioniert für Dateiauswahlen, Filteroptionen, Kategorieauswahlen und jede Mehrfachauswahl-Schnittstelle.

Umgang mit langen Listen

Für Listen mit vielen Elementen sollte eine Kürzung vor der Formatierung in Betracht gezogen werden:

const formatter = new Intl.ListFormat("en", { type: "conjunction" });

function formatUserList(users) {
  if (users.length <= 3) {
    return formatter.format(users);
  }

  const visible = users.slice(0, 2);
  const remaining = users.length - 2;

  return `${formatter.format(visible)}, and ${remaining} others`;
}

console.log(formatUserList(["Alice", "Bob", "Charlie", "David", "Eve"]));
// "Alice, Bob, and 3 others"

Dies erhält die Lesbarkeit und zeigt gleichzeitig die Gesamtanzahl an. Der genaue Schwellenwert hängt von den Einschränkungen der Benutzeroberfläche ab.

Browser-Unterstützung und Fallbacks

Intl.ListFormat funktioniert in allen modernen Browsern seit April 2021. Die Unterstützung umfasst Chrome 72+, Firefox 78+, Safari 14.1+ und Edge 79+.

Überprüfen Sie die Unterstützung mit Feature-Erkennung:

if (typeof Intl.ListFormat !== "undefined") {
  const formatter = new Intl.ListFormat("en");
  return formatter.format(items);
} else {
  // Fallback für ältere Browser
  return items.join(", ");
}

Für eine breitere Kompatibilität verwenden Sie einen Polyfill wie @formatjs/intl-listformat. Installieren Sie ihn nur für Umgebungen, die ihn benötigen:

if (typeof Intl.ListFormat === "undefined") {
  await import("@formatjs/intl-listformat/polyfill");
}

Angesichts der aktuellen Browser-Unterstützung können die meisten Anwendungen Intl.ListFormat direkt ohne Polyfills verwenden.

Häufige Fehler, die vermieden werden sollten

Das wiederholte Erstellen neuer Formatter verschwendet Ressourcen:

// Ineffizient
function display(items) {
  return new Intl.ListFormat("en").format(items);
}

// Effizient
const formatter = new Intl.ListFormat("en");
function display(items) {
  return formatter.format(items);
}

Die Verwendung von array.join() für benutzerorientierte Texte verursacht Lokalisierungsprobleme:

// Funktioniert in anderen Sprachen nicht
const text = items.join(", ");

// Funktioniert in allen Sprachen
const formatter = new Intl.ListFormat(userLocale);
const text = formatter.format(items);

Die Annahme, dass englische Konjunktionsregeln universell gelten, führt zu falschen Ausgaben in anderen Sprachen. Übergeben Sie immer die Sprache des Benutzers an den Konstruktor.

Die Nichtbehandlung leerer Arrays kann zu unerwarteten Ausgaben führen:

// Defensiv
function formatItems(items) {
  if (items.length === 0) {
    return "Keine Elemente";
  }
  return formatter.format(items);
}

Obwohl format([]) einen leeren String zurückgibt, verbessert die explizite Behandlung des leeren Zustands die Benutzererfahrung.

Wann Intl.ListFormat verwenden

Verwenden Sie Intl.ListFormat immer dann, wenn mehrere Elemente im Fließtext angezeigt werden. Dies umfasst Navigations-Breadcrumbs, ausgewählte Filter, Validierungsfehler, Benutzerlisten, Kategorie-Tags und Feature-Listen.

Verwenden Sie es nicht für strukturierte Datenanzeigen wie Tabellen oder Optionsmenüs. Diese Komponenten haben ihre eigenen Formatierungsanforderungen außerhalb der Regeln für Aufzählungen im Fließtext.

Die API ersetzt manuelle String-Verkettung und Verbindungsmuster. Wann immer Sie join(", ") für benutzerorientierten Text schreiben würden, sollten Sie überlegen, ob Intl.ListFormat eine bessere Lokalisierungsunterstützung bietet.