/** * Shared Intl object instances with lazy initialization. * * Intl constructors are expensive (~0.05-0.1ms each), so we cache instances * for reuse across the codebase instead of creating new ones each time. * Lazy initialization ensures we only pay the cost when actually needed. */ // Segmenters for Unicode text processing (lazily initialized) let graphemeSegmenter: Intl.Segmenter | null = null let wordSegmenter: Intl.Segmenter | null = null export function getGraphemeSegmenter(): Intl.Segmenter { if (!graphemeSegmenter) { graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme', }) } return graphemeSegmenter } /** * Extract the first grapheme cluster from a string. * Returns '' for empty strings. */ export function firstGrapheme(text: string): string { if (!text) return '' const segments = getGraphemeSegmenter().segment(text) const first = segments[Symbol.iterator]().next().value return first?.segment ?? '' } /** * Extract the last grapheme cluster from a string. * Returns '' for empty strings. */ export function lastGrapheme(text: string): string { if (!text) return '' let last = '' for (const { segment } of getGraphemeSegmenter().segment(text)) { last = segment } return last } export function getWordSegmenter(): Intl.Segmenter { if (!wordSegmenter) { wordSegmenter = new Intl.Segmenter(undefined, { granularity: 'word' }) } return wordSegmenter } // RelativeTimeFormat cache (keyed by style:numeric) const rtfCache = new Map() export function getRelativeTimeFormat( style: 'long' | 'short' | 'narrow', numeric: 'always' | 'auto', ): Intl.RelativeTimeFormat { const key = `${style}:${numeric}` let rtf = rtfCache.get(key) if (!rtf) { rtf = new Intl.RelativeTimeFormat('en', { style, numeric }) rtfCache.set(key, rtf) } return rtf } // Timezone is constant for the process lifetime let cachedTimeZone: string | null = null export function getTimeZone(): string { if (!cachedTimeZone) { cachedTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone } return cachedTimeZone } // System locale language subtag (e.g. 'en', 'ja') is constant for the process // lifetime. null = not yet computed; undefined = computed but unavailable (so // a stripped-ICU environment fails once instead of retrying on every call). let cachedSystemLocaleLanguage: string | undefined | null = null export function getSystemLocaleLanguage(): string | undefined { if (cachedSystemLocaleLanguage === null) { try { const locale = Intl.DateTimeFormat().resolvedOptions().locale cachedSystemLocaleLanguage = new Intl.Locale(locale).language } catch { cachedSystemLocaleLanguage = undefined } } return cachedSystemLocaleLanguage }