.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
84 lines
4.5 KiB
Swift
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())
|
|
}
|