Files
RouterOS/RouterOSAssistant/Features/Expert/ExpertView.swift
T
KayandClaude Sonnet 5 0d16d05cb3 M16: Zweisprachigkeit (DE/EN) im Experte-Tab, eigener L10n-Helfer statt String Catalog
.environment(\.locale) + Localizable.xcstrings schaltete Text(LocalizedStringKey)
live nachweislich nicht um. Ersetzt durch Core/Localization/L10n.swift
(Dictionary-Lookup je AppStorage("appLanguage")), Umschalt-Button in der Toolbar.
Vollständig übersetzt: alle Tab-Namen, alle Menü-Kategorien, kompletter
Experte-Tab inkl. Firewall-Formulare. 60 Tests grün, live bestätigt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YDmUd93KxsYGr2kLTotWnG
2026-09-15 15:54:26 +02:00

84 lines
4.5 KiB
Swift

import SwiftUI
struct ExpertView: View {
@ObservedObject var connectionService: ConnectionService
@StateObject private var viewModel: ExpertViewModel
@AppStorage("appLanguage") private var appLanguage: String = "de"
init(connectionService: ConnectionService) {
self.connectionService = connectionService
_viewModel = StateObject(wrappedValue: ExpertViewModel(connectionService: connectionService))
}
var body: some View {
Group {
if connectionService.credentials == nil {
ContentUnavailableView(
LocalizedStringKey(L10n.t("Nicht verbunden", appLanguage)),
systemImage: "network.slash",
description: Text(L10n.t("Verbinde dich zuerst im Tab \"Verbinden\" mit deinem Router.", appLanguage))
)
} else {
NavigationSplitView {
List {
ForEach(RouterOSMenuCategory.allCases) { category in
let schemas = RouterOSSchemaCatalog.schemas(in: category)
if !schemas.isEmpty {
Section(LocalizedStringKey(L10n.t(category.rawValue, appLanguage))) {
ForEach(schemas) { schema in
let isSelected = viewModel.selectedSchema?.menuPath == schema.menuPath
Button {
viewModel.open(schema)
} label: {
Text(L10n.t(schema.displayName, appLanguage))
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.vertical, 4)
.padding(.horizontal, 6)
.background(
RoundedRectangle(cornerRadius: 6)
.fill(isSelected ? Color.blue.opacity(0.25) : Color.clear)
)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
}
}
}
}
Section(LocalizedStringKey(L10n.t("Eigener Menüpfad", appLanguage))) {
Text(L10n.t("Jeder RouterOS-Menüpfad ist erreichbar, auch wenn er oben nicht gelistet ist — die Felder erscheinen dann generisch (Schlüssel/Wert), ohne kuratierte Erklärung.", appLanguage))
.font(.caption)
.foregroundStyle(.secondary)
TextField(L10n.t("Menüpfad, z.B. /ip firewall filter", appLanguage), text: $viewModel.customMenuPath)
.help(L10n.t("Der RouterOS-CLI-Pfad, z.B. \"/interface wireguard\" oder \"/routing ospf instance\".", appLanguage))
TextField(L10n.t("REST-Pfad, z.B. ip/firewall/filter", appLanguage), text: $viewModel.customRestPath)
.help(L10n.t("Derselbe Pfad in REST-API-Schreibweise: führende \"/\" weg, Leerzeichen zu \"/\".", appLanguage))
Button(L10n.t("Öffnen", appLanguage)) { viewModel.openCustomPath() }
.disabled(viewModel.customMenuPath.isEmpty || viewModel.customRestPath.isEmpty)
}
}
.navigationTitle(LocalizedStringKey(L10n.t("Experte", appLanguage)))
} detail: {
if let schema = viewModel.selectedSchema {
ExpertMenuDetailView(
viewModel: viewModel,
schema: schema
)
} else {
ContentUnavailableView(
LocalizedStringKey(L10n.t("Kein Menü ausgewählt", appLanguage)),
systemImage: "list.bullet.rectangle",
description: Text(L10n.t("Wähle links einen RouterOS-Bereich aus.", appLanguage))
)
}
}
}
}
}
}
#Preview {
ExpertView(connectionService: ConnectionService())
}