From bb87f859903ea4c6881434eb12b6f255498809d0 Mon Sep 17 00:00:00 2001 From: lemarjohnny781 Date: Mon, 27 Jul 2026 10:17:45 +0000 Subject: [PATCH] feat: locale-aware number and currency formatting --- src/app/solve/page.tsx | 7 +++---- src/components/SwapCard.tsx | 11 ++++++++--- src/lib/format.test.ts | 22 ++++++++++++++++++++++ src/lib/format.ts | 23 +++++++++++++++++++++++ 4 files changed, 56 insertions(+), 7 deletions(-) create mode 100644 src/lib/format.test.ts create mode 100644 src/lib/format.ts diff --git a/src/app/solve/page.tsx b/src/app/solve/page.tsx index fb3fdda..4a7b824 100644 --- a/src/app/solve/page.tsx +++ b/src/app/solve/page.tsx @@ -8,10 +8,9 @@ import { useAcceptIntent } from "@/hooks/useAcceptIntent"; import { useSolverRegistration } from "@/hooks/useSolverRegistration"; import { timeRemaining } from "@/lib/time"; import { isValidStellarPublicKey } from "@/lib/stellarAddress"; +import { formatCurrency } from "@/lib/format"; -const usdCompact = new Intl.NumberFormat("en-US", { - style: "currency", - currency: "USD", +const usdCompact = (value: number) => formatCurrency(value, undefined, { notation: "compact", maximumFractionDigits: 1, }); @@ -163,7 +162,7 @@ export default function SolvePage() {
Fills
-
{usdCompact.format(s.volumeUsd)}
+
{usdCompact(s.volumeUsd)}
Volume
diff --git a/src/components/SwapCard.tsx b/src/components/SwapCard.tsx index c8dd6c3..51f91c2 100644 --- a/src/components/SwapCard.tsx +++ b/src/components/SwapCard.tsx @@ -5,6 +5,7 @@ import { useQuote } from "@/hooks/useQuote"; import { useDebouncedValue } from "@/hooks/useDebouncedValue"; import { useSwapSubmission } from "@/hooks/useSwapSubmission"; import { CHAINS, SRC_TOKENS, DST_TOKENS } from "@/lib/marketData"; +import { formatCurrency, formatTokenAmount } from "@/lib/format"; const SUBMISSION_LABEL: Record = { connecting: "Connecting wallet…", @@ -147,7 +148,7 @@ export function SwapCard() { ${t.symbol === srcToken.symbol ? "bg-vx-lav-bg text-vx-lav" : "hover:bg-vx-surface text-vx-muted hover:text-vx-text"}`} > {t.symbol} - ${t.priceUSD.toLocaleString()} + {formatCurrency(t.priceUSD)} ))}
@@ -155,7 +156,7 @@ export function SwapCard() { {srcValueUSD > 0 && (
- ≈ ${srcValueUSD.toLocaleString("en-US", { maximumFractionDigits: 2 })} + ≈ {formatCurrency(srcValueUSD, undefined, { maximumFractionDigits: 2 })}
)} @@ -190,7 +191,11 @@ export function SwapCard() { ) : (
- {dstAmount > 0 ? dstAmount.toFixed(dstToken.symbol === "XLM" ? 2 : 4) : "0"} + {dstAmount > 0 + ? formatTokenAmount(dstAmount, undefined, { + maximumFractionDigits: dstToken.symbol === "XLM" ? 2 : 4, + }) + : "0"}
)} diff --git a/src/lib/format.test.ts b/src/lib/format.test.ts new file mode 100644 index 0000000..5333cc2 --- /dev/null +++ b/src/lib/format.test.ts @@ -0,0 +1,22 @@ +import { describe, expect, it } from "vitest"; +import { formatCurrency, formatTokenAmount } from "./format"; + +describe("formatCurrency", () => { + it("formats USD values for en-US locale", () => { + expect(formatCurrency(1234.5, "en-US")).toBe("$1,234.50"); + }); + + it("formats USD values for de-DE locale", () => { + expect(formatCurrency(1234.5, "de-DE")).toBe("1.234,50 $"); + }); +}); + +describe("formatTokenAmount", () => { + it("formats token amounts for en-US locale", () => { + expect(formatTokenAmount(1234.5678, "en-US", { maximumFractionDigits: 4 })).toBe("1,234.5678"); + }); + + it("formats token amounts for de-DE locale", () => { + expect(formatTokenAmount(1234.5678, "de-DE", { maximumFractionDigits: 4 })).toBe("1.234,5678"); + }); +}); diff --git a/src/lib/format.ts b/src/lib/format.ts new file mode 100644 index 0000000..61d576e --- /dev/null +++ b/src/lib/format.ts @@ -0,0 +1,23 @@ +const DEFAULT_LOCALE = "en-US"; + +function resolveLocale(locale?: string): string { + if (locale) return locale; + + if (typeof navigator !== "undefined" && navigator.language) { + return navigator.language; + } + + return DEFAULT_LOCALE; +} + +export function formatCurrency(value: number, locale?: string, options: Intl.NumberFormatOptions = {}) { + return new Intl.NumberFormat(resolveLocale(locale), { + style: "currency", + currency: "USD", + ...options, + }).format(value); +} + +export function formatTokenAmount(value: number, locale?: string, options: Intl.NumberFormatOptions = {}) { + return new Intl.NumberFormat(resolveLocale(locale), options).format(value); +}