mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-10-25 17:22:27 +08:00
fix(usage): drop the ≈ mark and cap decimals on converted amounts
Converted amounts no longer start with ≈; it took room and the USD value is still shown on hover. Since the rate is entered by hand, converted amounts now show at most two decimals (yen keeps two fewer), instead of the 4–6 used for USD in logs and hover text. An amount that would round to zero is written with two significant digits rather than as 0.00.
This commit is contained in:
1 parent
c6daf78cea
commit
9d83b2e872
7 files changed
+59
-33
No files matched your search
@@ -80,9 +80,21 @@ const CURRENCY_SYMBOL: Record<DisplayCurrency, string> = {
|
||||
TRY: "₺",
|
||||
};
|
||||
|
||||
/** 日元面额大(1 美元约 158 日元),同一处少显示两位小数:$0.0012 → ≈¥0.19 */
|
||||
/** 日元面额大(1 美元约 158 日元),同一处少显示两位小数:$0.0012 → ¥0.19 */
|
||||
const FEWER_DIGITS: Partial<Record<DisplayCurrency, number>> = { JPY: 2 };
|
||||
|
||||
/**
|
||||
* 换算出来的金额是按手填汇率估的,再多位数也不更准:最多两位小数(日元再少两位)。
|
||||
* 美元按调用方给的位数。
|
||||
*/
|
||||
function shownDigits(display: MoneyDisplay, digits: number): number {
|
||||
if (display.currency === "USD") return digits;
|
||||
return Math.max(
|
||||
0,
|
||||
Math.min(digits - (FEWER_DIGITS[display.currency] ?? 0), 2),
|
||||
);
|
||||
}
|
||||
|
||||
/** 整数部分加千位分隔:换成日元、里拉后动辄七位数 */
|
||||
function groupThousands(fixed: string): string {
|
||||
const [whole, frac] = fixed.split(".");
|
||||
@@ -108,13 +120,7 @@ export function resolveMoneyDisplay(
|
||||
};
|
||||
}
|
||||
|
||||
/** 换算出来的数只是按手填汇率估的,前面加「≈」;0 不加。 */
|
||||
function moneyPrefix(display: MoneyDisplay, value: number): string {
|
||||
const approx = display.currency !== "USD" && value !== 0 ? "≈" : "";
|
||||
return `${approx}${CURRENCY_SYMBOL[display.currency]}`;
|
||||
}
|
||||
|
||||
/** 固定小数位:`$1.23` / `≈¥8.36`。组件里用 `useMoneyFormat()`,别直接传 display。 */
|
||||
/** 固定小数位:`$1.23` / `¥8.36`。组件里用 `useMoneyFormat()`,别直接传 display。 */
|
||||
export function formatMoney(
|
||||
usd: unknown,
|
||||
digits: number,
|
||||
@@ -124,20 +130,26 @@ export function formatMoney(
|
||||
const num = parseFiniteNumber(usd);
|
||||
if (num == null) return fallback;
|
||||
const value = num * display.rate;
|
||||
const shown = Math.max(0, digits - (FEWER_DIGITS[display.currency] ?? 0));
|
||||
return `${moneyPrefix(display, value)}${groupThousands(value.toFixed(shown))}`;
|
||||
const symbol = CURRENCY_SYMBOL[display.currency];
|
||||
const shown = shownDigits(display, digits);
|
||||
const fixed = value.toFixed(shown);
|
||||
// 换算后位数少了,很小的一笔会四舍五入成 0:改写两位有效数字(¥0.0034),别像是免费
|
||||
if (shown < digits && value !== 0 && Number(fixed) === 0) {
|
||||
return `${symbol}${Number(value.toPrecision(2))}`;
|
||||
}
|
||||
return `${symbol}${groupThousands(fixed)}`;
|
||||
}
|
||||
|
||||
/** 按大小取位数并去掉末尾的 0:`$0.0081` / `$0.012` / `$1.25` */
|
||||
export function formatMoneyCompact(usd: number, display: MoneyDisplay): string {
|
||||
const value = usd * display.rate;
|
||||
const prefix = moneyPrefix(display, value);
|
||||
const prefix = CURRENCY_SYMBOL[display.currency];
|
||||
if (value < 0.01) return `${prefix}${Number(value.toFixed(4))}`;
|
||||
if (value < 1) return `${prefix}${Number(value.toFixed(3))}`;
|
||||
return `${prefix}${groupThousands(value.toFixed(2))}`;
|
||||
}
|
||||
|
||||
/** 侧栏这类窄处:100 以上不带小数(`≈¥280`),免得把旁边的标签挤成省略号。 */
|
||||
/** 侧栏这类窄处:100 以上不带小数(`¥280`),免得把旁边的标签挤成省略号。 */
|
||||
export function formatMoneyShort(usd: unknown, display: MoneyDisplay): string {
|
||||
const num = parseFiniteNumber(usd);
|
||||
if (num == null) return "--";
|
||||
@@ -146,7 +158,7 @@ export function formatMoneyShort(usd: unknown, display: MoneyDisplay): string {
|
||||
|
||||
/**
|
||||
* 坐标轴刻度:画图前就把数据换算成显示币种(刻度才会是 ¥50 这种整数),
|
||||
* 所以这里拿到的已是显示币种的值,只加符号;不定小数位,也不加「≈」。
|
||||
* 所以这里拿到的已是显示币种的值,只加符号,不定小数位。
|
||||
*/
|
||||
export function formatMoneyTick(value: unknown, display: MoneyDisplay): string {
|
||||
const num = parseFiniteNumber(value) ?? 0;
|
||||
|
||||
@@ -2380,7 +2380,7 @@
|
||||
"rate": "Exchange rate",
|
||||
"rateHint": "Entered by hand and used only for display; costs are still recorded in USD.",
|
||||
"rateInvalid": "Enter a number greater than 0",
|
||||
"approxNote": "Converted amounts are marked with ≈. Hover to see the USD value.",
|
||||
"approxNote": "Hover a converted amount to see the USD value.",
|
||||
"sidebarToday": "Sidebar \"Today\"",
|
||||
"sidebarTodayModes": {
|
||||
"cost": "Cost",
|
||||
|
||||
@@ -2380,7 +2380,7 @@
|
||||
"rate": "為替レート",
|
||||
"rateHint": "手動で入力し、表示の換算にのみ使います。費用は引き続き米ドルで記録されます。",
|
||||
"rateInvalid": "0 より大きい数値を入力してください",
|
||||
"approxNote": "換算した金額には「≈」が付きます。ホバーで米ドルの値を確認できます。",
|
||||
"approxNote": "換算した金額はホバーで米ドルの値を確認できます。",
|
||||
"sidebarToday": "サイドバーの「今日」",
|
||||
"sidebarTodayModes": {
|
||||
"cost": "金額",
|
||||
|
||||
@@ -2381,7 +2381,7 @@
|
||||
"rate": "匯率",
|
||||
"rateHint": "手動填寫,只用於換算顯示;費用仍以美元記錄。",
|
||||
"rateInvalid": "請輸入大於 0 的數字",
|
||||
"approxNote": "換算後的金額前面帶「≈」,滑鼠移上去可看美元原值。",
|
||||
"approxNote": "換算後的金額,滑鼠移上去可看美元原值。",
|
||||
"sidebarToday": "側欄「今日」",
|
||||
"sidebarTodayModes": {
|
||||
"cost": "金額",
|
||||
|
||||
@@ -2380,7 +2380,7 @@
|
||||
"rate": "汇率",
|
||||
"rateHint": "手动填写,只用于换算显示;费用仍按美元记录。",
|
||||
"rateInvalid": "请输入大于 0 的数字",
|
||||
"approxNote": "换算后的金额前面带「≈」,悬停可看美元原值。",
|
||||
"approxNote": "换算后的金额悬停可看美元原值。",
|
||||
"sidebarToday": "侧栏「今日」",
|
||||
"sidebarTodayModes": {
|
||||
"cost": "金额",
|
||||
|
||||
@@ -281,13 +281,13 @@ describe("display currency", () => {
|
||||
).toEqual({ currency: "CNY", rate: 7.25 });
|
||||
});
|
||||
|
||||
it("marks converted amounts with ≈ but leaves zero and axis ticks plain", () => {
|
||||
it("writes converted amounts with the plain currency symbol", () => {
|
||||
const cny = { currency: "CNY" as const, rate: 7 };
|
||||
expect(formatMoney(1.5, 2, cny)).toBe("≈¥10.50");
|
||||
expect(formatMoney(1.5, 2, cny)).toBe("¥10.50");
|
||||
expect(formatMoney(0, 2, cny)).toBe("¥0.00");
|
||||
expect(formatMoney(null, 2, cny)).toBe("--");
|
||||
expect(formatMoneyCompact(0.001, cny)).toBe("≈¥0.007");
|
||||
expect(formatMoneyCompact(0.0015, cny)).toBe("≈¥0.011");
|
||||
expect(formatMoneyCompact(0.001, cny)).toBe("¥0.007");
|
||||
expect(formatMoneyCompact(0.0015, cny)).toBe("¥0.011");
|
||||
// 刻度值画图前已换算,只加符号
|
||||
expect(formatMoneyTick(50, cny)).toBe("¥50");
|
||||
});
|
||||
@@ -295,30 +295,44 @@ describe("display currency", () => {
|
||||
it("writes each currency with its own symbol and groups thousands", () => {
|
||||
expect(formatMoney(30470.55, 2, USD_DISPLAY)).toBe("$30,470.55");
|
||||
expect(formatMoney(-1234, 0, USD_DISPLAY)).toBe("$-1,234");
|
||||
expect(formatMoney(2, 2, { currency: "EUR", rate: 0.87 })).toBe("≈€1.74");
|
||||
expect(formatMoney(2, 2, { currency: "TWD", rate: 32 })).toBe("≈NT$64.00");
|
||||
expect(formatMoney(2, 2, { currency: "EUR", rate: 0.87 })).toBe("€1.74");
|
||||
expect(formatMoney(2, 2, { currency: "TWD", rate: 32 })).toBe("NT$64.00");
|
||||
expect(formatMoney(100, 2, { currency: "TRY", rate: 48 })).toBe(
|
||||
"≈₺4,800.00",
|
||||
"₺4,800.00",
|
||||
);
|
||||
expect(formatMoneyTick(20000, { currency: "JPY", rate: 158 })).toBe(
|
||||
"¥20,000",
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps converted amounts to two decimals at most", () => {
|
||||
const twd = { currency: "TWD" as const, rate: 32 };
|
||||
// 悬停用的 6 位、日志用的 4 位,换算后都只留两位
|
||||
expect(formatMoney(64.912461, 6, twd)).toBe("NT$2,077.20");
|
||||
expect(formatMoney(0.0505, 4, twd)).toBe("NT$1.62");
|
||||
expect(formatMoney(1.5, 1, twd)).toBe("NT$48.0");
|
||||
// 小到四舍五入成 0 的一笔写两位有效数字,不写成 0.00
|
||||
expect(formatMoney(0.0001, 4, twd)).toBe("NT$0.0032");
|
||||
expect(formatMoney(0, 4, twd)).toBe("NT$0.00");
|
||||
// 美元照调用方给的位数
|
||||
expect(formatMoney(0.0001, 4, USD_DISPLAY)).toBe("$0.0001");
|
||||
expect(formatMoney(64.912461, 6, USD_DISPLAY)).toBe("$64.912461");
|
||||
});
|
||||
|
||||
it("shows yen with two fewer decimals", () => {
|
||||
const jpy = { currency: "JPY" as const, rate: 158 };
|
||||
expect(formatMoney(0.0012, 4, jpy)).toBe("≈¥0.19");
|
||||
expect(formatMoney(30470.55, 2, jpy)).toBe("≈¥4,814,347");
|
||||
expect(formatMoney(1.5, 1, jpy)).toBe("≈¥237");
|
||||
expect(formatMoneyShort(0.5, jpy)).toBe("≈¥79");
|
||||
expect(formatMoney(0.0012, 4, jpy)).toBe("¥0.19");
|
||||
expect(formatMoney(30470.55, 2, jpy)).toBe("¥4,814,347");
|
||||
expect(formatMoney(1.5, 1, jpy)).toBe("¥237");
|
||||
expect(formatMoneyShort(0.5, jpy)).toBe("¥79");
|
||||
});
|
||||
|
||||
it("drops the decimals in narrow spots once the amount reaches 100", () => {
|
||||
const cny = { currency: "CNY" as const, rate: 6.8 };
|
||||
expect(formatMoneyShort(41.2, USD_DISPLAY)).toBe("$41.20");
|
||||
expect(formatMoneyShort(123.456, USD_DISPLAY)).toBe("$123");
|
||||
expect(formatMoneyShort(41.2, cny)).toBe("≈¥280");
|
||||
expect(formatMoneyShort(14.5, cny)).toBe("≈¥98.60");
|
||||
expect(formatMoneyShort(41.2, cny)).toBe("¥280");
|
||||
expect(formatMoneyShort(14.5, cny)).toBe("¥98.60");
|
||||
expect(formatMoneyShort(undefined, cny)).toBe("--");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -31,9 +31,9 @@ describe("useMoneyFormat", () => {
|
||||
});
|
||||
expect(fmt.currency).toBe("CNY");
|
||||
expect(fmt.rate).toBe(7);
|
||||
expect(fmt.money(1.5, 2)).toBe("≈¥10.50");
|
||||
expect(fmt.moneyShort(41.2)).toBe("≈¥288");
|
||||
expect(fmt.money(1.5, 2)).toBe("¥10.50");
|
||||
expect(fmt.moneyShort(41.2)).toBe("¥288");
|
||||
expect(fmt.usdHint(1.5)).toBe("$1.500000");
|
||||
expect(fmt.moneyTitle(1.5, 6)).toBe("≈¥10.500000 ($1.500000)");
|
||||
expect(fmt.moneyTitle(1.5, 6)).toBe("¥10.50 ($1.500000)");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user