sadar / frontend /src /i18n.tsx
Ruperth's picture
feat: add team member names and fix spanish tower controller role
cb75520
Raw
History Blame Contribute Delete
8.31 kB
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];
}