nevaforget 4cd73a430b fix: Audit-Findings — Realize-Handler, Thread-Safety, Input-Validierung
- _on_realize implementiert (war nur connected, nicht definiert)
- do_unrealize für as_file() Context-Manager-Cleanup
- threading.Lock für _greetd_sock Race Condition
- TOML-Parsing-Fehler abfangen statt Crash
- last-user Datei: Längen- und Zeichenvalidierung
- detect_locale: non-alpha LANG-Werte abweisen
- exec_cmd Plausibility-Check mit shutil.which
- Exception-Details ins Log statt in die UI
- subprocess.run Timeout für Power-Actions
- Sequence[Path] statt tuple[Path, ...] in get_sessions
- Mock-Server _recvall für fragmentierte Reads
- [behavior]-Config-Sektion entfernt (unimplementiert)
- Design Decisions in CLAUDE.md dokumentiert
2026-03-26 12:56:52 +01:00

118 lines
3.9 KiB
Python

# ABOUTME: Locale detection and string lookup for the greeter UI.
# ABOUTME: Reads system locale (LANG or /etc/locale.conf) and provides DE or EN strings.
import os
from dataclasses import dataclass
from pathlib import Path
DEFAULT_LOCALE_CONF = Path("/etc/locale.conf")
@dataclass(frozen=True)
class Strings:
"""All user-visible strings for the greeter UI."""
# UI labels
password_placeholder: str
reboot_tooltip: str
shutdown_tooltip: str
# Error messages
no_session_selected: str
greetd_sock_not_set: str
greetd_sock_not_absolute: str
greetd_sock_not_socket: str
greetd_sock_unreachable: str
auth_failed: str
wrong_password: str
multi_stage_unsupported: str
invalid_session_command: str
session_start_failed: str
reboot_failed: str
shutdown_failed: str
# Error messages (continued)
connection_error: str
socket_error: str
# Templates (use .format())
faillock_attempts_remaining: str
faillock_locked: str
_STRINGS_DE = Strings(
password_placeholder="Passwort",
reboot_tooltip="Neustart",
shutdown_tooltip="Herunterfahren",
no_session_selected="Keine Session ausgewählt",
greetd_sock_not_set="GREETD_SOCK nicht gesetzt",
greetd_sock_not_absolute="GREETD_SOCK ist kein absoluter Pfad",
greetd_sock_not_socket="GREETD_SOCK zeigt nicht auf einen Socket",
greetd_sock_unreachable="GREETD_SOCK nicht erreichbar",
auth_failed="Authentifizierung fehlgeschlagen",
wrong_password="Falsches Passwort",
multi_stage_unsupported="Mehrstufige Authentifizierung wird nicht unterstützt",
invalid_session_command="Ungültiger Session-Befehl",
session_start_failed="Session konnte nicht gestartet werden",
reboot_failed="Neustart fehlgeschlagen",
shutdown_failed="Herunterfahren fehlgeschlagen",
connection_error="Verbindungsfehler",
socket_error="Socket-Fehler",
faillock_attempts_remaining="Noch {n} Versuch(e) vor Kontosperrung!",
faillock_locked="Konto ist möglicherweise gesperrt",
)
_STRINGS_EN = Strings(
password_placeholder="Password",
reboot_tooltip="Reboot",
shutdown_tooltip="Shut down",
no_session_selected="No session selected",
greetd_sock_not_set="GREETD_SOCK not set",
greetd_sock_not_absolute="GREETD_SOCK is not an absolute path",
greetd_sock_not_socket="GREETD_SOCK does not point to a socket",
greetd_sock_unreachable="GREETD_SOCK unreachable",
auth_failed="Authentication failed",
wrong_password="Wrong password",
multi_stage_unsupported="Multi-stage authentication is not supported",
invalid_session_command="Invalid session command",
session_start_failed="Failed to start session",
reboot_failed="Reboot failed",
shutdown_failed="Shutdown failed",
connection_error="Connection error",
socket_error="Socket error",
faillock_attempts_remaining="{n} attempt(s) remaining before lockout!",
faillock_locked="Account may be locked",
)
_LOCALE_MAP: dict[str, Strings] = {
"de": _STRINGS_DE,
"en": _STRINGS_EN,
}
def detect_locale(locale_conf_path: Path = DEFAULT_LOCALE_CONF) -> str:
"""Determine the system language from LANG env var or /etc/locale.conf."""
lang = os.environ.get("LANG")
if not lang and locale_conf_path.exists():
for line in locale_conf_path.read_text().splitlines():
if line.startswith("LANG="):
lang = line.split("=", 1)[1].strip()
break
if not lang or lang in ("C", "POSIX"):
return "en"
# Extract language prefix: "de_DE.UTF-8" → "de"
lang = lang.split("_")[0].split(".")[0].lower()
if not lang.isalpha():
return "en"
return lang
def load_strings(locale: str | None = None) -> Strings:
"""Return the string table for the given locale, defaulting to English."""
if locale is None:
locale = detect_locale()
return _LOCALE_MAP.get(locale, _STRINGS_EN)