From e52498ea8a6df4a0bac82fcd978906d36b6bcaf4 Mon Sep 17 00:00:00 2001 From: skloxo Date: Wed, 24 Jun 2026 10:30:19 +0800 Subject: [PATCH 1/8] feat(i18n): reactive Chinese (zh-CN) localization for all main pages --- .../components/charts/CandlestickChart.tsx | 3 +- .../components/charts/CorrelationMatrix.tsx | 3 +- .../src/components/charts/EquityChart.tsx | 3 +- .../src/components/charts/ValidationPanel.tsx | 52 ++-- .../components/chat/MandateProposalCard.tsx | 6 +- .../src/components/chat/PineScriptViewer.tsx | 2 +- frontend/src/components/chat/RunnerStatus.tsx | 8 +- .../src/components/chat/WelcomeScreen.tsx | 180 ++++++------- frontend/src/components/layout/Layout.tsx | 6 +- frontend/src/i18n/locales/en.json | 174 ++++++++++-- frontend/src/i18n/locales/zh-CN.json | 199 ++++++++++++-- frontend/src/pages/Agent.tsx | 51 ++-- frontend/src/pages/AlphaZoo.tsx | 250 ++++++++++-------- frontend/src/pages/Correlation.tsx | 10 +- frontend/src/pages/RunDetail.tsx | 6 +- 15 files changed, 631 insertions(+), 322 deletions(-) diff --git a/frontend/src/components/charts/CandlestickChart.tsx b/frontend/src/components/charts/CandlestickChart.tsx index 2a67a58e0..ee1c43a57 100644 --- a/frontend/src/components/charts/CandlestickChart.tsx +++ b/frontend/src/components/charts/CandlestickChart.tsx @@ -1,4 +1,5 @@ import { useEffect, useRef, useState, useMemo, useCallback } from "react"; +import i18n from "@/i18n"; import { cn } from "@/lib/utils"; import { ChevronDown } from "lucide-react"; import type { PriceBar, TradeMarker, IndicatorPoint } from "@/lib/api"; @@ -255,7 +256,7 @@ export function CandlestickChart({ data, markers, indicators, height = 500 }: Pr }, [data, markers, baseData, indicatorCache, extraIndicators, sub, range, overlays, dark]); if (data.length === 0) { - return
No price data
; + return
{i18n.t("charts.noPriceData")}
; } return ( diff --git a/frontend/src/components/charts/CorrelationMatrix.tsx b/frontend/src/components/charts/CorrelationMatrix.tsx index cdd0d9c38..6e77b54a3 100644 --- a/frontend/src/components/charts/CorrelationMatrix.tsx +++ b/frontend/src/components/charts/CorrelationMatrix.tsx @@ -1,4 +1,5 @@ import { useEffect, useRef } from "react"; +import i18n from "@/i18n"; import { echarts } from "@/lib/echarts"; import { getChartTheme } from "@/lib/chart-theme"; @@ -102,7 +103,7 @@ export function CorrelationMatrix({ labels, matrix, height = 500 }: Props) { }, [labels, matrix]); if (labels.length === 0) { - return
No correlation data
; + return
{i18n.t("charts.noCorrelationData")}
; } return
; } \ No newline at end of file diff --git a/frontend/src/components/charts/EquityChart.tsx b/frontend/src/components/charts/EquityChart.tsx index 6d05ae606..0bb5774c6 100644 --- a/frontend/src/components/charts/EquityChart.tsx +++ b/frontend/src/components/charts/EquityChart.tsx @@ -1,4 +1,5 @@ import { useEffect, useRef } from "react"; +import i18n from "@/i18n"; import type { EquityPoint } from "@/lib/api"; import { getChartTheme } from "@/lib/chart-theme"; import { abbreviateNum } from "@/lib/formatters"; @@ -106,7 +107,7 @@ export function EquityChart({ data, height = 300 }: Props) { }, [data, dark]); if (data.length === 0) { - return
No equity data
; + return
{i18n.t("charts.noEquityData")}
; } return
; } diff --git a/frontend/src/components/charts/ValidationPanel.tsx b/frontend/src/components/charts/ValidationPanel.tsx index 3148c0c94..a36e6b01a 100644 --- a/frontend/src/components/charts/ValidationPanel.tsx +++ b/frontend/src/components/charts/ValidationPanel.tsx @@ -41,17 +41,17 @@ function MonteCarloSection({ mc }: { mc: NonNullable
-

Monte Carlo Permutation Test

+

{i18n.t("validation.monteCarlo")}

- Shuffled trade order {mc.n_simulations.toLocaleString()} times to test if Sharpe is better than random. + {i18n.t("validation.monteCarloDesc", { n: mc.n_simulations.toLocaleString() })}

- - = 0.05"} /> - - + + = 0.05"} /> + +
{/* Visual: where actual falls in distribution */}
@@ -75,17 +75,17 @@ function BootstrapSection({ bs }: { bs: NonNullable return (
-

Bootstrap Sharpe CI

- 0" : "CI includes 0"} good={reliable} /> +

{i18n.t("validation.bootstrap")}

+

- Resampled daily returns {bs.n_bootstrap.toLocaleString()} times to estimate {(bs.confidence * 100).toFixed(0)}% confidence interval. + {i18n.t("validation.bootstrapDesc", { n: bs.n_bootstrap.toLocaleString(), pct: (bs.confidence * 100).toFixed(0) + "%" })}

- - - - + + + +
{/* CI bar */}
@@ -108,29 +108,29 @@ function WalkForwardSection({ wf }: { wf: NonNullable
-

Walk-Forward Analysis

- = 0.5 ? null : false} /> +

{i18n.t("validation.walkForward")}

+ = 0.5 ? null : false} />

- Split into {wf.n_windows} sequential windows to check performance consistency. + {i18n.t("validation.walkForwardDesc", { n: wf.n_windows })}

- - - - + + + +
{/* Per-window table */} - - - - - - + + + + + + diff --git a/frontend/src/components/chat/MandateProposalCard.tsx b/frontend/src/components/chat/MandateProposalCard.tsx index 3f17231ae..160fbdafc 100644 --- a/frontend/src/components/chat/MandateProposalCard.tsx +++ b/frontend/src/components/chat/MandateProposalCard.tsx @@ -90,10 +90,10 @@ function ProfileTile({ onClick={onAdjustToggle} disabled={disabled} className="inline-flex items-center gap-1 rounded-lg border px-2 py-0.5 text-[11px] font-medium text-muted-foreground transition-colors hover:text-foreground disabled:opacity-40" - title="Adjust this mandate" + title={i18n.t("mandate.adjustTitle")} > - Adjust + {i18n.t("mandate.adjust")} @@ -139,7 +139,7 @@ function ProfileTile({ onAdjustCancel(); } }} - placeholder="e.g. keep this but raise the daily cap to 10" + placeholder={i18n.t("mandate.adjustPlaceholder")} className="w-full rounded-lg border bg-background px-3 py-1.5 text-xs text-foreground outline-none focus:ring-2 focus:ring-primary/30" />
diff --git a/frontend/src/components/chat/PineScriptViewer.tsx b/frontend/src/components/chat/PineScriptViewer.tsx index 4c977dffb..c5369fb22 100644 --- a/frontend/src/components/chat/PineScriptViewer.tsx +++ b/frontend/src/components/chat/PineScriptViewer.tsx @@ -56,7 +56,7 @@ export const PineScriptViewer = memo(function PineScriptViewer({ code, onClose } className="inline-flex items-center gap-1 px-2 py-1.5 rounded-lg text-xs text-muted-foreground hover:text-foreground transition-colors" > - Docs + {t("pineViewer.docs")}
{/* Capability chips */}
- {CAPABILITY_CHIPS.map((chip) => ( + {CAPABILITY_CHIP_KEYS.map((key) => ( - {chip} + {t(`welcome.capabilities.${key}`)} ))}
@@ -210,23 +210,23 @@ export function WelcomeScreen({ onExample }: Props) {

{t('welcome.tryExample')}

{CATEGORIES.map((cat) => ( -
+
c.startsWith("text-")).join(" ")}`}> {cat.icon} - {cat.label} + {t(cat.labelKey)}
{cat.examples.map((ex) => ( ))} diff --git a/frontend/src/components/layout/Layout.tsx b/frontend/src/components/layout/Layout.tsx index ef16d7232..3368a5564 100644 --- a/frontend/src/components/layout/Layout.tsx +++ b/frontend/src/components/layout/Layout.tsx @@ -9,7 +9,7 @@ import { useAgentStore } from "@/stores/agent"; import { ConnectionBanner } from "@/components/layout/ConnectionBanner"; // Bump on each release; one place keeps the footer in sync with package.json. -const APP_VERSION = "v0.1.10"; +const APP_VERSION = "v0.1.10-s1"; export function Layout() { const { t, i18n: i18nHook } = useTranslation(); @@ -117,7 +117,7 @@ export function Layout() {
- Sessions + {t('layout.sessions')} {dark ? : } - {dark ? "Light" : "Dark"} + {dark ? t('layout.light') : t('layout.dark')}
@@ -1308,7 +1309,7 @@ export function Agent() { onClick={() => setGoalDetailsOpen((open) => !open)} className="inline-flex max-w-full items-center gap-1.5 justify-self-start rounded-lg bg-primary/10 px-2.5 py-1 text-left text-xs font-medium text-primary transition-colors hover:bg-primary/15" title={goalSnapshot.goal.objective} - aria-label="Active research goal" + aria-label={t("agent.activeResearchGoal")} aria-expanded={goalDetailsOpen} > @@ -1322,8 +1323,8 @@ export function Agent() { )} {goalProgress.evidenceTotal > 0 && ( - - {goalProgress.evidenceTotal} evidence + + {t("agent.evidenceCount", { count: goalProgress.evidenceTotal })} )} - Cancel + {t("agent.cancel")}
@@ -1372,7 +1373,7 @@ export function Agent() {
- Criteria + {t("agent.criteria")}
{goalProgress.label || "0/0"} @@ -1380,7 +1381,7 @@ export function Agent() {
- Evidence + {t("agent.evidence")}
{goalProgress.evidenceTotal} @@ -1417,7 +1418,7 @@ export function Agent() { {goalSnapshot.evidence.length > 0 && (
- Recent Evidence + {t("agent.recentEvidence")}
{latestGoalEvidence(goalSnapshot).map((item) => (
@@ -1440,7 +1441,7 @@ export function Agent() { className="inline-flex items-center gap-1 rounded-lg border px-2 py-1 text-[11px] font-medium text-muted-foreground transition-colors hover:text-foreground disabled:opacity-40" > - Continue + {t("agent.continue")}
@@ -1488,7 +1489,7 @@ export function Agent() { {uploading && (
- Uploading... + {t("agent.uploading")}
)} {/* Persistent kill switch — distinct from the per-turn Stop button @@ -1498,7 +1499,7 @@ export function Agent() { {liveIsHalted ? ( - Connector runtime halted + {t("agent.connectorHalted")} ) : ( )}
@@ -1522,7 +1523,7 @@ export function Agent() { onClick={() => setShowUploadMenu(prev => !prev)} disabled={status === "streaming" || uploading} className="w-9 h-9 rounded-full border flex items-center justify-center text-muted-foreground hover:text-foreground hover:bg-muted transition-colors disabled:opacity-40 shrink-0" - title="More options" + title={t("agent.moreOptions")} > @@ -1534,7 +1535,7 @@ export function Agent() { className="w-full px-3 py-2 text-left text-sm hover:bg-muted transition-colors flex items-center gap-2" > - Upload PDF document + {t("agent.uploadPdf")}
)} @@ -1630,8 +1631,8 @@ export function Agent() { }} placeholder={ goalComposerActive - ? "Describe the research goal to attach to this session" - : "e.g. Create a dual MA crossover strategy for 000001.SZ, backtest 2024" + ? t("agent.describeGoal") + : t("agent.placeholder") } className="flex-1 px-4 py-2.5 rounded-xl border bg-background text-sm focus:outline-none focus:ring-2 focus:ring-primary/40 transition-shadow resize-none max-h-32 overflow-y-auto" disabled={status === "streaming"} diff --git a/frontend/src/pages/AlphaZoo.tsx b/frontend/src/pages/AlphaZoo.tsx index 8ccf9eed6..2ceaa8462 100644 --- a/frontend/src/pages/AlphaZoo.tsx +++ b/frontend/src/pages/AlphaZoo.tsx @@ -212,7 +212,7 @@ function BrowseView() { {/* Hero */}
-

{loading @@ -220,10 +220,7 @@ function BrowseView() { : i18n.t("alphaZoo.prebuiltAlpha", { count: total })}

- Browse formula-driven cross-sectional signals from Qlib, the - Kakushadze 101 set, GTJA 191, and the academic anomaly literature. - Click any alpha to read its formula and source code, or run a bench - to score the whole zoo on a universe and period. + {i18n.t("alphaZoo.browseDesc")}

@@ -249,9 +246,9 @@ function BrowseView() { {z.approxCount}
-

{z.title}

+

{i18n.t("alphaZoo.zooCardTitle." + z.id)}

- {z.description} + {i18n.t("alphaZoo.zooCardDesc." + z.id)}

); @@ -262,7 +259,7 @@ function BrowseView() {
- +
@@ -336,16 +333,16 @@ function BrowseView() { -
@@ -353,27 +350,27 @@ function BrowseView() { {/* TODO(v0.2): switch to react-window if alpha count exceeds 5000 */}
-
#PeriodReturnSharpeMax DDTradesWin Rate{i18n.t("validation.period2")}{i18n.t("validation.return")}{i18n.t("reports.sharpe")}{i18n.t("validation.maxDd")}{i18n.t("runDetail.trades")}{i18n.t("validation.winRate")}
- +
Alpha catalogue
+ - @@ -382,13 +379,13 @@ function BrowseView() { ) : visible.length === 0 ? ( ) : ( @@ -424,10 +421,10 @@ function BrowseView() {
{i18n.t("alphaZoo.alphaCatalogue")}
- Select for compare + {i18n.t("alphaZoo.selectForCompare")} - ID + {i18n.t("alphaZoo.id")} - Zoo + {i18n.t("alphaZoo.zoo")} - Theme + {i18n.t("alphaZoo.theme")} - Universe + {i18n.t("alphaZoo.universe")} - Decay (days) + + {i18n.t("alphaZoo.decayDays")}
- No alphas match the current filters. + {i18n.t("alphaZoo.noAlphasMatch")}
{a.zoo} - {(a.theme || []).join(", ") || "—"} + {(a.theme || []).map((t) => i18n.t("alphaZoo.themes." + t, { defaultValue: t })).join(", ") || "—"} - {(a.universe || []).join(", ") || "—"} + {(a.universe || []).map((u) => i18n.t("alphaZoo.universeOption." + u, { defaultValue: u })).join(", ") || "—"} {a.decay_horizon ?? "—"} @@ -441,14 +438,14 @@ function BrowseView() { {!loading && visible.length < filtered.length && (
- Showing {visible.length} of {filtered.length} + {i18n.t("alphaZoo.showingOf", { visible: visible.length, total: filtered.length })}
)} @@ -494,7 +491,7 @@ function DetailView({ alphaId }: DetailProps) { if (loading) { return (
-
); } @@ -503,13 +500,13 @@ function DetailView({ alphaId }: DetailProps) { return (
-
); @@ -534,14 +531,14 @@ function DetailView({ alphaId }: DetailProps) { to="/alpha-zoo" className="text-sm text-muted-foreground hover:text-foreground inline-flex items-center gap-1" > -