import type { Request } from "express"; import type { Lang } from "./ruleCatalogI18n"; import { normalizeLang } from "./ruleCatalogI18n"; export type { Lang } from "./ruleCatalogI18n"; export { normalizeLang, SUPPORTED_LANGS } from "./ruleCatalogI18n"; type MessageKey = | "aiRulesDisabled" | "aiNoProvider" | "aiNoToken" | "aiUnknownError" | "invalidId" | "invalidInput" | "scanNotFound" | "ruleNotFound" | "promptNotFound" | "providerNotFound" | "rateLimited" | "zipMissing" | "fileMissing" | "textMissing" | "noAnalyzableFiles" | "skillUnreadable" | "analysisFailed" | "noDownloadableFiles" | "onlyPassedDownloadable" | "descriptionFailed" | "noApiTokenPlain" | "noApiTokenProvided" | "modelsLoadFailed" | "connSuccessReply" | "connSuccessModels" | "connSuccessNoModels" | "connReplyEmpty" | "connFailed"; const MESSAGES: Record> = { aiRulesDisabled: { de: "KI-Regeln sind im Regelwerk deaktiviert.", en: "AI rules are disabled in the rule set.", es: "Las reglas de IA están desactivadas en el conjunto de reglas.", }, aiNoProvider: { de: "Kein aktiver KI-Provider konfiguriert. Bitte im Admin-Bereich einrichten.", en: "No active AI provider configured. Please set one up in the admin area.", es: "No hay ningún proveedor de IA activo configurado. Configúrelo en el área de administración.", }, aiNoToken: { de: 'Für den Provider "{name}" ist kein API-Token hinterlegt.', en: 'No API token is stored for the provider "{name}".', es: 'No hay ningún token de API almacenado para el proveedor "{name}".', }, aiUnknownError: { de: "Unbekannter KI-Fehler", en: "Unknown AI error", es: "Error de IA desconocido", }, invalidId: { de: "Ungültige ID", en: "Invalid ID", es: "ID no válido", }, invalidInput: { de: "Ungültige Eingabe", en: "Invalid input", es: "Entrada no válida", }, scanNotFound: { de: "Scan nicht gefunden", en: "Scan not found", es: "Análisis no encontrado", }, ruleNotFound: { de: "Regel nicht gefunden", en: "Rule not found", es: "Regla no encontrada", }, promptNotFound: { de: "Prompt nicht gefunden", en: "Prompt not found", es: "Prompt no encontrado", }, providerNotFound: { de: "Provider nicht gefunden", en: "Provider not found", es: "Proveedor no encontrado", }, rateLimited: { de: "Zu viele Scans in kurzer Zeit. Bitte später erneut versuchen.", en: "Too many scans in a short time. Please try again later.", es: "Demasiados análisis en poco tiempo. Inténtelo de nuevo más tarde.", }, zipMissing: { de: "ZIP-Inhalt fehlt.", en: "ZIP content is missing.", es: "Falta el contenido del ZIP.", }, fileMissing: { de: "Dateiinhalt fehlt.", en: "File content is missing.", es: "Falta el contenido del archivo.", }, textMissing: { de: "Text fehlt.", en: "Text is missing.", es: "Falta el texto.", }, noAnalyzableFiles: { de: "Keine analysierbaren Dateien gefunden.", en: "No analyzable files found.", es: "No se encontraron archivos analizables.", }, skillUnreadable: { de: "Das Skill konnte nicht gelesen werden. Bitte prüfen Sie das Format (gültiges ZIP / Textdatei).", en: "The skill could not be read. Please check the format (valid ZIP / text file).", es: "No se pudo leer la skill. Compruebe el formato (ZIP válido / archivo de texto).", }, analysisFailed: { de: "Die Analyse ist fehlgeschlagen.", en: "The analysis failed.", es: "El análisis falló.", }, noDownloadableFiles: { de: "Für dieses Skill sind keine herunterladbaren Dateien gespeichert.", en: "No downloadable files are stored for this skill.", es: "No hay archivos descargables almacenados para esta skill.", }, onlyPassedDownloadable: { de: "Nur Skills mit dem Ergebnis „Bestanden“ können heruntergeladen werden.", en: "Only skills with a “Passed” result can be downloaded.", es: "Solo se pueden descargar las skills con resultado «Aprobado».", }, descriptionFailed: { de: "Die Beschreibung konnte nicht erzeugt werden. Bitte Provider-Konfiguration und KI-Prompts prüfen.", en: "The description could not be generated. Please check the provider configuration and AI prompts.", es: "No se pudo generar la descripción. Compruebe la configuración del proveedor y los prompts de IA.", }, noApiTokenPlain: { de: "Kein API-Token hinterlegt.", en: "No API token stored.", es: "No hay ningún token de API almacenado.", }, noApiTokenProvided: { de: "Kein API-Token angegeben.", en: "No API token provided.", es: "No se proporcionó ningún token de API.", }, modelsLoadFailed: { de: "Modelle konnten nicht geladen werden.", en: "Models could not be loaded.", es: "No se pudieron cargar los modelos.", }, connSuccessReply: { de: "Verbindung erfolgreich. Antwort: {reply}", en: "Connection successful. Reply: {reply}", es: "Conexión correcta. Respuesta: {reply}", }, connSuccessModels: { de: "Verbindung erfolgreich. {count} Modelle verfügbar.", en: "Connection successful. {count} models available.", es: "Conexión correcta. {count} modelos disponibles.", }, connSuccessNoModels: { de: "Verbindung erfolgreich. Es wurden keine Modelle gefunden – bitte das Modell manuell eingeben.", en: "Connection successful. No models were found – please enter the model manually.", es: "Conexión correcta. No se encontraron modelos: introduzca el modelo manualmente.", }, connReplyEmpty: { de: "(leer)", en: "(empty)", es: "(vacío)", }, connFailed: { de: "Verbindung fehlgeschlagen.", en: "Connection failed.", es: "La conexión falló.", }, }; export function t( key: MessageKey, lang: Lang, vars?: Record, ): string { let msg = MESSAGES[key][lang]; if (vars) { for (const [k, v] of Object.entries(vars)) { msg = msg.replace(`{${k}}`, v); } } return msg; } // Phrases injected into AI prompts so the model produces output in the // requested language. The directive overrides any language line baked into the // stored prompts (which historically said "Antworte auf Deutsch."). const LANGUAGE_DIRECTIVE: Record = { de: "WICHTIG: Verfasse alle Ausgabetexte (Beschreibung, Findings, Empfehlungen) ausschließlich auf Deutsch.", en: "IMPORTANT: Write all output text (description, findings, remediation) exclusively in English.", es: "IMPORTANTE: Redacta todos los textos de salida (descripción, hallazgos, recomendaciones) exclusivamente en español.", }; export function languageDirective(lang: Lang): string { return LANGUAGE_DIRECTIVE[lang]; } // Resolve the language for a request's user-facing error messages. Prefers an // explicit `?lang=` query param, then the `Accept-Language` header (the web // client sends the active UI language), defaulting to German. export function reqLang(req: Request): Lang { const q = req.query?.lang; if (typeof q === "string" && q) return normalizeLang(q); const header = req.headers["accept-language"]; if (typeof header === "string" && header) { return normalizeLang(header.split(",")[0].trim().slice(0, 2)); } return "de"; }