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

@@ -13,16 +13,16 @@ const CATEGORY_COLORS: Record<string, string> = {
}
const CATEGORY_LABELS: Record<string, string> = {
military: '⚔️ Militaire', sanctions: '🚫 Sanctions', elections: '🗳️ Élections',
natural_disaster: '🌪️ Catastrophe', health_crisis: '🏥 Santé',
resource_scarcity: '⚠️ Ressources', trade_war: '🤝 Commerce',
energy: '⚡ Énergie', political_speech: '🎙️ Discours',
financial_crisis: '💸 Finance', general: '📰 Général',
military: '⚔️ Military', sanctions: '🚫 Sanctions', elections: '🗳️ Elections',
natural_disaster: '🌪️ Disaster', health_crisis: '🏥 Health',
resource_scarcity: '⚠️ Resources', trade_war: '🤝 Trade',
energy: '⚡ Energy', political_speech: '🎙️ Speech',
financial_crisis: '💸 Finance', general: '📰 General',
}
const ASSET_LABELS: Record<string, string> = {
energy: '⛽ Énergie', metals: '🥇 Métaux', agriculture: '🌾 Agri',
equities: '📈 Actions', indices: '📊 Indices', forex: '💱 Forex',
energy: '⛽ Energy', metals: '🥇 Metals', agriculture: '🌾 Agri',
equities: '📈 Equities', indices: '📊 Indices', forex: '💱 Forex',
}
function ImpactBar({ value, label }: { value: number; label: string }) {
@@ -90,14 +90,14 @@ function NewsCard({ news }: { news: GeoNews }) {
)
})}
{(news as any).ai_resolution && (
<span className="text-[10px] px-1.5 py-0.5 rounded bg-blue-900/30 text-blue-400">🕊 résolution</span>
<span className="text-[10px] px-1.5 py-0.5 rounded bg-blue-900/30 text-blue-400">🕊 resolution</span>
)}
</div>
)}
<p className="text-xs text-slate-400 leading-relaxed mb-3">{news.summary}</p>
{Object.keys(news.asset_impacts).length > 0 && (
<div className="mb-3">
<div className="text-xs text-slate-600 mb-1">Impact par classe d'actif</div>
<div className="text-xs text-slate-600 mb-1">Impact by asset class</div>
{Object.entries(news.asset_impacts).map(([cls, val]) => (
<ImpactBar key={cls} label={ASSET_LABELS[cls] ?? cls} value={val} />
))}
@@ -114,14 +114,14 @@ function NewsCard({ news }: { news: GeoNews }) {
<a href={news.url} target="_blank" rel="noopener noreferrer"
className="text-xs text-blue-400 hover:text-blue-300 flex items-center gap-1"
onClick={e => e.stopPropagation()}>
<ExternalLink className="w-3 h-3" /> Lire l'article
<ExternalLink className="w-3 h-3" /> Read article
</a>
)}
</>
)}
<div className="flex items-center justify-between mt-2 text-xs text-slate-600">
<span>{news.date?.slice(0, 16)}</span>
<span>{expanded ? '▲ Réduire' : '▼ Détail'}</span>
<span>{expanded ? '▲ Collapse' : '▼ Details'}</span>
</div>
</div>
)
@@ -156,7 +156,7 @@ function PatternRelevanceCard({ p }: { p: any }) {
<div className="text-sm font-semibold text-white line-clamp-1">{p.name}</div>
<div className="flex items-center gap-1 mt-0.5 flex-wrap">
<span className="badge badge-blue text-xs">{p.asset_class}</span>
<span className="text-xs text-slate-500">{p.keyword_hits}/{p.keyword_total} mots-clés</span>
<span className="text-xs text-slate-500">{p.keyword_hits}/{p.keyword_total} keywords</span>
{hasNews && (
<span className="text-xs text-emerald-400 flex items-center gap-0.5">
<Search className="w-2.5 h-2.5" />{p.matching_news.length} news
@@ -170,7 +170,7 @@ function PatternRelevanceCard({ p }: { p: any }) {
: 'bg-emerald-900/30 text-emerald-400 border border-emerald-700/30'
)}>
<Brain className="w-2.5 h-2.5" />
{p.ai_contra_signal ? `contre-signal (${p.ai_alignment > 0 ? '+' : ''}${p.ai_alignment}pts)` : `IA aligné (+${p.ai_alignment}pts)`}
{p.ai_contra_signal ? `counter-signal (${p.ai_alignment > 0 ? '+' : ''}${p.ai_alignment}pts)` : `AI aligned (+${p.ai_alignment}pts)`}
</span>
)}
</div>
@@ -211,7 +211,7 @@ function PatternRelevanceCard({ p }: { p: any }) {
<>
<button onClick={() => setExpanded(!expanded)}
className="text-xs text-slate-600 hover:text-slate-400 mb-2">
{expanded ? '▲ Masquer les news' : `Voir les ${p.matching_news.length} news correspondantes`}
{expanded ? '▲ Hide news' : `See ${p.matching_news.length} matching news`}
</button>
{expanded && (
<div className="space-y-1.5">
@@ -235,7 +235,7 @@ function PatternRelevanceCard({ p }: { p: any }) {
<a href={n.url} target="_blank" rel="noopener noreferrer"
className="text-xs text-blue-400 hover:text-blue-300 flex items-center gap-1 mt-1"
onClick={e => e.stopPropagation()}>
<ExternalLink className="w-2.5 h-2.5" /> Lire
<ExternalLink className="w-2.5 h-2.5" /> Read
</a>
)}
</div>
@@ -246,7 +246,7 @@ function PatternRelevanceCard({ p }: { p: any }) {
)}
{!hasNews && p.relevance === 0 && (
<div className="text-xs text-slate-600">Aucune news correspondante sur la période</div>
<div className="text-xs text-slate-600">No matching news for this period</div>
)}
</div>
)
@@ -256,10 +256,10 @@ type SortMode = 'impact_desc' | 'impact_asc' | 'date_desc' | 'date_asc'
type DateRange = 'all' | 'today' | '7d' | '30d'
const DATE_RANGE_LABELS: Record<DateRange, string> = {
all: 'Toutes', today: "Aujourd'hui", '7d': '7 jours', '30d': '30 jours',
all: 'All', today: 'Today', '7d': '7 days', '30d': '30 days',
}
const SORT_LABELS: Record<SortMode, string> = {
impact_desc: 'Impact ↓', impact_asc: 'Impact ↑', date_desc: 'Récentes ↓', date_asc: 'Anciennes ↑',
impact_desc: 'Impact ↓', impact_asc: 'Impact ↑', date_desc: 'Recent ↓', date_asc: 'Oldest ↑',
}
function isWithinRange(dateStr: string, range: DateRange): boolean {
@@ -304,10 +304,10 @@ export default function GeoRadar() {
<div className="flex items-center justify-between">
<div>
<h1 className="text-xl font-bold text-white flex items-center gap-2">
<Globe className="w-5 h-5 text-blue-400" /> Radar Géopolitique
<Globe className="w-5 h-5 text-blue-400" /> Geopolitical Radar
</h1>
<p className="text-xs text-slate-500 mt-0.5">
Flux d'actualités · Correspondance avec les patterns de trading
News feed · Pattern matching for trading signals
</p>
</div>
{riskScore && (
@@ -337,7 +337,7 @@ export default function GeoRadar() {
'bg-blue-600 text-white': tab === t,
'text-slate-400 hover:text-slate-200': tab !== t,
})}>
{t === 'news' ? `📰 Actualités (${news?.length ?? 0})` : `🧩 Patterns (${patternsWithNews}/${totalPatterns} avec news)`}
{t === 'news' ? `📰 News (${news?.length ?? 0})` : `🧩 Patterns (${patternsWithNews}/${totalPatterns} with news)`}
</button>
))}
</div>
@@ -345,7 +345,7 @@ export default function GeoRadar() {
{/* Day selector — only shown on patterns tab */}
{tab === 'patterns' && (
<div className="flex items-center gap-2">
<span className="text-xs text-slate-500">Fenêtre :</span>
<span className="text-xs text-slate-500">Window:</span>
<div className="flex gap-0.5 bg-dark-700 p-0.5 rounded">
{[1, 2, 7, 30].map(d => (
<button key={d} onClick={() => setDays(d)}
@@ -353,11 +353,11 @@ export default function GeoRadar() {
'bg-slate-600 text-white': days === d,
'text-slate-400 hover:text-slate-200': days !== d,
})}>
{d}j
{d}d
</button>
))}
</div>
<span className="text-xs text-slate-600">derniers jours</span>
<span className="text-xs text-slate-600">last days</span>
</div>
)}
</div>
@@ -375,7 +375,7 @@ export default function GeoRadar() {
'bg-blue-600 border-blue-500 text-white': filterCat === cat,
'border-slate-700 text-slate-400 hover:border-slate-500': filterCat !== cat,
})}>
{cat === 'all' ? 'Toutes catégories' : CATEGORY_LABELS[cat] ?? cat}
{cat === 'all' ? 'All categories' : CATEGORY_LABELS[cat] ?? cat}
</button>
))}
</div>
@@ -384,7 +384,7 @@ export default function GeoRadar() {
{/* Sort + date range row */}
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<span className="text-xs text-slate-500">Période :</span>
<span className="text-xs text-slate-500">Period:</span>
<div className="flex gap-0.5 bg-dark-700 p-0.5 rounded">
{(['today', '7d', '30d', 'all'] as DateRange[]).map(r => (
<button key={r} onClick={() => setDateRange(r)}
@@ -398,7 +398,7 @@ export default function GeoRadar() {
</div>
</div>
<div className="flex items-center gap-2">
<span className="text-xs text-slate-500">Tri :</span>
<span className="text-xs text-slate-500">Sort:</span>
<div className="flex gap-0.5 bg-dark-700 p-0.5 rounded">
{(['impact_desc', 'impact_asc', 'date_desc', 'date_asc'] as SortMode[]).map(s => (
<button key={s} onClick={() => setSortMode(s)}
@@ -423,7 +423,7 @@ export default function GeoRadar() {
{filtered.map((n, i) => <NewsCard key={n.id || i} news={n} />)}
{filtered.length === 0 && (
<div className="card col-span-2 text-center py-8 text-slate-500">
Démarrer le backend pour charger les actualités
Start the backend to load news
</div>
)}
</div>
@@ -437,11 +437,11 @@ export default function GeoRadar() {
<div className="flex items-center gap-3 text-xs text-slate-500 bg-dark-700/40 rounded px-3 py-2">
<Search className="w-3.5 h-3.5 shrink-0 text-blue-400" />
<span>
Correspondance entre les mots-clés de chaque pattern et les news des {days} derniers jours.
Ce signal sert à enrichir le contexte donné à l'IA lors du scoring.
Matching each pattern's keywords against news from the last {days} days.
This signal enriches the context sent to the AI during scoring.
{patternsWithNews > 0 && (
<span className="text-emerald-400 ml-1">
{patternsWithNews} patterns ont des news correspondantes.
{patternsWithNews} patterns have matching news.
</span>
)}
</span>
@@ -458,7 +458,7 @@ export default function GeoRadar() {
))}
{(!patternRelevance || patternRelevance.length === 0) && (
<div className="card text-center py-8 text-slate-500">
Démarrer le backend pour calculer la correspondance news-patterns
Start the backend to compute news-pattern matching
</div>
)}
</div>