65 lines
2.7 KiB
TypeScript
65 lines
2.7 KiB
TypeScript
/**
|
|
* 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('');
|
|
}
|