/** * The currency a workspace writes money in. * * One setting for the whole workspace rather than one per column: a business * keeps its books in a currency, not per field, and a workspace where the * invoice total is in euros and the line price in dollars is a bug however it * got that way. Columns stay `currency`-typed and simply read in whatever the * workspace is settled on. * * The code is stored, never the symbol — `EUR` outlives whatever glyph a * browser decides to render it with, and it is what `Intl` wants anyway. */ export const DEFAULT_CURRENCY = 'EUR'; export interface CurrencyOption { code: string; name: string; } /** * The list the settings dialog offers. Not exhaustive, and deliberately not: * anything ISO 4217 accepts is storable, so a workspace that needs a currency * missing here is never stuck — it is just not one click away. */ export const CURRENCIES: CurrencyOption[] = [ { code: 'EUR', name: 'Euro' }, { code: 'USD', name: 'US dollar' }, { code: 'GBP', name: 'Pound sterling' }, { code: 'CHF', name: 'Swiss franc' }, { code: 'SEK', name: 'Swedish krona' }, { code: 'NOK', name: 'Norwegian krone' }, { code: 'DKK', name: 'Danish krone' }, { code: 'PLN', name: 'Polish złoty' }, { code: 'CZK', name: 'Czech koruna' }, { code: 'CAD', name: 'Canadian dollar' }, { code: 'AUD', name: 'Australian dollar' }, { code: 'JPY', name: 'Japanese yen' }, { code: 'INR', name: 'Indian rupee' }, { code: 'BRL', name: 'Brazilian real' }, ]; /** Whether `value` is shaped like an ISO 4217 code, which is all `Intl` asks. */ export function isCurrencyCode(value: string): boolean { return /^[A-Za-z]{3}$/.test(value); } const symbols = new Map(); /** * The short sign for a currency — `€`, `$`, `kr` — for places too small to * hold a formatted amount, like the type marker on a column header. * * Falls back to the code itself, which is what `Intl` does for currencies with * no symbol in the first place. */ export function currencySymbol(code: string): string { const cached = symbols.get(code); if (cached !== undefined) return cached; let symbol = code; try { const parts = new Intl.NumberFormat('en-US', { style: 'currency', currency: code, currencyDisplay: 'narrowSymbol', }).formatToParts(0); symbol = parts.find((p) => p.type === 'currency')?.value ?? code; } catch { // An unusable code renders as itself rather than taking the page down. } symbols.set(code, symbol); return symbol; }