localize login page

This commit is contained in:
Gamosoft 2025-12-15 18:18:06 +01:00
parent 849ff88134
commit 2bbcbe21f0
6 changed files with 99 additions and 17 deletions

View File

@ -97,7 +97,8 @@ app.add_middleware(
)
print(f"🌐 CORS allowed origins: {allowed_origins}")
# ============================================================================
# ===========================================================
# =================
# Security Helpers
# ============================================================================
@ -253,14 +254,7 @@ async def login_page(request: Request, error: str = None):
async with aiofiles.open(login_path, 'r', encoding='utf-8') as f:
content = await f.read()
# Inject error message if present
if error:
content = content.replace('<!-- ERROR_CLASS_PLACEHOLDER -->', 'class="error"')
content = content.replace('<!-- ERROR_MESSAGE_PLACEHOLDER -->',
f'<div class="error-message">{error}</div>')
else:
content = content.replace('<!-- ERROR_CLASS_PLACEHOLDER -->', '')
content = content.replace('<!-- ERROR_MESSAGE_PLACEHOLDER -->', '')
# No server-side manipulation needed - frontend handles error display via URL params
return content
@ -281,8 +275,8 @@ async def login(request: Request, password: str = Form(...)):
request.session['authenticated'] = True
return RedirectResponse(url="/", status_code=303)
else:
# Redirect back to login with error message
return RedirectResponse(url="/login?error=Incorrect+password.+Please+try+again.", status_code=303)
# Redirect back to login with error code (frontend will translate)
return RedirectResponse(url="/login?error=incorrect_password", status_code=303)
@app.get("/logout")

View File

@ -6,6 +6,40 @@
<title>Login - NoteDiscovery</title>
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
<!-- Preload translations (same approach as index.html) -->
<script>
(function() {
const locale = localStorage.getItem('locale') || 'en-US';
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/locales/' + locale, false);
try {
xhr.send();
if (xhr.status === 200) {
window.__translations = JSON.parse(xhr.responseText);
} else if (locale !== 'en-US') {
xhr.open('GET', '/api/locales/en-US', false);
xhr.send();
if (xhr.status === 200) {
window.__translations = JSON.parse(xhr.responseText);
}
}
} catch (e) {
console.error('Failed to preload translations:', e);
window.__translations = {};
}
// Simple translation function
window.t = function(key) {
const keys = key.split('.');
let value = window.__translations;
for (const k of keys) {
value = value?.[k];
}
return typeof value === 'string' ? value : key;
};
})();
</script>
<!-- Theme styles will be loaded dynamically -->
<script>
// Load theme immediately (same approach as index.html)
@ -191,7 +225,7 @@
<div class="login-container">
<img src="/static/logo.svg" alt="NoteDiscovery" class="logo">
<h1>NoteDiscovery</h1>
<p class="tagline">Your Self-Hosted Knowledge Base</p>
<p class="tagline" id="tagline"></p>
<form method="post" action="/login">
<div>
@ -199,18 +233,16 @@
type="password"
name="password"
id="password"
placeholder="Enter your password"
required
autofocus
<!-- ERROR_CLASS_PLACEHOLDER -->
>
<!-- ERROR_MESSAGE_PLACEHOLDER -->
<div class="error-message" id="error-message" style="display: none;"></div>
</div>
<button type="submit">🔓 Unlock</button>
<button type="submit" id="submit-btn"></button>
</form>
<div class="footer">
🔒 Secure & Self-Hosted
<span id="footer-text"></span>
<div style="margin-top: 8px; font-size: 12px; opacity: 0.6;">
<a href="https://www.notediscovery.com" target="_blank" rel="noopener noreferrer" style="color: inherit; text-decoration: none; display: inline-flex; align-items: center; gap: 4px;">
<svg style="width: 14px; height: 14px;" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@ -221,6 +253,26 @@
</div>
</div>
</div>
<script>
// Apply translations after DOM is ready
document.getElementById('tagline').textContent = t('login.tagline');
document.getElementById('password').placeholder = t('login.password_placeholder');
document.getElementById('submit-btn').textContent = t('login.unlock_button');
document.getElementById('footer-text').textContent = t('login.footer');
document.title = t('login.title') + ' - NoteDiscovery';
// Handle error from URL params
var urlParams = new URLSearchParams(window.location.search);
var errorCode = urlParams.get('error');
if (errorCode) {
var errorEl = document.getElementById('error-message');
var passwordEl = document.getElementById('password');
errorEl.textContent = t('login.error_' + errorCode);
errorEl.style.display = 'flex';
passwordEl.classList.add('error');
}
</script>
</body>
</html>

View File

@ -147,6 +147,15 @@
"failed": "HTML-Export fehlgeschlagen: {{error}}"
},
"login": {
"title": "Anmelden",
"tagline": "Deine Selbstgehostete Wissensdatenbank",
"password_placeholder": "Passwort eingeben",
"unlock_button": "🔓 Entsperren",
"footer": "🔒 Sicher & Selbstgehostet",
"error_incorrect_password": "Falsches Passwort. Bitte versuche es erneut."
},
"images": {
"confirm_delete": "Bild \"{{name}}\" löschen?",
"upload_failed": "Bild-Upload fehlgeschlagen",

View File

@ -147,6 +147,15 @@
"failed": "Failed to export HTML: {{error}}"
},
"login": {
"title": "Login",
"tagline": "Your Self-Hosted Knowledge Base",
"password_placeholder": "Enter your password",
"unlock_button": "🔓 Unlock",
"footer": "🔒 Secure & Self-Hosted",
"error_incorrect_password": "Incorrect password. Please try again."
},
"images": {
"confirm_delete": "Delete image \"{{name}}\"?",
"upload_failed": "Failed to upload image",

View File

@ -147,6 +147,15 @@
"failed": "Error al exportar HTML: {{error}}"
},
"login": {
"title": "Iniciar sesión",
"tagline": "Tu Base de Conocimientos Autoalojada",
"password_placeholder": "Introduce tu contraseña",
"unlock_button": "🔓 Desbloquear",
"footer": "🔒 Seguro y Autoalojado",
"error_incorrect_password": "Contraseña incorrecta. Por favor, inténtalo de nuevo."
},
"images": {
"confirm_delete": "¿Eliminar imagen \"{{name}}\"?",
"upload_failed": "Error al subir imagen",

View File

@ -147,6 +147,15 @@
"failed": "Échec de l'export HTML : {{error}}"
},
"login": {
"title": "Connexion",
"tagline": "Votre Base de Connaissances Auto-Hébergée",
"password_placeholder": "Entrez votre mot de passe",
"unlock_button": "🔓 Déverrouiller",
"footer": "🔒 Sécurisé et Auto-Hébergé",
"error_incorrect_password": "Mot de passe incorrect. Veuillez réessayer."
},
"images": {
"confirm_delete": "Supprimer l'image \"{{name}}\" ?",
"upload_failed": "Échec du téléchargement de l'image",