fix(i18n): localize sidebar, settings tabs, and settings section titles

Three connected bugs where the Label/SettingsSection APIs took a `String`,
which routes through the StringProtocol overloads and bypasses localization
entirely. Identified by the user after testing zh-Hans / de / fr — the
sidebar menu items, Settings tab bar, and Settings section headers all
remained English under any App Language override.

- SidebarSection now exposes displayName: LocalizedStringResource; SidebarView
  builds Label via the Text/Image builders so the catalog key is actually
  used.
- SettingsTab gets the same displayName treatment; the .tabItem Label builds
  through the Text/Image builder too.
- SettingsSection.title changes from String → LocalizedStringKey so literal
  call sites (all ~20 of them) now extract into the catalog. Two call sites
  that were passing String variables (PlatformsView, CredentialPoolsView) are
  wrapped via LocalizedStringKey(...) — brand/provider names fall through to
  English as before. AuxiliaryTab's static task list gets a LocalizedStringKey
  column so its section titles extract too.

This change newly extracts 65 previously-invisible section-title keys into
the catalog; translations added for all six locales. Catalog: 575 → 644
source keys, each locale translated for 583 of them (brand names / protocol
names / format-only keys intentionally fall through).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alan Wizemann
2026-04-21 03:32:32 +02:00
parent 1726a613a5
commit f47034d4ad
14 changed files with 3078 additions and 17 deletions
@@ -106,7 +106,7 @@ struct CredentialPoolsView: View {
@ViewBuilder
private func poolSection(_ pool: HermesCredentialPool) -> some View {
SettingsSection(title: pool.provider, icon: "key.horizontal") {
SettingsSection(title: LocalizedStringKey(pool.provider), icon: "key.horizontal") {
PickerRow(label: "Rotation", selection: pool.strategy, options: viewModel.strategyOptions) { strategy in
viewModel.setStrategy(strategy, for: pool.provider)
}
@@ -139,7 +139,7 @@ struct PlatformsView: View {
case "homeassistant": HomeAssistantSetupView(context: ctx)
case "webhook": WebhookSetupView(context: ctx)
default:
SettingsSection(title: viewModel.selected.displayName, icon: KnownPlatforms.icon(for: viewModel.selected.name)) {
SettingsSection(title: LocalizedStringKey(viewModel.selected.displayName), icon: KnownPlatforms.icon(for: viewModel.selected.name)) {
ReadOnlyRow(label: "Setup", value: "No setup form for this platform yet.")
}
}
@@ -6,7 +6,7 @@ import AppKit
/// on large view bodies (per project guidance in CLAUDE.md).
struct SettingsSection<Content: View>: View {
let title: String
let title: LocalizedStringKey
let icon: String
@ViewBuilder let content: Content
@@ -26,6 +26,22 @@ struct SettingsView: View {
case advanced = "Advanced"
var id: String { rawValue }
var displayName: LocalizedStringResource {
switch self {
case .general: return "General"
case .display: return "Display"
case .agent: return "Agent"
case .terminal: return "Terminal"
case .browser: return "Browser"
case .voice: return "Voice"
case .memory: return "Memory"
case .auxiliary: return "Aux Models"
case .security: return "Security"
case .advanced: return "Advanced"
}
}
var icon: String {
switch self {
case .general: return "gear"
@@ -56,7 +72,11 @@ struct SettingsView: View {
.frame(maxWidth: .infinity, alignment: .topLeading)
}
.tabItem {
Label(tab.rawValue, systemImage: tab.icon)
Label {
Text(tab.displayName)
} icon: {
Image(systemName: tab.icon)
}
}
.tag(tab)
}
@@ -6,7 +6,7 @@ struct AuxiliaryTab: View {
@Bindable var viewModel: SettingsViewModel
// Keyed by the config path name matches `auxiliary.<task>.*` in config.yaml.
private let tasks: [(key: String, title: String, icon: String)] = [
private let tasks: [(key: String, title: LocalizedStringKey, icon: String)] = [
("vision", "Vision", "eye"),
("web_extract", "Web Extract", "doc.richtext"),
("compression", "Compression", "arrow.down.right.and.arrow.up.left.circle"),
File diff suppressed because it is too large Load Diff
@@ -31,6 +31,33 @@ enum SidebarSection: String, CaseIterable, Identifiable {
var id: String { rawValue }
var displayName: LocalizedStringResource {
switch self {
case .dashboard: return "Dashboard"
case .insights: return "Insights"
case .sessions: return "Sessions"
case .activity: return "Activity"
case .projects: return "Projects"
case .chat: return "Chat"
case .memory: return "Memory"
case .skills: return "Skills"
case .platforms: return "Platforms"
case .personalities: return "Personalities"
case .quickCommands: return "Quick Commands"
case .credentialPools: return "Credential Pools"
case .plugins: return "Plugins"
case .webhooks: return "Webhooks"
case .profiles: return "Profiles"
case .tools: return "Tools"
case .mcpServers: return "MCP Servers"
case .gateway: return "Gateway"
case .cron: return "Cron"
case .health: return "Health"
case .logs: return "Logs"
case .settings: return "Settings"
}
}
var icon: String {
switch self {
case .dashboard: return "gauge.with.dots.needle.33percent"
+30 -10
View File
@@ -8,32 +8,52 @@ struct SidebarView: View {
List(selection: $coordinator.selectedSection) {
Section("Monitor") {
ForEach([SidebarSection.dashboard, .insights, .sessions, .activity]) { section in
Label(section.rawValue, systemImage: section.icon)
.tag(section)
Label {
Text(section.displayName)
} icon: {
Image(systemName: section.icon)
}
.tag(section)
}
}
Section("Projects") {
ForEach([SidebarSection.projects]) { section in
Label(section.rawValue, systemImage: section.icon)
.tag(section)
Label {
Text(section.displayName)
} icon: {
Image(systemName: section.icon)
}
.tag(section)
}
}
Section("Interact") {
ForEach([SidebarSection.chat, .memory, .skills]) { section in
Label(section.rawValue, systemImage: section.icon)
.tag(section)
Label {
Text(section.displayName)
} icon: {
Image(systemName: section.icon)
}
.tag(section)
}
}
Section("Configure") {
ForEach([SidebarSection.platforms, .personalities, .quickCommands, .credentialPools, .plugins, .webhooks, .profiles]) { section in
Label(section.rawValue, systemImage: section.icon)
.tag(section)
Label {
Text(section.displayName)
} icon: {
Image(systemName: section.icon)
}
.tag(section)
}
}
Section("Manage") {
ForEach([SidebarSection.tools, .mcpServers, .gateway, .cron, .health, .logs, .settings]) { section in
Label(section.rawValue, systemImage: section.icon)
.tag(section)
Label {
Text(section.displayName)
} icon: {
Image(systemName: section.icon)
}
.tag(section)
}
}
}