mirror of
https://github.com/awizemann/scarf.git
synced 2026-05-10 10:36:35 +00:00
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:
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user