JavaScript Basics: Sort Human Labels with Intl.Collator
People expect language-aware ordering and numbered labels that sort naturally. Intl.Collator creates a reusable comparator with an explicit locale and comparison policy. This goes beyond code-unit ordering or subtracting numeric array values.
These examples were tested on Linux with Node.js v24.19.0. They use built-in APIs only, require no network access, and include assertions that check the results. See MDN’s Intl.Collator reference. Locale support and collation data depend on the runtime’s ICU build.
Prepare a scratch directory
You need Bash, a text editor, Node.js 24 on your PATH, and the mktemp, rm, and rmdir utilities. Keep using the same terminal. Save each named source file as UTF-8; .mjs selects ES modules. Each example runs independently.
node --version
start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1The tested runtime prints:
v24.19.01. Choose a language for alphabetic order
Save this as sort-german-labels.mjs:
import assert from 'node:assert/strict';
assert.equal(Intl.Collator.supportedLocalesOf(['de']).length, 1);
const compare = new Intl.Collator('de', { sensitivity: 'base' }).compare;
const names = ['Zebra', 'Öl', 'Apfel'];
const ordered = names.toSorted(compare);
assert.deepEqual(ordered, ['Apfel', 'Öl', 'Zebra']);
assert.deepEqual(names, ['Zebra', 'Öl', 'Apfel']);
console.log(ordered.join(', '));
console.log(`Ö before Z: ${compare('Ö', 'Z') < 0}`);Run it:
node sort-german-labels.mjsOutput:
Apfel, Öl, Zebra
Ö before Z: trueGerman collation places the label beginning with Ö near O. A different language can order accented letters differently. Pass the locale your interface needs instead of relying on the host default. Compare the result’s sign; the API does not promise exactly -1 or 1.
2. Order digit sequences numerically
Save this as sort-numbered-labels.mjs:
import assert from 'node:assert/strict';
const collator = new Intl.Collator('en', { numeric: true });
const labels = ['item10', 'item2', 'item1'];
const ordered = labels.toSorted(collator.compare);
assert.deepEqual(ordered, ['item1', 'item2', 'item10']);
assert.equal(collator.compare('item2', 'item02'), 0);
console.log(ordered.join(', '));
console.log(`Leading-zero labels tie: ${collator.compare('item2', 'item02') === 0}`);Run it:
node sort-numbered-labels.mjsOutput:
item1, item2, item10
Leading-zero labels tie: truenumeric: true compares digit sequences as numbers inside labels. Leading zeros can produce ties, as shown here. This is useful for filenames or chapter labels, but it is not a version-number parser or a validator for decimal quantities. Reuse one collator for repeated comparisons.
3. Keep equivalent names in a predictable order
Save this as sort-insensitive-ties.mjs:
import assert from 'node:assert/strict';
const collator = new Intl.Collator('en', { sensitivity: 'base' });
const entries = [
{ name: 'résumé', id: 3 },
{ name: 'Resume', id: 1 },
{ name: 'apple', id: 2 },
];
const ordered = entries.toSorted((a, b) =>
collator.compare(a.name, b.name) || a.id - b.id);
assert.equal(collator.compare('résumé', 'Resume'), 0);
assert.deepEqual(ordered.map(entry => entry.id), [2, 1, 3]);
console.log(ordered.map(entry => `${entry.name}:${entry.id}`).join(', '));Run it:
node sort-insensitive-ties.mjsOutput:
apple:2, Resume:1, résumé:3With this English base-sensitivity policy, case and accents do not distinguish the two résumé labels. A numeric ID supplies an explicit secondary ordering. Comparison equality is not string identity and should not decide authorization or unique account names. Exact output for other inputs may change with locale-data versions.
Clean up
In the same terminal, remove only the named example files, return to your original directory, and remove the empty scratch directory:
rm -- sort-german-labels.mjs sort-numbered-labels.mjs sort-insensitive-ties.mjs
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dirAcknowledgements
This post was created with assistance from AI.