diff --git a/app/send/components/AutomaticSplitCard.tsx b/app/send/components/AutomaticSplitCard.tsx
index b9ee49a..ac3bdff 100644
--- a/app/send/components/AutomaticSplitCard.tsx
+++ b/app/send/components/AutomaticSplitCard.tsx
@@ -9,11 +9,13 @@ import {
Shield,
Info,
} from "lucide-react";
+import { useClientLocale } from "@/lib/i18n/client";
+import { formatCurrency } from "@/lib/utils/format-currency";
interface SplitCategoryProps {
icon: React.ElementType;
label: string;
- amount: number;
+ amount: string;
percentage: number;
}
@@ -34,7 +36,7 @@ const SplitCategory = ({
{
+ it("formats USD with en-US locale", () => {
+ expect(formatCurrency(1234.5, "USD", "en-US")).toBe("$1,234.50");
+ });
+
+ it("formats negative values consistently", () => {
+ expect(formatCurrency(-9876.543, "USD", "en-US")).toBe("-$9,876.54");
+ });
+
+ it("formats zero values correctly", () => {
+ expect(formatCurrency(0, "USD", "en-US")).toBe("$0.00");
+ });
+
+ it("falls back for unknown stablecoin codes", () => {
+ expect(formatCurrency(1234.5, "USDC", "en-US")).toBe("1,234.50 USDC");
+ });
+
+ it("formats es locale using locale-specific separators", () => {
+ const result = formatCurrency(1234.5, "USD", "es-ES");
+ expect(result).toMatch(/1[.,\u202F]?234[.,]50/);
+ });
+});
+
+describe("formatAmount alias", () => {
+ it("behaves the same as formatCurrency", () => {
+ expect(formatAmount(1234.5, "USD", "en-US")).toBe(formatCurrency(1234.5, "USD", "en-US"));
+ });
+});
diff --git a/lib/utils/format-currency.ts b/lib/utils/format-currency.ts
new file mode 100644
index 0000000..84f6bda
--- /dev/null
+++ b/lib/utils/format-currency.ts
@@ -0,0 +1,55 @@
+export type FormatCurrencyOptions = {
+ locale?: string;
+ minimumFractionDigits?: number;
+ maximumFractionDigits?: number;
+};
+
+const DEFAULT_MINIMUM_FRACTION_DIGITS = 2;
+const DEFAULT_MAXIMUM_FRACTION_DIGITS = 2;
+
+function formatNumber(
+ amount: number,
+ locale: string,
+ minimumFractionDigits: number,
+ maximumFractionDigits: number
+) {
+ return new Intl.NumberFormat(locale, {
+ minimumFractionDigits,
+ maximumFractionDigits,
+ }).format(amount);
+}
+
+/**
+ * Formats a numeric amount for a locale and currency/asset code.
+ *
+ * Falls back to a plain localized number with a currency suffix when
+ * Intl does not recognize the currency code (for example, stablecoin codes).
+ */
+export function formatCurrency(
+ amount: number,
+ currency: string,
+ locale = "en",
+ options: FormatCurrencyOptions = {}
+): string {
+ const { minimumFractionDigits = DEFAULT_MINIMUM_FRACTION_DIGITS, maximumFractionDigits = DEFAULT_MAXIMUM_FRACTION_DIGITS } = options;
+ const normalizedCurrency = currency?.trim();
+ const resolvedLocale = locale || "en";
+
+ if (!normalizedCurrency) {
+ return formatNumber(amount, resolvedLocale, minimumFractionDigits, maximumFractionDigits);
+ }
+
+ try {
+ return new Intl.NumberFormat(resolvedLocale, {
+ style: "currency",
+ currency: normalizedCurrency,
+ minimumFractionDigits,
+ maximumFractionDigits,
+ }).format(amount);
+ } catch {
+ const formattedAmount = formatNumber(amount, resolvedLocale, minimumFractionDigits, maximumFractionDigits);
+ return `${formattedAmount} ${normalizedCurrency}`;
+ }
+}
+
+export const formatAmount = formatCurrency;
diff --git a/package-lock.json b/package-lock.json
index d95e5d8..467aee7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10865,6 +10865,7 @@
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
@@ -14227,6 +14228,7 @@
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,