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

File diff suppressed because it is too large Load Diff

View File

@@ -49,7 +49,7 @@ def _max_similarity_vs_existing(candidate_kws: List[str], existing: List[Dict])
return max((_jaccard(candidate_kws, p.get("keywords") or []) for p in existing), default=0.0)
_EMBED_SIM_THRESHOLD = 0.75 # seuil cosinus pour considérer deux patterns comme doublons
_EMBED_SIM_THRESHOLD = 0.75 # cosine threshold to consider two patterns as duplicates
def _is_duplicate_pattern(
@@ -59,8 +59,8 @@ def _is_duplicate_pattern(
jaccard_threshold: float = 0.30,
) -> bool:
"""
Retourne True si le candidat est trop similaire à un pattern existant.
Essaie les embeddings cosinus (Sprint 4.3) avec fallback Jaccard.
Returns True if the candidate is too similar to an existing pattern.
Tries cosine embeddings (Sprint 4.3) with Jaccard fallback.
"""
if not existing:
return False
@@ -77,7 +77,7 @@ def _is_duplicate_pattern(
api_key=api_key,
candidate_id=candidate.get("id"),
)
if sim > 0: # embedding a fonctionné
if sim > 0: # embedding worked
return sim >= _EMBED_SIM_THRESHOLD
except Exception as _emb_err:
logger.debug(f"[Cycle] Embedding failed, fallback Jaccard: {_emb_err}")
@@ -104,13 +104,13 @@ def _run_portfolio_monitor(risk: dict, cycle_id: str) -> dict:
n_alerts = len(risk.get("alerts", []))
system_prompt = (
"Tu es un risk manager spécialisé dans les portefeuilles d'options géopolitiques. "
"Analyse l'état du portefeuille simulé ci-dessous et génère des recommandations concrètes. "
"Réponds en JSON avec ce schéma exact: "
'{"assessment": "<2 phrases max sur l\'état global>", '
"You are a risk manager specializing in geopolitical options portfolios. "
"Analyze the state of the simulated portfolio below and generate concrete recommendations. "
"Respond in JSON with this exact schema: "
'{"assessment": "<max 2 sentences on the overall state>", '
'"actions": [{"priority": "high|medium", "type": "close_trade|rebalance|monitor", '
'"trade_id": <int ou null>, "underlying": "<ticker>", "reason": "<raison courte>"}], '
'"rebalance_suggestion": "<suggestion concrète de rééquilibrage en 1 phrase>"}'
'"trade_id": <int or null>, "underlying": "<ticker>", "reason": "<short reason>"}], '
'"rebalance_suggestion": "<concrete rebalancing suggestion in 1 sentence>"}'
)
user_prompt = context

View File

@@ -99,13 +99,13 @@ def get_portfolio_concentration(open_trades: List[Dict]) -> Dict[str, int]:
def build_portfolio_context_block(open_trades: List[Dict], concentration: Dict[str, int]) -> str:
"""Build a prompt section describing current portfolio for injection into AI prompts."""
if not open_trades:
return "\n## PORTEFEUILLE ACTUEL\nAucun trade en cours — portefeuille vide.\n"
return "\n## CURRENT PORTFOLIO\nNo open trades — empty portfolio.\n"
total = len(open_trades)
conc_sorted = sorted(concentration.items(), key=lambda x: -x[1])
conc_str = " | ".join(f"{cls.upper()}: {n}" for cls, n in conc_sorted)
lines: List[str] = [f"Total: {total} trade(s) ouvert(s) | Concentration: {conc_str}", ""]
lines: List[str] = [f"Total: {total} open trade(s) | Concentration: {conc_str}", ""]
for t in open_trades:
sym = t["underlying"]
@@ -118,27 +118,27 @@ def build_portfolio_context_block(open_trades: List[Dict], concentration: Dict[s
pat = t.get("pattern_name", "")
entry = t.get("entry_price")
cur = t.get("current_price")
ep_str = f"entrée {entry:.2f}actuel {cur:.2f}" if entry and cur else ""
ep_str = f"entry {entry:.2f}current {cur:.2f}" if entry and cur else ""
line = f"{sym} | {strat} [{cls}] | {held}j tenu / {rem}j restants | J-1: {m1d_str} | J-5: {m5d_str}"
line = f"{sym} | {strat} [{cls}] | {held}d held / {rem}d remaining | D-1: {m1d_str} | D-5: {m5d_str}"
if ep_str:
line += f" | {ep_str}"
if pat:
line += f" | thèse: «{pat}»"
line += f" | thesis: «{pat}»"
lines.append(line)
# Identify overweight classes
overweight = [cls for cls, n in conc_sorted if n >= 3]
ow_str = ", ".join(overweight) if overweight else "aucune"
ow_str = ", ".join(overweight) if overweight else "none"
block = (
"\n## PORTEFEUILLE ACTUEL — POSITIONS OUVERTES\n"
"\n## CURRENT PORTFOLIO — OPEN POSITIONS\n"
+ "\n".join(lines)
+ f"\n\nClasses surpondérées (≥3 trades): {ow_str}\n"
+ "\n⚠️ CONSIGNES IMPÉRATIVES (nongociables):\n"
+ "1. NE PAS suggérer un nouveau trade sur un sous-jacent déjà en portefeuille — doublement interdit.\n"
+ "2. Signaler explicitement dans 'rationale' si une suggestion CONTREDIT une position ouverte (signal de clôture potentiel).\n"
+ "3. Éviter d'alourdir une classe surpondérée (≥3 trades) sauf catalyseur exceptionnel justifié.\n"
+ "4. Un signal opposé à une position ouverte = opportunité de SORTIE à documenter, pas d'entrée inversée.\n"
+ f"\n\nOverweight classes (≥3 trades): {ow_str}\n"
+ "\n⚠️ MANDATORY RULES (non-negotiable):\n"
+ "1. DO NOT suggest a new trade on an underlying already in the portfolio — strictly forbidden.\n"
+ "2. Explicitly flag in 'rationale' if a suggestion CONTRADICTS an open position (potential exit signal).\n"
+ "3. Avoid adding to an overweight class (≥3 trades) unless an exceptional justified catalyst exists.\n"
+ "4. A signal opposing an open position = EXIT opportunity to document, not a reverse entry.\n"
)
return block