feat: translate all UI strings to English for international release
Complete French→English translation across all frontend pages and backend services — every label, button, header, empty state, toast, and nav item is now in English. Build verified clean (tsc + vite). No i18n library added; direct string replacement throughout. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -11,7 +11,7 @@ import clsx from 'clsx'
|
||||
|
||||
async function fetchLatest() {
|
||||
const r = await fetch('/api/var/latest')
|
||||
if (!r.ok) throw new Error(`Backend inaccessible (${r.status})`)
|
||||
if (!r.ok) throw new Error(`Backend unreachable (${r.status})`)
|
||||
return r.json()
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ function MetricCard({
|
||||
<div className="text-[11px] text-slate-500 mb-3">{method}</div>
|
||||
<div className="space-y-2">
|
||||
<div>
|
||||
<div className="text-[10px] text-slate-500 mb-0.5">VaR {horizon}j</div>
|
||||
<div className="text-[10px] text-slate-500 mb-0.5">VaR {horizon}d</div>
|
||||
<div className={clsx('text-2xl font-bold', displayed < 0 ? 'text-red-400' : 'text-emerald-400')}>
|
||||
{pct(displayed)}
|
||||
</div>
|
||||
@@ -101,10 +101,10 @@ function IdleState({ onCompute, computing }: { onCompute: () => void; computing:
|
||||
<div className="flex flex-col items-center justify-center py-24 gap-6">
|
||||
<ShieldAlert className="w-16 h-16 text-slate-700" />
|
||||
<div className="text-center">
|
||||
<h2 className="text-lg font-semibold text-slate-400 mb-2">Aucun snapshot VaR disponible</h2>
|
||||
<h2 className="text-lg font-semibold text-slate-400 mb-2">No VaR snapshot available</h2>
|
||||
<p className="text-slate-600 text-sm max-w-md">
|
||||
Le calcul VaR ne se lance pas automatiquement pour éviter des appels réseau inutiles.
|
||||
Cliquez sur "Calculer maintenant" ou configurez le scheduler dans la Configuration.
|
||||
The VaR calculation does not run automatically to avoid unnecessary network calls.
|
||||
Click "Compute now" or configure the scheduler in Settings.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
@@ -113,7 +113,7 @@ function IdleState({ onCompute, computing }: { onCompute: () => void; computing:
|
||||
className="flex items-center gap-2 bg-blue-600 hover:bg-blue-500 disabled:opacity-50 text-white px-6 py-3 rounded-lg font-semibold text-sm"
|
||||
>
|
||||
<Play className={clsx('w-4 h-4', computing && 'animate-pulse')} />
|
||||
{computing ? 'Calcul en cours…' : 'Calculer maintenant'}
|
||||
{computing ? 'Computing…' : 'Compute now'}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
@@ -180,15 +180,15 @@ export default function VaRAnalysis() {
|
||||
<div>
|
||||
<div className="flex items-center gap-3 mb-1">
|
||||
<ShieldAlert className="w-6 h-6 text-red-400" />
|
||||
<h1 className="text-xl font-bold text-white">Analyse VaR — Value at Risk</h1>
|
||||
<h1 className="text-xl font-bold text-white">VaR Analysis — Value at Risk</h1>
|
||||
</div>
|
||||
<p className="text-slate-400 text-sm">
|
||||
Approche delta Black-Scholes
|
||||
Delta Black-Scholes approach
|
||||
{displayedMeta && (
|
||||
<span className="ml-2 text-slate-500">
|
||||
· snapshot du {displayedMeta.computed_at?.slice(0, 16).replace('T', ' ')} UTC
|
||||
· snapshot from {displayedMeta.computed_at?.slice(0, 16).replace('T', ' ')} UTC
|
||||
{displayedMeta.data_source === 'simulated' && (
|
||||
<span className="ml-2 text-amber-400">⚠ données simulées</span>
|
||||
<span className="ml-2 text-amber-400">⚠ simulated data</span>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
@@ -199,7 +199,7 @@ export default function VaRAnalysis() {
|
||||
<div className="flex flex-wrap gap-3 items-end">
|
||||
{/* Confidence */}
|
||||
<div>
|
||||
<div className="text-[10px] text-slate-500 mb-1 uppercase tracking-wider">Confiance</div>
|
||||
<div className="text-[10px] text-slate-500 mb-1 uppercase tracking-wider">Confidence</div>
|
||||
<div className="flex gap-1">
|
||||
{[0.90, 0.95, 0.99].map(c => (
|
||||
<button key={c} onClick={() => setConfidence(c)}
|
||||
@@ -218,14 +218,14 @@ export default function VaRAnalysis() {
|
||||
<button key={h} onClick={() => setHorizon(h)}
|
||||
className={clsx('px-2.5 py-1.5 rounded text-xs font-semibold',
|
||||
horizon === h ? 'bg-violet-600 text-white' : 'bg-dark-700 text-slate-400 hover:text-white')}>
|
||||
{h}j
|
||||
{h}d
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{/* Lookback */}
|
||||
<div>
|
||||
<div className="text-[10px] text-slate-500 mb-1 uppercase tracking-wider">Historique</div>
|
||||
<div className="text-[10px] text-slate-500 mb-1 uppercase tracking-wider">Lookback</div>
|
||||
<div className="flex gap-1">
|
||||
{[63, 126, 252].map(l => (
|
||||
<button key={l} onClick={() => setLookback(l)}
|
||||
@@ -238,7 +238,7 @@ export default function VaRAnalysis() {
|
||||
</div>
|
||||
{/* IV */}
|
||||
<div>
|
||||
<div className="text-[10px] text-slate-500 mb-1 uppercase tracking-wider">IV défaut</div>
|
||||
<div className="text-[10px] text-slate-500 mb-1 uppercase tracking-wider">Default IV</div>
|
||||
<div className="flex gap-1">
|
||||
{[0.15, 0.20, 0.30, 0.40].map(v => (
|
||||
<button key={v} onClick={() => setIv(v)}
|
||||
@@ -256,7 +256,7 @@ export default function VaRAnalysis() {
|
||||
className="flex items-center gap-1.5 bg-blue-600 hover:bg-blue-500 disabled:opacity-50 text-white px-4 py-2 rounded text-sm font-semibold"
|
||||
>
|
||||
<Play className={clsx('w-3.5 h-3.5', computing && 'animate-pulse')} />
|
||||
{computing ? 'Calcul…' : 'Calculer'}
|
||||
{computing ? 'Computing…' : 'Compute'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -265,7 +265,7 @@ export default function VaRAnalysis() {
|
||||
{backendDown && (
|
||||
<div className="bg-red-900/20 border border-red-700/40 rounded-xl p-4 text-red-400 flex items-center gap-2 text-sm">
|
||||
<AlertTriangle className="w-4 h-4 shrink-0" />
|
||||
Backend inaccessible — vérifiez que le serveur FastAPI est démarré sur le port 8000.
|
||||
Backend unreachable — check that the FastAPI server is running on port 8000.
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -281,12 +281,12 @@ export default function VaRAnalysis() {
|
||||
{snapshots.length > 1 && (
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<Database className="w-3.5 h-3.5 text-slate-500" />
|
||||
<span className="text-xs text-slate-500">Historique :</span>
|
||||
<span className="text-xs text-slate-500">History:</span>
|
||||
<button
|
||||
onClick={() => setActiveSnap(null)}
|
||||
className={clsx('text-xs px-2 py-1 rounded', !activeSnap ? 'bg-blue-900/40 text-blue-400 border border-blue-700/40' : 'bg-dark-700 text-slate-500 hover:text-slate-300')}
|
||||
>
|
||||
Dernier
|
||||
Latest
|
||||
</button>
|
||||
{snapshots.slice(1).map((s: any) => (
|
||||
<button
|
||||
@@ -307,7 +307,7 @@ export default function VaRAnalysis() {
|
||||
|
||||
{/* Empty / loading state */}
|
||||
{loadingLatest && !displayedResult && (
|
||||
<div className="text-center py-16 text-slate-600">Chargement du dernier snapshot…</div>
|
||||
<div className="text-center py-16 text-slate-600">Loading latest snapshot…</div>
|
||||
)}
|
||||
|
||||
{!loadingLatest && !displayedResult && !backendDown && (
|
||||
@@ -322,9 +322,9 @@ export default function VaRAnalysis() {
|
||||
{[
|
||||
{ label: 'Positions', val: String(portfolio.n_positions) },
|
||||
{ label: 'Notionnel', val: `${portfolio.total_notional_eur.toLocaleString('fr-FR', { maximumFractionDigits: 0 })} €` },
|
||||
{ label: 'Confiance', val: `${portfolio.confidence_pct}%` },
|
||||
{ label: 'Lookback', val: `${portfolio.lookback_days}j` },
|
||||
{ label: 'Source', val: portfolio.data_source === 'simulated' ? '⚠ Simulée' : '✓ Live' },
|
||||
{ label: 'Confidence', val: `${portfolio.confidence_pct}%` },
|
||||
{ label: 'Lookback', val: `${portfolio.lookback_days}d` },
|
||||
{ label: 'Source', val: portfolio.data_source === 'simulated' ? '⚠ Simulated' : '✓ Live' },
|
||||
].map(({ label, val }) => (
|
||||
<div key={label} className="bg-dark-800 border border-slate-700/30 rounded-lg px-3 py-2">
|
||||
<div className="text-slate-500">{label}</div>
|
||||
@@ -336,21 +336,21 @@ export default function VaRAnalysis() {
|
||||
{/* 3 VaR method cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<MetricCard
|
||||
label="Historique" method="Percentile empirique"
|
||||
label="Historical" method="Empirical percentile"
|
||||
var1d={varData.historical.var_1d_pct} varNd={varData.historical.var_nd_pct}
|
||||
cvar={varData.historical.cvar_pct}
|
||||
varEur={varData.historical.var_1d_eur} cvarEur={varData.historical.cvar_eur}
|
||||
horizon={horizon} colorClass="border-blue-700/40"
|
||||
/>
|
||||
<MetricCard
|
||||
label="Paramétrique" method="Distribution normale"
|
||||
label="Parametric" method="Normal distribution"
|
||||
var1d={varData.parametric.var_1d_pct} varNd={varData.parametric.var_nd_pct}
|
||||
cvar={varData.parametric.cvar_pct}
|
||||
varEur={varData.parametric.var_1d_eur} cvarEur={varData.parametric.cvar_eur}
|
||||
horizon={horizon} colorClass="border-violet-700/40"
|
||||
/>
|
||||
<MetricCard
|
||||
label="Monte Carlo" method="Vol stressée ×1.5"
|
||||
label="Monte Carlo" method="Stressed vol ×1.5"
|
||||
var1d={varData.monte_carlo.var_1d_pct} varNd={varData.monte_carlo.var_nd_pct}
|
||||
cvar={varData.monte_carlo.cvar_pct}
|
||||
varEur={varData.monte_carlo.var_1d_eur} cvarEur={varData.monte_carlo.cvar_eur}
|
||||
@@ -364,8 +364,8 @@ export default function VaRAnalysis() {
|
||||
<div className="bg-dark-800 border border-slate-700/40 rounded-xl p-4">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Activity className="w-4 h-4 text-slate-400" />
|
||||
<h3 className="text-sm font-semibold text-white">Distribution des Retours</h3>
|
||||
<span className="text-[10px] text-slate-600 ml-auto">trait rouge = VaR hist.</span>
|
||||
<h3 className="text-sm font-semibold text-white">Return Distribution</h3>
|
||||
<span className="text-[10px] text-slate-600 ml-auto">red line = hist. VaR</span>
|
||||
</div>
|
||||
{histogram.length > 0 ? (
|
||||
<ResponsiveContainer width="100%" height={210}>
|
||||
@@ -383,7 +383,7 @@ export default function VaRAnalysis() {
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
) : (
|
||||
<div className="h-[210px] flex items-center justify-center text-slate-600 text-sm">Aucune donnée</div>
|
||||
<div className="h-[210px] flex items-center justify-center text-slate-600 text-sm">No data</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -391,7 +391,7 @@ export default function VaRAnalysis() {
|
||||
<div className="bg-dark-800 border border-slate-700/40 rounded-xl p-4">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<TrendingDown className="w-4 h-4 text-red-400" />
|
||||
<h3 className="text-sm font-semibold text-white">VaR 95% Glissante (fenêtre 30J)</h3>
|
||||
<h3 className="text-sm font-semibold text-white">Rolling VaR 95% (30d window)</h3>
|
||||
</div>
|
||||
{rolling.length > 0 ? (
|
||||
<ResponsiveContainer width="100%" height={210}>
|
||||
@@ -406,7 +406,7 @@ export default function VaRAnalysis() {
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
) : (
|
||||
<div className="h-[210px] flex items-center justify-center text-slate-600 text-sm">Historique insuffisant</div>
|
||||
<div className="h-[210px] flex items-center justify-center text-slate-600 text-sm">Insufficient history</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -415,7 +415,7 @@ export default function VaRAnalysis() {
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
{/* Positions */}
|
||||
<div className="bg-dark-800 border border-slate-700/40 rounded-xl p-4">
|
||||
<h3 className="text-sm font-semibold text-white mb-3">Positions & Deltas</h3>
|
||||
<h3 className="text-sm font-semibold text-white mb-3">Positions & Deltas</h3>
|
||||
<div className="space-y-1.5">
|
||||
{positions.map((p: any, i: number) => (
|
||||
<div key={i} className="flex items-center justify-between text-xs py-1 border-b border-slate-700/20 last:border-0">
|
||||
@@ -442,13 +442,13 @@ export default function VaRAnalysis() {
|
||||
{/* Kupiec backtest */}
|
||||
{backtest && (
|
||||
<div className="bg-dark-800 border border-slate-700/40 rounded-xl p-4">
|
||||
<h3 className="text-sm font-semibold text-white mb-3">Backtest — Test de Kupiec</h3>
|
||||
<h3 className="text-sm font-semibold text-white mb-3">Backtest — Kupiec Test</h3>
|
||||
<div className="space-y-2.5">
|
||||
{[
|
||||
{ label: 'Observations', val: String(backtest.n_observations) },
|
||||
{ label: 'Violations VaR', val: String(backtest.n_breaches) },
|
||||
{ label: 'Taux réel', val: `${backtest.breach_rate_pct}%`, color: backtest.kupiec_ok ? 'text-emerald-400' : 'text-red-400' },
|
||||
{ label: 'Taux attendu', val: `${backtest.expected_breach_rate_pct}%`, color: 'text-slate-300' },
|
||||
{ label: 'VaR Violations', val: String(backtest.n_breaches) },
|
||||
{ label: 'Actual rate', val: `${backtest.breach_rate_pct}%`, color: backtest.kupiec_ok ? 'text-emerald-400' : 'text-red-400' },
|
||||
{ label: 'Expected rate', val: `${backtest.expected_breach_rate_pct}%`, color: 'text-slate-300' },
|
||||
].map(({ label, val, color }) => (
|
||||
<div key={label} className="flex justify-between text-sm">
|
||||
<span className="text-slate-400">{label}</span>
|
||||
@@ -460,11 +460,11 @@ export default function VaRAnalysis() {
|
||||
? 'bg-emerald-900/20 border border-emerald-700/40 text-emerald-400'
|
||||
: 'bg-red-900/20 border border-red-700/40 text-red-400')}>
|
||||
{backtest.kupiec_ok
|
||||
? '✓ Modèle validé — violations dans la tolérance'
|
||||
: '✗ Excès de violations — modèle sous-estime le risque'}
|
||||
? '✓ Model validated — violations within tolerance'
|
||||
: '✗ Excess violations — model underestimates risk'}
|
||||
</div>
|
||||
<div className="text-[10px] text-slate-600">
|
||||
Règle Kupiec : taux réel ≤ {(backtest.expected_breach_rate_pct * 2).toFixed(1)}%
|
||||
Kupiec rule: actual rate ≤ {(backtest.expected_breach_rate_pct * 2).toFixed(1)}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user