This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Benjamin Admin 21a844cb8a fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +01:00

251 lines
7.6 KiB
JavaScript

/**
* BreakPilot Studio - i18n Module
*
* Internationalisierungs-Funktionen:
* - t(key): Übersetzungsfunktion
* - setLanguage(lang): Sprache wechseln
* - applyLanguage(): UI-Texte aktualisieren
* - getCurrentLang(): Aktuelle Sprache abrufen
*
* Refactored: 2026-01-19
*/
import { translations, rtlLanguages, defaultLanguage, availableLanguages } from './translations.js';
// Aktuelle Sprache (aus localStorage oder Standard)
let currentLang = localStorage.getItem('bp_language') || defaultLanguage;
/**
* Übersetzungsfunktion
* @param {string} key - Übersetzungsschlüssel
* @returns {string} - Übersetzter Text oder Fallback
*/
export function t(key) {
const lang = translations[currentLang] || translations[defaultLanguage];
return lang[key] || translations[defaultLanguage][key] || key;
}
/**
* Aktuelle Sprache abrufen
* @returns {string} - Sprachcode (de, en, tr, etc.)
*/
export function getCurrentLang() {
return currentLang;
}
/**
* Prüft ob aktuelle Sprache RTL ist
* @returns {boolean}
*/
export function isRTL() {
return rtlLanguages.includes(currentLang);
}
/**
* Sprache wechseln
* @param {string} lang - Neuer Sprachcode
*/
export function setLanguage(lang) {
if (translations[lang]) {
currentLang = lang;
localStorage.setItem('bp_language', lang);
applyLanguage();
return true;
}
console.warn(`Language '${lang}' not available`);
return false;
}
/**
* Wendet die aktuelle Sprache auf alle UI-Elemente an
*/
export function applyLanguage() {
// RTL-Unterstützung
if (isRTL()) {
document.body.classList.add('rtl');
document.documentElement.setAttribute('dir', 'rtl');
} else {
document.body.classList.remove('rtl');
document.documentElement.setAttribute('dir', 'ltr');
}
// Alle Elemente mit data-i18n-Attribut aktualisieren
document.querySelectorAll('[data-i18n]').forEach(el => {
const key = el.getAttribute('data-i18n');
const translated = t(key);
// Verschiedene Element-Typen behandeln
if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') {
el.placeholder = translated;
} else {
el.textContent = translated;
}
});
// Elemente mit data-i18n-title für Tooltips
document.querySelectorAll('[data-i18n-title]').forEach(el => {
const key = el.getAttribute('data-i18n-title');
el.title = t(key);
});
// Elemente mit data-i18n-value für value-Attribute
document.querySelectorAll('[data-i18n-value]').forEach(el => {
const key = el.getAttribute('data-i18n-value');
el.value = t(key);
});
// Custom Event für andere Module
window.dispatchEvent(new CustomEvent('languageChanged', {
detail: { language: currentLang }
}));
}
/**
* Aktualisiert spezifische UI-Texte (Legacy-Kompatibilität)
* Diese Funktion wird von älterem Code verwendet der direkt UI-IDs referenziert
*/
export function updateUITexts() {
// Sidebar
const sidebarAreas = document.querySelector('.sidebar h4');
if (sidebarAreas) sidebarAreas.textContent = t('sidebar_areas');
// Breadcrumb / Brand
const brandSub = document.querySelector('.brand-sub');
if (brandSub) brandSub.textContent = t('brand_sub');
// Tab Labels
const navCompare = document.getElementById('nav-compare');
if (navCompare) navCompare.textContent = t('nav_compare');
const navTiles = document.getElementById('nav-tiles');
if (navTiles) navTiles.textContent = t('nav_tiles');
// Buttons
const uploadBtn = document.getElementById('uploadBtn');
if (uploadBtn) {
const textSpan = uploadBtn.querySelector('.btn-text');
if (textSpan) textSpan.textContent = t('btn_upload');
}
const deleteBtn = document.getElementById('deleteBtn');
if (deleteBtn) {
const textSpan = deleteBtn.querySelector('.btn-text');
if (textSpan) textSpan.textContent = t('btn_delete');
}
// Card Headers
document.querySelectorAll('.card-header').forEach(header => {
const icon = header.querySelector('i');
const iconHTML = icon ? icon.outerHTML : '';
// Original / Cleaned Sections
if (header.closest('.scan-section')?.classList.contains('original-scan')) {
header.innerHTML = iconHTML + ' ' + t('original_scan');
} else if (header.closest('.scan-section')?.classList.contains('cleaned-scan')) {
header.innerHTML = iconHTML + ' ' + t('cleaned_version');
}
});
// Tiles - MC
const mcTile = document.querySelector('.mc-tile');
if (mcTile) {
const title = mcTile.querySelector('.tile-content h3');
if (title) title.textContent = t('mc_title');
const desc = mcTile.querySelector('.tile-content p');
if (desc) desc.textContent = t('mc_desc');
}
// Tiles - Cloze
const clozeTile = document.querySelector('.cloze-tile');
if (clozeTile) {
const title = clozeTile.querySelector('.tile-content h3');
if (title) title.textContent = t('cloze_title');
const desc = clozeTile.querySelector('.tile-content p');
if (desc) desc.textContent = t('cloze_desc');
}
// Tiles - Q&A
const qaTile = document.querySelector('.qa-tile');
if (qaTile) {
const title = qaTile.querySelector('.tile-content h3');
if (title) title.textContent = t('qa_title');
const desc = qaTile.querySelector('.tile-content p');
if (desc) desc.textContent = t('qa_desc');
}
// Tiles - Mindmap
const mindmapTile = document.querySelector('.mindmap-tile');
if (mindmapTile) {
const title = mindmapTile.querySelector('.tile-content h3');
if (title) title.textContent = t('mindmap_title');
const desc = mindmapTile.querySelector('.tile-content p');
if (desc) desc.textContent = t('mindmap_desc');
}
// Footer
const imprintLink = document.querySelector('footer a[href*="imprint"]');
if (imprintLink) imprintLink.textContent = t('imprint');
const privacyLink = document.querySelector('footer a[href*="privacy"]');
if (privacyLink) privacyLink.textContent = t('privacy');
const contactLink = document.querySelector('footer a[href*="contact"]');
if (contactLink) contactLink.textContent = t('contact');
// Process Button
const fullProcessBtn = document.getElementById('fullProcessBtn');
if (fullProcessBtn) {
const textSpan = fullProcessBtn.querySelector('.btn-text');
if (textSpan) textSpan.textContent = t('btn_full_process');
}
// Status Bar
const statusText = document.getElementById('statusText');
if (statusText && statusText.textContent === 'Bereit' || statusText?.textContent === 'Ready') {
statusText.textContent = t('status_ready');
}
}
/**
* Initialisiert das Sprachwahl-UI
* @param {string} containerId - ID des Containers für Sprachauswahl
*/
export function initLanguageSelector(containerId = 'language-selector') {
const container = document.getElementById(containerId);
if (!container) return;
// Dropdown erstellen
container.innerHTML = `
<select id="language-select" class="language-select">
${Object.entries(availableLanguages).map(([code, name]) =>
`<option value="${code}" ${code === currentLang ? 'selected' : ''}>${name}</option>`
).join('')}
</select>
`;
// Event Handler
const select = document.getElementById('language-select');
if (select) {
select.addEventListener('change', (e) => {
setLanguage(e.target.value);
});
}
}
/**
* Formatiert einen Status-Text mit Platzhaltern
* @param {string} key - Übersetzungsschlüssel
* @param {Object} vars - Variablen zum Einsetzen
* @returns {string} - Formatierter Text
*/
export function tFormat(key, vars = {}) {
let text = t(key);
Object.entries(vars).forEach(([k, v]) => {
text = text.replace(new RegExp(`\\{${k}\\}`, 'g'), v);
});
return text;
}
// Re-export für Convenience
export { translations, rtlLanguages, defaultLanguage, availableLanguages };