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:
OpenSquared
2026-06-22 09:06:37 +02:00
parent f8a0a6d023
commit dcbc9f19fc
24 changed files with 1443 additions and 1444 deletions

View File

@@ -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 &amp; 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>