From 7b4bc7b2a12443a5574f8883d9e8c5018a5cb0b9 Mon Sep 17 00:00:00 2001 From: stqfdyr <89149493+stqfdyr@users.noreply.github.com> Date: Fri, 2 Oct 2026 18:19:49 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20=E4=BB=B7=E6=A0=BC=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E6=8C=89=E4=B8=AD=E6=96=87=E5=86=99=E6=B3=95=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=EF=BC=8C=E8=B4=A7=E5=B8=81=E7=AC=A6=E5=8F=B7=E6=88=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E4=B8=80=E5=BE=8B=E5=9C=A8=E6=95=B0=E5=AD=97=E5=89=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/format.test.ts | 12 ++++++++++-- src/lib/format.ts | 22 ++++++++++++++++++++-- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/lib/format.test.ts b/src/lib/format.test.ts index 0392f0c..3bc0e2a 100644 --- a/src/lib/format.test.ts +++ b/src/lib/format.test.ts @@ -4,8 +4,8 @@ // // Nothing imports it, so the bundle never includes it. import { - axisBytes, axisTop, bytes, cpuName, cycle, daysUntil, despike, osName, pair, percent, quarters, RATE_FLOOR, - rateAxis, tickClock, timeTicks, uptime, windows, withGaps, + axisBytes, axisTop, bytes, cpuName, cycle, daysUntil, despike, money, osName, pair, percent, quarters, + RATE_FLOOR, rateAxis, tickClock, timeTicks, uptime, windows, withGaps, } from "./format.ts" let failed = 0 @@ -173,6 +173,14 @@ eq(uptime(2 * 86400 + 5 * 3600), "2 天 5 小时", "超过一天不再写分钟" // 旧 hub 存名称,新 hub 把其余长度存成 `m`;两种写法同一个长度读法一致。 eq(["yearly", "12m", "60m", "18m", "once", "weekly"].map(cycle), ["年付", "年付", "5 年付", "18 个月付", "一次性", "weekly"], "付款周期") +// money: zh-CN whatever the browser's language, so US$ stands apart from HK$ +// and JP¥ from ¥; a code Intl refuses is shown rather than thrown. +eq(money(100, "CNY"), "¥100.00", "人民币") +eq(money(100, "USD"), "US$100.00", "美元与港币等其它元区分开") +eq(money(100, "HKD"), "HK$100.00", "港币同样符号在前") +eq(money(1200, "JPY"), "JP¥1,200", "日元不带小数,与人民币分开") +eq(money(100, "港币"), "港币 100.00", "旧 hub 存下的非法代码不抛错") + eq(osName("Debian GNU/Linux 12 (bookworm)"), "Debian 12", "发行版名去掉代号") eq(cpuName("Intel(R) Xeon(R) CPU E5-2680 8-Core Processor"), "Intel Xeon E5-2680", "CPU 名去掉商标和核数") diff --git a/src/lib/format.ts b/src/lib/format.ts index 9c2a4a0..64270de 100644 --- a/src/lib/format.ts +++ b/src/lib/format.ts @@ -78,10 +78,28 @@ export function daysUntil(date?: string | null): number | null { */ export const FOREVER = "∞" -const SYMBOLS: Record = { USD: "$", CNY: "¥", EUR: "€", GBP: "£", JPY: "¥" } +const MONEY = new Map() +/** + * A price as zh-CN writes it: ¥12.00, US$12.00, HK$12.00, JP¥1,200, and the code + * ahead of the amount where the locale has no symbol, as in SGD 12.00. The + * locale is fixed so the figure does not vary with the browser's language, and + * so JPY reads JP¥, apart from CNY. A formatter costs about 100 µs to build, + * hence one per currency. + */ export function money(amount: number, currency: string): string { - return `${SYMBOLS[currency] ?? ""}${amount.toFixed(2)}${SYMBOLS[currency] ? "" : ` ${currency}`}` + try { + let format = MONEY.get(currency) + if (!format) { + format = new Intl.NumberFormat("zh-CN", { style: "currency", currency }) + MONEY.set(currency, format) + } + return format.format(amount) + } catch { + // Intl throws on anything but three letters, which hubs before 1.3.1 stored + // unchecked when written through the API. + return `${currency} ${amount.toFixed(2)}`.trim() + } } // Hub 1.3.0 and earlier store only these names; later hubs store any other From 8406300f720065009d2b918489a6db4907c7b35f Mon Sep 17 00:00:00 2001 From: stqfdyr <89149493+stqfdyr@users.noreply.github.com> Date: Fri, 2 Oct 2026 18:35:41 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20=E4=BB=B7=E6=A0=BC=E6=9C=80=E5=A4=9A?= =?UTF-8?q?=E4=B8=A4=E4=BD=8D=E5=B0=8F=E6=95=B0=EF=BC=8C=E6=97=A5=E5=85=83?= =?UTF-8?q?=E7=AD=89=E6=97=A0=E5=B0=8F=E6=95=B0=E4=BD=8D=E7=9A=84=E8=B4=A7?= =?UTF-8?q?=E5=B8=81=E5=A1=AB=E4=BA=86=E5=B0=8F=E6=95=B0=E4=B9=9F=E7=85=A7?= =?UTF-8?q?=E5=AE=9E=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/format.test.ts | 1 + src/lib/format.ts | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/lib/format.test.ts b/src/lib/format.test.ts index 3bc0e2a..10f1b89 100644 --- a/src/lib/format.test.ts +++ b/src/lib/format.test.ts @@ -179,6 +179,7 @@ eq(money(100, "CNY"), "¥100.00", "人民币") eq(money(100, "USD"), "US$100.00", "美元与港币等其它元区分开") eq(money(100, "HKD"), "HK$100.00", "港币同样符号在前") eq(money(1200, "JPY"), "JP¥1,200", "日元不带小数,与人民币分开") +eq(money(1200.5, "JPY"), "JP¥1,200.5", "日元填了小数照实显示,不取整") eq(money(100, "港币"), "港币 100.00", "旧 hub 存下的非法代码不抛错") eq(osName("Debian GNU/Linux 12 (bookworm)"), "Debian 12", "发行版名去掉代号") diff --git a/src/lib/format.ts b/src/lib/format.ts index 64270de..dc40680 100644 --- a/src/lib/format.ts +++ b/src/lib/format.ts @@ -84,14 +84,16 @@ const MONEY = new Map() * A price as zh-CN writes it: ¥12.00, US$12.00, HK$12.00, JP¥1,200, and the code * ahead of the amount where the locale has no symbol, as in SGD 12.00. The * locale is fixed so the figure does not vary with the browser's language, and - * so JPY reads JP¥, apart from CNY. A formatter costs about 100 µs to build, - * hence one per currency. + * so JPY reads JP¥, apart from CNY. Fractions stop at two places, the precision + * the price is entered in, not at the currency's minor unit: rounding to whole + * yen would show a price of 0.4 as JP¥0. A formatter costs about 100 µs to + * build, hence one per currency. */ export function money(amount: number, currency: string): string { try { let format = MONEY.get(currency) if (!format) { - format = new Intl.NumberFormat("zh-CN", { style: "currency", currency }) + format = new Intl.NumberFormat("zh-CN", { style: "currency", currency, maximumFractionDigits: 2 }) MONEY.set(currency, format) } return format.format(amount)