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:
@@ -19,10 +19,10 @@ import {
|
||||
import { scoreColor } from '../components/TradeIdeas'
|
||||
|
||||
const riskGauge = (score: number) => {
|
||||
if (score < 25) return { color: 'text-emerald-400', bg: 'bg-emerald-500', label: 'FAIBLE' }
|
||||
if (score < 50) return { color: 'text-yellow-400', bg: 'bg-yellow-500', label: 'MODÉRÉ' }
|
||||
if (score < 75) return { color: 'text-orange-400', bg: 'bg-orange-500', label: 'ÉLEVÉ' }
|
||||
return { color: 'text-red-400', bg: 'bg-red-500', label: 'EXTRÊME' }
|
||||
if (score < 25) return { color: 'text-emerald-400', bg: 'bg-emerald-500', label: 'LOW' }
|
||||
if (score < 50) return { color: 'text-yellow-400', bg: 'bg-yellow-500', label: 'MODERATE' }
|
||||
if (score < 75) return { color: 'text-orange-400', bg: 'bg-orange-500', label: 'HIGH' }
|
||||
return { color: 'text-red-400', bg: 'bg-red-500', label: 'EXTREME' }
|
||||
}
|
||||
|
||||
const assetEmoji: Record<string, string> = {
|
||||
@@ -37,9 +37,9 @@ const ASSET_COLORS: Record<string, string> = {
|
||||
}
|
||||
|
||||
const REGIME_LABELS: Record<string, string> = {
|
||||
goldilocks: 'Goldilocks', desinflation: 'Désinflation', stagflation: 'Stagflation',
|
||||
recession: 'Récession', crise_liquidite: 'Crise liq.', reflation: 'Reflation',
|
||||
soft_landing: 'Soft Landing', inflation_shock: 'Infl. Choc', incertain: 'Incertain',
|
||||
goldilocks: 'Goldilocks', desinflation: 'Disinflation', stagflation: 'Stagflation',
|
||||
recession: 'Recession', crise_liquidite: 'Liq. Crisis', reflation: 'Reflation',
|
||||
soft_landing: 'Soft Landing', inflation_shock: 'Infl. Shock', incertain: 'Uncertain',
|
||||
}
|
||||
|
||||
function ViewToggle({ value, onChange }: { value: 'simulated' | 'portfolio'; onChange: (v: 'simulated' | 'portfolio') => void }) {
|
||||
@@ -51,7 +51,7 @@ function ViewToggle({ value, onChange }: { value: 'simulated' | 'portfolio'; onC
|
||||
<button onClick={click('simulated')}
|
||||
className={clsx('px-1.5 py-0.5 rounded transition-colors',
|
||||
value === 'simulated' ? 'bg-blue-600 text-white' : 'text-slate-600 hover:text-slate-400')}>
|
||||
Simulé
|
||||
Simulated
|
||||
</button>
|
||||
<button onClick={click('portfolio')}
|
||||
className={clsx('px-1.5 py-0.5 rounded transition-colors',
|
||||
@@ -195,7 +195,7 @@ export default function Dashboard() {
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-xl font-bold text-white">Cockpit GeoOptions</h1>
|
||||
<h1 className="text-xl font-bold text-white">GeoOptions Cockpit</h1>
|
||||
<p className="text-xs text-slate-500 mt-0.5">
|
||||
{format(new Date(), "EEEE d MMMM yyyy · HH:mm", { locale: fr })}
|
||||
</p>
|
||||
@@ -235,10 +235,10 @@ export default function Dashboard() {
|
||||
<div className="card col-span-1">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="section-title flex items-center gap-1 mb-0">
|
||||
<Globe className="w-3 h-3" /> Risque Géopolitique
|
||||
<Globe className="w-3 h-3" /> Geopolitical Risk
|
||||
</div>
|
||||
<Link to="/geo" className="flex items-center gap-0.5 text-[10px] text-slate-600 hover:text-slate-300 transition-colors">
|
||||
News géo <ArrowUpRight className="w-2.5 h-2.5" />
|
||||
Geo news <ArrowUpRight className="w-2.5 h-2.5" />
|
||||
</Link>
|
||||
</div>
|
||||
{riskLoading ? (
|
||||
@@ -259,12 +259,12 @@ export default function Dashboard() {
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
) : <div className="text-slate-500 text-xs">Backend requis</div>}
|
||||
) : <div className="text-slate-500 text-xs">Backend required</div>}
|
||||
</div>
|
||||
|
||||
{/* Radar */}
|
||||
<div className="card col-span-1">
|
||||
<div className="section-title">Cartographie risques</div>
|
||||
<div className="section-title">Risk Map</div>
|
||||
{radarData.length > 0 ? (
|
||||
<ResponsiveContainer width="100%" height={160}>
|
||||
<RadarChart data={radarData}>
|
||||
@@ -273,18 +273,18 @@ export default function Dashboard() {
|
||||
<Radar dataKey="score" stroke="#3b82f6" fill="#3b82f6" fillOpacity={0.25} />
|
||||
</RadarChart>
|
||||
</ResponsiveContainer>
|
||||
) : <div className="h-40 flex items-center justify-center text-slate-600 text-xs">Chargement...</div>}
|
||||
) : <div className="h-40 flex items-center justify-center text-slate-600 text-xs">Loading...</div>}
|
||||
</div>
|
||||
|
||||
{/* Patterns — AI scores */}
|
||||
<div className="card col-span-1 overflow-y-auto max-h-64">
|
||||
<div className="section-title flex items-center gap-1">
|
||||
<Brain className="w-3 h-3" /> Scores patterns
|
||||
<Brain className="w-3 h-3" /> Pattern Scores
|
||||
{allPatterns.length > 0 && <span className="text-slate-600 text-xs ml-auto font-normal">{allPatterns.length} patterns</span>}
|
||||
</div>
|
||||
<div className="space-y-1.5 mt-1">
|
||||
{allPatterns.length === 0 ? (
|
||||
<div className="text-slate-600 text-xs">Backend requis</div>
|
||||
<div className="text-slate-600 text-xs">Backend required</div>
|
||||
) : (
|
||||
[...allPatterns]
|
||||
.sort((a, b) => {
|
||||
@@ -321,7 +321,7 @@ export default function Dashboard() {
|
||||
|
||||
{/* Calendrier */}
|
||||
<div className="card col-span-1 space-y-3">
|
||||
<div className="section-title flex items-center gap-1"><Clock className="w-3 h-3" /> Prochains catalyseurs</div>
|
||||
<div className="section-title flex items-center gap-1"><Clock className="w-3 h-3" /> Upcoming Catalysts</div>
|
||||
<div className="space-y-1.5">
|
||||
{calendar?.slice(0, 4).map((ev, i) => (
|
||||
<div key={i} className="flex items-center gap-2 text-xs">
|
||||
@@ -397,7 +397,7 @@ export default function Dashboard() {
|
||||
<div className="grid grid-cols-2 gap-2 flex-shrink-0">
|
||||
{/* Left: Unrealized */}
|
||||
<div className="bg-dark-700/40 rounded px-2.5 py-2 border border-slate-700/30">
|
||||
<div className="text-[9px] text-slate-600 uppercase tracking-wide mb-1">Ouvertes</div>
|
||||
<div className="text-[9px] text-slate-600 uppercase tracking-wide mb-1">Open</div>
|
||||
{pnlView === 'simulated' ? (
|
||||
<>
|
||||
<div className={clsx('text-xl font-bold font-mono leading-none',
|
||||
@@ -437,7 +437,7 @@ export default function Dashboard() {
|
||||
|
||||
{/* Right: Realized */}
|
||||
<div className="bg-dark-700/40 rounded px-2.5 py-2 border border-slate-700/30">
|
||||
<div className="text-[9px] text-slate-600 uppercase tracking-wide mb-1">Réalisé</div>
|
||||
<div className="text-[9px] text-slate-600 uppercase tracking-wide mb-1">Realized</div>
|
||||
{pnlView === 'simulated' ? (
|
||||
<>
|
||||
<div className={clsx('text-xl font-bold font-mono leading-none',
|
||||
@@ -451,10 +451,10 @@ export default function Dashboard() {
|
||||
closedProfitEur >= 0 ? 'text-emerald-500' : 'text-red-400')}>
|
||||
{closedCapital > 0
|
||||
? `${closedProfitEur >= 0 ? '+' : ''}${closedProfitEur.toFixed(0)}€`
|
||||
: closedTrades.length > 0 ? 'sans capital' : ''}
|
||||
: closedTrades.length > 0 ? 'no capital' : ''}
|
||||
</div>
|
||||
<div className="text-[10px] text-slate-500 mt-1">
|
||||
{closedTrades.length} fermés · <span className="text-emerald-500">{closedWinners}✓</span>{' '}
|
||||
{closedTrades.length} closed · <span className="text-emerald-500">{closedWinners}✓</span>{' '}
|
||||
<span className="text-red-400">{closedLosers}✗</span>
|
||||
</div>
|
||||
</>
|
||||
@@ -473,7 +473,7 @@ export default function Dashboard() {
|
||||
</div>
|
||||
)}
|
||||
<div className="text-[10px] text-slate-500 mt-1">
|
||||
{pf?.closed_positions ?? 0} fermées
|
||||
{pf?.closed_positions ?? 0} closed
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
@@ -483,7 +483,7 @@ export default function Dashboard() {
|
||||
{/* Total line */}
|
||||
<div className="mt-1.5 flex items-center justify-between text-[10px] flex-shrink-0">
|
||||
<span className="text-slate-600">
|
||||
Investi{isEstimated ? ' ~' : ''} {pnlView === 'simulated'
|
||||
Invested{isEstimated ? ' ~' : ''} {pnlView === 'simulated'
|
||||
? (totalCapital > 0 ? `${totalCapital.toFixed(0)}€` : '—')
|
||||
: (pfInvest != null ? `${pfInvest.toFixed(0)}€` : '—')}
|
||||
</span>
|
||||
@@ -509,7 +509,7 @@ export default function Dashboard() {
|
||||
const gradId = 'pnlGrad'
|
||||
return (
|
||||
<div className="mt-1.5 pt-1.5 border-t border-slate-700/30">
|
||||
<div className="text-[9px] text-slate-600 mb-0.5">Courbe PnL historique ({snaps.length} pts)</div>
|
||||
<div className="text-[9px] text-slate-600 mb-0.5">Historical PnL curve ({snaps.length} pts)</div>
|
||||
<ResponsiveContainer width="100%" height={58}>
|
||||
<AreaChart data={chartData} margin={{ top: 2, right: 0, left: -38, bottom: 0 }}>
|
||||
<defs>
|
||||
@@ -552,14 +552,14 @@ export default function Dashboard() {
|
||||
return (
|
||||
<Link to="/risk" className="card flex flex-col h-[288px] overflow-hidden hover:border-slate-600/60 transition-all cursor-pointer">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<span className="section-title mb-0 text-[10px]">🛡️ Risque</span>
|
||||
<span className="section-title mb-0 text-[10px]">🛡️ Risk</span>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<ViewToggle value={riskView} onChange={setRiskViewPersist} />
|
||||
<ArrowUpRight className="w-3 h-3 text-slate-600" />
|
||||
</div>
|
||||
</div>
|
||||
<div className={clsx('text-lg font-bold mt-0.5', alertCount > 0 ? 'text-red-400' : 'text-emerald-400')}>
|
||||
{alertCount > 0 ? `${alertCount} alerte${alertCount > 1 ? 's' : ''}` : 'OK'}
|
||||
{alertCount > 0 ? `${alertCount} alert${alertCount > 1 ? 's' : ''}` : 'OK'}
|
||||
</div>
|
||||
{sortedClasses.length > 0 && (
|
||||
<div className="mt-2 space-y-1">
|
||||
@@ -568,11 +568,11 @@ export default function Dashboard() {
|
||||
<span className="text-[9px] text-slate-500 w-12 truncate capitalize">{cls}</span>
|
||||
<div className="flex-1 h-2 bg-dark-700 rounded-full overflow-hidden flex">
|
||||
{data.bullish > 0 && (
|
||||
<div className="h-full rounded-l-full" title={`${data.bullish} haussier`}
|
||||
<div className="h-full rounded-l-full" title={`${data.bullish} bullish`}
|
||||
style={{ width: `${(data.bullish / openCount) * 100}%`, background: ASSET_COLORS[cls] ?? '#3b82f6', opacity: 0.9 }} />
|
||||
)}
|
||||
{data.bearish > 0 && (
|
||||
<div className="h-full rounded-r-full" title={`${data.bearish} baissier`}
|
||||
<div className="h-full rounded-r-full" title={`${data.bearish} bearish`}
|
||||
style={{ width: `${(data.bearish / openCount) * 100}%`, background: '#ef4444', opacity: 0.7 }} />
|
||||
)}
|
||||
</div>
|
||||
@@ -580,15 +580,15 @@ export default function Dashboard() {
|
||||
</div>
|
||||
))}
|
||||
<div className="flex items-center gap-1 text-[9px] text-slate-700 pt-0.5">
|
||||
<span className="inline-block w-2 h-1.5 rounded-sm bg-blue-500/60"></span>haussier
|
||||
<span className="inline-block w-2 h-1.5 rounded-sm bg-red-500/60 ml-1"></span>baissier
|
||||
{conflictCount > 0 && <span className="ml-auto text-red-400">{conflictCount} conflit{conflictCount > 1 ? 's' : ''}</span>}
|
||||
<span className="inline-block w-2 h-1.5 rounded-sm bg-blue-500/60"></span>bullish
|
||||
<span className="inline-block w-2 h-1.5 rounded-sm bg-red-500/60 ml-1"></span>bearish
|
||||
{conflictCount > 0 && <span className="ml-auto text-red-400">{conflictCount} conflict{conflictCount > 1 ? 's' : ''}</span>}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{sortedClasses.length === 0 && (
|
||||
<div className="text-[10px] text-slate-600 mt-1">
|
||||
{openCount > 0 ? `${openCount} positions` : 'Aucune position'}
|
||||
{openCount > 0 ? `${openCount} positions` : 'No positions'}
|
||||
</div>
|
||||
)}
|
||||
{/* Latest VaR snapshot */}
|
||||
@@ -629,7 +629,7 @@ export default function Dashboard() {
|
||||
return (
|
||||
<Link to="/journal" className="card flex flex-col h-[288px] overflow-hidden hover:border-slate-600/60 transition-all cursor-pointer">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="section-title mb-0 text-[10px]">🔄 Derniers Cycles</span>
|
||||
<span className="section-title mb-0 text-[10px]">🔄 Recent Cycles</span>
|
||||
<ArrowUpRight className="w-3 h-3 text-slate-600" />
|
||||
</div>
|
||||
{/* Column headers */}
|
||||
@@ -638,7 +638,7 @@ export default function Dashboard() {
|
||||
<span className="text-[8px] text-slate-700 w-8 shrink-0 text-right">Pat.</span>
|
||||
<span className="text-[8px] text-slate-700 w-8 shrink-0 text-right">Log.</span>
|
||||
<span className="text-[8px] text-slate-700 w-8 shrink-0 text-right">Clos</span>
|
||||
<span className="text-[8px] text-slate-700 flex-1 truncate">Régime</span>
|
||||
<span className="text-[8px] text-slate-700 flex-1 truncate">Regime</span>
|
||||
</div>
|
||||
{runs.length > 0 ? (
|
||||
<div className="space-y-2.5 flex-1 overflow-hidden">
|
||||
@@ -668,7 +668,7 @@ export default function Dashboard() {
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-[10px] text-slate-600 mt-1">Aucun cycle enregistré</div>
|
||||
<div className="text-[10px] text-slate-600 mt-1">No cycles recorded</div>
|
||||
)}
|
||||
</Link>
|
||||
)
|
||||
@@ -694,38 +694,38 @@ export default function Dashboard() {
|
||||
key: 'vix', label: 'VIX',
|
||||
color: (v: number) => v < 18 ? 'text-emerald-400' : v < 25 ? 'text-yellow-400' : 'text-red-400',
|
||||
fmt: (v: number) => v.toFixed(1),
|
||||
hint: (v: number) => v < 18 ? '↓ risque bas' : v < 25 ? '↑ alerte' : '↑ crise',
|
||||
hint: (v: number) => v < 18 ? '↓ low risk' : v < 25 ? '↑ alert' : '↑ crisis',
|
||||
},
|
||||
{
|
||||
key: 'spx_vs_200d', label: 'S&P/200j',
|
||||
key: 'spx_vs_200d', label: 'S&P/200d',
|
||||
color: (v: number) => v >= 0 ? 'text-emerald-400' : 'text-red-400',
|
||||
fmt: (v: number) => `${v >= 0 ? '+' : ''}${v.toFixed(1)}%`,
|
||||
hint: (v: number) => v >= 5 ? 'bull fort' : v >= 0 ? 'bull mod.' : 'bear',
|
||||
hint: (v: number) => v >= 5 ? 'strong bull' : v >= 0 ? 'mod. bull' : 'bear',
|
||||
},
|
||||
{
|
||||
key: 'slope_10y3m', label: 'Pente 10Y-3M',
|
||||
key: 'slope_10y3m', label: 'Slope 10Y-3M',
|
||||
color: (v: number) => v >= 0.5 ? 'text-emerald-400' : v >= 0 ? 'text-yellow-400' : 'text-red-400',
|
||||
fmt: (v: number) => `${v >= 0 ? '+' : ''}${v.toFixed(2)}`,
|
||||
hint: (v: number) => v >= 0.5 ? 'normale' : v >= 0 ? 'plate' : 'inversée',
|
||||
hint: (v: number) => v >= 0.5 ? 'normal' : v >= 0 ? 'flat' : 'inverted',
|
||||
},
|
||||
{
|
||||
key: 'copper', label: 'Cuivre',
|
||||
key: 'copper', label: 'Copper',
|
||||
color: (v: number, chg: number) => chg >= 0 ? 'text-emerald-400' : 'text-red-400',
|
||||
fmt: (v: number, chg: number) => `${chg >= 0 ? '+' : ''}${chg.toFixed(2)}%`,
|
||||
hint: (v: number, chg: number) => chg >= 0 ? '↑ croissance' : '↓ ralentis.',
|
||||
hint: (v: number, chg: number) => chg >= 0 ? '↑ growth' : '↓ slowing',
|
||||
},
|
||||
{
|
||||
key: 'gold', label: 'Or',
|
||||
key: 'gold', label: 'Gold',
|
||||
color: (v: number, chg: number) => chg >= 1 ? 'text-amber-400' : chg <= -1 ? 'text-emerald-400' : 'text-slate-400',
|
||||
fmt: (v: number, chg: number) => `${chg >= 0 ? '+' : ''}${chg.toFixed(2)}%`,
|
||||
hint: (v: number, chg: number) => chg >= 1 ? '↑ refuge' : chg <= -1 ? '↓ risk-on' : '≈ neutre',
|
||||
hint: (v: number, chg: number) => chg >= 1 ? '↑ safe haven' : chg <= -1 ? '↓ risk-on' : '≈ neutral',
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<Link to="/macro" className="card flex flex-col h-[288px] overflow-hidden hover:border-slate-600/60 transition-all cursor-pointer">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<span className="section-title mb-0 text-[10px]">🌐 Régime Macro</span>
|
||||
<span className="section-title mb-0 text-[10px]">🌐 Macro Regime</span>
|
||||
<ArrowUpRight className="w-3 h-3 text-slate-600" />
|
||||
</div>
|
||||
<div className={clsx('text-base font-bold mt-1', colorClass)}>
|
||||
@@ -834,7 +834,7 @@ export default function Dashboard() {
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-[10px] text-slate-600 mt-1">Aucun trade loggé</div>
|
||||
<div className="text-[10px] text-slate-600 mt-1">No trades logged</div>
|
||||
)}
|
||||
</Link>
|
||||
)
|
||||
@@ -849,7 +849,7 @@ export default function Dashboard() {
|
||||
return (
|
||||
<Link to="/journal" className="card block hover:border-slate-600/60 transition-all cursor-pointer">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<span className="section-title mb-0 text-[10px]">📥 Derniers trades loggés</span>
|
||||
<span className="section-title mb-0 text-[10px]">📥 Latest Logged Trades</span>
|
||||
<ArrowUpRight className="w-3 h-3 text-slate-600" />
|
||||
</div>
|
||||
{recent.length > 0 ? (
|
||||
@@ -873,7 +873,7 @@ export default function Dashboard() {
|
||||
{pnl >= 0 ? '+' : ''}{pnl.toFixed(1)}%
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-slate-700 shrink-0 text-[9px]">en cours</span>
|
||||
<span className="text-slate-700 shrink-0 text-[9px]">in progress</span>
|
||||
)}
|
||||
</div>
|
||||
{entryDate && (
|
||||
@@ -884,7 +884,7 @@ export default function Dashboard() {
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-[10px] text-slate-600 mt-1">Aucun trade loggé</div>
|
||||
<div className="text-[10px] text-slate-600 mt-1">No trades logged</div>
|
||||
)}
|
||||
</Link>
|
||||
)
|
||||
@@ -899,7 +899,7 @@ export default function Dashboard() {
|
||||
return (
|
||||
<Link to="/patterns" className="card block hover:border-slate-600/60 transition-all cursor-pointer">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<span className="section-title mb-0 text-[10px]">⭐ Derniers Patterns ajoutés</span>
|
||||
<span className="section-title mb-0 text-[10px]">⭐ Latest Patterns Added</span>
|
||||
<ArrowUpRight className="w-3 h-3 text-slate-600" />
|
||||
</div>
|
||||
{recent.length > 0 ? (
|
||||
@@ -932,7 +932,7 @@ export default function Dashboard() {
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-[10px] text-slate-600 mt-1">Aucun pattern enregistré</div>
|
||||
<div className="text-[10px] text-slate-600 mt-1">No patterns recorded</div>
|
||||
)}
|
||||
</Link>
|
||||
)
|
||||
@@ -969,7 +969,7 @@ export default function Dashboard() {
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-[10px] text-slate-600 mt-1">Chargement des news…</div>
|
||||
<div className="text-[10px] text-slate-600 mt-1">Loading news…</div>
|
||||
)}
|
||||
</Link>
|
||||
)
|
||||
@@ -982,7 +982,7 @@ export default function Dashboard() {
|
||||
<div key={cls} className="card">
|
||||
<div className="section-title">{assetEmoji[cls]} {cls}</div>
|
||||
{allQuotes?.[cls]?.map(q => <QuoteRow key={q.symbol} q={q} />) ?? (
|
||||
<div className="text-xs text-slate-600">Chargement...</div>
|
||||
<div className="text-xs text-slate-600">Loading...</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user