Back to Blog
JavaScriptIntlCollatorSorting

JavaScript Intl.Collator Guide

Master JavaScript Intl.Collator for locale-aware string comparison and sorting.

B
Bootspring Team
Engineering
January 15, 2019
6 min read

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', 'á')); // -1

Case 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.

Share this article

Help spread the word about Bootspring