localize login page
This commit is contained in:
parent
849ff88134
commit
2bbcbe21f0
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Reference in New Issue