/** * Locale-aware value formatting for charts. Uses the app i18n locale (via * `resolveI18n`) instead of a hardcoded locale, with graceful fallbacks. */ import { getI18n } from '../../../i18n' function currentLocale(): string { try { const loc = getI18n().global.locale as unknown const v = (loc as { value?: string })?.value ?? loc if (typeof v === 'string' && v) return v } catch { /* i18n not initialized — fall through */ } if (typeof document !== 'undefined') { return document.documentElement.lang || navigator?.language || 'en' } return 'en' } export interface ValueFormat { /** ISO currency code (e.g. 'USD', 'ILS'). When set, formats as currency. */ currency?: string prefix?: string suffix?: string /** Compact large numbers (1.2K, 3.4M). Default true for axis ticks. */ compact?: boolean maximumFractionDigits?: number } export function formatValue(value: number, fmt: ValueFormat = {}): string { const locale = currentLocale() let out: string try { if (fmt.currency) { out = new Intl.NumberFormat(locale, { style: 'currency', currency: fmt.currency, notation: fmt.compact ? 'compact' : 'standard', maximumFractionDigits: fmt.maximumFractionDigits ?? (fmt.compact ? 1 : 0), }).format(value) } else { out = new Intl.NumberFormat(locale, { notation: fmt.compact ? 'compact' : 'standard', maximumFractionDigits: fmt.maximumFractionDigits ?? (fmt.compact ? 1 : 2), }).format(value) } } catch { out = String(value) } return `${fmt.prefix ?? ''}${out}${fmt.suffix ?? ''}` } /** Format an x label that may be an ISO date; otherwise pass through. */ export function formatLabel(label: string): string { // Only treat as a date if it parses AND looks date-ish (has - or /). if (/[-/]/.test(label)) { const d = new Date(label) if (!Number.isNaN(d.getTime())) { try { return d.toLocaleDateString(currentLocale(), { month: 'short', day: 'numeric' }) } catch { /* noop */ } } } return label }