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];
}