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

@@ -33,25 +33,25 @@ const BUCKET_ICONS: Record<string, string> = {
}
export const CATEGORIES = [
{ key: 'all', label: 'Tous' },
{ key: 'energy', label: '⛽ Énergie' },
{ key: 'metals', label: '🥇 Métaux' },
{ key: 'all', label: 'All' },
{ key: 'energy', label: '⛽ Energy' },
{ key: 'metals', label: '🥇 Metals' },
{ key: 'agriculture', label: '🌾 Agri' },
{ key: 'indices', label: '📊 Indices' },
{ key: 'equities', label: '📈 Actions' },
{ key: 'equities', label: '📈 Equities' },
{ key: 'forex', label: '💱 Forex' },
]
export const THEMATIC_CATEGORIES = [
{ key: 'all', label: 'Toutes' },
{ key: 'géopolitique', label: '⚔️ Géopo' },
{ key: 'all', label: 'All' },
{ key: 'géopolitique', label: '⚔️ Geopo' },
{ key: 'macro_monétaire', label: '🏦 Macro' },
{ key: 'technique', label: '📐 Technique' },
{ key: 'technique', label: '📐 Technical' },
{ key: 'commodités_supply',label: '🛢️ Supply' },
{ key: 'risk_off', label: '🌩️ Risk-off' },
{ key: 'flux_saisonnier', label: '📅 Flux' },
{ key: 'géo_économique', label: '🌐 Géo-éco' },
{ key: 'crédit_stress', label: '💳 Crédit' },
{ key: 'flux_saisonnier', label: '📅 Flows' },
{ key: 'géo_économique', label: '🌐 Geo-eco' },
{ key: 'crédit_stress', label: '💳 Credit' },
]
const SIGNAL_DIR: Record<string, { label: string; color: string }> = {
@@ -80,12 +80,12 @@ export interface TradeItem {
}
const BIAS_DISPLAY: Record<string, { label: string; color: string }> = {
'bullish+': { label: '★★ Compatible', color: '#10b981' },
'bullish+': { label: '★★ Aligned', color: '#10b981' },
'bullish': { label: '★ Compatible', color: '#34d399' },
'neutral': { label: '→ Neutre', color: '#64748b' },
'bearish': { label: '✗ favorable', color: '#f97316' },
'neutral': { label: '→ Neutral', color: '#64748b' },
'bearish': { label: '✗ Unfavorable', color: '#f97316' },
'bearish+': { label: '✗✗ Contra', color: '#ef4444' },
'defensive':{ label: '⚠ Défensif', color: '#f59e0b' },
'defensive':{ label: '⚠ Defensive', color: '#f59e0b' },
}
// ── IBKR helpers ──────────────────────────────────────────────────────────────
@@ -234,33 +234,33 @@ function IBKRTicket({ strategy, underlying, strikeGuidance, expiryDays, entryPri
return (
<div className="bg-blue-950/20 border border-blue-700/30 rounded p-3 space-y-2.5">
<div className="text-[10px] font-bold text-blue-300 uppercase tracking-wide flex items-center gap-1.5">
<Terminal className="w-3 h-3" /> Ticket IBKR
{!entryPrice && <span className="ml-auto text-slate-600 font-normal normal-case">Prix non disponible vérifier dans IBKR</span>}
<Terminal className="w-3 h-3" /> IBKR Ticket
{!entryPrice && <span className="ml-auto text-slate-600 font-normal normal-case">Price unavailable check in IBKR</span>}
</div>
<div className="grid grid-cols-3 gap-3 text-[10px]">
<div>
<div className="text-slate-600 mb-0.5">Sous-jacent</div>
<div className="text-slate-600 mb-0.5">Underlying</div>
<div className="text-white font-mono font-bold text-sm">{underlying}</div>
<div className="text-slate-600 text-[9px]">Security Type: OPT</div>
</div>
<div>
<div className="text-slate-600 mb-0.5">Stratégie</div>
<div className="text-slate-600 mb-0.5">Strategy</div>
<div className="text-slate-200 font-semibold">{strategy}</div>
{entryPrice && <div className="text-slate-600 text-[9px]">Sous-jacent: ${entryPrice.toFixed(2)}</div>}
{entryPrice && <div className="text-slate-600 text-[9px]">Underlying: ${entryPrice.toFixed(2)}</div>}
</div>
<div>
<div className="text-slate-600 mb-0.5">Expiration</div>
<div className="text-slate-600 mb-0.5">Expiry</div>
<div className="text-slate-200 font-mono">
{expiryDate ? format(expiryDate, 'd MMM yyyy', { locale: fr }) : expiryDays ? `~${expiryDays}j` : '—'}
{expiryDate ? format(expiryDate, 'd MMM yyyy', { locale: fr }) : expiryDays ? `~${expiryDays}d` : '—'}
</div>
<div className="text-slate-600 text-[9px]">{expiryDays ? `${expiryDays}j DTE` : ''}</div>
<div className="text-slate-600 text-[9px]">{expiryDays ? `${expiryDays}d DTE` : ''}</div>
</div>
</div>
<div className="space-y-1">
{legs.map((leg, i) => (
<div key={i} className="flex items-center gap-2 bg-slate-900/70 rounded px-2.5 py-1.5 font-mono text-[10px]">
<span className={clsx('w-8 font-bold', leg.action === 'BUY' ? 'text-emerald-400' : 'text-red-400')}>{leg.action}</span>
<span className="text-slate-400">1 contrat</span>
<span className="text-slate-400">1 contract</span>
<span className={clsx('font-bold w-10 text-center', leg.type === 'CALL' ? 'text-blue-300' : leg.type === 'PUT' ? 'text-orange-300' : 'text-slate-300')}>{leg.type}</span>
<span className="text-slate-600">Strike</span>
<span className="text-yellow-300 font-bold">{fmtStrike(leg.strike)}</span>
@@ -270,17 +270,17 @@ function IBKRTicket({ strategy, underlying, strikeGuidance, expiryDays, entryPri
</div>
<div className="grid grid-cols-3 gap-3 text-[10px]">
<div>
<div className="text-slate-600 mb-0.5">Type d'ordre</div>
<div className="text-slate-600 mb-0.5">Order type</div>
<div className="text-slate-300 font-semibold">LIMIT</div>
<div className="text-slate-600 text-[9px]">Prix = prime dans IBKR</div>
<div className="text-slate-600 text-[9px]">Price = premium in IBKR</div>
</div>
<div>
<div className="text-slate-600 mb-0.5">Budget max</div>
<div className="text-slate-300 font-mono">{maxLoss ? `${Math.abs(maxLoss).toFixed(0)}€` : '~1 000'}</div>
<div className="text-slate-600 text-[9px]">Perte max estimée</div>
<div className="text-slate-600 mb-0.5">Max budget</div>
<div className="text-slate-300 font-mono">{maxLoss ? `${Math.abs(maxLoss).toFixed(0)}` : '~1,000€'}</div>
<div className="text-slate-600 text-[9px]">Estimated max loss</div>
</div>
<div>
<div className="text-slate-600 mb-0.5">Cible</div>
<div className="text-slate-600 mb-0.5">Target</div>
<div className="text-emerald-400 font-mono">{targetGain ? `+${targetGain.toFixed(0)}` : '—'}</div>
</div>
</div>
@@ -373,7 +373,7 @@ export function TradeCard({ item, onAdd, macroInfo, addedInfo, profiles }: {
)}
</div>
) : (
<span className="ml-2 shrink-0 text-xs text-slate-500 bg-dark-600 border border-slate-700/40 rounded px-1.5 py-0.5 whitespace-nowrap">à scorer</span>
<span className="ml-2 shrink-0 text-xs text-slate-500 bg-dark-600 border border-slate-700/40 rounded px-1.5 py-0.5 whitespace-nowrap">to score</span>
)}
</div>
{convergenceCount > 0 && (
@@ -404,7 +404,7 @@ export function TradeCard({ item, onAdd, macroInfo, addedInfo, profiles }: {
<><span className="text-slate-700">·</span>
{matchedProfile
? <span className="font-semibold" style={{ color: matchedProfile.color }}> {matchedProfile.name}</span>
: <span className="text-red-400/80">✗ {gainPct === 0 ? 'Gain non défini' : 'Aucun profil'}</span>
: <span className="text-red-400/80"> {gainPct === 0 ? 'Gain undefined' : 'No profile'}</span>
}</>
)}
</div>
@@ -435,7 +435,7 @@ export function TradeCard({ item, onAdd, macroInfo, addedInfo, profiles }: {
<button onClick={() => setExpanded(!expanded)}
className="flex items-center gap-1 text-xs text-slate-500 hover:text-slate-300 mb-1.5 transition-colors">
{expanded ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />}
{expanded ? 'Masquer la justification' : ' Justification du score'}
{expanded ? 'Hide breakdown' : '↳ Score breakdown'}
</button>
{expanded && (
<div className="mb-2">
@@ -463,7 +463,7 @@ export function TradeCard({ item, onAdd, macroInfo, addedInfo, profiles }: {
{addedInfo && (
<div className="flex items-center gap-1.5 mb-2 text-[10px] text-emerald-400 bg-emerald-900/20 border border-emerald-700/30 rounded px-2 py-1">
<CheckCircle2 className="w-3 h-3 shrink-0" />
<span>Ajouté le {format(new Date(addedInfo.entry_date), "d MMM yyyy", { locale: fr })}</span>
<span>Added on {format(new Date(addedInfo.entry_date), "d MMM yyyy", { locale: fr })}</span>
</div>
)}
<button onClick={() => onAdd(item)}
@@ -473,7 +473,7 @@ export function TradeCard({ item, onAdd, macroInfo, addedInfo, profiles }: {
: 'text-blue-400 hover:text-white hover:bg-blue-600 border-blue-500/30 hover:border-blue-500'
)}>
<Plus className="w-3 h-3" />
{addedInfo ? 'Ajouter à nouveau' : 'Ajouter au portefeuille'}
{addedInfo ? 'Add again' : 'Add to portfolio'}
</button>
</div>
)
@@ -560,7 +560,7 @@ export function TradeRow({ item, onAdd, macroInfo, addedInfo, profiles, rank }:
)}
</div>
</div>
) : <span className="text-slate-600 text-[10px]">à scorer</span>}
) : <span className="text-slate-600 text-[10px]">to score</span>}
</td>
<td className="px-2 py-2 w-20">
{effectiveConviction !== null && (
@@ -646,7 +646,7 @@ export function TradeRow({ item, onAdd, macroInfo, addedInfo, profiles, rank }:
{addedInfo && (
<div className="flex items-center gap-1.5 text-[10px] text-emerald-400 bg-emerald-900/20 border border-emerald-700/30 rounded px-2 py-1">
<CheckCircle2 className="w-3 h-3 shrink-0" />
Ajouté le {format(new Date(addedInfo.entry_date), "d MMM yyyy", { locale: fr })}
Added on {format(new Date(addedInfo.entry_date), "d MMM yyyy", { locale: fr })}
</div>
)}
<button
@@ -657,7 +657,7 @@ export function TradeRow({ item, onAdd, macroInfo, addedInfo, profiles, rank }:
: 'text-blue-400 hover:text-white hover:bg-blue-600 border-blue-500/30 hover:border-blue-500'
)}>
<Plus className="w-3 h-3" />
{addedInfo ? 'Ajouter à nouveau' : 'Ajouter au portefeuille'}
{addedInfo ? 'Add again' : 'Add to portfolio'}
</button>
</div>
</div>
@@ -826,7 +826,7 @@ export function TradeIdeasTab() {
}, {
onSuccess: () => {
refetchPositions()
setToast({ title: 'Ajouté au portefeuille', sub: `${t.strategy ?? ''} ${t.underlying ?? ''} · ${item.patternName}`.trim() })
setToast({ title: 'Added to portfolio', sub: `${t.strategy ?? ''} ${t.underlying ?? ''} · ${item.patternName}`.trim() })
},
})
}
@@ -844,13 +844,13 @@ export function TradeIdeasTab() {
<div className="flex items-center justify-between gap-3 flex-wrap">
<div className="flex items-center gap-2 flex-wrap">
<h2 className="section-title flex items-center gap-1 mb-0">
<Target className="w-3 h-3" /> Idées de trade
<Target className="w-3 h-3" /> Trade Ideas
</h2>
{scoredAt && (
<span className="text-xs text-slate-600">— scoré le {format(new Date(scoredAt), "d MMM à HH:mm", { locale: fr })}</span>
<span className="text-xs text-slate-600"> scored on {format(new Date(scoredAt), "d MMM HH:mm", { locale: fr })}</span>
)}
{topScored.length > 0 && (
<span className="text-xs text-emerald-500">{topScored.length} scoré{topScored.length > 1 ? 's' : ''} · {allUnscored.length} à scorer</span>
<span className="text-xs text-emerald-500">{topScored.length} scored · {allUnscored.length} to score</span>
)}
</div>
<div className="flex items-center gap-2 flex-wrap">
@@ -886,7 +886,7 @@ export function TradeIdeasTab() {
'bg-slate-600 text-white': topN === n,
'text-slate-400 hover:text-slate-200': topN !== n,
})}>
{n === 0 ? 'Tous' : `Top ${n}`}
{n === 0 ? 'All' : `Top ${n}`}
</button>
))}
</div>
@@ -904,10 +904,10 @@ export function TradeIdeasTab() {
<button onClick={() => scorePatterns({})}
disabled={scoring}
className="flex items-center gap-1.5 bg-blue-600 hover:bg-blue-500 disabled:opacity-50 text-white px-3 py-1.5 rounded text-xs font-semibold transition-all">
{scoring ? <><RefreshCw className="w-3 h-3 animate-spin" /> Scoring GPT-4o...</> : <><Brain className="w-3 h-3" /> Scorer les patterns</>}
{scoring ? <><RefreshCw className="w-3 h-3 animate-spin" /> Scoring GPT-4o...</> : <><Brain className="w-3 h-3" /> Score patterns</>}
</button>
) : (
<span className="text-xs text-slate-600 border border-slate-700/30 rounded px-2 py-1">Clé OpenAI requise</span>
<span className="text-xs text-slate-600 border border-slate-700/30 rounded px-2 py-1">OpenAI key required</span>
)}
</div>
</div>
@@ -927,16 +927,16 @@ export function TradeIdeasTab() {
<tr className="border-b border-slate-700/60 text-slate-600 text-[10px] uppercase tracking-wide">
<th className="pl-3 py-2 text-left w-6">#</th>
<th className="px-2 py-2 text-left">Pattern / Ticker</th>
<th className="px-2 py-2 text-left">Stratégie</th>
<th className="px-2 py-2 text-left">Strategy</th>
<th className="px-2 py-2 text-right">Score</th>
<th className="px-2 py-2 w-20"></th>
<th className="px-2 py-2 text-right">EV</th>
<th className="px-2 py-2 text-right">Gain</th>
<th className="px-2 py-2 text-right">Max/Cible</th>
<th className="px-2 py-2">Profil</th>
<th className="px-2 py-2 text-right">Move</th>
<th className="px-2 py-2 text-right">Max/Target</th>
<th className="px-2 py-2">Profile</th>
<th className="px-2 py-2">Macro</th>
<th className="px-2 py-2 text-right">Entrée</th>
<th className="px-2 py-2 text-right">Durée</th>
<th className="px-2 py-2 text-right">Entry</th>
<th className="px-2 py-2 text-right">Duration</th>
<th className="pr-3 py-2 w-6"></th>
</tr>
</thead>
@@ -956,7 +956,7 @@ export function TradeIdeasTab() {
{topScored.length > 0 && (
<div className="flex items-center gap-2 text-xs text-slate-600 mb-3">
<span className="border-b border-slate-700/30 flex-1"></span>
{allUnscored.length} trade{allUnscored.length > 1 ? 's' : ''} à scorer
{allUnscored.length} trade{allUnscored.length > 1 ? 's' : ''} to score
<span className="border-b border-slate-700/30 flex-1"></span>
</div>
)}
@@ -982,7 +982,7 @@ export function TradeIdeasTab() {
{allPatterns.length === 0 && (
<div className="card text-center py-8 text-slate-500 text-sm">
Démarrer le backend patterns en cours de chargement
Start the backend patterns loading
</div>
)}

View File

@@ -8,23 +8,23 @@ import clsx from 'clsx'
const nav = [
{ to: '/', icon: LayoutDashboard, label: 'Cockpit' },
{ to: '/geo', icon: Globe, label: 'Radar Géopolitique' },
{ to: '/markets', icon: BarChart2, label: 'Marchés & Prix' },
{ to: '/macro', icon: Activity, label: 'Régime Macro' },
{ to: '/geo', icon: Globe, label: 'Geopolitical Radar' },
{ to: '/markets', icon: BarChart2, label: 'Markets & Prices' },
{ to: '/macro', icon: Activity, label: 'Macro Regime' },
{ to: '/options', icon: TrendingUp, label: 'Options Lab' },
{ to: '/patterns', icon: Zap, label: 'Patterns' },
{ to: '/portfolio', icon: DollarSign, label: 'Portefeuille' },
{ to: '/journal', icon: BookOpen, label: 'Journal de Bord' },
{ to: '/rapport', icon: FileBarChart, label: 'Rapport de Cycle' },
{ to: '/super-contexte', icon: Brain, label: 'Super Contexte' },
{ to: '/portfolio', icon: DollarSign, label: 'Portfolio' },
{ to: '/journal', icon: BookOpen, label: 'Trading Journal' },
{ to: '/rapport', icon: FileBarChart, label: 'Cycle Report' },
{ to: '/super-contexte', icon: Brain, label: 'Super Context' },
{ to: '/analytics', icon: FlaskConical, label: 'Analytics' },
{ to: '/analytics-advanced', icon: Microscope, label: 'Analytics Avancées' },
{ to: '/analytics-advanced', icon: Microscope, label: 'Advanced Analytics' },
{ to: '/risk', icon: ShieldAlert, label: 'Risk Dashboard' },
{ to: '/var', icon: Gauge, label: 'VaR Analyse' },
{ to: '/position-history', icon: GitCompare, label: 'Historique Positions' },
{ to: '/var', icon: Gauge, label: 'VaR Analysis' },
{ to: '/position-history', icon: GitCompare, label: 'Position History' },
{ to: '/backtest', icon: History, label: 'Backtest' },
{ to: '/calendar', icon: Calendar, label: 'Calendrier' },
{ to: '/logs', icon: ScrollText, label: 'Logs Système' },
{ to: '/calendar', icon: Calendar, label: 'Calendar' },
{ to: '/logs', icon: ScrollText, label: 'System Logs' },
{ to: '/config', icon: Settings, label: 'Configuration' },
]
@@ -56,7 +56,7 @@ export default function Sidebar() {
{/* Geo risk indicator */}
{riskScore && (
<div className={clsx('mx-3 mt-3 px-3 py-2 rounded border text-xs', riskColors[riskScore.level])}>
<div className="font-semibold uppercase tracking-wider">Risque Géo</div>
<div className="font-semibold uppercase tracking-wider">Geo Risk</div>
<div className="text-lg font-bold">{riskScore.score}/100</div>
<div className="capitalize">{riskScore.level}</div>
</div>
@@ -65,7 +65,7 @@ export default function Sidebar() {
{/* Portfolio mini summary */}
{summary && (summary.open_positions > 0 || summary.unrealized_pnl !== 0) && (
<div className="mx-3 mt-2 px-3 py-2 rounded border border-slate-700/30 bg-dark-700/50 text-xs">
<div className="text-slate-500 uppercase tracking-wider text-xs mb-1">Portefeuille</div>
<div className="text-slate-500 uppercase tracking-wider text-xs mb-1">Portfolio</div>
<div className="flex justify-between">
<span className="text-slate-400">{summary.open_positions} positions</span>
<span className={clsx('font-bold', summary.unrealized_pnl >= 0 ? 'text-emerald-400' : 'text-red-400')}>
@@ -97,7 +97,7 @@ export default function Sidebar() {
'bg-dark-700 text-slate-600': !aiStatus?.enabled,
})}>
<BrainCircuit className="w-3.5 h-3.5" />
<span>{aiStatus?.enabled ? 'IA GPT-4o active' : 'IA non configurée'}</span>
<span>{aiStatus?.enabled ? 'AI GPT-4o active' : 'AI not configured'}</span>
</div>
</div>