Englische Übersetzungen für die restlichen Admin-Seiten

API-Schlüssel, Integration & Wartung, Voucher-Import, Backup & Restore,
Reporting, Sicherheit (2FA) und Audit-Log waren fest auf Deutsch
verdrahtet, obwohl in der Kopfzeile ein DE/EN-Umschalter sitzt. Diese
Seiten laufen jetzt komplett über lang/de.php bzw. lang/en.php.

- rund 200 neue Sprachschlüssel, beide Dateien deckungsgleich
- Aktionsnamen im Audit-Log werden übersetzt statt fest ausgegeben
- Bestätigungsdialoge und Statusmeldungen in JavaScript ebenfalls
- admin/security.php initialisiert jetzt I18n und setzt <html lang>
  passend zur Auswahl

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Friederich Loheide 2026-09-23 06:34:25 +00:00
parent 30d0ce3a23
commit 850a04d628
16 changed files with 580 additions and 196 deletions

View file

@ -49,13 +49,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['save'])) {
$db->setSetting('cleanup_audit_days', max(0, (int)($_POST['cleanup_audit_days'] ?? 0)));
$db->setSetting('cleanup_login_days', max(0, (int)($_POST['cleanup_login_days'] ?? 30)));
$auth->writeAuditLog($_SESSION['user_id'], 'settings_update', 'config', null, 'Integration/Wartung gespeichert');
$success = 'Einstellungen gespeichert.';
$success = __('settings_saved');
}
}
if (isset($_GET['test_webhook']) && isset($_GET['token']) && $auth->validateCsrfToken($_GET['token'])) {
Notifier::send('✅ Test-Benachrichtigung vom UniFi Voucher System.', ['type' => 'test']);
$success = 'Test-Benachrichtigung gesendet (sofern Webhook aktiv & URL gültig).';
$success = __('int_webhook_test_sent');
}
$enforce2fa = $db->getSetting('enforce_2fa_admins', '0') === '1';
@ -92,12 +92,12 @@ $adminBase = '';
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Integration & Wartung <?= htmlspecialchars($appTitle) ?></title>
<title><?= __('int_title') ?> <?= htmlspecialchars($appTitle) ?></title>
<?php require __DIR__ . '/../includes/admin_nav.php'; ?>
<div class="page-header">
<div>
<h1 class="page-title">Integration &amp; Wartung</h1>
<p class="page-subtitle">SSO, Webhooks, SMS-Versand und Aufbewahrungsfristen.</p>
<h1 class="page-title"><?= __('int_title') ?></h1>
<p class="page-subtitle"><?= __('int_subtitle') ?></p>
</div>
</div>
@ -108,64 +108,64 @@ $adminBase = '';
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrf) ?>">
<div class="card">
<h2>Sicherheitsrichtlinie</h2>
<p class="muted">Erzwingt Zwei-Faktor-Authentifizierung für alle Administrator-Konten (lokale Accounts). Admins ohne 2FA werden bei der nächsten Aktion zur Einrichtung geleitet.</p>
<label class="chk"><input type="checkbox" name="enforce_2fa_admins" <?= $enforce2fa ? 'checked' : '' ?>> 2FA für Administratoren verpflichtend</label>
<label>Tageslimit Voucher pro Nicht-Admin-Benutzer (0 = unbegrenzt)</label>
<h2><?= __('int_security') ?></h2>
<p class="muted"><?= __('int_security_hint') ?></p>
<label class="chk"><input type="checkbox" name="enforce_2fa_admins" <?= $enforce2fa ? 'checked' : '' ?>> <?= __('int_enforce_2fa') ?></label>
<label><?= __('int_daily_limit') ?></label>
<input class="input" type="number" min="0" name="user_daily_voucher_limit" value="<?= $dailyLimit ?>" style="max-width:200px;">
<label>Session-Speicher</label>
<label><?= __('int_session_driver') ?></label>
<select class="input" name="session_driver" style="max-width:340px;">
<option value="php" <?= $sessionDriver==='php'?'selected':'' ?>>PHP-Standard (Dateien)</option>
<option value="db" <?= $sessionDriver==='db'?'selected':'' ?>>Datenbank (ermöglicht „überall abmelden")</option>
<option value="php" <?= $sessionDriver==='php'?'selected':'' ?>><?= __('int_session_php') ?></option>
<option value="db" <?= $sessionDriver==='db'?'selected':'' ?>><?= __('int_session_db') ?></option>
</select>
<label>Captcha im öffentlichen Modus</label>
<label><?= __('int_captcha') ?></label>
<select class="input" name="captcha_mode" style="max-width:340px;">
<option value="off" <?= $captchaMode==='off'?'selected':'' ?>>Aus</option>
<option value="math" <?= $captchaMode==='math'?'selected':'' ?>>Rechenaufgabe (ohne externen Dienst)</option>
<option value="off" <?= $captchaMode==='off'?'selected':'' ?>><?= __('int_captcha_off') ?></option>
<option value="math" <?= $captchaMode==='math'?'selected':'' ?>><?= __('int_captcha_math') ?></option>
<option value="hcaptcha" <?= $captchaMode==='hcaptcha'?'selected':'' ?>>hCaptcha</option>
</select>
<div class="row3" style="margin-top:10px;">
<div><label>hCaptcha Site-Key</label><input class="input" type="text" name="captcha_site_key" value="<?= htmlspecialchars($captchaSiteKey) ?>"></div>
<div><label>hCaptcha Secret<?= $captchaSecretSet ? ' (gesetzt)' : '' ?></label><input class="input" type="password" name="captcha_secret" placeholder="<?= $captchaSecretSet ? '••••••• (leer = unverändert)' : '' ?>"></div>
<div><label>hCaptcha Secret<?= $captchaSecretSet ? __('int_secret_set') : '' ?></label><input class="input" type="password" name="captcha_secret" placeholder="<?= $captchaSecretSet ? __('int_secret_placeholder') : '' ?>"></div>
</div>
</div>
<div class="card">
<h2>Reverse-Proxy</h2>
<p class="muted">IP-Adressen vertrauenswürdiger Proxies (kommasepariert). Nur dann wird die echte Client-IP aus <code>X-Forwarded-For</code> für Rate-Limit & Audit verwendet.</p>
<h2><?= __('int_proxy') ?></h2>
<p class="muted"><?= __('int_proxy_hint') ?> <code>X-Forwarded-For</code> <?= __('int_proxy_hint2') ?></p>
<input class="input" type="text" name="trusted_proxy" value="<?= htmlspecialchars($trustedProxy) ?>" placeholder="z.B. 10.0.0.1, 172.18.0.1">
</div>
<div class="card">
<h2>Webhook-Benachrichtigungen</h2>
<p class="muted">Slack-, Microsoft-Teams- oder generische JSON-Webhook-URL. Wird bei Voucher-Erstellung ausgelöst.</p>
<label class="chk"><input type="checkbox" name="webhook_enabled" <?= $webhookEnabled ? 'checked' : '' ?>> Webhook aktiv</label>
<label>Webhook-URL</label>
<h2><?= __('int_webhook') ?></h2>
<p class="muted"><?= __('int_webhook_hint') ?></p>
<label class="chk"><input type="checkbox" name="webhook_enabled" <?= $webhookEnabled ? 'checked' : '' ?>> <?= __('int_webhook_active') ?></label>
<label><?= __('int_webhook_url') ?></label>
<input class="input" type="url" name="webhook_url" value="<?= htmlspecialchars($webhookUrl) ?>" placeholder="https://hooks.slack.com/services/…">
<div style="margin-top:12px;">
<a class="btn btn-secondary" href="?test_webhook=1&token=<?= urlencode($csrf) ?>">Test senden</a>
<a class="btn btn-secondary" href="?test_webhook=1&token=<?= urlencode($csrf) ?>"><?= __('int_webhook_test') ?></a>
</div>
</div>
<div class="card">
<h2>SMS-Versand (Twilio)</h2>
<p class="muted">Voucher-Codes optional per SMS versenden. Erfordert ein Twilio-Konto.</p>
<label class="chk"><input type="checkbox" name="sms_enabled" <?= $smsEnabled ? 'checked' : '' ?>> SMS-Versand aktiv</label>
<h2><?= __('int_sms') ?></h2>
<p class="muted"><?= __('int_sms_hint') ?></p>
<label class="chk"><input type="checkbox" name="sms_enabled" <?= $smsEnabled ? 'checked' : '' ?>> <?= __('int_sms_active') ?></label>
<div class="row3" style="margin-top:10px;">
<div><label>Account SID</label><input class="input" type="text" name="twilio_sid" value="<?= htmlspecialchars($twilioSid) ?>"></div>
<div><label>Auth Token<?= $twilioTokenSet ? ' (gesetzt)' : '' ?></label><input class="input" type="password" name="twilio_token" placeholder="<?= $twilioTokenSet ? '••••••• (leer = unverändert)' : '' ?>"></div>
<div><label>Absender (From)</label><input class="input" type="text" name="twilio_from" value="<?= htmlspecialchars($twilioFrom) ?>" placeholder="+49…"></div>
<div><label>Auth Token<?= $twilioTokenSet ? __('int_secret_set') : '' ?></label><input class="input" type="password" name="twilio_token" placeholder="<?= $twilioTokenSet ? __('int_secret_placeholder') : '' ?>"></div>
<div><label><?= __('int_sms_from') ?></label><input class="input" type="text" name="twilio_from" value="<?= htmlspecialchars($twilioFrom) ?>" placeholder="+49…"></div>
</div>
</div>
<div class="card">
<h2>Single Sign-On (OpenID Connect)</h2>
<p class="muted">Generischer OIDC-Provider (z.B. Keycloak, Authentik, Google, Auth0). Redirect-URI: <code><?= htmlspecialchars(((!empty($_SERVER['HTTPS'])&&$_SERVER['HTTPS']!=='off')?'https':'http').'://'.$_SERVER['HTTP_HOST'].rtrim(dirname($_SERVER['SCRIPT_NAME']),'/').'/../oidc_callback.php') ?></code></p>
<label class="chk"><input type="checkbox" name="oidc_enabled" <?= $oidcEnabled ? 'checked' : '' ?>> OIDC-Login aktiv</label>
<h2><?= __('int_sso') ?></h2>
<p class="muted"><?= __('int_sso_hint') ?> <code><?= htmlspecialchars(((!empty($_SERVER['HTTPS'])&&$_SERVER['HTTPS']!=='off')?'https':'http').'://'.$_SERVER['HTTP_HOST'].rtrim(dirname($_SERVER['SCRIPT_NAME']),'/').'/../oidc_callback.php') ?></code></p>
<label class="chk"><input type="checkbox" name="oidc_enabled" <?= $oidcEnabled ? 'checked' : '' ?>> <?= __('int_sso_active') ?></label>
<div class="row3" style="margin-top:10px;">
<div><label>Button-Text</label><input class="input" type="text" name="oidc_name" value="<?= htmlspecialchars($oidcName) ?>"></div>
<div><label><?= __('int_sso_button') ?></label><input class="input" type="text" name="oidc_name" value="<?= htmlspecialchars($oidcName) ?>"></div>
<div><label>Client ID</label><input class="input" type="text" name="oidc_client_id" value="<?= htmlspecialchars($oidcClientId) ?>"></div>
<div><label>Client Secret<?= $oidcSecretSet ? ' (gesetzt)' : '' ?></label><input class="input" type="password" name="oidc_client_secret" placeholder="<?= $oidcSecretSet ? '••••••• (leer = unverändert)' : '' ?>"></div>
<div><label>Client Secret<?= $oidcSecretSet ? __('int_secret_set') : '' ?></label><input class="input" type="password" name="oidc_client_secret" placeholder="<?= $oidcSecretSet ? __('int_secret_placeholder') : '' ?>"></div>
</div>
<label>Authorization Endpoint</label><input class="input" type="url" name="oidc_auth_url" value="<?= htmlspecialchars($oidcAuthUrl) ?>" placeholder="https://idp/authorize">
<label>Token Endpoint</label><input class="input" type="url" name="oidc_token_url" value="<?= htmlspecialchars($oidcTokenUrl) ?>" placeholder="https://idp/token">
@ -174,18 +174,18 @@ $adminBase = '';
</div>
<div class="card">
<h2>Datenhaltung & Cleanup (DSGVO)</h2>
<p class="muted">Aufbewahrungsfristen in Tagen (0 = deaktiviert). Ausführung per <code>cron_cleanup.php</code> (täglich empfohlen).
<?php if ($lastCleanup): ?><br>Letzter Lauf: <?= htmlspecialchars($lastCleanup) ?><?php endif; ?>
<h2><?= __('int_cleanup') ?></h2>
<p class="muted"><?= __('int_cleanup_hint') ?> <code>cron_cleanup.php</code> <?= __('int_cleanup_hint2') ?>
<?php if ($lastCleanup): ?><br><?= __('int_cleanup_last') ?> <?= htmlspecialchars($lastCleanup) ?><?php endif; ?>
</p>
<div class="row">
<div><label>Abgelaufene Voucher</label><input class="input" type="number" min="0" name="cleanup_expired_days" value="<?= $cleanupExpired ?>"></div>
<div><label>Audit-Log</label><input class="input" type="number" min="0" name="cleanup_audit_days" value="<?= $cleanupAudit ?>"></div>
<div><label>Login-Versuche</label><input class="input" type="number" min="0" name="cleanup_login_days" value="<?= $cleanupLogin ?>"></div>
<div><label><?= __('int_cleanup_expired') ?></label><input class="input" type="number" min="0" name="cleanup_expired_days" value="<?= $cleanupExpired ?>"></div>
<div><label><?= __('int_cleanup_audit') ?></label><input class="input" type="number" min="0" name="cleanup_audit_days" value="<?= $cleanupAudit ?>"></div>
<div><label><?= __('int_cleanup_logins') ?></label><input class="input" type="number" min="0" name="cleanup_login_days" value="<?= $cleanupLogin ?>"></div>
</div>
</div>
<button class="btn btn-primary" type="submit" name="save">Speichern</button>
<button class="btn btn-primary" type="submit" name="save"><?= __('btn_save') ?></button>
</form>
</main>