File size: 8,309 Bytes
96bba34 cb75520 96bba34 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 | import { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from "react";
export type Lang = "en" | "es";
interface Panel {
title: string;
body: string;
}
interface Dict {
appTitle: string;
nav: { monitor: string; simulator: string; metrics: string; presentation: string };
monitor: { mostAnomalous: string; offline: string; actual: string; reconstructed: string; scoreTitle: string };
alert: { alert: string; nominal: string; peak: string; threshold: string; latency: string };
simulator: {
baseFlight: string;
injectedAnomaly: string;
intensity: string;
onset: string;
onsetSuffix: string;
original: string;
injected: string;
kinds: Record<string, string>;
};
metrics: {
title: string;
model: string;
realRoc: string;
realPr: string;
synRoc: string;
none: string;
selected: string;
};
timeline: { threshold: string };
scenes: {
skip: string;
enter: string;
finalNav: string;
continueToConsole: string;
terminal: { kicker: string; title: string; sub: string };
jetbridge: { kicker: string; title: string };
cabin: { kicker: string; title: string; sub?: string };
closing: { kicker: string; title: string };
explainHeader: string;
panels: Panel[];
opening: { kicker: string; title: string };
leaving: { kicker: string; title: string };
ack: {
kicker: string;
subtitle: string;
teamHeader: string;
thanks: string;
roles: { tower1: string; pilot: string; tower2: string };
};
};
}
const EN: Dict = {
appTitle: "TOWER CONSOLE · LEMD-9F4C",
nav: { monitor: "Monitor", simulator: "Simulator", metrics: "Metrics", presentation: "Presentation" },
monitor: {
mostAnomalous: "Most anomalous windows",
offline: "backend offline. start it with: make serve",
actual: "actual",
reconstructed: "reconstructed",
scoreTitle: "Anomaly score",
},
alert: { alert: "ALERT", nominal: "NOMINAL", peak: "peak", threshold: "thr", latency: "latency" },
simulator: {
baseFlight: "Base flight (normal)",
injectedAnomaly: "Injected anomaly",
intensity: "Intensity",
onset: "Onset",
onsetSuffix: "% into the window",
original: "original",
injected: "injected",
kinds: {
route_deviation: "Route deviation",
altitude: "Altitude bust",
speed: "Speed anomaly",
holding: "Holding turns",
freeze: "Transponder cut",
},
},
metrics: {
title: "Model comparison on held-out data",
model: "Model",
realRoc: "real ROC",
realPr: "real PR-AUC",
synRoc: "syn ROC",
none: "no metrics yet. generate them with: make compare",
selected: "selected final model",
},
timeline: { threshold: "THRESHOLD" },
scenes: {
skip: "Skip",
enter: "Enter the console",
finalNav: "Final ▸",
continueToConsole: "Continue to the console",
terminal: {
kicker: "Madrid Barajas / LEMD",
title: "You walk through the terminal",
sub: "Every day, around a thousand flights follow the same quiet pattern.",
},
jetbridge: { kicker: "Boarding", title: "Down the jet bridge" },
cabin: { kicker: "On board", title: "Take your seat" },
closing: { kicker: "Doors closed", title: "The flight begins" },
explainHeader: "Air traffic control / LEMD",
panels: [
{
title: "From the ground, we look after every flight",
body: "While you fly, the control tower keeps watch, making sure your route is the one it should be.",
},
{
title: "Every route checked against the pattern",
body: "We watch over your flight from start to finish, and anything that breaks the rhythm of flight stands out.",
},
{
title: "Deviations and incidents, caught early",
body: "An odd route, an altitude that does not fit, a transponder that goes silent: spotted as it begins, not after.",
},
],
opening: { kicker: "Destination", title: "Arrived safely" },
leaving: { kicker: "Conformance confirmed", title: "You walk out" },
ack: {
kicker: "Thank you",
subtitle: "Smart Anomaly Detection for Aviation Routes",
teamHeader: "The team",
thanks: "Thank you.",
roles: {
tower1: "Approach controller",
pilot: "Pilot",
tower2: "Tower controller",
},
},
},
};
const ES: Dict = {
appTitle: "TOWER CONSOLE · LEMD-9F4C",
nav: { monitor: "Monitor", simulator: "Simulador", metrics: "Métricas", presentation: "Presentación" },
monitor: {
mostAnomalous: "Ventanas más anómalas",
offline: "backend apagado. arráncalo con: make serve",
actual: "real",
reconstructed: "reconstruido",
scoreTitle: "Score de anomalía",
},
alert: { alert: "ALERTA", nominal: "NORMAL", peak: "pico", threshold: "umbral", latency: "latencia" },
simulator: {
baseFlight: "Vuelo base (normal)",
injectedAnomaly: "Anomalía inyectada",
intensity: "Intensidad",
onset: "Inicio",
onsetSuffix: "% de la ventana",
original: "original",
injected: "inyectado",
kinds: {
route_deviation: "Desvío de ruta",
altitude: "Anomalía de altitud",
speed: "Anomalía de velocidad",
holding: "Giros de espera",
freeze: "Corte de transpondedor",
},
},
metrics: {
title: "Comparativa de modelos (datos no vistos)",
model: "Modelo",
realRoc: "ROC real",
realPr: "PR-AUC real",
synRoc: "ROC sint",
none: "sin métricas. genéralas con: make compare",
selected: "modelo final elegido",
},
timeline: { threshold: "UMBRAL" },
scenes: {
skip: "Saltar",
enter: "Entrar a la consola",
finalNav: "Final ▸",
continueToConsole: "Continuar a la consola",
terminal: {
kicker: "Madrid Barajas / LEMD",
title: "Cruzas la terminal",
sub: "Cada día, unos mil vuelos siguen el mismo patrón tranquilo.",
},
jetbridge: { kicker: "Embarque", title: "Por el pasillo de embarque" },
cabin: { kicker: "A bordo", title: "Toma asiento" },
closing: { kicker: "Puertas cerradas", title: "Comienza el vuelo" },
explainHeader: "Control de tráfico aéreo / LEMD",
panels: [
{
title: "Desde tierra, cuidamos de cada vuelo",
body: "Mientras vuelas, la torre de control vigila, asegurándose de que tu ruta es la que debe ser.",
},
{
title: "Cada ruta, comparada con el patrón",
body: "Vigilamos tu vuelo de principio a fin, y todo lo que rompa la cadencia del vuelo salta a la vista.",
},
{
title: "Desviaciones e incidencias, a tiempo",
body: "Una ruta rara, una altitud que no encaja, un transpondedor que se calla: se detecta en cuanto empieza, no después.",
},
],
opening: { kicker: "Destino", title: "Has llegado" },
leaving: { kicker: "Conformidad confirmada", title: "Sales caminando" },
ack: {
kicker: "",
subtitle: "Detección inteligente de anomalías en rutas aéreas",
teamHeader: "El equipo",
thanks: "Gracias.",
roles: {
tower1: "Control de aproximación",
pilot: "Piloto",
tower2: "Torre de control",
},
},
},
};
const DICTS: Record<Lang, Dict> = { en: EN, es: ES };
interface LangContextValue {
lang: Lang;
setLang: (lang: Lang) => void;
toggle: () => void;
}
const LangContext = createContext<LangContextValue>({ lang: "en", setLang: () => { }, toggle: () => { } });
export function LanguageProvider({ children }: { children: ReactNode }) {
const [lang, setLang] = useState<Lang>(() => {
const saved = localStorage.getItem("sadar-lang");
if (saved === "en" || saved === "es") return saved;
return navigator.language.startsWith("es") ? "es" : "en";
});
useEffect(() => {
localStorage.setItem("sadar-lang", lang);
}, [lang]);
const value = useMemo<LangContextValue>(
() => ({ lang, setLang, toggle: () => setLang(lang === "en" ? "es" : "en") }),
[lang],
);
return <LangContext.Provider value={value}>{children}</LangContext.Provider>;
}
export function useLang(): LangContextValue {
return useContext(LangContext);
}
export function useT(): Dict {
return DICTS[useContext(LangContext).lang];
}
|