export function getCountryName(iso: string, locale = "en"): string { try { const dn = new Intl.DisplayNames([locale], { type: "region" }); return dn.of(iso.toUpperCase()) ?? iso; } catch { return iso; } } export function formatPrice(cents: number, currency = "EUR", locale = "en"): string { try { return new Intl.NumberFormat(locale, { style: "currency", currency, minimumFractionDigits: 2, }).format(cents / 100); } catch { return `${currency} ${(cents / 100).toFixed(2)}`; } } /** * A plan's data allowance as a shopper reads it. * * Zero is not "0 GB" — an unmetered plan is the thing the customer is being * sold, and printing a zero next to a price reads as a broken row. */ export function formatDataAllowance(dataGb: number): string { if (!Number.isFinite(dataGb) || dataGb <= 0) return "Unlimited data"; if (dataGb < 1) return `${Math.round(dataGb * 1024)} MB data`; return `${Number.isInteger(dataGb) ? dataGb : dataGb.toFixed(1)} GB data`; } /** A plan's validity window, singular-aware. */ export function formatValidity(validityDays: number): string { if (!Number.isFinite(validityDays) || validityDays <= 0) return "No expiry"; if (validityDays === 1) return "1 day"; if (validityDays % 7 === 0 && validityDays >= 7) { const weeks = validityDays / 7; return weeks === 1 ? "1 week" : `${weeks} weeks`; } return `${validityDays} days`; } /** * Bytes as a usage meter shows them. * * Decimal units (1 GB = 1000 MB), because that is the unit a data bundle is * sold in — showing 0.93 GB left of a 1 GB plan the moment it is bought is a * support ticket, not a rounding preference. */ export function formatBytes(bytes: number): string { if (!Number.isFinite(bytes) || bytes <= 0) return "0 MB"; if (bytes >= 1_000_000_000) return `${(bytes / 1_000_000_000).toFixed(bytes >= 10_000_000_000 ? 0 : 1)} GB`; if (bytes >= 1_000_000) return `${Math.round(bytes / 1_000_000)} MB`; return `${Math.max(1, Math.round(bytes / 1_000))} KB`; } /** Days left until an ISO timestamp, floored at zero. Null when unparseable. */ export function daysUntil(iso: string | undefined, now: Date = new Date()): number | null { if (!iso) return null; const then = Date.parse(iso); if (Number.isNaN(then)) return null; return Math.max(0, Math.ceil((then - now.getTime()) / 86_400_000)); } export type DisplayCurrency = "EUR" | "USD" | "GBP"; export const SUPPORTED_CURRENCIES: DisplayCurrency[] = ["EUR", "USD", "GBP"]; export function getDisplayCurrency(countryIso: string): DisplayCurrency { const map: Record = { US: "USD", GB: "GBP", }; return map[countryIso.toUpperCase()] ?? "EUR"; }