From 526bfe104d785b280df34fdb7df7bc6fbc84bbdd Mon Sep 17 00:00:00 2001 From: ZProger Date: Wed, 9 Sep 2026 00:32:22 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20=D0=BD=D0=B5=D0=BE=D0=B4=D0=BD=D0=BE?= =?UTF-8?q?=D0=B7=D0=BD=D0=B0=D1=87=D0=BD=D1=8B=D0=B5=20=D1=81=D0=B8=D0=BC?= =?UTF-8?q?=D0=B2=D0=BE=D0=BB=D1=8B=20=D0=B2=D0=B0=D0=BB=D1=8E=D1=82=20(CN?= =?UTF-8?q?=C2=A5,=20HK$)=20=D0=B1=D0=B5=D0=B7=20=D0=BE=D0=B3=D1=80=D1=83?= =?UTF-8?q?=D0=B1=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BE=D1=81=D1=82=D0=B0?= =?UTF-8?q?=D0=BB=D1=8C=D0=BD=D1=8B=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit currencyDisplay: narrowSymbol вместо code — чинит CNY/HKD, но не превращает $/€/₽ в USD/EUR/RUB для всех остальных валют. --- frontend/src/lib/format.ts | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/frontend/src/lib/format.ts b/frontend/src/lib/format.ts index ed9035e..1763b2c 100644 --- a/frontend/src/lib/format.ts +++ b/frontend/src/lib/format.ts @@ -5,16 +5,32 @@ export function getIntlLocale(language: Language = getLanguage()): string { return language === "ru" ? "ru-RU" : "en-US"; } +/** Builds the currency formatter every money helper here shares, so amounts + * are labelled identically everywhere in the app. + * + * `currencyDisplay: "narrowSymbol"` avoids Intl's default "symbol" mode, + * which prefixes symbols it considers ambiguous with a region marker — CNY + * renders as "CN¥", HKD as "HK$" (both are in lib/currency.ts's supported + * list) — reading like a typo next to the amount. narrowSymbol drops that + * prefix (plain "¥", "$") while leaving every unambiguous currency (USD, + * EUR, RUB, ...) exactly as compact as plain "symbol" already renders them + * — unlike currencyDisplay: "code", which would fix CNY/HKD but turn every + * other currency's "$1,234"/"1 234 ₽" into "USD 1,234"/"1 234 RUB". */ +function createCurrencyFormatter(currency: string, maximumFractionDigits: number): Intl.NumberFormat { + return new Intl.NumberFormat(getIntlLocale(), { + style: "currency", + currency, + currencyDisplay: "narrowSymbol", + maximumFractionDigits, + }); +} + // `currency` defaults to the app's primary currency setting (Settings page) // — call sites only need to pass it explicitly when formatting a value known // to be in a *different* currency than that setting. export function formatCurrency(amount: number | string, currency: string = getCurrency()): string { const value = typeof amount === "string" ? Number(amount) : amount; - return new Intl.NumberFormat(getIntlLocale(), { - style: "currency", - currency, - maximumFractionDigits: 0, - }).format(value); + return createCurrencyFormatter(currency, 0).format(value); } /** Same currency formatting as formatCurrency, but scales decimal precision @@ -37,7 +53,7 @@ export function formatCryptoAmount(amount: number | string, currency: string = g // that — e.g. 0.000000006894 has 8 leading zeros, so this shows // 12 decimal places, landing exactly on "6894" and nothing more. Math.min(20, Math.max(0, -Math.floor(Math.log10(abs)) - 1) + 4); - return new Intl.NumberFormat(getIntlLocale(), { style: "currency", currency, maximumFractionDigits }).format(value); + return createCurrencyFormatter(currency, maximumFractionDigits).format(value); } export function formatSignedCurrency(amount: number | string, currency: string = getCurrency()): string {