From 5ffdcfe235c9ad222d05c124c18e67457945a663 Mon Sep 17 00:00:00 2001 From: temycodes Date: Mon, 19 Oct 2026 21:28:18 +0100 Subject: [PATCH 1/2] fix(i18n): honor the requested currency in formatCurrency (#920) --- src/constants/i18n.test.ts | 20 ++++++++++++++++++++ src/constants/i18n.ts | 20 ++++++++++---------- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/constants/i18n.test.ts b/src/constants/i18n.test.ts index f87318ee..c2dc050d 100644 --- a/src/constants/i18n.test.ts +++ b/src/constants/i18n.test.ts @@ -35,6 +35,26 @@ describe("i18n utility functions", () => { expect(formatCurrency(1200, "USD", "en-US")).toContain("$"); }); + it("uses the requested currency even when the locale has a different default", () => { + const usdInGerman = formatCurrency(100, "USD", "de-DE"); + expect(usdInGerman).toContain("$"); + expect(usdInGerman).not.toContain("€"); + expect(formatCurrency(100, "EUR", "en-US")).toContain("€"); + expect(formatCurrency(100, "GBP", "ja-JP")).toContain("£"); + }); + + it("falls back to the locale default currency when none is requested", () => { + expect(formatCurrency(100, undefined, "de-DE")).toContain("€"); + expect(formatCurrency(100, undefined, "ja-JP")).toBe( + new Intl.NumberFormat("ja-JP", { style: "currency", currency: "JPY" }).format(100), + ); + }); + + it("only drops decimals when the requested currency is JPY", () => { + expect(formatCurrency(1234.5, "JPY", "en-US")).not.toMatch(/\.\d/); + expect(formatCurrency(1234.5, "USD", "ja-JP")).toContain(".50"); + }); + it("formats dates using locale settings", () => { expect(formatDate("2026-05-10", "en-US")).toContain("2026"); expect(formatDate("2026-05-10", "de-DE")).toContain("2026"); diff --git a/src/constants/i18n.ts b/src/constants/i18n.ts index ac2ed41b..a28b72e5 100644 --- a/src/constants/i18n.ts +++ b/src/constants/i18n.ts @@ -62,7 +62,7 @@ export const LOCALE_FORMAT_SETTINGS: Record = { defaultCurrency: "USD", date: { year: "numeric", month: "short", day: "numeric" }, number: { maximumFractionDigits: 2 }, - currency: { style: "currency", currency: "USD", currencyDisplay: "symbol", maximumFractionDigits: 2 }, + currency: { style: "currency", currencyDisplay: "symbol" }, }, "de-DE": { code: "de-DE", @@ -71,7 +71,7 @@ export const LOCALE_FORMAT_SETTINGS: Record = { defaultCurrency: "EUR", date: { year: "numeric", month: "numeric", day: "numeric" }, number: { maximumFractionDigits: 2 }, - currency: { style: "currency", currency: "EUR", currencyDisplay: "symbol", maximumFractionDigits: 2 }, + currency: { style: "currency", currencyDisplay: "symbol" }, }, "ja-JP": { code: "ja-JP", @@ -80,7 +80,7 @@ export const LOCALE_FORMAT_SETTINGS: Record = { defaultCurrency: "JPY", date: { year: "numeric", month: "numeric", day: "numeric" }, number: { maximumFractionDigits: 0, useGrouping: true }, - currency: { style: "currency", currency: "JPY", currencyDisplay: "symbol", maximumFractionDigits: 0 }, + currency: { style: "currency", currencyDisplay: "symbol" }, }, "ar-SA": { code: "ar-SA", @@ -89,7 +89,7 @@ export const LOCALE_FORMAT_SETTINGS: Record = { defaultCurrency: "SAR", date: { year: "numeric", month: "numeric", day: "numeric" }, number: { maximumFractionDigits: 2, useGrouping: true }, - currency: { style: "currency", currency: "SAR", currencyDisplay: "symbol", maximumFractionDigits: 2 }, + currency: { style: "currency", currencyDisplay: "symbol" }, }, "fr-FR": { code: "fr-FR", @@ -98,7 +98,7 @@ export const LOCALE_FORMAT_SETTINGS: Record = { defaultCurrency: "EUR", date: { year: "numeric", month: "numeric", day: "numeric" }, number: { maximumFractionDigits: 2 }, - currency: { style: "currency", currency: "EUR", currencyDisplay: "symbol", maximumFractionDigits: 2 }, + currency: { style: "currency", currencyDisplay: "symbol" }, }, "es-ES": { code: "es-ES", @@ -107,7 +107,7 @@ export const LOCALE_FORMAT_SETTINGS: Record = { defaultCurrency: "EUR", date: { year: "numeric", month: "numeric", day: "numeric" }, number: { maximumFractionDigits: 2 }, - currency: { style: "currency", currency: "EUR", currencyDisplay: "symbol", maximumFractionDigits: 2 }, + currency: { style: "currency", currencyDisplay: "symbol" }, }, "zh-CN": { code: "zh-CN", @@ -116,7 +116,7 @@ export const LOCALE_FORMAT_SETTINGS: Record = { defaultCurrency: "CNY", date: { year: "numeric", month: "numeric", day: "numeric" }, number: { maximumFractionDigits: 2, useGrouping: true }, - currency: { style: "currency", currency: "CNY", currencyDisplay: "symbol", maximumFractionDigits: 2 }, + currency: { style: "currency", currencyDisplay: "symbol" }, }, "en-GB": { code: "en-GB", @@ -125,7 +125,7 @@ export const LOCALE_FORMAT_SETTINGS: Record = { defaultCurrency: "GBP", date: { year: "numeric", month: "short", day: "numeric" }, number: { maximumFractionDigits: 2 }, - currency: { style: "currency", currency: "GBP", currencyDisplay: "symbol", maximumFractionDigits: 2 }, + currency: { style: "currency", currencyDisplay: "symbol" }, }, }; @@ -165,13 +165,13 @@ export function formatCurrency( locale: string = "en-US", options: Intl.NumberFormatOptions = {}, ): string { - const defaultCurr = currency ?? LOCALE_FORMAT_SETTINGS[locale]?.defaultCurrency ?? "USD"; + const resolvedCurrency = currency ?? LOCALE_FORMAT_SETTINGS[locale]?.defaultCurrency ?? "USD"; const setting = LOCALE_FORMAT_SETTINGS[locale]?.currency ?? {}; return new Intl.NumberFormat(locale, { style: "currency", - currency: defaultCurr, currencyDisplay: "symbol", ...setting, + currency: resolvedCurrency, ...options, }).format(value); } From e1376222ee5385fec52289cf0d26ebec0a70bc01 Mon Sep 17 00:00:00 2001 From: temycodes Date: Mon, 19 Oct 2026 21:40:53 +0100 Subject: [PATCH 2/2] test(i18n): compare GBP in ja-JP against Intl instead of a hard-coded symbol (#920) --- src/constants/i18n.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/constants/i18n.test.ts b/src/constants/i18n.test.ts index c2dc050d..f4a6bac5 100644 --- a/src/constants/i18n.test.ts +++ b/src/constants/i18n.test.ts @@ -40,7 +40,9 @@ describe("i18n utility functions", () => { expect(usdInGerman).toContain("$"); expect(usdInGerman).not.toContain("€"); expect(formatCurrency(100, "EUR", "en-US")).toContain("€"); - expect(formatCurrency(100, "GBP", "ja-JP")).toContain("£"); + expect(formatCurrency(100, "GBP", "ja-JP")).toBe( + new Intl.NumberFormat("ja-JP", { style: "currency", currency: "GBP" }).format(100), + ); }); it("falls back to the locale default currency when none is requested", () => {