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>
251 lines
7.6 KiB
JavaScript
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 };
|