Intl.Collator provides locale-aware string comparison for proper sorting across languages. Here's how to use it.
Basic Usage#
1// Create collator for locale
2const collator = new Intl.Collator('en');
3
4// Compare strings
5console.log(collator.compare('a', 'b')); // -1 (a < b)
6console.log(collator.compare('b', 'a')); // 1 (b > a)
7console.log(collator.compare('a', 'a')); // 0 (equal)
8
9// Sort array
10const fruits = ['banana', 'Apple', 'cherry'];
11fruits.sort(collator.compare);
12console.log(fruits); // ['Apple', 'banana', 'cherry']Locale-Specific Sorting#
1// German sorting (ä sorts with a)
2const germanCollator = new Intl.Collator('de');
3const germanWords = ['ä', 'z', 'a'];
4germanWords.sort(germanCollator.compare);
5console.log(germanWords); // ['a', 'ä', 'z']
6
7// Swedish sorting (ä sorts after z)
8const swedishCollator = new Intl.Collator('sv');
9const swedishWords = ['ä', 'z', 'a'];
10swedishWords.sort(swedishCollator.compare);
11console.log(swedishWords); // ['a', 'z', 'ä']
12
13// Spanish sorting (ñ after n)
14const spanishCollator = new Intl.Collator('es');
15const spanishWords = ['nube', 'ñu', 'nada'];
16spanishWords.sort(spanishCollator.compare);
17console.log(spanishWords); // ['nada', 'nube', 'ñu']Sensitivity Options#
1// base: a ≠ b, a = á = A
2const baseCollator = new Intl.Collator('en', { sensitivity: 'base' });
3console.log(baseCollator.compare('a', 'A')); // 0
4console.log(baseCollator.compare('a', 'á')); // 0
5console.log(baseCollator.compare('a', 'b')); // -1
6
7// accent: a ≠ b, a ≠ á, a = A
8const accentCollator = new Intl.Collator('en', { sensitivity: 'accent' });
9console.log(accentCollator.compare('a', 'A')); // 0
10console.log(accentCollator.compare('a', 'á')); // -1
11
12// case: a ≠ b, a = á, a ≠ A
13const caseCollator = new Intl.Collator('en', { sensitivity: 'case' });
14console.log(caseCollator.compare('a', 'A')); // -1
15console.log(caseCollator.compare('a', 'á')); // 0
16
17// variant: a ≠ b, a ≠ á, a ≠ A (default)
18const variantCollator = new Intl.Collator('en', { sensitivity: 'variant' });
19console.log(variantCollator.compare('a', 'A')); // -1
20console.log(variantCollator.compare('a', 'á')); // -1Case Ordering#
1// Upper case first
2const upperFirstCollator = new Intl.Collator('en', {
3 caseFirst: 'upper',
4});
5
6const names1 = ['abc', 'ABC', 'Abc'];
7names1.sort(upperFirstCollator.compare);
8console.log(names1); // ['ABC', 'Abc', 'abc']
9
10// Lower case first
11const lowerFirstCollator = new Intl.Collator('en', {
12 caseFirst: 'lower',
13});
14
15const names2 = ['abc', 'ABC', 'Abc'];
16names2.sort(lowerFirstCollator.compare);
17console.log(names2); // ['abc', 'Abc', 'ABC']
18
19// Case-insensitive sort
20const caseInsensitive = new Intl.Collator('en', {
21 sensitivity: 'base',
22});
23
24const names3 = ['abc', 'ABC', 'def', 'Abc'];
25names3.sort(caseInsensitive.compare);
26console.log(names3); // ['abc', 'ABC', 'Abc', 'def']Numeric Sorting#
1// Without numeric option
2const defaultCollator = new Intl.Collator('en');
3const files1 = ['file10', 'file2', 'file1'];
4files1.sort(defaultCollator.compare);
5console.log(files1); // ['file1', 'file10', 'file2']
6
7// With numeric option (natural sort)
8const numericCollator = new Intl.Collator('en', { numeric: true });
9const files2 = ['file10', 'file2', 'file1'];
10files2.sort(numericCollator.compare);
11console.log(files2); // ['file1', 'file2', 'file10']
12
13// Version sorting
14const versions = ['v1.10', 'v1.2', 'v1.1', 'v2.0'];
15versions.sort(numericCollator.compare);
16console.log(versions); // ['v1.1', 'v1.2', 'v1.10', 'v2.0']Ignore Punctuation#
1const punctuationCollator = new Intl.Collator('en', {
2 ignorePunctuation: true,
3});
4
5console.log(punctuationCollator.compare('re-sort', 'resort')); // 0
6console.log(punctuationCollator.compare("it's", 'its')); // 0
7
8// Useful for searching
9const items = ['re-sort', 'resort', 'reset'];
10const searchTerm = 'resort';
11
12const matches = items.filter(
13 item => punctuationCollator.compare(item, searchTerm) === 0
14);
15console.log(matches); // ['re-sort', 'resort']Sorting Objects#
1// Sort array of objects by property
2const users = [
3 { name: 'Zoe', age: 25 },
4 { name: 'alice', age: 30 },
5 { name: 'Bob', age: 20 },
6];
7
8const collator = new Intl.Collator('en', { sensitivity: 'base' });
9
10// Sort by name
11users.sort((a, b) => collator.compare(a.name, b.name));
12console.log(users.map(u => u.name)); // ['alice', 'Bob', 'Zoe']
13
14// Sort by multiple fields
15const products = [
16 { category: 'Electronics', name: 'Phone' },
17 { category: 'Books', name: 'Novel' },
18 { category: 'Electronics', name: 'Laptop' },
19 { category: 'Books', name: 'Comic' },
20];
21
22products.sort((a, b) => {
23 const categoryCompare = collator.compare(a.category, b.category);
24 if (categoryCompare !== 0) return categoryCompare;
25 return collator.compare(a.name, b.name);
26});Search and Filter#
1// Case-insensitive search
2function createSearcher(locale) {
3 const collator = new Intl.Collator(locale, {
4 sensitivity: 'base',
5 ignorePunctuation: true,
6 });
7
8 return {
9 includes(text, search) {
10 const textLower = text.toLowerCase();
11 const searchLower = search.toLowerCase();
12
13 for (let i = 0; i <= textLower.length - searchLower.length; i++) {
14 const slice = textLower.slice(i, i + searchLower.length);
15 if (collator.compare(slice, searchLower) === 0) {
16 return true;
17 }
18 }
19 return false;
20 },
21
22 filter(items, search, getField = (x) => x) {
23 return items.filter((item) => this.includes(getField(item), search));
24 },
25 };
26}
27
28const searcher = createSearcher('en');
29const cities = ['New York', 'Los Angeles', 'new orleans'];
30const results = searcher.filter(cities, 'new');
31console.log(results); // ['New York', 'new orleans']Performance Optimization#
1// Reuse collator instance
2const collator = new Intl.Collator('en', { numeric: true });
3
4// Good: reuse compare function
5const compare = collator.compare;
6largeArray.sort(compare);
7
8// Bad: creating new collator each time
9largeArray.sort((a, b) => new Intl.Collator('en').compare(a, b));
10
11// For multiple locales, cache collators
12const collatorCache = new Map();
13
14function getCollator(locale, options = {}) {
15 const key = `${locale}-${JSON.stringify(options)}`;
16
17 if (!collatorCache.has(key)) {
18 collatorCache.set(key, new Intl.Collator(locale, options));
19 }
20
21 return collatorCache.get(key);
22}Resolved Options#
1const collator = new Intl.Collator('de', {
2 sensitivity: 'base',
3 numeric: true,
4});
5
6console.log(collator.resolvedOptions());
7// {
8// locale: 'de',
9// usage: 'sort',
10// sensitivity: 'base',
11// ignorePunctuation: false,
12// collation: 'default',
13// numeric: true,
14// caseFirst: 'false'
15// }Practical Examples#
1// Contact list sorting
2function sortContacts(contacts, locale = 'en') {
3 const collator = new Intl.Collator(locale, {
4 sensitivity: 'base',
5 });
6
7 return [...contacts].sort((a, b) => {
8 // Sort by last name, then first name
9 const lastNameCompare = collator.compare(a.lastName, b.lastName);
10 if (lastNameCompare !== 0) return lastNameCompare;
11 return collator.compare(a.firstName, b.firstName);
12 });
13}
14
15// File browser sorting
16function sortFiles(files) {
17 const collator = new Intl.Collator(undefined, {
18 numeric: true,
19 sensitivity: 'base',
20 });
21
22 return [...files].sort((a, b) => {
23 // Folders first
24 if (a.isFolder && !b.isFolder) return -1;
25 if (!a.isFolder && b.isFolder) return 1;
26 return collator.compare(a.name, b.name);
27 });
28}
29
30// Autocomplete sorting
31function sortSuggestions(suggestions, query) {
32 const collator = new Intl.Collator(undefined, {
33 sensitivity: 'base',
34 });
35
36 return [...suggestions].sort((a, b) => {
37 // Exact matches first
38 const aExact = collator.compare(a, query) === 0;
39 const bExact = collator.compare(b, query) === 0;
40 if (aExact && !bExact) return -1;
41 if (!aExact && bExact) return 1;
42
43 // Then starts-with
44 const aStarts = a.toLowerCase().startsWith(query.toLowerCase());
45 const bStarts = b.toLowerCase().startsWith(query.toLowerCase());
46 if (aStarts && !bStarts) return -1;
47 if (!aStarts && bStarts) return 1;
48
49 // Then alphabetical
50 return collator.compare(a, b);
51 });
52}Best Practices#
Configuration:
✓ Choose appropriate sensitivity
✓ Enable numeric for versions/files
✓ Match locale to user preference
✓ Cache collator instances
Common Options:
✓ sensitivity: 'base' for search
✓ numeric: true for natural sort
✓ ignorePunctuation for fuzzy match
✓ caseFirst for specific ordering
Performance:
✓ Reuse collator instances
✓ Extract compare function
✓ Cache for multiple locales
✓ Avoid creating in loops
Avoid:
✗ localeCompare for large sorts
✗ Creating collators repeatedly
✗ Ignoring locale requirements
✗ Assuming ASCII ordering
Conclusion#
Intl.Collator provides locale-aware string comparison essential for proper internationalization. Use sensitivity options to control how strictly strings are compared, numeric option for natural sorting of numbers in strings, and cache collator instances for performance. It handles locale-specific sorting rules automatically, making it far superior to simple string comparison for user-facing content.