JS Intl.Collator Locale-Sensitive String Comparison

The Intl.Collator object in JavaScript provides a high-performance mechanism for comparing and sorting strings according to the specific rules of different languages and cultures. Unlike standard JavaScript comparison operators that sort strings based on Unicode code point values, Intl.Collator understands language-specific nuances such as accent marks, case sensitivity, and unique alphabetizations. This article explains how Intl.Collator works, its configuration options, and why it is the preferred approach for locale-aware string operations.

The Limitation of Default Comparisons

Standard JavaScript comparison operators (<, >, ===) compare strings by their UTF-16 code unit values. This binary comparison often produces incorrect results when sorting human languages. For example, in Swedish, “ö” comes after “z”, whereas in German, “ö” is treated similarly to “o”. Binary comparison places all accented characters after standard ASCII characters regardless of context, leading to unnatural sorting orders.

While String.prototype.localeCompare() handles locale rules, invoking it repeatedly inside an Array.prototype.sort() callback causes performance overhead because it must resolve the locale and rules on every comparison.

How Intl.Collator Works

The Intl.Collator constructor creates a reusable instance configured with a specific locale and set of collation rules. Once instantiated, its compare method takes two strings and returns: - A negative number if the first string comes before the second. - A positive number if the first string comes after the second. - 0 if the strings are considered equivalent under the configured rules.

const germanCollator = new Intl.Collator('de');
const swedishCollator = new Intl.Collator('sv');

console.log(germanCollator.compare('ä', 'z')); // -1 ('ä' comes before 'z' in German)
console.log(swedishCollator.compare('ä', 'z')); // 1  ('ä' comes after 'z' in Swedish)

Key Configuration Options

Intl.Collator accepts an options object that customizes how strings are evaluated:

  1. sensitivity: Defines which differences between characters are significant.

    • 'base': Ignores case and accents (e.g., a = A = á).
    • 'accent': Considers base characters and accents, ignores case (e.g., a = A, but aá).
    • 'case': Considers base characters and case, ignores accents (e.g., a = á, but aA).
    • 'variant': Default setting; considers base characters, accents, and case as distinct.
  2. numeric: A boolean value. When set to true, strings containing numbers are sorted using natural sorting order (e.g., “item2” comes before “item10”).

  3. caseFirst: Determines whether uppercase or lowercase characters take precedence ('upper', 'lower', or 'false').

  4. usage: Specifies whether the collator is intended for sorting lists ('sort', default) or for filtering/searching matches ('search').

const naturalSortCollator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' });
const files = ['file10.txt', 'file2.txt', 'FILE1.txt'];

files.sort(naturalSortCollator.compare);
// Output: ['FILE1.txt', 'file2.txt', 'file10.txt']

Performance Optimization

The primary advantage of Intl.Collator over String.prototype.localeCompare is performance. Initializing a collation instance is computationally expensive because it loads and parses the locale data. By instantiating Intl.Collator once and passing collator.compare directly into Array.prototype.sort(), the locale setup happens once, yielding significantly faster sorting times on large datasets.