- create i18n.py for managing translations and resolving locale - add German and English translations for various UI components - integrate translation functions into templates for dynamic language support
350 lines
15 KiB
Python
350 lines
15 KiB
Python
from typing import Dict
|
||
|
||
AVAILABLE_LANGUAGES: Dict[str, str] = {
|
||
"de": "Deutsch",
|
||
"en": "English",
|
||
}
|
||
|
||
translations: Dict[str, Dict[str, str]] = {
|
||
"de": {
|
||
"brand.tagline": "Deine gemieteten Server im Blick",
|
||
"nav.admin": "Admin-Dashboard",
|
||
"nav.users": "User",
|
||
"nav.overview": "Übersicht",
|
||
"nav.map": "Karte",
|
||
"nav.new_server": "Server anlegen",
|
||
"nav.logout": "Logout",
|
||
"nav.login": "Login",
|
||
"nav.register": "Registrieren",
|
||
"nav.logged_in_as": "Eingeloggt als",
|
||
"nav.console": "Konsole öffnen",
|
||
"footer.left": "Selfhosted VPS overview",
|
||
"footer.right": "PWA ready · Dark Mode",
|
||
"warning.no_encryption": "Hinweis: ENCRYPTION_KEY nicht gesetzt – Passwörter werden nicht gespeichert.",
|
||
"warning.no_encryption_short": "ENCRYPTION_KEY ist nicht gesetzt – eingegebene Management-Passwörter werden nicht gespeichert.",
|
||
"server_list.title": "Deine Server",
|
||
"server_list.subtitle": "Übersicht aller gemieteten VPS, dedizierten Server und Storage-Systeme.",
|
||
"stats.total": "Gesamt",
|
||
"stats.active": "aktive Server",
|
||
"stats.costs": "Laufende Kosten",
|
||
"stats.per_month": "pro Monat",
|
||
"stats.mixed_currencies": "(gemischte Währungen)",
|
||
"stats.expiring_soon": "Laufen bald aus",
|
||
"stats.expired": "Abgelaufen",
|
||
"stats.expiring_soon_hint": "≤ 30 Tage",
|
||
"stats.expired_hint": "Vertrag beendet",
|
||
"table.name": "Name",
|
||
"table.provider": "Provider",
|
||
"table.type": "Typ",
|
||
"table.location": "Location",
|
||
"table.ipv4": "IPv4",
|
||
"table.costs": "Kosten",
|
||
"table.action": "Aktion",
|
||
"status.expired": "abgelaufen",
|
||
"status.expiring": "läuft bald aus",
|
||
"server_list.empty": "Noch keine Server erfasst. Leg den ersten mit „Server anlegen“ oben rechts an.",
|
||
"price.month": "Monat",
|
||
"price.year": "Jahr",
|
||
"server_detail.server": "Server",
|
||
"server_detail.contract_end": "Vertragsende",
|
||
"server_detail.days_ago": "vor {days} Tagen",
|
||
"server_detail.today": "heute",
|
||
"server_detail.in_days": "in {days} Tagen",
|
||
"server_detail.edit": "Bearbeiten",
|
||
"server_detail.archive": "Archivieren",
|
||
"server_detail.archive_confirm": "Diesen Server archivieren?",
|
||
"section.net_costs": "Netz & Kosten",
|
||
"section.hardware": "Hardware",
|
||
"section.access": "Zugänge",
|
||
"section.notes": "Notizen",
|
||
"field.ipv4": "IPv4",
|
||
"field.ipv6": "IPv6",
|
||
"field.costs": "Kosten",
|
||
"field.contract": "Vertrag",
|
||
"field.cpu": "CPU",
|
||
"field.ram": "RAM",
|
||
"field.storage": "Storage",
|
||
"field.management": "Management",
|
||
"field.mgmt_user": "Mgmt-User",
|
||
"field.mgmt_password": "Mgmt-Passwort",
|
||
"field.ssh_user": "SSH User",
|
||
"field.ssh_key_hint": "SSH Key Hint",
|
||
"note.ssh_keys": "Hinweis: Es werden nur Key-Namen gespeichert, keine privaten SSH-Schlüssel.",
|
||
"notes.empty": "Keine Notizen.",
|
||
"mgmt.password_encrypted_missing": "verschlüsselt gespeichert (Key fehlt?)",
|
||
"back.overview": "Zurück zur Übersicht",
|
||
"updated_at": "Zuletzt aktualisiert:",
|
||
"form.title.new": "Neuen Server anlegen",
|
||
"form.title.edit": "Server bearbeiten",
|
||
"form.subtitle": "Trage alle relevanten Infos zu deinem VPS / Server ein.",
|
||
"section.general": "Allgemein",
|
||
"label.name": "Name",
|
||
"label.provider": "Provider",
|
||
"label.hostname": "Hostname",
|
||
"label.type": "Typ",
|
||
"label.location": "Location",
|
||
"label.tags": "Tags (kommagetrennt)",
|
||
"label.ipv4": "IPv4",
|
||
"label.ipv6": "IPv6",
|
||
"section.network": "Netzwerk",
|
||
"section.costs": "Kosten",
|
||
"label.amount": "Betrag",
|
||
"label.currency": "Währung",
|
||
"label.billing": "Abrechnung",
|
||
"label.contract_start": "Vertragsbeginn",
|
||
"label.contract_end": "Vertragsende",
|
||
"section.hardware.small": "Hardware",
|
||
"label.cpu_model": "CPU-Modell",
|
||
"label.cpu_cores": "CPU Cores",
|
||
"label.ram_mb": "RAM (MB)",
|
||
"label.storage_gb": "Storage (GB)",
|
||
"label.storage_type": "Storage-Typ",
|
||
"section.access.small": "Zugänge",
|
||
"hint.ssh": "SSH: hier nur Key-Namen oder Hints eintragen, keine privaten Keys.",
|
||
"label.mgmt_url": "Management URL",
|
||
"label.mgmt_user": "Management User",
|
||
"label.mgmt_password": "Management Passwort",
|
||
"label.ssh_user": "SSH User",
|
||
"label.ssh_key_hint": "SSH Key Hint",
|
||
"section.notes.small": "Notizen",
|
||
"btn.cancel": "Abbrechen",
|
||
"btn.save": "Speichern",
|
||
"btn.save_changes": "Änderungen speichern",
|
||
"map.title": "Server-Karte",
|
||
"map.subtitle": "Zeigt alle nicht archivierten Server mit gesetzter Location auf einer Karte. Für grobe Übersicht reicht die Stadt – keine exakten GPS-Daten notwendig.",
|
||
"map.note": "Hinweis: Marker werden anhand der Location-Namen grob auf der Weltkarte platziert. Mehrere Server in derselben Stadt werden leicht versetzt dargestellt, damit sie klickbar bleiben.",
|
||
"admin.title": "Admin-Dashboard",
|
||
"admin.subtitle": "Globale Übersicht über alle nicht archivierten Server und Benutzer.",
|
||
"admin.users": "Benutzer",
|
||
"admin.users_caption": "Accounts",
|
||
"admin.servers": "Server",
|
||
"admin.servers_caption": "nicht archiviert",
|
||
"admin.costs": "Laufende Kosten",
|
||
"admin.per_month": "pro Monat",
|
||
"admin.contract_status": "Vertragstatus",
|
||
"admin.expiring": "Bald auslaufend",
|
||
"admin.expired": "Abgelaufen",
|
||
"admin.by_provider": "Nach Provider",
|
||
"admin.provider": "Provider",
|
||
"admin.count": "Server",
|
||
"admin.monthly_costs": "Monatskosten",
|
||
"admin.expiring_soon": "Laufen bald aus",
|
||
"admin.expired_count": "Abgelaufen",
|
||
"admin.contracts_expiring": "Laufen bald aus (≤ 30 Tage)",
|
||
"admin.contracts_none_soon": "Keine Verträge laufen in den nächsten 30 Tagen aus.",
|
||
"admin.contracts_expired": "Abgelaufene Verträge",
|
||
"admin.contracts_none_expired": "Keine abgelaufenen Verträge gefunden.",
|
||
"users.title": "Benutzerverwaltung",
|
||
"users.subtitle": "Admins können Benutzer aktivieren/deaktivieren. Der eigene Account kann nicht deaktiviert werden.",
|
||
"users.username": "Benutzername",
|
||
"users.email": "E-Mail",
|
||
"users.role": "Rolle",
|
||
"users.status": "Status",
|
||
"users.action": "Aktion",
|
||
"role.admin": "Admin",
|
||
"role.user": "User",
|
||
"status.active": "aktiv",
|
||
"status.inactive": "deaktiviert",
|
||
"action.deactivate": "Deaktivieren",
|
||
"action.activate": "Aktivieren",
|
||
"users.own_account": "Eigener Account",
|
||
"login.title": "Login",
|
||
"login.subtitle": "Melde dich an, um deine Server zu verwalten.",
|
||
"login.username": "Benutzername",
|
||
"login.password": "Passwort",
|
||
"login.no_account": "Noch kein Account?",
|
||
"login.register": "Registrieren",
|
||
"login.submit": "Einloggen",
|
||
"register.title": "Registrieren",
|
||
"register.subtitle": "Erstelle einen neuen Account. Der erste Benutzer wird automatisch Admin.",
|
||
"register.username": "Benutzername",
|
||
"register.email": "E-Mail (optional)",
|
||
"register.password": "Passwort",
|
||
"register.password_confirm": "Passwort bestätigen",
|
||
"register.submit": "Account anlegen",
|
||
},
|
||
"en": {
|
||
"brand.tagline": "Your rented servers at a glance",
|
||
"nav.admin": "Admin Dashboard",
|
||
"nav.users": "Users",
|
||
"nav.overview": "Overview",
|
||
"nav.map": "Map",
|
||
"nav.new_server": "Add server",
|
||
"nav.logout": "Logout",
|
||
"nav.login": "Login",
|
||
"nav.register": "Register",
|
||
"nav.logged_in_as": "Logged in as",
|
||
"nav.console": "Open console",
|
||
"footer.left": "Selfhosted VPS overview",
|
||
"footer.right": "PWA ready · Dark Mode",
|
||
"warning.no_encryption": "Note: ENCRYPTION_KEY not set – passwords will not be stored.",
|
||
"warning.no_encryption_short": "ENCRYPTION_KEY is not set – management passwords will not be stored.",
|
||
"server_list.title": "Your servers",
|
||
"server_list.subtitle": "Overview of all rented VPS, dedicated servers and storage systems.",
|
||
"stats.total": "Total",
|
||
"stats.active": "active servers",
|
||
"stats.costs": "Recurring costs",
|
||
"stats.per_month": "per month",
|
||
"stats.mixed_currencies": "(mixed currencies)",
|
||
"stats.expiring_soon": "Expiring soon",
|
||
"stats.expired": "Expired",
|
||
"stats.expiring_soon_hint": "≤ 30 days",
|
||
"stats.expired_hint": "Contract ended",
|
||
"table.name": "Name",
|
||
"table.provider": "Provider",
|
||
"table.type": "Type",
|
||
"table.location": "Location",
|
||
"table.ipv4": "IPv4",
|
||
"table.costs": "Costs",
|
||
"table.action": "Action",
|
||
"status.expired": "expired",
|
||
"status.expiring": "expiring soon",
|
||
"server_list.empty": "No servers yet. Create the first one via “Add server” in the top right.",
|
||
"price.month": "Month",
|
||
"price.year": "Year",
|
||
"server_detail.server": "Server",
|
||
"server_detail.contract_end": "Contract end",
|
||
"server_detail.days_ago": "{days} days ago",
|
||
"server_detail.today": "today",
|
||
"server_detail.in_days": "in {days} days",
|
||
"server_detail.edit": "Edit",
|
||
"server_detail.archive": "Archive",
|
||
"server_detail.archive_confirm": "Archive this server?",
|
||
"section.net_costs": "Network & Costs",
|
||
"section.hardware": "Hardware",
|
||
"section.access": "Access",
|
||
"section.notes": "Notes",
|
||
"field.ipv4": "IPv4",
|
||
"field.ipv6": "IPv6",
|
||
"field.costs": "Costs",
|
||
"field.contract": "Contract",
|
||
"field.cpu": "CPU",
|
||
"field.ram": "RAM",
|
||
"field.storage": "Storage",
|
||
"field.management": "Management",
|
||
"field.mgmt_user": "Mgmt user",
|
||
"field.mgmt_password": "Mgmt password",
|
||
"field.ssh_user": "SSH user",
|
||
"field.ssh_key_hint": "SSH key hint",
|
||
"note.ssh_keys": "Note: Only key names are stored, no private SSH keys.",
|
||
"notes.empty": "No notes.",
|
||
"mgmt.password_encrypted_missing": "stored encrypted (key missing?)",
|
||
"back.overview": "Back to overview",
|
||
"updated_at": "Last updated:",
|
||
"form.title.new": "Create new server",
|
||
"form.title.edit": "Edit server",
|
||
"form.subtitle": "Enter all relevant details about your VPS / server.",
|
||
"section.general": "General",
|
||
"label.name": "Name",
|
||
"label.provider": "Provider",
|
||
"label.hostname": "Hostname",
|
||
"label.type": "Type",
|
||
"label.location": "Location",
|
||
"label.tags": "Tags (comma-separated)",
|
||
"label.ipv4": "IPv4",
|
||
"label.ipv6": "IPv6",
|
||
"section.network": "Network",
|
||
"section.costs": "Costs",
|
||
"label.amount": "Amount",
|
||
"label.currency": "Currency",
|
||
"label.billing": "Billing",
|
||
"label.contract_start": "Contract start",
|
||
"label.contract_end": "Contract end",
|
||
"section.hardware.small": "Hardware",
|
||
"label.cpu_model": "CPU model",
|
||
"label.cpu_cores": "CPU cores",
|
||
"label.ram_mb": "RAM (MB)",
|
||
"label.storage_gb": "Storage (GB)",
|
||
"label.storage_type": "Storage type",
|
||
"section.access.small": "Access",
|
||
"hint.ssh": "SSH: only enter key names or hints, no private keys.",
|
||
"label.mgmt_url": "Management URL",
|
||
"label.mgmt_user": "Management user",
|
||
"label.mgmt_password": "Management password",
|
||
"label.ssh_user": "SSH user",
|
||
"label.ssh_key_hint": "SSH key hint",
|
||
"section.notes.small": "Notes",
|
||
"btn.cancel": "Cancel",
|
||
"btn.save": "Save",
|
||
"btn.save_changes": "Save changes",
|
||
"map.title": "Server map",
|
||
"map.subtitle": "Shows all non-archived servers with a location on a map. City names are sufficient for rough positioning.",
|
||
"map.note": "Note: Markers are placed roughly based on location names. Multiple servers in one city are slightly offset to remain clickable.",
|
||
"admin.title": "Admin dashboard",
|
||
"admin.subtitle": "Global overview of all non-archived servers and users.",
|
||
"admin.users": "Users",
|
||
"admin.users_caption": "Accounts",
|
||
"admin.servers": "Servers",
|
||
"admin.servers_caption": "not archived",
|
||
"admin.costs": "Recurring costs",
|
||
"admin.per_month": "per month",
|
||
"admin.contract_status": "Contract status",
|
||
"admin.expiring": "Expiring soon",
|
||
"admin.expired": "Expired",
|
||
"admin.by_provider": "By provider",
|
||
"admin.provider": "Provider",
|
||
"admin.count": "Servers",
|
||
"admin.monthly_costs": "Monthly costs",
|
||
"admin.expiring_soon": "Expiring soon",
|
||
"admin.expired_count": "Expired",
|
||
"admin.contracts_expiring": "Expiring soon (≤ 30 days)",
|
||
"admin.contracts_none_soon": "No contracts expiring in the next 30 days.",
|
||
"admin.contracts_expired": "Expired contracts",
|
||
"admin.contracts_none_expired": "No expired contracts found.",
|
||
"users.title": "User management",
|
||
"users.subtitle": "Admins can activate/deactivate users. You cannot deactivate your own account.",
|
||
"users.username": "Username",
|
||
"users.email": "Email",
|
||
"users.role": "Role",
|
||
"users.status": "Status",
|
||
"users.action": "Action",
|
||
"role.admin": "Admin",
|
||
"role.user": "User",
|
||
"status.active": "active",
|
||
"status.inactive": "deactivated",
|
||
"action.deactivate": "Deactivate",
|
||
"action.activate": "Activate",
|
||
"users.own_account": "Own account",
|
||
"login.title": "Login",
|
||
"login.subtitle": "Sign in to manage your servers.",
|
||
"login.username": "Username",
|
||
"login.password": "Password",
|
||
"login.no_account": "No account yet?",
|
||
"login.register": "Register",
|
||
"login.submit": "Log in",
|
||
"register.title": "Register",
|
||
"register.subtitle": "Create a new account. The first user becomes admin automatically.",
|
||
"register.username": "Username",
|
||
"register.email": "Email (optional)",
|
||
"register.password": "Password",
|
||
"register.password_confirm": "Confirm password",
|
||
"register.submit": "Create account",
|
||
},
|
||
}
|
||
|
||
|
||
def resolve_locale(request) -> str:
|
||
"""Detect locale from session override or Accept-Language; default to de."""
|
||
session_lang = request.session.get("lang")
|
||
if session_lang in AVAILABLE_LANGUAGES:
|
||
return session_lang
|
||
|
||
accept = request.headers.get("accept-language", "")
|
||
for part in accept.split(","):
|
||
code = part.split(";")[0].strip().lower()
|
||
if code.startswith("de"):
|
||
return "de"
|
||
if code.startswith("en"):
|
||
return "en"
|
||
return "de"
|
||
|
||
|
||
def translate(key: str, locale: str = "de", **kwargs) -> str:
|
||
"""Return translated string for a key."""
|
||
text = translations.get(locale, {}).get(
|
||
key, translations["en"].get(key, key)
|
||
)
|
||
try:
|
||
return text.format(**kwargs)
|
||
except Exception:
|
||
return text
|