Intl.PluralRules API

Wie man Pluralformen in JavaScript korrekt handhabt

Einführung

Pluralisierung ist der Prozess, bei dem unterschiedliche Texte basierend auf einer Anzahl angezeigt werden. Im Englischen könnte man "1 item" für einen einzelnen Gegenstand und "2 items" für mehrere Gegenstände anzeigen. Die meisten Entwickler handhaben dies mit einer einfachen Bedingung, die ein "s" für Anzahlen außer eins hinzufügt.

Dieser Ansatz funktioniert nicht für andere Sprachen als Englisch. Polnisch verwendet unterschiedliche Formen für 1, 2-4 und 5 oder mehr. Arabisch hat Formen für null, eins, zwei, wenige und viele. Walisisch hat sechs verschiedene Formen. Selbst im Englischen erfordern unregelmäßige Pluralformen wie "person" zu "people" eine spezielle Behandlung.

Die Intl.PluralRules API löst dieses Problem, indem sie die Pluralformkategorie für jede Zahl in jeder Sprache bereitstellt. Sie geben eine Anzahl an, und die API teilt Ihnen mit, welche Form basierend auf den Regeln der Zielsprache zu verwenden ist. Dies ermöglicht es Ihnen, internationalisierungsbereiten Code zu schreiben, der sprachübergreifend korrekt funktioniert, ohne sprachspezifische Regeln manuell zu kodieren.

Wie Sprachen Pluralformen handhaben

Sprachen unterscheiden sich stark darin, wie sie Mengen ausdrücken. Englisch hat zwei Formen: Singular für eins, Plural für alles andere. Dies erscheint unkompliziert, bis man auf Sprachen mit unterschiedlichen Systemen trifft.

Russisch und Polnisch verwenden drei Formen. Der Singular gilt für einen Gegenstand. Eine spezielle Form gilt für Zahlen, die auf 2, 3 oder 4 enden (aber nicht 12, 13 oder 14). Alle anderen Zahlen verwenden eine dritte Form.

Arabisch verwendet sechs Formen: null, eins, zwei, wenige (3-10), viele (11-99) und andere (100+). Walisisch hat ebenfalls sechs Formen mit unterschiedlichen numerischen Grenzen.

Einige Sprachen wie Chinesisch und Japanisch unterscheiden überhaupt nicht zwischen Singular und Plural. Dieselbe Form funktioniert für jede Anzahl.

Die Intl.PluralRules API abstrahiert diese Unterschiede mithilfe standardisierter Kategorienamen, die auf den Unicode CLDR-Pluralregeln basieren. Die sechs Kategorien sind: zero, one, two, few, many und other. Nicht jede Sprache verwendet alle sechs Kategorien. Englisch verwendet nur one und other. Arabisch verwendet alle sechs.

Erstellen einer PluralRules-Instanz für eine Locale

Der Intl.PluralRules-Konstruktor nimmt eine Locale-Kennung entgegen und gibt ein Objekt zurück, das bestimmen kann, welche Pluralkategorie auf eine bestimmte Zahl zutrifft.

const enRules = new Intl.PluralRules('en-US');

Erstellen Sie eine Instanz pro Locale und verwenden Sie diese wieder. Die Konstruktion einer neuen Instanz für jede Pluralisierung ist verschwenderisch. Speichern Sie die Instanz in einer Variablen oder verwenden Sie einen Caching-Mechanismus.

Der Standardtyp ist kardinal, der das Zählen von Objekten behandelt. Sie können auch Regeln für Ordinalzahlen erstellen, indem Sie ein Options-Objekt übergeben.

const enOrdinalRules = new Intl.PluralRules('en-US', { type: 'ordinal' });

Kardinale Regeln gelten für Zählungen wie "1 Apfel, 2 Äpfel". Ordinale Regeln gelten für Positionen wie "1. Platz, 2. Platz".

Verwenden von select(), um die Pluralkategorie für eine Zahl zu erhalten

Die select()-Methode nimmt eine Zahl entgegen und gibt zurück, zu welcher Pluralkategorie sie in der Zielsprache gehört.

const enRules = new Intl.PluralRules('en-US');

enRules.select(0);  // 'other'
enRules.select(1);  // 'one'
enRules.select(2);  // 'other'
enRules.select(5);  // 'other'

Der Rückgabewert ist immer einer der sechs Kategorienamen: zero, one, two, few, many oder other. Englisch gibt nur one und other zurück, weil das die einzigen Formen sind, die im Englischen verwendet werden.

Für Arabisch, das komplexere Regeln hat, sehen Sie alle sechs Kategorien in Verwendung:

const arRules = new Intl.PluralRules('ar-EG');

arRules.select(0);   // 'zero'
arRules.select(1);   // 'one'
arRules.select(2);   // 'two'
arRules.select(6);   // 'few'
arRules.select(18);  // 'many'
arRules.select(100); // 'other'

Kategorien zu lokalisierten Strings zuordnen

Die API teilt Ihnen nur mit, welche Kategorie zutrifft. Sie stellen den tatsächlichen Text für jede Kategorie bereit. Speichern Sie die Textformen in einer Map oder einem Objekt, indiziert nach Kategorienamen.

const enRules = new Intl.PluralRules('en-US');
const enForms = new Map([
  ['one', 'item'],
  ['other', 'items'],
]);

function formatItems(count) {
  const category = enRules.select(count);
  const form = enForms.get(category);
  return `${count} ${form}`;
}

formatItems(1);  // '1 item'
formatItems(5);  // '5 items'

Dieses Muster trennt Logik von Daten. Die PluralRules-Instanz behandelt die Regeln. Die Map enthält die Übersetzungen. Die Funktion kombiniert sie.

Für Sprachen mit mehr Kategorien fügen Sie weitere Einträge zur Map hinzu:

const arRules = new Intl.PluralRules('ar-EG');
const arForms = new Map([
  ['zero', 'عناصر'],
  ['one', 'عنصر واحد'],
  ['two', 'عنصران'],
  ['few', 'عناصر'],
  ['many', 'عنصرًا'],
  ['other', 'عنصر'],
]);

function formatItems(count) {
  const category = arRules.select(count);
  const form = arForms.get(category);
  return `${count} ${form}`;
}

Stellen Sie immer Einträge für jede Kategorie bereit, die die Sprache verwendet. Fehlende Kategorien verursachen undefinierte Nachschlagungen. Wenn Sie sich nicht sicher sind, welche Kategorien eine Sprache verwendet, überprüfen Sie die Unicode CLDR-Pluralregeln oder testen Sie mit der API über verschiedene Zahlen hinweg.

Umgang mit Dezimal- und Bruchzahlen

Die select()-Methode funktioniert mit Dezimalzahlen. Im Englischen werden Dezimalzahlen als Plural behandelt, selbst für Werte zwischen 0 und 2.

const enRules = new Intl.PluralRules('en-US');

enRules.select(1);    // 'one'
enRules.select(1.0);  // 'one'
enRules.select(1.5);  // 'other'
enRules.select(0.5);  // 'other'

Andere Sprachen haben unterschiedliche Regeln für Dezimalzahlen. Einige behandeln jede Dezimalzahl als Plural, während andere nuanciertere Regeln basierend auf dem Bruchteil verwenden.

Wenn Ihre Benutzeroberfläche Bruchmengen wie "1,5 GB" oder "2,7 Kilometer" anzeigt, übergeben Sie die Bruchzahl direkt an select(). Runden Sie nicht vorher ab, es sei denn, Ihre Benutzeroberfläche rundet den angezeigten Wert.

Formatierung von Ordnungszahlen wie 1., 2., 3.

Ordnungszahlen geben Position oder Rang an. Im Englischen werden Ordnungszahlen durch Hinzufügen von Suffixen gebildet: 1st, 2nd, 3rd, 4th. Das Muster ist nicht einfach "th hinzufügen", da 1, 2 und 3 spezielle Formen haben, und Zahlen, die auf 1, 2 oder 3 enden, folgen speziellen Regeln (21st, 22nd, 23rd), außer wenn sie auf 11, 12 oder 13 enden (11th, 12th, 13th).

Die Intl.PluralRules API behandelt diese Regeln, wenn Sie type: 'ordinal' angeben.

const enOrdinalRules = new Intl.PluralRules('en-US', { type: 'ordinal' });

enOrdinalRules.select(1);   // 'one'
enOrdinalRules.select(2);   // 'two'
enOrdinalRules.select(3);   // 'few'
enOrdinalRules.select(4);   // 'other'
enOrdinalRules.select(11);  // 'other'
enOrdinalRules.select(21);  // 'one'
enOrdinalRules.select(22);  // 'two'
enOrdinalRules.select(23);  // 'few'

Ordnen Sie die Kategorien den Ordnungssuffixen zu:

const enOrdinalRules = new Intl.PluralRules('en-US', { type: 'ordinal' });
const enOrdinalSuffixes = new Map([
  ['one', 'st'],
  ['two', 'nd'],
  ['few', 'rd'],
  ['other', 'th'],
]);

function formatOrdinal(n) {
  const category = enOrdinalRules.select(n);
  const suffix = enOrdinalSuffixes.get(category);
  return `${n}${suffix}`;
}

formatOrdinal(1);   // '1st'
formatOrdinal(2);   // '2nd'
formatOrdinal(3);   // '3rd'
formatOrdinal(4);   // '4th'
formatOrdinal(11);  // '11th'
formatOrdinal(21);  // '21st'

Andere Sprachen haben völlig unterschiedliche Ordnungszahlensysteme. Im Französischen wird "1er" für den ersten und "2e" für alle anderen verwendet. Spanisch hat geschlechtsspezifische Ordnungszahlen. Die API liefert die Kategorie, und Sie stellen die lokalisierten Formen bereit.

Bereiche mit selectRange() behandeln

Die Methode selectRange() bestimmt die Pluralkategorie für einen Zahlenbereich, wie "1-5 Artikel" oder "10-20 Ergebnisse". Einige Sprachen haben unterschiedliche Pluralregeln für Bereiche im Vergleich zu einzelnen Zählungen.

const enRules = new Intl.PluralRules('en-US');

enRules.selectRange(1, 5);   // 'other'
enRules.selectRange(0, 1);   // 'other'

Im Englischen sind Bereiche fast immer im Plural, selbst wenn der Bereich bei 1 beginnt. Andere Sprachen haben komplexere Bereichsregeln.

const slRules = new Intl.PluralRules('sl');

slRules.selectRange(102, 201);  // 'few'

const ptRules = new Intl.PluralRules('pt');

ptRules.selectRange(102, 102);  // 'other'

Verwenden Sie selectRange(), wenn Sie Bereiche explizit in Ihrer Benutzeroberfläche anzeigen. Für einzelne Zählungen verwenden Sie select().

Kombination mit Intl.NumberFormat für lokalisierte Zahlenanzeige

Pluralformen erscheinen oft zusammen mit formatierten Zahlen. Verwenden Sie Intl.NumberFormat, um die Zahl gemäß den Locale-Konventionen zu formatieren, und dann Intl.PluralRules, um den korrekten Text auszuwählen.

const locale = 'en-US';
const numberFormat = new Intl.NumberFormat(locale);
const pluralRules = new Intl.PluralRules(locale);
const forms = new Map([
  ['one', 'item'],
  ['other', 'items'],
]);

function formatCount(count) {
  const formattedNumber = numberFormat.format(count);
  const category = pluralRules.select(count);
  const form = forms.get(category);
  return `${formattedNumber} ${form}`;
}

formatCount(1);      // '1 item'
formatCount(1000);   // '1,000 items'
formatCount(1.5);    // '1.5 items'

Für Deutsch, das Punkte als Tausendertrennzeichen und Kommas als Dezimaltrennzeichen verwendet:

const locale = 'de-DE';
const numberFormat = new Intl.NumberFormat(locale);
const pluralRules = new Intl.PluralRules(locale);
const forms = new Map([
  ['one', 'Artikel'],
  ['other', 'Artikel'],
]);

function formatCount(count) {
  const formattedNumber = numberFormat.format(count);
  const category = pluralRules.select(count);
  const form = forms.get(category);
  return `${formattedNumber} ${form}`;
}

formatCount(1);      // '1 Artikel'
formatCount(1000);   // '1.000 Artikel'
formatCount(1.5);    // '1,5 Artikel'

Dieses Muster stellt sicher, dass sowohl die Zahlenformatierung als auch die Textform den Erwartungen des Benutzers für die Locale entsprechen.

Den Nullfall bei Bedarf explizit behandeln

Wie Null pluralisiert wird, variiert je nach Sprache. Englisch verwendet typischerweise die Pluralform: "0 items", "0 results". Einige Sprachen verwenden für Null die Singularform. Andere haben eine eigene Nullkategorie.

Die Intl.PluralRules API gibt die entsprechende Kategorie für Null basierend auf den Sprachregeln zurück. Im Englischen gibt Null 'other' zurück, was der Pluralform entspricht:

const enRules = new Intl.PluralRules('en-US');

enRules.select(0);  // 'other'

Im Arabischen hat Null eine eigene Kategorie:

const arRules = new Intl.PluralRules('ar-EG');

arRules.select(0);  // 'zero'

Ihr Text sollte dies berücksichtigen. Für Englisch möchten Sie möglicherweise "No items" anstelle von "0 items" für eine bessere Benutzererfahrung anzeigen. Behandeln Sie dies, bevor Sie die Pluralregeln aufrufen:

function formatItems(count) {
  if (count === 0) {
    return 'No items';
  }
  const category = enRules.select(count);
  const form = enForms.get(category);
  return `${count} ${form}`;
}

Für Arabisch stellen Sie eine spezifische Nullform in Ihren Übersetzungen bereit:

const arForms = new Map([
  ['zero', 'لا توجد عناصر'],
  ['one', 'عنصر واحد'],
  ['two', 'عنصران'],
  ['few', 'عناصر'],
  ['many', 'عنصرًا'],
  ['other', 'عنصر'],
]);

Dies respektiert die sprachlichen Konventionen jeder Sprache und ermöglicht es Ihnen gleichzeitig, den Nullfall für eine bessere Benutzererfahrung anzupassen.

PluralRules-Instanzen für bessere Performance wiederverwenden

Das Erstellen einer PluralRules-Instanz umfasst das Parsen des Gebietsschemas und das Laden von Pluralregeldaten. Tun Sie dies einmal pro Gebietsschema, nicht bei jedem Funktionsaufruf oder Renderzyklus.

// Gut: einmal erstellen, wiederverwenden
const enRules = new Intl.PluralRules('en-US');
const enForms = new Map([
  ['one', 'item'],
  ['other', 'items'],
]);

function formatItems(count) {
  const category = enRules.select(count);
  const form = enForms.get(category);
  return `${count} ${form}`;
}

Wenn Sie mehrere Gebietsschemas unterstützen, erstellen Sie Instanzen für jedes Gebietsschema und speichern Sie sie in einer Map oder einem Cache:

const rulesCache = new Map();

function getPluralRules(locale) {
  if (!rulesCache.has(locale)) {
    rulesCache.set(locale, new Intl.PluralRules(locale));
  }
  return rulesCache.get(locale);
}

const rules = getPluralRules('en-US');

Dieses Muster verteilt die Initialisierungskosten auf viele Aufrufe.

Browser-Unterstützung und Kompatibilität

Intl.PluralRules wird seit 2019 in allen modernen Browsern unterstützt. Dazu gehören Chrome 63+, Firefox 58+, Safari 13+ und Edge 79+. Im Internet Explorer wird es nicht unterstützt.

Für Anwendungen, die auf moderne Browser abzielen, können Sie Intl.PluralRules ohne Polyfill verwenden. Wenn Sie ältere Browser unterstützen müssen, sind Polyfills über Pakete wie intl-pluralrules auf npm verfügbar.

Die selectRange()-Methode ist neuer und hat eine etwas eingeschränktere Unterstützung. Sie ist in Chrome 106+, Firefox 116+, Safari 15.4+ und Edge 106+ verfügbar. Überprüfen Sie die Kompatibilität, wenn Sie selectRange() verwenden und ältere Browser-Versionen unterstützen müssen.

Vermeiden Sie hartcodierte Pluralformen in der Logik

Überprüfen Sie nicht die Anzahl und verzweigen Sie im Code, um eine Pluralform auszuwählen. Dieser Ansatz skaliert nicht für Sprachen mit mehr als zwei Formen und koppelt Ihre Logik an englische Regeln.

// Vermeiden Sie dieses Muster
function formatItems(count) {
  if (count === 1) {
    return `${count} item`;
  }
  return `${count} items`;
}

Verwenden Sie Intl.PluralRules und eine Datenstruktur, um Formen zu speichern. Dies hält Ihren Code sprachunabhängig und erleichtert das Hinzufügen neuer Sprachen durch Bereitstellung neuer Übersetzungen.

// Bevorzugen Sie dieses Muster
const rules = new Intl.PluralRules('en-US');
const forms = new Map([
  ['one', 'item'],
  ['other', 'items'],
]);

function formatItems(count) {
  const category = rules.select(count);
  const form = forms.get(category);
  return `${count} ${form}`;
}

Dieses Muster funktioniert für jede Sprache identisch. Nur die rules-Instanz und die forms-Map ändern sich.

Testen Sie mit mehreren Locales und Grenzfällen

Pluralregeln haben Grenzfälle, die leicht übersehen werden, wenn nur auf Englisch getestet wird. Testen Sie Ihre Pluralisierungslogik mit mindestens einer Sprache, die mehr als zwei Formen verwendet, wie Polnisch oder Arabisch.

Testen Sie Zahlen, die verschiedene Kategorien auslösen:

  • Null
  • Eins
  • Zwei
  • Einige (3-10 im Arabischen)
  • Viele (11-99 im Arabischen)
  • Große Zahlen (100+)
  • Dezimalwerte (0,5, 1,5, 2,3)
  • Negative Zahlen, wenn Ihre UI diese anzeigt

Wenn Sie Ordinalregeln verwenden, testen Sie Zahlen, die verschiedene Suffixe auslösen: 1, 2, 3, 4, 11, 21, 22, 23. Dies stellt sicher, dass Sie die Sonderfälle korrekt behandeln.

Das Testen mit mehreren Locales frühzeitig verhindert Überraschungen, wenn Sie später neue Sprachen hinzufügen. Es validiert auch, dass Ihre Datenstruktur alle notwendigen Kategorien enthält und dass Ihre Logik sie korrekt verarbeitet.