diff --git a/simulator-ui/src/main.tsx b/simulator-ui/src/main.tsx index abd961e..94e467c 100644 --- a/simulator-ui/src/main.tsx +++ b/simulator-ui/src/main.tsx @@ -30,58 +30,65 @@ type LogEntry = { createdAt: string; }; -const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || window.location.origin; +type Language = "en" | "fr"; +type Translation = { + en: string; + fr: string; +}; -const scenarios: Array<{ key: ScenarioKey; title: string; description: string }> = [ +const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || window.location.origin; +const LANGUAGE_STORAGE_KEY = "plast-track-simulator-language"; + +const scenarios: Array<{ key: ScenarioKey; title: Translation; description: Translation }> = [ { key: "power_on", - title: "Power ON", - description: "Simulate machine power available.", + title: { en: "Power ON", fr: "Marche ON" }, + description: { en: "Simulate machine power available.", fr: "Simuler la presence alimentation machine." }, }, { key: "power_off", - title: "Power OFF", - description: "Close open downtime and move out of planning.", + title: { en: "Power OFF", fr: "Arret OFF" }, + description: { en: "Close open downtime and move out of planning.", fr: "Fermer l'arret ouvert et sortir du planning." }, }, { key: "cycle_completed", - title: "Single Cycle", - description: "Publish one normalized completed cycle.", + title: { en: "Single Cycle", fr: "Cycle simple" }, + description: { en: "Publish one normalized completed cycle.", fr: "Publier un cycle termine normalise." }, }, { key: "cycle_burst", - title: "Cycle Burst", - description: "Generate many cycles with the selected cycle time.", + title: { en: "Cycle Burst", fr: "Rafale cycles" }, + description: { en: "Generate many cycles with the selected cycle time.", fr: "Generer plusieurs cycles avec le temps choisi." }, }, { key: "cycle_signal_edge", - title: "Passive Edge", - description: "Toggle cycle_signal using the machine configured edge.", + title: { en: "Passive Edge", fr: "Front passif" }, + description: { en: "Toggle cycle_signal using the machine configured edge.", fr: "Basculer cycle_signal selon le front configure." }, }, { key: "debounce_noise", - title: "Debounce Noise", - description: "Send a too-fast edge that should be ignored.", + title: { en: "Debounce Noise", fr: "Bruit anti-rebond" }, + description: { en: "Send a too-fast edge that should be ignored.", fr: "Envoyer un front trop rapide qui doit etre ignore." }, }, { key: "machine_stopped", - title: "Open Stop", - description: "Force an unqualified downtime if an OF is running.", + title: { en: "Open Stop", fr: "Ouvrir arret" }, + description: { en: "Force an unqualified downtime if an OF is running.", fr: "Forcer un arret non qualifie si un OF est en cours." }, }, { key: "recover_with_cycle", - title: "Recover", - description: "Close downtime by producing a new cycle.", + title: { en: "Recover", fr: "Reprise" }, + description: { en: "Close downtime by producing a new cycle.", fr: "Fermer l'arret avec un nouveau cycle." }, }, { key: "alarm_on", - title: "Alarm ON", - description: "Raise the general_alarm passive signal.", + title: { en: "Alarm ON", fr: "Alarme ON" }, + description: { en: "Raise the general_alarm passive signal.", fr: "Activer le signal passif general_alarm." }, }, { key: "alarm_off", - title: "Alarm OFF", - description: "Clear the general_alarm passive signal.", + title: { en: "Alarm OFF", fr: "Alarme OFF" }, + description: { en: "Clear the general_alarm passive signal.", fr: "Desactiver le signal passif general_alarm." }, }, ]; @@ -113,6 +120,10 @@ function parseInputValue(value: string): boolean | number | string { } function App() { + const [language, setLanguage] = useState(() => { + const savedLanguage = localStorage.getItem(LANGUAGE_STORAGE_KEY); + return savedLanguage === "en" || savedLanguage === "fr" ? savedLanguage : "fr"; + }); const [machines, setMachines] = useState([]); const [selectedMachine, setSelectedMachine] = useState(""); const [cycleTimeSec, setCycleTimeSec] = useState(20); @@ -122,15 +133,27 @@ function App() { const [loading, setLoading] = useState(false); const [logs, setLogs] = useState([]); + function t(en: string, fr: string) { + return language === "fr" ? fr : en; + } + + function tt(value: Translation) { + return value[language]; + } + useEffect(() => { apiRequest("/api/machines") .then((items) => { setMachines(items); setSelectedMachine((current) => current || items[0]?.code || ""); }) - .catch((error: Error) => addLog("Load machines", "error", error.message)); + .catch((error: Error) => addLog(t("Load machines", "Chargement machines"), "error", error.message)); }, []); + useEffect(() => { + localStorage.setItem(LANGUAGE_STORAGE_KEY, language); + }, [language]); + function addLog(label: string, status: LogEntry["status"], details: string) { setLogs((current) => [ { @@ -146,7 +169,7 @@ function App() { async function runScenario(scenario: ScenarioKey) { if (!selectedMachine) { - addLog("Scenario blocked", "error", "Select a machine first."); + addLog(t("Scenario blocked", "Scenario bloque"), "error", t("Select a machine first.", "Selectionner une machine d'abord.")); return; } setLoading(true); @@ -160,9 +183,10 @@ function App() { count: burstCount, }), }); - addLog(scenario, "ok", JSON.stringify(result, null, 2)); + const scenarioLabel = scenarios.find((item) => item.key === scenario)?.title; + addLog(scenarioLabel ? tt(scenarioLabel) : scenario, "ok", JSON.stringify(result, null, 2)); } catch (error) { - addLog(scenario, "error", error instanceof Error ? error.message : "Unknown error"); + addLog(scenario, "error", error instanceof Error ? error.message : t("Unknown error", "Erreur inconnue")); } finally { setLoading(false); } @@ -171,7 +195,7 @@ function App() { async function sendRawInput(event: FormEvent) { event.preventDefault(); if (!selectedMachine || !rawInputName.trim()) { - addLog("Raw input blocked", "error", "Machine and input name are required."); + addLog(t("Raw input blocked", "Entree brute bloquee"), "error", t("Machine and input name are required.", "Machine et nom entree sont obligatoires.")); return; } setLoading(true); @@ -187,28 +211,40 @@ function App() { }); addLog(`raw:${rawInputName}`, "ok", JSON.stringify(result, null, 2)); } catch (error) { - addLog("Raw input", "error", error instanceof Error ? error.message : "Unknown error"); + addLog(t("Raw input", "Entree brute"), "error", error instanceof Error ? error.message : t("Unknown error", "Erreur inconnue")); } finally { setLoading(false); } } const selectedMachineInfo = machines.find((machine) => machine.code === selectedMachine); + const machinePowerLabel = selectedMachineInfo?.powered_on ? t("Powered ON", "Sous tension") : t("Powered OFF", "Hors tension"); return (
-

Separate test console

-

Passive Signal Simulator

+

{t("Separate test console", "Console de test separee")}

+

{t("Passive Signal Simulator", "Simulateur signaux passifs")}

- Simulate cycles, power states, stops, alarms, signal edges, debounce noise, and raw digital inputs without - adding test controls to the production dashboard. + {t( + "Simulate cycles, power states, stops, alarms, signal edges, debounce noise, and raw digital inputs without adding test controls to the production dashboard.", + "Simuler cycles, etats alimentation, arrets, alarmes, fronts signal, bruit anti-rebond et entrees digitales brutes sans ajouter de controles de test au tableau de production.", + )}

-
- Backend API - {API_BASE_URL} +
+ +
+ {t("Backend API", "API backend")} + {API_BASE_URL} +
@@ -216,16 +252,16 @@ function App() {
-

Target

-

Machine

+

{t("Target", "Cible")}

+

{t("Machine", "Machine")}