/** * Pure string utilities shared across modules. */ /** * Spanish single-letter conjunctions that stay lowercase in Title Case, * except when they are the first word of the string. * * Standard Spanish typography rule (RAE): preposiciones y conjunciones * (a, con, de, e, en, o, por, u, y…) van en minúscula salvo que sean la * primera palabra. Single-letter conjunctions are included here so a * "Y" between two words renders correctly. */ const LOWERCASE_WORDS = new Set(['y', 'e', 'o', 'u']); /** * Convert a brand/category name to Title Case. * * Splits on word boundaries (space, hyphen, dot, ampersand, slash) and * uppercases the first letter of each word, lowercasing the rest of the * word. Punctuation and whitespace separators are preserved verbatim. * * Behavior: * - If the input is ALL-CAPS (every letter is uppercase and at least one * is lowercase-equivalent), each word is Title Cased and Spanish * single-letter conjunctions (y/e/o/u) are lowercased unless they * are the first word. * - If the input has mixed case (e.g. "BioSana"), it is returned * unchanged. This makes the function idempotent for re-application: * a Title-Cased string is a fixed point, and a mixed-case string is * not what we're trying to fix. * - Empty string → empty string. * * @example * toTitleCase('A.VOGEL') // 'A.Vogel' * toTitleCase('BIOCOP') // 'Biocop' * toTitleCase('EL GRANERO INTEGRAL') // 'El Granero Integral' * toTitleCase('COMPLEMENTOS Y NUTRICIÓN') // 'Complementos y Nutrición' * toTitleCase('DULCES LISSEN') // 'Dulces Lissen' * toTitleCase('La Finestra Sul Cielo') // 'La Finestra Sul Cielo' (unchanged) * toTitleCase('BioSana') // 'BioSana' (unchanged) * toTitleCase('') // '' */ export function toTitleCase(input: string): string { if (!input) return input; // Only transform strings that are entirely uppercase. A mixed-case string // is treated as already-formatted and returned verbatim. if (input !== input.toUpperCase() || input === input.toLowerCase()) { return input; } // Split keeping the delimiters so we can reconstruct the string verbatim. const parts = input.split(/([\s.\-/&]+)/g); let isFirstWord = true; return parts .map((part) => { if (/^[\s.\-/&]+$/.test(part)) return part; if (part.length === 0) return part; const lower = part.toLocaleLowerCase('es-ES'); if (!isFirstWord && LOWERCASE_WORDS.has(lower)) return lower; isFirstWord = false; return part.charAt(0).toLocaleUpperCase('es-ES') + part.slice(1).toLocaleLowerCase('es-ES'); }) .join(''); }