style: translate all units, currencies, and scale symbols into Persian across entire app

This commit is contained in:
alireza 2026-08-26 14:37:39 +03:30
parent 984d44a18b
commit 23898868ec
3 changed files with 93 additions and 33 deletions

View File

@ -512,9 +512,9 @@ export function LmeView() {
<div className="flex items-center justify-between border-b border-border/50 pb-3">
<div className="flex items-center gap-2">
<DollarSign className="w-4 h-4 text-primary" />
<h3 className="text-sm font-semibold text-foreground">نرخهای برابری ارز (Exchange Rates)</h3>
<h3 className="text-sm font-semibold text-foreground">نرخهای برابری ارز</h3>
</div>
<span className="text-[11px] text-muted-foreground font-mono">EUR / USD</span>
<span className="text-[11px] text-muted-foreground">یورو / دلار</span>
</div>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3 pt-1">

View File

@ -163,3 +163,64 @@ export function commodityLabel(symbol: string, fallback: string): string {
export function commodityGroup(symbol: string): CommodityGroup {
return COMMODITIES[symbol]?.group ?? "metals";
}
export const COMMODITY_UNITS: Record<string, string> = {
"CL=F": "دلار / بشکه",
"BZ=F": "دلار / بشکه",
"NG=F": "دلار / MMBtu",
"RB=F": "دلار / گالن",
"HO=F": "دلار / گالن",
"GC=F": "دلار / اونس",
"SI=F": "دلار / اونس",
"HG=F": "دلار / پوند",
"PL=F": "دلار / اونس",
"PA=F": "دلار / اونس",
"ALI=F": "دلار / تن",
"NI=F": "دلار / تن",
"ZC=F": "سنت / بوشل",
"ZW=F": "سنت / بوشل",
"ZS=F": "سنت / بوشل",
"KC=F": "سنت / پوند",
"CT=F": "سنت / پوند",
"CC=F": "دلار / تن",
"SB=F": "سنت / پوند",
"OJ=F": "سنت / پوند",
};
export function faUnit(u: string | null | undefined): string {
if (!u) return "";
const clean = u.trim();
const map: Record<string, string> = {
"USD": "دلار",
"$": "دلار",
"USc": "سنت",
"cents": "سنت",
"IRR": "ریال",
"Rial": "ریال",
"EUR": "یورو",
"GBP": "پوند",
"RMB": "یوان",
"CNY": "یوان",
"JPY": "ین",
"CAD": "دلار کانادا",
"AUD": "دلار استرالیا",
"TRY": "لیر",
"INR": "روپیه",
"BRL": "رئال",
"USD/mt": "دلار / تن",
"USD/dmt": "دلار / تن خشک",
"RMB/mt": "یوان / تن",
"USD/bbl": "دلار / بشکه",
"USD/oz": "دلار / اونس",
"USD/t.oz": "دلار / اونس",
"USD/lb": "دلار / پوند",
"c/lb": "سنت / پوند",
"USD/bu": "دلار / بوشل",
"c/bu": "سنت / بوشل",
"T$": "تریلیون دلار",
"B$": "میلیارد دلار",
"M$": "میلیون دلار",
"bps": "نقطه پایه",
};
return map[clean] ?? clean;
}

View File

@ -70,6 +70,8 @@ import {
commodityGroup,
COMMODITY_GROUP_LABELS,
COMMODITY_GROUP_ORDER,
COMMODITY_UNITS,
faUnit,
} from "@/lib/labels";
import { cn } from "@/lib/utils";
import { API_BASE } from "@/lib/api";
@ -1203,7 +1205,7 @@ function CommodityView() {
return chosen.map((r) => ({
label: commodityLabel(r.symbol, r.name),
value: r.price,
unit: r.currency,
unit: COMMODITY_UNITS[r.symbol] || faUnit(r.currency),
pct: r.pct,
dir: (r.up ? "up" : "down") as "up" | "down",
series: series[r.symbol],
@ -1295,7 +1297,7 @@ function CommodityCard({
value={row.price}
format={(n) => faDigits(n.toLocaleString("en-US", { maximumFractionDigits: 2 }))}
/>
<span className="mr-1 text-[10px] font-normal text-muted-foreground">{row.currency}</span>
<span className="mr-1 text-[10px] font-normal text-muted-foreground">{COMMODITY_UNITS[row.symbol] || faUnit(row.currency)}</span>
</div>
{series && series.length > 1 ? (
<div className="-mx-4 mt-2">
@ -1444,17 +1446,18 @@ function SteelStocksView() {
const kpis: KpiItem[] = rows.slice(0, 3).map((r) => ({
label: r.name,
value: r.price || 0,
unit: "$",
unit: "دلار",
pct: r.today_pct ?? undefined,
dir: r.dir,
series: series[r.code],
decimals: 2,
}));
const capStr = (v: number | null) =>
v == null
? "—"
: faDigits("$" + (v >= 1000 ? (v / 1000).toFixed(2) + "T" : v.toFixed(2) + "B"));
const capStr = (v: number | null) => {
if (v == null) return "—";
if (v >= 1000) return `${faDigits((v / 1000).toFixed(2))} تریلیون دلار`;
return `${faDigits(v.toFixed(2))} میلیارد دلار`;
};
const pctText = (r: SteelRow) =>
r.today_pct != null
? faDigits(
@ -1475,11 +1478,11 @@ function SteelStocksView() {
<ViewHeader
live={rows.length > 0}
title="قیمت سهام شرکت‌ها"
subtitle={faDigits(
`${rows.length} شرکت فولادی · مجموع ارزش بازار ${
totalCap >= 1000 ? (totalCap / 1000).toFixed(2) + "T$" : totalCap.toFixed(0) + "B$"
}`,
)}
subtitle={
`${faDigits(rows.length)} شرکت فولادی · مجموع ارزش بازار ${
totalCap >= 1000 ? faDigits((totalCap / 1000).toFixed(2)) + " تریلیون دلار" : faDigits(totalCap.toFixed(0)) + " میلیارد دلار"
}`
}
lastUpdate={lastUpdate}
/>
@ -1550,7 +1553,7 @@ function SteelStocksView() {
<div className="flex flex-col">
<span className="text-[10px] text-muted-foreground">قیمت</span>
<span className="font-mono text-sm font-bold tabular-nums text-foreground">
{r.price != null ? "$" + usd(r.price) : "—"}
{r.price != null ? usd(r.price) + " دلار" : "—"}
</span>
</div>
<div className="flex flex-col">
@ -1585,7 +1588,7 @@ function SteelStocksView() {
{capStr(r.market_cap)}
</div>
<div className="col-span-2 text-left font-mono tabular-nums text-foreground whitespace-nowrap">
{r.price != null ? "$" + usd(r.price) : "—"}
{r.price != null ? usd(r.price) + " دلار" : "—"}
</div>
<div className="col-span-2 flex items-center justify-center gap-2">
<SteelBar pct={r.today_pct} dir={r.dir} />
@ -2404,21 +2407,17 @@ type CryptoPayload = {
const cryptoPrice = (p: number | null) => {
if (p == null || !Number.isFinite(p)) return "—";
if (p >= 1)
return faDigits(
"$" + p.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 }),
);
return faDigits(p.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })) + " دلار";
if (p >= 0.01)
return faDigits(
"$" + p.toLocaleString("en-US", { minimumFractionDigits: 4, maximumFractionDigits: 4 }),
);
return faDigits("$" + p.toLocaleString("en-US", { maximumFractionDigits: 8 }));
return faDigits(p.toLocaleString("en-US", { minimumFractionDigits: 4, maximumFractionDigits: 4 })) + " دلار";
return faDigits(p.toLocaleString("en-US", { maximumFractionDigits: 8 })) + " دلار";
};
const usdAbbr = (n: number | null) => {
if (n == null || !Number.isFinite(n)) return "—";
if (n >= 1e12) return faDigits("$" + (n / 1e12).toFixed(2) + "T");
if (n >= 1e9) return faDigits("$" + (n / 1e9).toFixed(2) + "B");
if (n >= 1e6) return faDigits("$" + (n / 1e6).toFixed(2) + "M");
return faDigits("$" + n.toLocaleString("en-US", { maximumFractionDigits: 0 }));
if (n >= 1e12) return faDigits((n / 1e12).toFixed(2)) + " تریلیون دلار";
if (n >= 1e9) return faDigits((n / 1e9).toFixed(2)) + " میلیارد دلار";
if (n >= 1e6) return faDigits((n / 1e6).toFixed(2)) + " میلیون دلار";
return faDigits(n.toLocaleString("en-US", { maximumFractionDigits: 0 })) + " دلار";
};
const pctClass = (p: number | null) =>
p == null ? "text-muted-foreground" : p >= 0 ? "text-emerald-700" : "text-red-600";
@ -2751,7 +2750,7 @@ function WorldEconomyView() {
const fmtCell = (col: (typeof WE_COLS)[number], val: number | null) => {
if (val == null) return "—";
if (col.gdp) return faDigits((val / 1000).toFixed(2) + " T$");
if (col.gdp) return faDigits((val / 1000).toFixed(2) + " تریلیون دلار");
return faDigits(formatNumber(val) + (col.suffix || ""));
};
@ -2860,7 +2859,7 @@ function WorldEconomyView() {
{us10y && <DeltaPill pct={us10y.change} dir={us10y.change >= 0 ? "up" : "down"} />}
</div>
<div className="mt-1 text-[11px] text-muted-foreground font-mono">
تغییر: {us10y ? faDigits((us10y.change_abs >= 0 ? "+" : "") + (us10y.change_abs * 100).toFixed(1) + " bps") : "—"}
تغییر: {us10y ? faDigits((us10y.change_abs >= 0 ? "+" : "") + (us10y.change_abs * 100).toFixed(1) + " نقطه پایه") : "—"}
</div>
</div>
@ -2876,7 +2875,7 @@ function WorldEconomyView() {
{de10y && <DeltaPill pct={de10y.change} dir={de10y.change >= 0 ? "up" : "down"} />}
</div>
<div className="mt-1 text-[11px] text-muted-foreground font-mono">
تغییر: {de10y ? faDigits((de10y.change_abs >= 0 ? "+" : "") + (de10y.change_abs * 100).toFixed(1) + " bps") : "—"}
تغییر: {de10y ? faDigits((de10y.change_abs >= 0 ? "+" : "") + (de10y.change_abs * 100).toFixed(1) + " نقطه پایه") : "—"}
</div>
</div>
@ -2892,7 +2891,7 @@ function WorldEconomyView() {
{gb10y && <DeltaPill pct={gb10y.change} dir={gb10y.change >= 0 ? "up" : "down"} />}
</div>
<div className="mt-1 text-[11px] text-muted-foreground font-mono">
تغییر: {gb10y ? faDigits((gb10y.change_abs >= 0 ? "+" : "") + (gb10y.change_abs * 100).toFixed(1) + " bps") : "—"}
تغییر: {gb10y ? faDigits((gb10y.change_abs >= 0 ? "+" : "") + (gb10y.change_abs * 100).toFixed(1) + " نقطه پایه") : "—"}
</div>
</div>
@ -2903,7 +2902,7 @@ function WorldEconomyView() {
</div>
<div className="mt-2 flex items-baseline justify-between">
<span className={cn("text-2xl font-bold font-mono tabular-nums", (spread10_2 ?? 0) >= 0 ? "text-emerald-700" : "text-red-600")}>
{spread10_2 != null ? faDigits((spread10_2 >= 0 ? "+" : "") + (spread10_2 * 100).toFixed(1) + " bps") : "—"}
{spread10_2 != null ? faDigits((spread10_2 >= 0 ? "+" : "") + (spread10_2 * 100).toFixed(1) + " نقطه پایه") : "—"}
</span>
<span className="text-[11px] px-2 py-0.5 rounded-full font-mono font-medium bg-muted text-muted-foreground">
{(spread10_2 ?? 0) >= 0 ? "نرمال" : "وارونه (Inverted)"}
@ -2924,7 +2923,7 @@ function WorldEconomyView() {
<th className="p-3 font-semibold text-center">سررسید</th>
<th className="p-3 font-semibold text-center">نرخ بازدهی (Yield)</th>
<th className="p-3 font-semibold text-center">تغییر روزانه</th>
<th className="p-3 font-semibold text-center">تغییر پایه (bps)</th>
<th className="p-3 font-semibold text-center">تغییر (نقطه پایه)</th>
<th className="p-3 font-semibold text-center">کف / سقف امروز</th>
<th className="p-3 font-semibold text-center">بازدهی هفتگی</th>
<th className="p-3 font-semibold text-center">بازدهی ۱ ماهه</th>