/** * Copyright since 2025 Mifos Initiative * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** * Casing for client names: the name is stored exactly as the officer typed it, except that a word * starting with a lower-case letter has that first letter raised. Nothing else is touched. * * 'keshav Singh sisodiya' → 'Keshav Singh Sisodiya' * 'lincpay lTD' → 'Lincpay LTD' * 'Technoludium Solutions Private Limited' → unchanged * 'TECHNOLUDIUM SOLUTIONS' → unchanged * * Deliberately NOT full title-casing. Lower-casing the rest of each word would rewrite an entity * name the officer entered as it appears on the certificate — 'TECHNOLUDIUM' becoming * 'Technoludium', 'Ltd' becoming a forced 'LTD'. That name is matched character-for-character * against the PAN holder's registered name during KYC verification, so the typed spelling is the * thing to preserve; only an unintended lower-case first letter is corrected. */ /** Characters after which a new word starts: 'jean-marie' → 'Jean-Marie'. */ const SEGMENT_STARTERS = new Set([ '-', "'", '’' ]); /** * Raises the first letter of each word in a single space-delimited token, leaving every other * character as typed. */ function capitalizeWord(word: string): string { let result = ''; for (let i = 0; i < word.length; i++) { const previous = i === 0 ? '' : word[i - 1]; let startsWord = i === 0 || SEGMENT_STARTERS.has(previous); if (previous === "'" || previous === '’') { // 'o'brien' / 'd'souza' capitalise after the apostrophe, but a possessive // ('lincpay's') must not — only a single leading letter marks the name prefix. startsWord = i === 2; } result += startsWord ? word[i].toUpperCase() : word[i]; } return result; } /** * Applies the casing rule above to a person or entity name. * Trims and collapses runs of whitespace; non-string input yields ''. */ export function toProperNameCase(value: string | null | undefined): string { if (typeof value !== 'string') { return ''; } const collapsed = value.trim().replace(/\s+/g, ' '); if (!collapsed) { return ''; } return collapsed.split(' ').map(capitalizeWord).join(' '); }