const accentMap = { à: 'a', á: 'a', ä: 'a', å: 'a', ã: 'a', â: 'a', æ: 'ae', ç: 'c', è: 'e', é: 'e', ë: 'e', ẽ: 'e', ê: 'e', ì: 'i', í: 'i', ï: 'i', ĩ: 'i', î: 'i', ǹ: 'n', ń: 'n', ñ: 'n', ò: 'o', ó: 'o', ö: 'o', õ: 'o', ô: 'o', ø: 'o', œ: 'oe', ß: 'ss', ù: 'u', ú: 'u', ü: 'u', ũ: 'u', û: 'u', ỳ: 'y', ý: 'y', ÿ: 'y', ỹ: 'y', }; const plainCharRegex = /[a-zA-Z0-9]/; const defaultPunctuationRegex = /[.,?!:;&'‘’"“”…()[]{}<>\/*—-]/; const defaultSymbolRegex = /[@$€£#%^+=\\~]/; export default function slugify(strings, delimiter = '-', { limit = 1000, lower = true, encode = false, accents: keepAccents = false, punctuation: keepPunctuation = false, punctuationRegex = defaultPunctuationRegex, symbols: keepSymbols = false, symbolRegex = defaultSymbolRegex, } = {}) { if (!strings || (typeof strings !== 'string' && !Array.isArray(strings))) { return strings; } const string = [].concat(strings).join(' '); const casedString = lower ? string.toLowerCase() : string; const normalized = casedString .split('') .map((char) => { if (char === ' ') { return char; } const lowChar = char.toLowerCase(); if (accentMap[lowChar]) { if (keepAccents) { return char; } // match original case after mapping if (char === lowChar) { return accentMap[lowChar]; } return accentMap[lowChar].toUpperCase(); } if (plainCharRegex.test(char)) { return char; } if (punctuationRegex.test(char)) { if (keepPunctuation === 'split') { return ' '; } if (keepPunctuation) { return char; } } if (symbolRegex.test(char)) { if (keepSymbols === 'split') { return ' '; } if (keepSymbols) { return char; } } return ''; }).join(''); const components = normalized.trim().split(/\s+/).filter(Boolean); if (components.length === 0) { return ''; } const slug = components.reduce((acc, component, index) => { const accSlug = `${acc}${index > 0 ? delimiter : ''}${component}`; if (accSlug.length < limit) { return accSlug; } return acc; }).slice(0, limit); // in case first word exceeds limit if (encode) { return encodeURI(slug); } return slug; }