import "server-only"; import { createHash } from "node:crypto"; import { dictionaries } from "./dictionaries"; import { flattenDict } from "./flatten"; /** Aktuelle deutsche Vorlage als flache Schlüssel-Wert-Paare – Basis * für Export (Vorlage für Übersetzer), Vollständigkeits- und * Veraltet-Prüfung. */ export function currentTemplate(): Record { return flattenDict(dictionaries.de); } /** Hash über die (sortierte) Schlüsselliste der aktuellen Vorlage – * ändert sich nur, wenn Schlüssel hinzukommen/wegfallen, nicht bei * reinen Text-/Tippfehler-Korrekturen am deutschen Text. */ export function currentTemplateHash(): string { const keys = Object.keys(currentTemplate()).sort(); return createHash("sha256").update(keys.join("\n")).digest("hex"); } export interface LanguageCompleteness { total: number; translated: number; percent: number; missingKeys: string[]; } export function computeCompleteness( translated: Record ): LanguageCompleteness { const templateKeys = Object.keys(currentTemplate()); const missingKeys = templateKeys.filter((k) => !(k in translated)); const translatedCount = templateKeys.length - missingKeys.length; return { total: templateKeys.length, translated: translatedCount, percent: templateKeys.length === 0 ? 100 : Math.round((translatedCount / templateKeys.length) * 100), missingKeys, }; } function extractPlaceholders(text: string): Set { return new Set([...text.matchAll(/\{(\w+)\}/g)].map((m) => m[1])); } export interface ImportValidationResult { entries: { key: string; value: string }[]; skipped: { key: string; reason: string }[]; } /** Prüft eine hochgeladene Übersetzung gegen die aktuelle Vorlage: * unbekannte Schlüssel werden stillschweigend ignoriert (z. B. von * einer älteren Vorlagen-Version), Zeilen mit fehlenden Platzhaltern * wie {message} übersprungen statt kaputte Texte zu übernehmen. */ export function validateImport( uploaded: Record ): ImportValidationResult { const template = currentTemplate(); const entries: { key: string; value: string }[] = []; const skipped: { key: string; reason: string }[] = []; for (const [key, rawValue] of Object.entries(uploaded)) { const templateValue = template[key]; if (templateValue === undefined) continue; if (typeof rawValue !== "string" || !rawValue.trim()) { skipped.push({ key, reason: "empty" }); continue; } const expected = extractPlaceholders(templateValue); const actual = extractPlaceholders(rawValue); const missing = [...expected].filter((p) => !actual.has(p)); if (missing.length > 0) { skipped.push({ key, reason: `missing_placeholder:${missing[0]}` }); continue; } entries.push({ key, value: rawValue }); } return { entries, skipped }; }