M16: Verbinden-Tab zweisprachig (DE/EN)

ConnectView.swift auf L10n.t(...) umgestellt: Verbindungsformular, Status,
Geräte-/Routerboard-Infos, Update-/Firmware-Dialoge, Zertifikats-/SSH-
Warnungen. Dialog-Helper-Funktionen bekommen appLanguage-Parameter (liegen
außerhalb der View-Struct). Nebenbei fehlenden "Passwort"-Label-Eintrag im
Experte-Katalog nachgetragen. L10n.swift jetzt 444 Einträge, keine
Duplikate. 60 Tests grün, live bestätigt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YDmUd93KxsYGr2kLTotWnG
This commit is contained in:
Kay
2026-09-15 16:18:02 +02:00
co-authored by Claude Sonnet 5
parent 7af550ae85
commit b6bb4bc289
6 changed files with 241 additions and 104 deletions
+86 -1
View File
@@ -528,6 +528,91 @@ enum L10n {
"Meist 587 (STARTTLS) oder 465 (SSL).": "Usually 587 (STARTTLS) or 465 (SSL).",
"Absenderadresse": "Sender Address",
"Z.B. router@example.com.": "E.g. router@example.com.",
"Zugangsdaten des Mailservers.": "Credentials for the mail server."
"Zugangsdaten des Mailservers.": "Credentials for the mail server.",
"Passwort": "Password",
// MARK: - Connect tab (ConnectView.swift)
"Verbindung": "Connection",
"IP-Adresse oder Hostname": "IP Address or Hostname",
"Die Adresse deines Routers im Netzwerk. Werkseinstellung bei Mikrotik ist meist 192.168.88.1.":
"Your router's address on the network. MikroTik's factory default is usually 192.168.88.1.",
"Der Admin-Benutzername deines Routers. Werkseinstellung ist meist \"admin\".":
"Your router's admin username. The factory default is usually \"admin\".",
"Das Passwort für diesen Benutzer. Bei unverändertem Werkszustand oft leer.":
"The password for this user. Often empty in an unchanged factory state.",
"Passwort merken": "Remember Password",
"Speichert das Passwort verschlüsselt in der macOS-Schlüsselbundverwaltung, damit du es nicht jedes Mal neu eingeben musst.":
"Stores the password encrypted in macOS Keychain so you don't have to type it in every time.",
"Verbinde…": "Connecting…",
"Verbunden": "Connected",
"Trennen": "Disconnect",
"Beendet die Verbindung zum Router. Zugangsdaten bleiben erhalten (falls gemerkt).":
"Ends the connection to the router. Credentials are kept (if remembered).",
"Sichere…": "Backing up…",
"Jetzt sichern": "Back Up Now",
"Sichert die aktuelle Router-Konfiguration — sinnvoll direkt nach dem Verbinden, bevor du im Einrichten-Tab etwas änderst.":
"Backs up the current router configuration — useful right after connecting, before you change anything in the Setup tab.",
"Zuletzt gesichert:": "Last backed up:",
"Unbekanntes Zertifikat": "Unknown Certificate",
"Vertrauen und verbinden": "Trust and Connect",
"Der Router hat sich mit einem unbekannten Zertifikat gemeldet.": "The router presented an unknown certificate.",
"Fingerabdruck:": "Fingerprint:",
"Nur bestätigen, wenn dies dein eigenes Gerät im lokalen Netzwerk ist.":
"Only confirm if this is your own device on the local network.",
"Unbekannter SSH-Schlüssel": "Unknown SSH Key",
"Der Router hat sich mit einem unbekannten SSH-Schlüssel gemeldet.": "The router presented an unknown SSH key.",
"Nur bestätigen, wenn dies dein eigenes Gerät im lokalen Netzwerk ist. Falls du diesen Router schon einmal verbunden hattest und sich der Fingerabdruck geändert hat, könnte das auf ein manipuliertes Netzwerk hindeuten — im Zweifel nicht bestätigen.":
"Only confirm if this is your own device on the local network. If you've connected to this router before and the fingerprint has changed, that could indicate a tampered network — when in doubt, don't confirm.",
"Sicherung fehlgeschlagen": "Backup Failed",
"Gerät": "Device",
"Modell": "Model",
"RouterOS-Version": "RouterOS Version",
"Architektur": "Architecture",
"Laufzeit": "Uptime",
"Modell (RB-Code)": "Model (RB code)",
"Seriennummer": "Serial Number",
"Firmware-Typ": "Firmware Type",
"Firmware (aktuell)": "Firmware (current)",
"Firmware (minimal)": "Firmware (minimum)",
"Firmware (verfügbar)": "Firmware (available)",
"Firmware-Update verfügbar": "Firmware Update Available",
"Aktualisiere…": "Updating…",
"Firmware aktualisieren": "Update Firmware",
"Firmware aktuell": "Firmware Up to Date",
"Starte neu…": "Restarting…",
"Jetzt neu starten": "Restart Now",
"Nötig, damit die neu geschriebene Firmware aktiv wird — passiert nicht automatisch.":
"Needed for the newly written firmware to take effect — doesn't happen automatically.",
"Software-Update": "Software Update",
"Kanal": "Channel",
"Installiert": "Installed",
"Neueste Version": "Latest Version",
"Installiere…": "Installing…",
"Update installieren": "Install Update",
"Prüfe…": "Checking…",
"Jetzt prüfen": "Check Now",
"Prüft bei MikroTik, ob eine neuere RouterOS-Version verfügbar ist. Braucht Internetzugang auf dem Router.":
"Checks with MikroTik whether a newer RouterOS version is available. Needs internet access on the router.",
"Update-Befehl gesendet — der Router lädt herunter, installiert und startet danach automatisch neu.":
"Update command sent — the router will download, install, and then reboot automatically.",
"Verbinde dich mit deinem Router, um Geräteinformationen zu sehen.":
"Connect to your router to see device information.",
"RouterOS-Update installieren?": "Install RouterOS Update?",
"Installieren": "Install",
"Lädt RouterOS": "Downloading RouterOS",
"die neueste Version": "the latest version",
" herunter, installiert es und startet den Router danach automatisch neu — die Verbindung geht dabei verloren. Vorher am besten eine Sicherung erstellen (\"Jetzt sichern\" oben).":
" and installs it, then automatically reboots the router — the connection will be lost. It's best to create a backup first (\"Back Up Now\" above).",
"Update-Prüfung fehlgeschlagen": "Update Check Failed",
"Update-Installation fehlgeschlagen": "Update Installation Failed",
"Routerboard-Firmware aktualisieren?": "Update Routerboard Firmware?",
"Schreibt die neue Bootloader-Firmware auf den Router. Danach ist ein manueller Neustart nötig — passiert nicht automatisch. Dieser Befehl fragt in einer interaktiven Sitzung normalerweise erst nach Bestätigung; ob das über diese App genauso abläuft, ist nicht abschließend getestet (ähnlich interaktive Befehle liefen bisher aber problemlos). Falls die App hier ungewöhnlich lange ohne Rückmeldung bleibt, bitte melden.":
"Writes the new bootloader firmware to the router. A manual reboot is then needed — it doesn't happen automatically. This command normally asks for confirmation in an interactive session; whether that's also the case over this app hasn't been conclusively tested (similarly interactive commands have gone smoothly so far). If the app hangs here unusually long without responding, please report it.",
"Firmware-Aktualisierung fehlgeschlagen": "Firmware Update Failed",
"Router jetzt neu starten?": "Restart Router Now?",
"Neu starten": "Restart",
"Startet den Router sofort neu. Die Verbindung geht dabei kurz verloren, danach im Tab \"Verbinden\" erneut verbinden.":
"Restarts the router immediately. The connection will briefly drop — reconnect in the \"Connect\" tab afterwards.",
"Neustart fehlgeschlagen": "Restart Failed"
]
}
@@ -7,6 +7,7 @@ struct ConnectView: View {
@State private var showUpdateInstallConfirmation = false
@State private var showFirmwareUpgradeConfirmation = false
@State private var showRebootConfirmation = false
@AppStorage("appLanguage") private var appLanguage: String = "de"
init(connectionService: ConnectionService) {
self.connectionService = connectionService
@@ -15,8 +16,8 @@ struct ConnectView: View {
var body: some View {
mainContent
.withUpdateDialogs(viewModel: viewModel, connectionService: connectionService, showUpdateInstallConfirmation: $showUpdateInstallConfirmation, showFirmwareUpgradeConfirmation: $showFirmwareUpgradeConfirmation)
.withRebootDialogs(viewModel: viewModel, connectionService: connectionService, isPresented: $showRebootConfirmation)
.withUpdateDialogs(viewModel: viewModel, connectionService: connectionService, appLanguage: appLanguage, showUpdateInstallConfirmation: $showUpdateInstallConfirmation, showFirmwareUpgradeConfirmation: $showFirmwareUpgradeConfirmation)
.withRebootDialogs(viewModel: viewModel, connectionService: connectionService, appLanguage: appLanguage, isPresented: $showRebootConfirmation)
}
/// Split out of `body` same reasoning as `BackupListView.restoreAwareContent` (Bug 19 in
@@ -25,19 +26,19 @@ struct ConnectView: View {
private var mainContent: some View {
NavigationSplitView {
Form {
Section("Verbindung") {
TextField("IP-Adresse oder Hostname", text: $viewModel.host)
.help("Die Adresse deines Routers im Netzwerk. Werkseinstellung bei Mikrotik ist meist 192.168.88.1.")
TextField("Benutzername", text: $viewModel.username)
.help("Der Admin-Benutzername deines Routers. Werkseinstellung ist meist \"admin\".")
SecureField("Passwort", text: $viewModel.password)
.help("Das Passwort für diesen Benutzer. Bei unverändertem Werkszustand oft leer.")
Toggle("Passwort merken", isOn: $viewModel.rememberPassword)
.help("Speichert das Passwort verschlüsselt in der macOS-Schlüsselbundverwaltung, damit du es nicht jedes Mal neu eingeben musst.")
Section(L10n.t("Verbindung", appLanguage)) {
TextField(L10n.t("IP-Adresse oder Hostname", appLanguage), text: $viewModel.host)
.help(L10n.t("Die Adresse deines Routers im Netzwerk. Werkseinstellung bei Mikrotik ist meist 192.168.88.1.", appLanguage))
TextField(L10n.t("Benutzername", appLanguage), text: $viewModel.username)
.help(L10n.t("Der Admin-Benutzername deines Routers. Werkseinstellung ist meist \"admin\".", appLanguage))
SecureField(L10n.t("Passwort", appLanguage), text: $viewModel.password)
.help(L10n.t("Das Passwort für diesen Benutzer. Bei unverändertem Werkszustand oft leer.", appLanguage))
Toggle(L10n.t("Passwort merken", appLanguage), isOn: $viewModel.rememberPassword)
.help(L10n.t("Speichert das Passwort verschlüsselt in der macOS-Schlüsselbundverwaltung, damit du es nicht jedes Mal neu eingeben musst.", appLanguage))
}
Section {
Button("Verbinden") {
Button(L10n.t("Verbinden", appLanguage)) {
viewModel.connect()
}
.disabled(viewModel.host.isEmpty || viewModel.username.isEmpty)
@@ -55,38 +56,42 @@ struct ConnectView: View {
backupViewModel.load()
}
.alert(
"Unbekanntes Zertifikat",
L10n.t("Unbekanntes Zertifikat", appLanguage),
isPresented: certificateAlertBinding,
presenting: certificateFingerprint
) { fingerprint in
Button("Vertrauen und verbinden") {
Button(L10n.t("Vertrauen und verbinden", appLanguage)) {
viewModel.trustAndRetry(fingerprint: fingerprint)
}
Button("Abbrechen", role: .cancel) {}
Button(L10n.t("Abbrechen", appLanguage), role: .cancel) {}
} message: { fingerprint in
Text("Der Router hat sich mit einem unbekannten Zertifikat gemeldet.\nFingerabdruck: \(fingerprint)\n\nNur bestätigen, wenn dies dein eigenes Gerät im lokalen Netzwerk ist.")
Text(L10n.t("Der Router hat sich mit einem unbekannten Zertifikat gemeldet.", appLanguage)
+ "\n" + L10n.t("Fingerabdruck:", appLanguage) + " \(fingerprint)\n\n"
+ L10n.t("Nur bestätigen, wenn dies dein eigenes Gerät im lokalen Netzwerk ist.", appLanguage))
}
.alert(
"Unbekannter SSH-Schlüssel",
L10n.t("Unbekannter SSH-Schlüssel", appLanguage),
isPresented: sshHostKeyAlertBinding,
presenting: sshHostKeyFingerprint
) { fingerprint in
Button("Vertrauen und verbinden") {
Button(L10n.t("Vertrauen und verbinden", appLanguage)) {
viewModel.trustSSHHostKeyAndRetry(fingerprint: fingerprint)
}
Button("Abbrechen", role: .cancel) {}
Button(L10n.t("Abbrechen", appLanguage), role: .cancel) {}
} message: { fingerprint in
Text("Der Router hat sich mit einem unbekannten SSH-Schlüssel gemeldet.\nFingerabdruck: \(fingerprint)\n\nNur bestätigen, wenn dies dein eigenes Gerät im lokalen Netzwerk ist. Falls du diesen Router schon einmal verbunden hattest und sich der Fingerabdruck geändert hat, könnte das auf ein manipuliertes Netzwerk hindeuten — im Zweifel nicht bestätigen.")
Text(L10n.t("Der Router hat sich mit einem unbekannten SSH-Schlüssel gemeldet.", appLanguage)
+ "\n" + L10n.t("Fingerabdruck:", appLanguage) + " \(fingerprint)\n\n"
+ L10n.t("Nur bestätigen, wenn dies dein eigenes Gerät im lokalen Netzwerk ist. Falls du diesen Router schon einmal verbunden hattest und sich der Fingerabdruck geändert hat, könnte das auf ein manipuliertes Netzwerk hindeuten — im Zweifel nicht bestätigen.", appLanguage))
}
.alert(
"Sicherung fehlgeschlagen",
L10n.t("Sicherung fehlgeschlagen", appLanguage),
isPresented: Binding(
get: { backupViewModel.errorMessage != nil },
set: { _ in backupViewModel.errorMessage = nil }
),
presenting: backupViewModel.errorMessage
) { _ in
Button("OK") {}
Button(L10n.t("OK", appLanguage)) {}
} message: { message in
Text(message)
}
@@ -129,18 +134,18 @@ struct ConnectView: View {
Section {
HStack {
ProgressView()
Text("Verbinde…")
Text(L10n.t("Verbinde…", appLanguage))
}
}
case .connected(let kind):
Section {
Label("Verbunden (\(kind == .rest ? "REST-API" : "SSH"))", systemImage: "checkmark.circle.fill")
Label(L10n.t("Verbunden", appLanguage) + " (\(kind == .rest ? "REST-API" : "SSH"))", systemImage: "checkmark.circle.fill")
.foregroundStyle(.green)
Button("Trennen", role: .destructive) {
Button(L10n.t("Trennen", appLanguage), role: .destructive) {
Task { await connectionService.disconnect() }
}
.help("Beendet die Verbindung zum Router. Zugangsdaten bleiben erhalten (falls gemerkt).")
.help(L10n.t("Beendet die Verbindung zum Router. Zugangsdaten bleiben erhalten (falls gemerkt).", appLanguage))
Button {
if let credentials = connectionService.credentials {
@@ -150,18 +155,18 @@ struct ConnectView: View {
if backupViewModel.isCreatingBackup {
HStack {
ProgressView()
Text("Sichere…")
Text(L10n.t("Sichere…", appLanguage))
}
} else {
Label("Jetzt sichern", systemImage: "square.and.arrow.down")
Label(L10n.t("Jetzt sichern", appLanguage), systemImage: "square.and.arrow.down")
}
}
.disabled(backupViewModel.isCreatingBackup)
.help("Sichert die aktuelle Router-Konfiguration — sinnvoll direkt nach dem Verbinden, bevor du im Einrichten-Tab etwas änderst.")
.help(L10n.t("Sichert die aktuelle Router-Konfiguration — sinnvoll direkt nach dem Verbinden, bevor du im Einrichten-Tab etwas änderst.", appLanguage))
if let lastBackup = backupViewModel.backups.first(where: { $0.host == connectionService.credentials?.host }) {
Label(
"Zuletzt gesichert: \(lastBackup.createdAt.formatted(date: .abbreviated, time: .standard))",
L10n.t("Zuletzt gesichert:", appLanguage) + " \(lastBackup.createdAt.formatted(date: .abbreviated, time: .standard))",
systemImage: "checkmark"
)
.font(.caption)
@@ -180,40 +185,40 @@ struct ConnectView: View {
private var deviceDetail: some View {
if let info = connectionService.deviceInfo {
Form {
Section("Gerät") {
LabeledContent("Modell", value: info.boardName)
LabeledContent("RouterOS-Version", value: info.routerOSVersion)
LabeledContent("Architektur", value: info.architecture)
LabeledContent("Laufzeit", value: info.uptime)
Section(L10n.t("Gerät", appLanguage)) {
LabeledContent(L10n.t("Modell", appLanguage), value: info.boardName)
LabeledContent(L10n.t("RouterOS-Version", appLanguage), value: info.routerOSVersion)
LabeledContent(L10n.t("Architektur", appLanguage), value: info.architecture)
LabeledContent(L10n.t("Laufzeit", appLanguage), value: info.uptime)
}
if let board = connectionService.routerBoardInfo {
Section("Routerboard") {
LabeledContent("Modell (RB-Code)", value: board.model)
LabeledContent("Revision", value: board.revision)
LabeledContent("Seriennummer", value: board.serialNumber)
LabeledContent(L10n.t("Modell (RB-Code)", appLanguage), value: board.model)
LabeledContent(L10n.t("Revision", appLanguage), value: board.revision)
LabeledContent(L10n.t("Seriennummer", appLanguage), value: board.serialNumber)
.textSelection(.enabled)
LabeledContent("Firmware-Typ", value: board.firmwareType)
LabeledContent("Firmware (aktuell)", value: board.currentFirmware)
LabeledContent("Firmware (minimal)", value: board.minimumFirmware)
LabeledContent("Firmware (verfügbar)", value: board.upgradeFirmware)
LabeledContent(L10n.t("Firmware-Typ", appLanguage), value: board.firmwareType)
LabeledContent(L10n.t("Firmware (aktuell)", appLanguage), value: board.currentFirmware)
LabeledContent(L10n.t("Firmware (minimal)", appLanguage), value: board.minimumFirmware)
LabeledContent(L10n.t("Firmware (verfügbar)", appLanguage), value: board.upgradeFirmware)
// Confirmed via docs: same value = up to date, different = a bootloader
// update is available (only ever becomes available after installing a
// newer RouterOS package first the firmware ships bundled inside it).
if board.currentFirmware != board.upgradeFirmware {
Label("Firmware-Update verfügbar", systemImage: "arrow.up.circle.fill")
Label(L10n.t("Firmware-Update verfügbar", appLanguage), systemImage: "arrow.up.circle.fill")
.foregroundStyle(.orange)
Button {
showFirmwareUpgradeConfirmation = true
} label: {
if viewModel.isUpgradingFirmware {
HStack { ProgressView(); Text("Aktualisiere…") }
HStack { ProgressView(); Text(L10n.t("Aktualisiere…", appLanguage)) }
} else {
Label("Firmware aktualisieren", systemImage: "arrow.up.circle")
Label(L10n.t("Firmware aktualisieren", appLanguage), systemImage: "arrow.up.circle")
}
}
.disabled(viewModel.isUpgradingFirmware)
} else {
Label("Firmware aktuell", systemImage: "checkmark.circle")
Label(L10n.t("Firmware aktuell", appLanguage), systemImage: "checkmark.circle")
.font(.caption)
.foregroundStyle(.secondary)
}
@@ -223,30 +228,30 @@ struct ConnectView: View {
showRebootConfirmation = true
} label: {
if viewModel.isRebooting {
HStack { ProgressView(); Text("Starte neu…") }
HStack { ProgressView(); Text(L10n.t("Starte neu…", appLanguage)) }
} else {
Label("Jetzt neu starten", systemImage: "arrow.clockwise")
Label(L10n.t("Jetzt neu starten", appLanguage), systemImage: "arrow.clockwise")
}
}
.disabled(viewModel.isRebooting)
.help("Nötig, damit die neu geschriebene Firmware aktiv wird — passiert nicht automatisch.")
.help(L10n.t("Nötig, damit die neu geschriebene Firmware aktiv wird — passiert nicht automatisch.", appLanguage))
}
}
}
Section("Software-Update") {
Section(L10n.t("Software-Update", appLanguage)) {
if let update = viewModel.packageUpdateInfo {
LabeledContent("Kanal", value: update.channel)
LabeledContent("Installiert", value: update.installedVersion)
LabeledContent("Neueste Version", value: update.latestVersion)
LabeledContent("Status", value: update.status)
LabeledContent(L10n.t("Kanal", appLanguage), value: update.channel)
LabeledContent(L10n.t("Installiert", appLanguage), value: update.installedVersion)
LabeledContent(L10n.t("Neueste Version", appLanguage), value: update.latestVersion)
LabeledContent(L10n.t("Status", appLanguage), value: update.status)
if update.status == "New version is available" {
Button {
showUpdateInstallConfirmation = true
} label: {
if viewModel.isInstallingUpdate {
HStack { ProgressView(); Text("Installiere…") }
HStack { ProgressView(); Text(L10n.t("Installiere…", appLanguage)) }
} else {
Label("Update installieren", systemImage: "arrow.down.circle")
Label(L10n.t("Update installieren", appLanguage), systemImage: "arrow.down.circle")
}
}
.disabled(viewModel.isInstallingUpdate)
@@ -258,17 +263,17 @@ struct ConnectView: View {
}
} label: {
if viewModel.isCheckingForUpdates {
HStack { ProgressView(); Text("Prüfe…") }
HStack { ProgressView(); Text(L10n.t("Prüfe…", appLanguage)) }
} else {
Label("Jetzt prüfen", systemImage: "arrow.triangle.2.circlepath")
Label(L10n.t("Jetzt prüfen", appLanguage), systemImage: "arrow.triangle.2.circlepath")
}
}
.disabled(viewModel.isCheckingForUpdates)
.help("Prüft bei MikroTik, ob eine neuere RouterOS-Version verfügbar ist. Braucht Internetzugang auf dem Router.")
.help(L10n.t("Prüft bei MikroTik, ob eine neuere RouterOS-Version verfügbar ist. Braucht Internetzugang auf dem Router.", appLanguage))
if viewModel.didSendUpdateInstall {
Label(
"Update-Befehl gesendet — der Router lädt herunter, installiert und startet danach automatisch neu.",
L10n.t("Update-Befehl gesendet — der Router lädt herunter, installiert und startet danach automatisch neu.", appLanguage),
systemImage: "arrow.clockwise"
)
.font(.caption)
@@ -291,9 +296,9 @@ struct ConnectView: View {
.formStyle(.grouped)
} else {
ContentUnavailableView(
"Nicht verbunden",
LocalizedStringKey(L10n.t("Nicht verbunden", appLanguage)),
systemImage: "network.slash",
description: Text("Verbinde dich mit deinem Router, um Geräteinformationen zu sehen.")
description: Text(L10n.t("Verbinde dich mit deinem Router, um Geräteinformationen zu sehen.", appLanguage))
)
}
}
@@ -306,29 +311,32 @@ private extension View {
func withUpdateDialogs(
viewModel: ConnectViewModel,
connectionService: ConnectionService,
appLanguage: String,
showUpdateInstallConfirmation: Binding<Bool>,
showFirmwareUpgradeConfirmation: Binding<Bool>
) -> some View {
self
.withSoftwareUpdateDialogs(viewModel: viewModel, connectionService: connectionService, isPresented: showUpdateInstallConfirmation)
.withFirmwareUpgradeDialogs(viewModel: viewModel, connectionService: connectionService, isPresented: showFirmwareUpgradeConfirmation)
.withSoftwareUpdateDialogs(viewModel: viewModel, connectionService: connectionService, appLanguage: appLanguage, isPresented: showUpdateInstallConfirmation)
.withFirmwareUpgradeDialogs(viewModel: viewModel, connectionService: connectionService, appLanguage: appLanguage, isPresented: showFirmwareUpgradeConfirmation)
}
func withSoftwareUpdateDialogs(viewModel: ConnectViewModel, connectionService: ConnectionService, isPresented: Binding<Bool>) -> some View {
func withSoftwareUpdateDialogs(viewModel: ConnectViewModel, connectionService: ConnectionService, appLanguage: String, isPresented: Binding<Bool>) -> some View {
self
.confirmationDialog(
"RouterOS-Update installieren?",
L10n.t("RouterOS-Update installieren?", appLanguage),
isPresented: isPresented,
titleVisibility: .visible
) {
Button("Installieren", role: .destructive) {
Button(L10n.t("Installieren", appLanguage), role: .destructive) {
if let credentials = connectionService.credentials {
viewModel.installUpdate(for: credentials)
}
}
Button("Abbrechen", role: .cancel) {}
Button(L10n.t("Abbrechen", appLanguage), role: .cancel) {}
} message: {
Text("Lädt RouterOS \(viewModel.packageUpdateInfo?.latestVersion ?? "die neueste Version") herunter, installiert es und startet den Router danach automatisch neu — die Verbindung geht dabei verloren. Vorher am besten eine Sicherung erstellen (\"Jetzt sichern\" oben).")
Text(L10n.t("Lädt RouterOS", appLanguage)
+ " \(viewModel.packageUpdateInfo?.latestVersion ?? L10n.t("die neueste Version", appLanguage))"
+ L10n.t(" herunter, installiert es und startet den Router danach automatisch neu — die Verbindung geht dabei verloren. Vorher am besten eine Sicherung erstellen (\"Jetzt sichern\" oben).", appLanguage))
}
.onChange(of: viewModel.didSendUpdateInstall) { _, didSend in
if didSend {
@@ -336,76 +344,76 @@ private extension View {
}
}
.alert(
"Update-Prüfung fehlgeschlagen",
L10n.t("Update-Prüfung fehlgeschlagen", appLanguage),
isPresented: Binding(
get: { viewModel.updateCheckError != nil },
set: { _ in viewModel.updateCheckError = nil }
),
presenting: viewModel.updateCheckError
) { _ in
Button("OK") {}
Button(L10n.t("OK", appLanguage)) {}
} message: { message in
Text(message)
}
.alert(
"Update-Installation fehlgeschlagen",
L10n.t("Update-Installation fehlgeschlagen", appLanguage),
isPresented: Binding(
get: { viewModel.installUpdateError != nil },
set: { _ in viewModel.installUpdateError = nil }
),
presenting: viewModel.installUpdateError
) { _ in
Button("OK") {}
Button(L10n.t("OK", appLanguage)) {}
} message: { message in
Text(message)
}
}
func withFirmwareUpgradeDialogs(viewModel: ConnectViewModel, connectionService: ConnectionService, isPresented: Binding<Bool>) -> some View {
func withFirmwareUpgradeDialogs(viewModel: ConnectViewModel, connectionService: ConnectionService, appLanguage: String, isPresented: Binding<Bool>) -> some View {
self
.confirmationDialog(
"Routerboard-Firmware aktualisieren?",
L10n.t("Routerboard-Firmware aktualisieren?", appLanguage),
isPresented: isPresented,
titleVisibility: .visible
) {
Button("Aktualisieren", role: .destructive) {
Button(L10n.t("Aktualisieren", appLanguage), role: .destructive) {
if let credentials = connectionService.credentials {
viewModel.upgradeRouterboardFirmware(for: credentials)
}
}
Button("Abbrechen", role: .cancel) {}
Button(L10n.t("Abbrechen", appLanguage), role: .cancel) {}
} message: {
Text("Schreibt die neue Bootloader-Firmware auf den Router. Danach ist ein manueller Neustart nötig — passiert nicht automatisch. Dieser Befehl fragt in einer interaktiven Sitzung normalerweise erst nach Bestätigung; ob das über diese App genauso abläuft, ist nicht abschließend getestet (ähnlich interaktive Befehle liefen bisher aber problemlos). Falls die App hier ungewöhnlich lange ohne Rückmeldung bleibt, bitte melden.")
Text(L10n.t("Schreibt die neue Bootloader-Firmware auf den Router. Danach ist ein manueller Neustart nötig — passiert nicht automatisch. Dieser Befehl fragt in einer interaktiven Sitzung normalerweise erst nach Bestätigung; ob das über diese App genauso abläuft, ist nicht abschließend getestet (ähnlich interaktive Befehle liefen bisher aber problemlos). Falls die App hier ungewöhnlich lange ohne Rückmeldung bleibt, bitte melden.", appLanguage))
}
.alert(
"Firmware-Aktualisierung fehlgeschlagen",
L10n.t("Firmware-Aktualisierung fehlgeschlagen", appLanguage),
isPresented: Binding(
get: { viewModel.firmwareUpgradeError != nil },
set: { _ in viewModel.firmwareUpgradeError = nil }
),
presenting: viewModel.firmwareUpgradeError
) { _ in
Button("OK") {}
Button(L10n.t("OK", appLanguage)) {}
} message: { message in
Text(message)
}
}
func withRebootDialogs(viewModel: ConnectViewModel, connectionService: ConnectionService, isPresented: Binding<Bool>) -> some View {
func withRebootDialogs(viewModel: ConnectViewModel, connectionService: ConnectionService, appLanguage: String, isPresented: Binding<Bool>) -> some View {
self
.confirmationDialog(
"Router jetzt neu starten?",
L10n.t("Router jetzt neu starten?", appLanguage),
isPresented: isPresented,
titleVisibility: .visible
) {
Button("Neu starten", role: .destructive) {
Button(L10n.t("Neu starten", appLanguage), role: .destructive) {
if let credentials = connectionService.credentials {
viewModel.reboot(for: credentials)
}
}
Button("Abbrechen", role: .cancel) {}
Button(L10n.t("Abbrechen", appLanguage), role: .cancel) {}
} message: {
Text("Startet den Router sofort neu. Die Verbindung geht dabei kurz verloren, danach im Tab \"Verbinden\" erneut verbinden.")
Text(L10n.t("Startet den Router sofort neu. Die Verbindung geht dabei kurz verloren, danach im Tab \"Verbinden\" erneut verbinden.", appLanguage))
}
.onChange(of: viewModel.didSendReboot) { _, didSend in
if didSend {
@@ -413,14 +421,14 @@ private extension View {
}
}
.alert(
"Neustart fehlgeschlagen",
L10n.t("Neustart fehlgeschlagen", appLanguage),
isPresented: Binding(
get: { viewModel.rebootError != nil },
set: { _ in viewModel.rebootError = nil }
),
presenting: viewModel.rebootError
) { _ in
Button("OK") {}
Button(L10n.t("OK", appLanguage)) {}
} message: { message in
Text(message)
}