From 37fa255a6366bf49839349c9c12f64e821e5dfc6 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Fri, 19 Jun 2026 10:20:19 +0200 Subject: [PATCH] refactor(i18n): route command + settings manifest labels through the catalog (T-462) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apply the manifest-i18n foundation across every extension: each CommandContribution (45, command-palette + menu) gains titleKey + i18nNamespace, and each SettingsCategory/section/field/option gains its key — with the English text added to the owning extension's catalog. The settings renderer (settings_category_view + settings_modal) threads the category's i18nNamespace down and resolves every label/help/option through it; new catalogs created for view, cli-install, keybindings-ui. No en_US behaviour change — the command palette, menus, and settings panel now localize from the catalog (D-21). Co-Authored-By: Claude Opus 4.8 (1M context) --- lib/builtin/claude/src/extension.dart | 56 ++++++++- lib/builtin/cli_install/src/extension.dart | 2 + lib/builtin/deeplink/src/extension.dart | 9 +- lib/builtin/default_layout/src/extension.dart | 91 ++++++++++++-- lib/builtin/extensions_ui/src/extension.dart | 15 ++- lib/builtin/keybindings_ui/src/extension.dart | 17 ++- lib/builtin/menubar/src/extension.dart | 8 ++ lib/builtin/output/src/extension.dart | 2 + lib/builtin/settings_ui/src/extension.dart | 10 +- .../src/settings_category_view.dart | 114 +++++++++++++----- .../settings_ui/src/settings_modal.dart | 9 +- lib/builtin/theme_picker/src/extension.dart | 30 ++++- lib/builtin/view/src/extension.dart | 6 + lib/builtin/vim/src/extension.dart | 2 + lib/builtin/welcome/src/extension.dart | 2 + .../i18n/catalog/builtin.claude_en_us.json | 34 +++++- .../catalog/builtin.cli-install_en_us.json | 3 + .../i18n/catalog/builtin.deeplink_en_us.json | 3 +- .../catalog/builtin.default-layout_en_us.json | 19 ++- .../catalog/builtin.extensions-ui_en_us.json | 5 +- .../catalog/builtin.keybindings-ui_en_us.json | 14 +++ .../i18n/catalog/builtin.menubar_en_us.json | 6 +- .../i18n/catalog/builtin.output_en_us.json | 3 +- .../catalog/builtin.settings-ui_en_us.json | 1 + .../catalog/builtin.theme-picker_en_us.json | 17 ++- .../src/i18n/catalog/builtin.view_en_us.json | 5 + .../src/i18n/catalog/builtin.vim_en_us.json | 5 +- .../i18n/catalog/builtin.welcome_en_us.json | 3 +- 28 files changed, 419 insertions(+), 72 deletions(-) create mode 100644 lib/kernel/src/i18n/catalog/builtin.cli-install_en_us.json create mode 100644 lib/kernel/src/i18n/catalog/builtin.keybindings-ui_en_us.json create mode 100644 lib/kernel/src/i18n/catalog/builtin.view_en_us.json diff --git a/lib/builtin/claude/src/extension.dart b/lib/builtin/claude/src/extension.dart index 05341979..266d114b 100644 --- a/lib/builtin/claude/src/extension.dart +++ b/lib/builtin/claude/src/extension.dart @@ -76,6 +76,8 @@ class ClaudeExtension extends ClideExtension { id: 'claude.new-secondary', command: 'claude.new-secondary', title: 'Claude: open a secondary session', + titleKey: 'command.newSecondary', + i18nNamespace: id, run: (_) async { _hostKey.currentState?.addSecondary(); return IpcResponse.ok(id: '', data: const {'status': 'spawned'}); @@ -85,12 +87,16 @@ class ClaudeExtension extends ClideExtension { id: 'claude.kill-all-sessions', command: 'claude.kill-all-sessions', title: 'Claude: kill all sessions for this repo', + titleKey: 'command.killAllSessions', + i18nNamespace: id, run: _killAllSessions, ), CommandContribution( id: 'claude.session-storage', command: 'claude.session-storage', title: 'Claude: session storage (disk usage + cleanup)', + titleKey: 'command.sessionStorage', + i18nNamespace: id, run: _manageStorage, ), // T-235: cycle how aggressively the activity card folds meta steps @@ -100,33 +106,40 @@ class ClaudeExtension extends ClideExtension { id: 'claude.activity.fold-level', command: 'claude.activity.fold-level', title: 'Claude: cycle activity fold level', + titleKey: 'command.activity.foldLevel', + i18nNamespace: id, run: _cycleFoldLevel, ), // Activity settings category (T-453) — the fold level as a schema field, // written to kActivityFoldLevelKey; the panes already rebuild off the // settings notifier, so picking a level applies live. - const SettingsCategoryContribution( + SettingsCategoryContribution( id: 'activity', category: SettingsCategory( id: 'activity', title: 'Activity', + titleKey: 'settings.activity.title', + i18nNamespace: id, iconName: 'cards-three', priority: 50, sections: [ SettingsSection( label: 'Conversation', + labelKey: 'settings.activity.conversation.label', fields: [ SettingsField( key: kActivityFoldLevelKey, kind: SettingsFieldKind.select, label: 'Fold level', + labelKey: 'settings.activity.foldLevel.label', help: 'How aggressively the conversation folds tool calls, thinking, and results.', + helpKey: 'settings.activity.foldLevel.help', defaultValue: 'tools', options: [ - SettingsOption(value: 'none', label: 'Show everything'), - SettingsOption(value: 'tools', label: 'Fold tool calls'), - SettingsOption(value: 'thinking', label: 'Fold tools + thinking'), - SettingsOption(value: 'everything', label: 'Fold all but prose'), + SettingsOption(value: 'none', label: 'Show everything', labelKey: 'settings.activity.opt.none'), + SettingsOption(value: 'tools', label: 'Fold tool calls', labelKey: 'settings.activity.opt.tools'), + SettingsOption(value: 'thinking', label: 'Fold tools + thinking', labelKey: 'settings.activity.opt.thinking'), + SettingsOption(value: 'everything', label: 'Fold all but prose', labelKey: 'settings.activity.opt.everything'), ], ), ], @@ -142,17 +155,22 @@ class ClaudeExtension extends ClideExtension { category: SettingsCategory( id: 'claude', title: 'Claude', + titleKey: 'settings.claude.title', + i18nNamespace: id, iconName: 'sparkle', priority: 40, sections: [ SettingsSection( label: 'New session defaults', + labelKey: 'settings.claude.newSessionDefaults.label', fields: [ SettingsField( key: kDefaultModelKey, kind: SettingsFieldKind.select, label: 'Model', + labelKey: 'settings.claude.model.label', help: 'Model for new sessions.', + helpKey: 'settings.claude.model.help', defaultValue: 'default', options: [for (final m in kFallbackModels) SettingsOption(value: m.value, label: m.displayName)], ), @@ -160,7 +178,9 @@ class ClaudeExtension extends ClideExtension { key: kDefaultEffortKey, kind: SettingsFieldKind.select, label: 'Effort', + labelKey: 'settings.claude.effort.label', help: 'Reasoning effort for new sessions (applied via --effort at spawn).', + helpKey: 'settings.claude.effort.help', defaultValue: 'high', options: [for (final l in kEffortLevels) SettingsOption(value: l.value, label: l.displayName)], ), @@ -168,7 +188,9 @@ class ClaudeExtension extends ClideExtension { key: kDefaultPermissionModeKey, kind: SettingsFieldKind.select, label: 'Permission mode', + labelKey: 'settings.claude.permissionMode.label', help: 'Starting permission mode for new sessions.', + helpKey: 'settings.claude.permissionMode.help', defaultValue: 'default', options: [for (final p in kPermissionModes) SettingsOption(value: p.value, label: p.displayName)], ), @@ -183,6 +205,8 @@ class ClaudeExtension extends ClideExtension { id: 'claude.agent.show', command: 'claude.agent.show', title: 'Claude: show an agent session pane', + titleKey: 'command.agent.show', + i18nNamespace: id, run: (args) async { final id = args.firstOrNull; if (id == null) return _userErr('missing session id'); @@ -194,6 +218,8 @@ class ClaudeExtension extends ClideExtension { id: 'claude.agent.hide', command: 'claude.agent.hide', title: 'Claude: hide an agent session pane', + titleKey: 'command.agent.hide', + i18nNamespace: id, run: (args) async { final id = args.firstOrNull; if (id == null) return _userErr('missing session id'); @@ -205,6 +231,8 @@ class ClaudeExtension extends ClideExtension { id: 'claude.agent.close', command: 'claude.agent.close', title: 'Claude: close (kill) an agent session', + titleKey: 'command.agent.close', + i18nNamespace: id, run: (args) async { final id = args.firstOrNull; if (id == null) return _userErr('missing session id'); @@ -216,6 +244,8 @@ class ClaudeExtension extends ClideExtension { id: 'claude.agent.mute', command: 'claude.agent.mute', title: 'Claude: mute broker delivery to an agent session', + titleKey: 'command.agent.mute', + i18nNamespace: id, run: (args) async { final id = args.firstOrNull; if (id == null) return _userErr('missing session id'); @@ -227,6 +257,8 @@ class ClaudeExtension extends ClideExtension { id: 'claude.agent.unmute', command: 'claude.agent.unmute', title: 'Claude: unmute broker delivery to an agent session', + titleKey: 'command.agent.unmute', + i18nNamespace: id, run: (args) async { final id = args.firstOrNull; if (id == null) return _userErr('missing session id'); @@ -239,6 +271,8 @@ class ClaudeExtension extends ClideExtension { id: 'claude.agent.inject-message', command: 'claude.agent.inject-message', title: 'Claude: inject a text turn into an agent session', + titleKey: 'command.agent.injectMessage', + i18nNamespace: id, run: (args) async { final id = args.firstOrNull; if (id == null) return _userErr('missing session id'); @@ -257,6 +291,8 @@ class ClaudeExtension extends ClideExtension { id: 'claude.agent.set-permission-mode', command: 'claude.agent.set-permission-mode', title: 'Claude: set permission mode for an agent session', + titleKey: 'command.agent.setPermissionMode', + i18nNamespace: id, run: (args) async { final id = args.firstOrNull; if (id == null) return _userErr('missing session id'); @@ -276,6 +312,8 @@ class ClaudeExtension extends ClideExtension { id: 'claude.mode.cycle', command: 'claude.mode.cycle', title: 'Claude: Cycle permission mode', + titleKey: 'command.mode.cycle', + i18nNamespace: id, run: (_) async { final managed = _orchestrator?.byId('primary'); if (managed == null) return _notFound('no primary session'); @@ -289,6 +327,8 @@ class ClaudeExtension extends ClideExtension { id: 'claude.task.reassign', command: 'claude.task.reassign', title: 'Claude: reassign a shared task to an agent', + titleKey: 'command.task.reassign', + i18nNamespace: id, run: (args) async { if (args.length < 2) return _userErr('usage: '); final taskId = args[0]; @@ -319,6 +359,8 @@ class ClaudeExtension extends ClideExtension { id: 'claude.team-chat.open', command: 'claude.team-chat.open', title: 'Claude: open the team chat pane', + titleKey: 'command.teamChat.open', + i18nNamespace: id, run: (args) async { _ctx?.panels.activateTab(Slots.workspace, 'claude.team-chat'); return IpcResponse.ok(id: '', data: const {'status': 'opened'}); @@ -331,6 +373,8 @@ class ClaudeExtension extends ClideExtension { id: 'claude.team-chat.post', command: 'claude.team-chat.post', title: 'Claude: post a message into the team channel as the user', + titleKey: 'command.teamChat.post', + i18nNamespace: id, run: (args) async { if (args.isEmpty) return _userErr('usage: [@name] '); final raw = args.join(' '); @@ -357,6 +401,8 @@ class ClaudeExtension extends ClideExtension { id: 'claude.agent.fork', command: 'claude.agent.fork', title: 'Claude: fork a managed session into a new branch session', + titleKey: 'command.agent.fork', + i18nNamespace: id, run: (args) async { final sourceId = args.firstOrNull; if (sourceId == null) { diff --git a/lib/builtin/cli_install/src/extension.dart b/lib/builtin/cli_install/src/extension.dart index 0566f354..9a870c23 100644 --- a/lib/builtin/cli_install/src/extension.dart +++ b/lib/builtin/cli_install/src/extension.dart @@ -65,6 +65,8 @@ class CliInstallExtension extends ClideExtension { id: 'clide.installCli', command: 'clide.installCli', title: "clide: Install 'clide' command in PATH", + titleKey: 'command.clide.installCli', + i18nNamespace: id, run: (_) async { final r = _resolved.install(); final ctx = _ctx; diff --git a/lib/builtin/deeplink/src/extension.dart b/lib/builtin/deeplink/src/extension.dart index 8f7dde49..5a5a9eb0 100644 --- a/lib/builtin/deeplink/src/extension.dart +++ b/lib/builtin/deeplink/src/extension.dart @@ -24,7 +24,14 @@ class DeepLinkExtension extends ClideExtension { @override List get contributions => [ - CommandContribution(id: 'deeplink.invoke', command: 'deeplink.invoke', title: 'Open a clide:// deep link', run: _invoke), + CommandContribution( + id: 'deeplink.invoke', + command: 'deeplink.invoke', + title: 'Open a clide:// deep link', + titleKey: 'command.deeplink.invoke', + i18nNamespace: id, + run: _invoke, + ), ]; @override diff --git a/lib/builtin/default_layout/src/extension.dart b/lib/builtin/default_layout/src/extension.dart index d1410406..bef9f378 100644 --- a/lib/builtin/default_layout/src/extension.dart +++ b/lib/builtin/default_layout/src/extension.dart @@ -16,13 +16,30 @@ class DefaultLayoutExtension extends ClideExtension { @override List get contributions => [ _preset ?? classicPreset(), - CommandContribution(id: 'layout.reset', command: 'layout.reset', title: 'Layout: Reset to Classic', run: _reset), - CommandContribution(id: 'palette.toggle', command: 'palette.toggle', title: 'Command Palette', defaultBinding: 'ctrl+shift+p', run: _togglePalette), + CommandContribution( + id: 'layout.reset', + command: 'layout.reset', + title: 'Layout: Reset to Classic', + titleKey: 'command.reset', + i18nNamespace: id, + run: _reset, + ), + CommandContribution( + id: 'palette.toggle', + command: 'palette.toggle', + title: 'Command Palette', + titleKey: 'command.palette.toggle', + i18nNamespace: id, + defaultBinding: 'ctrl+shift+p', + run: _togglePalette, + ), // Collapse toggles (D-051, D-054) CommandContribution( id: 'sidebar.collapse', command: 'sidebar.collapse', title: 'Toggle Sidebar Collapse', + titleKey: 'command.sidebar.collapse', + i18nNamespace: id, defaultBinding: 'ctrl+shift+1', run: _collapseSidebar, ), @@ -30,19 +47,55 @@ class DefaultLayoutExtension extends ClideExtension { id: 'context.collapse', command: 'context.collapse', title: 'Toggle Context Panel Collapse', + titleKey: 'command.context.collapse', + i18nNamespace: id, defaultBinding: 'ctrl+shift+3', run: _collapseContext, ), // Panel focus (D-054) - CommandContribution(id: 'panel.focus.left', command: 'panel.focus.left', title: 'Focus Left Panel', defaultBinding: 'ctrl+1', run: _focusLeft), - CommandContribution(id: 'panel.focus.middle', command: 'panel.focus.middle', title: 'Focus Middle Panel', defaultBinding: 'ctrl+2', run: _focusMiddle), - CommandContribution(id: 'panel.focus.right', command: 'panel.focus.right', title: 'Focus Right Panel', defaultBinding: 'ctrl+3', run: _focusRight), + CommandContribution( + id: 'panel.focus.left', + command: 'panel.focus.left', + title: 'Focus Left Panel', + titleKey: 'command.panel.focus.left', + i18nNamespace: id, + defaultBinding: 'ctrl+1', + run: _focusLeft, + ), + CommandContribution( + id: 'panel.focus.middle', + command: 'panel.focus.middle', + title: 'Focus Middle Panel', + titleKey: 'command.panel.focus.middle', + i18nNamespace: id, + defaultBinding: 'ctrl+2', + run: _focusMiddle, + ), + CommandContribution( + id: 'panel.focus.right', + command: 'panel.focus.right', + title: 'Focus Right Panel', + titleKey: 'command.panel.focus.right', + i18nNamespace: id, + defaultBinding: 'ctrl+3', + run: _focusRight, + ), // Focus mode (D-052, D-054) - CommandContribution(id: 'panel.focusMode', command: 'panel.focusMode', title: 'Toggle Focus Mode', defaultBinding: 'ctrl+.', run: _toggleFocusMode), + CommandContribution( + id: 'panel.focusMode', + command: 'panel.focusMode', + title: 'Toggle Focus Mode', + titleKey: 'command.panel.focusMode', + i18nNamespace: id, + defaultBinding: 'ctrl+.', + run: _toggleFocusMode, + ), CommandContribution( id: 'panel.focusMode.exit', command: 'panel.focusMode.exit', title: 'Exit Focus Mode', + titleKey: 'command.panel.focusMode.exit', + i18nNamespace: id, defaultBinding: 'escape', // Stand down in Vim insert/visual mode so Esc returns to normal // mode instead of closing the editor (T-257). Symmetric with @@ -51,8 +104,24 @@ class DefaultLayoutExtension extends ClideExtension { run: _exitFocusMode, ), // Editor split (D-049, D-054) - CommandContribution(id: 'editor.open', command: 'editor.open', title: 'Open Editor', defaultBinding: 'ctrl+e', run: _openEditor), - CommandContribution(id: 'editor.close', command: 'editor.close', title: 'Close Editor', defaultBinding: 'ctrl+w', run: _closeEditor), + CommandContribution( + id: 'editor.open', + command: 'editor.open', + title: 'Open Editor', + titleKey: 'command.editor.open', + i18nNamespace: id, + defaultBinding: 'ctrl+e', + run: _openEditor, + ), + CommandContribution( + id: 'editor.close', + command: 'editor.close', + title: 'Close Editor', + titleKey: 'command.editor.close', + i18nNamespace: id, + defaultBinding: 'ctrl+w', + run: _closeEditor, + ), // Workspace tab cycling (T-405). Preset-neutral ctrl+pagedown/up across every // preset; the vim preset additionally binds gt/gT to these (T-405 part 2, // once a global multi-chord matcher lands — see T-404). @@ -60,6 +129,8 @@ class DefaultLayoutExtension extends ClideExtension { id: 'workspace.tab.next', command: 'workspace.tab.next', title: 'Next Workspace Tab', + titleKey: 'command.workspace.tab.next', + i18nNamespace: id, defaultBinding: 'ctrl+pagedown', run: _nextWorkspaceTab, ), @@ -67,6 +138,8 @@ class DefaultLayoutExtension extends ClideExtension { id: 'workspace.tab.previous', command: 'workspace.tab.previous', title: 'Previous Workspace Tab', + titleKey: 'command.workspace.tab.previous', + i18nNamespace: id, defaultBinding: 'ctrl+pageup', run: _prevWorkspaceTab, ), @@ -76,6 +149,8 @@ class DefaultLayoutExtension extends ClideExtension { id: 'sidebar.section.${i + 1}', command: 'sidebar.section.${i + 1}', title: 'Sidebar: Section ${i + 1}', + titleKey: 'command.sidebar.section.${i + 1}', + i18nNamespace: id, defaultBinding: 'alt+${i + 1}', run: (args) => _switchSidebarSection(i), ), diff --git a/lib/builtin/extensions_ui/src/extension.dart b/lib/builtin/extensions_ui/src/extension.dart index 1692b9fc..866c067a 100644 --- a/lib/builtin/extensions_ui/src/extension.dart +++ b/lib/builtin/extensions_ui/src/extension.dart @@ -20,17 +20,28 @@ class ExtensionsUiExtension extends ClideExtension { @override List get contributions => [ SettingsControlContribution(id: 'extensions-ui.notice-control', customId: 'extensions.notice', builder: (_) => const ExtensionsNotice()), - const SettingsCategoryContribution( + SettingsCategoryContribution( id: 'extensions', category: SettingsCategory( id: 'extensions', title: 'Extensions', + titleKey: 'settings.extensions.title', + i18nNamespace: id, iconName: 'puzzle-piece', priority: 80, sections: [ SettingsSection( label: '', - fields: [SettingsField(key: 'app.extensions._notice', kind: SettingsFieldKind.custom, label: '', customId: 'extensions.notice')], + labelKey: 'settings.extensions.section.notice', + fields: [ + SettingsField( + key: 'app.extensions._notice', + kind: SettingsFieldKind.custom, + label: '', + labelKey: 'settings.extensions.field.notice.label', + customId: 'extensions.notice', + ), + ], ), ], ), diff --git a/lib/builtin/keybindings_ui/src/extension.dart b/lib/builtin/keybindings_ui/src/extension.dart index 1e3bc71a..c78d8d0b 100644 --- a/lib/builtin/keybindings_ui/src/extension.dart +++ b/lib/builtin/keybindings_ui/src/extension.dart @@ -37,6 +37,8 @@ class KeybindingsUiExtension extends ClideExtension { id: 'keymap.preset.${entry.key}', command: 'keymap.preset.${entry.key}', title: entry.value, + titleKey: 'command.preset.${entry.key}', + i18nNamespace: id, run: (_) async { await _keymap?.setPreset(entry.key); return IpcResponse.ok(id: '', data: {'preset': entry.key}); @@ -46,29 +48,34 @@ class KeybindingsUiExtension extends ClideExtension { // from kKeymapPresetSetting; picking one runs `keymap.preset.` // (applyCommandPrefix), which calls KeymapService.setPreset — persisting // and reloading the layered keymap live. - const SettingsCategoryContribution( + SettingsCategoryContribution( id: 'keymap', category: SettingsCategory( id: 'keymap', title: 'Keymap', + titleKey: 'settings.keymap.title', + i18nNamespace: id, iconName: 'keyboard', priority: 20, sections: [ SettingsSection( label: 'Preset', + labelKey: 'settings.keymap.section.preset', fields: [ SettingsField( key: kKeymapPresetSetting, kind: SettingsFieldKind.select, label: 'Active preset', + labelKey: 'settings.keymap.field.preset.label', help: 'Keyboard layout for the whole app.', + helpKey: 'settings.keymap.field.preset.help', defaultValue: 'default', applyCommandPrefix: 'keymap.preset.', options: [ - SettingsOption(value: 'default', label: 'Default'), - SettingsOption(value: 'vim', label: 'Vim'), - SettingsOption(value: 'vscode', label: 'VS Code'), - SettingsOption(value: 'jetbrains', label: 'JetBrains'), + SettingsOption(value: 'default', label: 'Default', labelKey: 'settings.keymap.field.preset.option.default'), + SettingsOption(value: 'vim', label: 'Vim', labelKey: 'settings.keymap.field.preset.option.vim'), + SettingsOption(value: 'vscode', label: 'VS Code', labelKey: 'settings.keymap.field.preset.option.vscode'), + SettingsOption(value: 'jetbrains', label: 'JetBrains', labelKey: 'settings.keymap.field.preset.option.jetbrains'), ], ), ], diff --git a/lib/builtin/menubar/src/extension.dart b/lib/builtin/menubar/src/extension.dart index bf3c5075..d58a1fb1 100644 --- a/lib/builtin/menubar/src/extension.dart +++ b/lib/builtin/menubar/src/extension.dart @@ -30,6 +30,8 @@ class MenuBarExtension extends ClideExtension { id: 'file.openFolder', command: 'file.openFolder', title: 'File: Open Folder…', + titleKey: 'command.file.openFolder', + i18nNamespace: id, run: (_) async { await _file.openFolder(); return IpcResponse.ok(id: '', data: const {}); @@ -39,6 +41,8 @@ class MenuBarExtension extends ClideExtension { id: 'file.newWindow', command: 'file.newWindow', title: 'File: New Window', + titleKey: 'command.file.newWindow', + i18nNamespace: id, run: (_) async { _file.newWindow(); return IpcResponse.ok(id: '', data: const {}); @@ -48,6 +52,8 @@ class MenuBarExtension extends ClideExtension { id: 'file.closeWorkspace', command: 'file.closeWorkspace', title: 'File: Close Project', + titleKey: 'command.file.closeWorkspace', + i18nNamespace: id, run: (_) async { _file.closeWorkspace(); return IpcResponse.ok(id: '', data: const {}); @@ -57,6 +63,8 @@ class MenuBarExtension extends ClideExtension { id: 'help.about', command: 'help.about', title: 'Help: About clide', + titleKey: 'command.help.about', + i18nNamespace: id, run: (_) async { services.dialog.show((ctx, dismiss) => AboutDialog(onDismiss: () => dismiss())); return IpcResponse.ok(id: '', data: const {}); diff --git a/lib/builtin/output/src/extension.dart b/lib/builtin/output/src/extension.dart index d3e3d927..914ae8a0 100644 --- a/lib/builtin/output/src/extension.dart +++ b/lib/builtin/output/src/extension.dart @@ -61,6 +61,8 @@ class OutputExtension extends ClideExtension { id: 'dock.toggle', command: 'dock.toggle', title: 'Toggle output dock', + titleKey: 'command.dock.toggle', + i18nNamespace: id, defaultBinding: 'ctrl+j', run: (_) async { final ctx = _ctx; diff --git a/lib/builtin/settings_ui/src/extension.dart b/lib/builtin/settings_ui/src/extension.dart index f7953842..13971b54 100644 --- a/lib/builtin/settings_ui/src/extension.dart +++ b/lib/builtin/settings_ui/src/extension.dart @@ -27,7 +27,15 @@ class SettingsUiExtension extends ClideExtension { List get contributions => [ // Opens the Settings panel. Palette + File-menu entry come for free off // the title; ctrl+, is the conventional settings shortcut. - CommandContribution(id: 'settings.open', command: 'settings.open', title: 'Settings…', defaultBinding: 'ctrl+,', run: _open), + CommandContribution( + id: 'settings.open', + command: 'settings.open', + title: 'Settings…', + titleKey: 'command.open', + i18nNamespace: id, + defaultBinding: 'ctrl+,', + run: _open, + ), ]; Future _open(List args) async { diff --git a/lib/builtin/settings_ui/src/settings_category_view.dart b/lib/builtin/settings_ui/src/settings_category_view.dart index f60e3693..87a1b953 100644 --- a/lib/builtin/settings_ui/src/settings_category_view.dart +++ b/lib/builtin/settings_ui/src/settings_category_view.dart @@ -5,6 +5,15 @@ import 'package:flutter/widgets.dart'; /// i18n namespace shared with the settings modal shell. const _settingsNs = 'builtin.settings-ui'; +/// Resolve a schema label through the catalog under the owning category's +/// [namespace], falling back to the English [english] label when the [key] is +/// null or the [namespace] is null/empty (T-462). Short-circuiting before the +/// lookup keeps key-less categories warning-free and pixel-identical. +String _localized(BuildContext context, String? key, String? namespace, String english) { + if (key == null || namespace == null || namespace.isEmpty) return english; + return ClideSettings.i18n.string(context, key, namespace: namespace, placeholder: english); +} + /// The schema-driven field renderer (T-448, epic T-444): turns a /// [SettingsCategory] into carded sections of field rows. Each field binds to a /// `SettingsStore` key — read with `get` (falling back to the schema default), @@ -28,7 +37,7 @@ class SettingsCategoryView extends StatelessWidget { padding: const EdgeInsets.fromLTRB(20, 16, 20, 20), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, - children: [for (final section in category.sections) _SectionCard(section: section, store: store)], + children: [for (final section in category.sections) _SectionCard(section: section, store: store, i18nNamespace: category.i18nNamespace)], ), ), ); @@ -37,6 +46,9 @@ class SettingsCategoryView extends StatelessWidget { /// True when [field] matches [query] (case-insensitive; label or help). An /// empty query matches everything. +/// +/// Context-free: matches the English label/help only — display localizes the +/// shown text, but search-by-translation is out of scope (T-462). bool settingsFieldMatches(SettingsField field, String query) { final q = query.trim().toLowerCase(); if (q.isEmpty) return true; @@ -49,7 +61,8 @@ List settingsMatchingSections(SettingsCategory category, String final out = []; for (final s in category.sections) { final fields = s.fields.where((f) => settingsFieldMatches(f, query)).toList(); - if (fields.isNotEmpty) out.add(SettingsSection(label: s.label, fields: fields)); + // Carry labelKey so search-result section headers still localize (T-462). + if (fields.isNotEmpty) out.add(SettingsSection(label: s.label, fields: fields, labelKey: s.labelKey)); } return out; } @@ -82,11 +95,16 @@ class SettingsSearchResults extends StatelessWidget { blocks.add( Padding( padding: EdgeInsets.only(top: blocks.isEmpty ? 0 : 14, bottom: 8), - child: ClideText(c.title, fontSize: 13, fontWeight: FontWeight.w600, color: tokens.globalForeground), + child: ClideText( + _localized(context, c.titleKey, c.i18nNamespace, c.title), + fontSize: 13, + fontWeight: FontWeight.w600, + color: tokens.globalForeground, + ), ), ); for (final s in sections) { - blocks.add(_SectionCard(section: s, store: store)); + blocks.add(_SectionCard(section: s, store: store, i18nNamespace: c.i18nNamespace)); } } if (blocks.isEmpty) { @@ -112,24 +130,28 @@ class SettingsSearchResults extends StatelessWidget { /// One section: a small-caps header above an elevated card of field rows. class _SectionCard extends StatelessWidget { - const _SectionCard({required this.section, required this.store}); + const _SectionCard({required this.section, required this.store, this.i18nNamespace}); final SettingsSection section; final SettingsStore store; + /// Owning category's i18n namespace (T-462); null for key-less categories. + final String? i18nNamespace; + @override Widget build(BuildContext context) { final tokens = ClideSettings.theme.of(context).surface; + final sectionLabel = _localized(context, section.labelKey, i18nNamespace, section.label); return Padding( padding: const EdgeInsets.only(bottom: 18), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - if (section.label.isNotEmpty) + if (sectionLabel.isNotEmpty) Padding( padding: const EdgeInsets.only(left: 2, bottom: 6), child: ClideText( - section.label.toUpperCase(), + sectionLabel.toUpperCase(), fontSize: clideFontCaption, color: tokens.sidebarSectionHeader, fontFamily: ClideSettings.fonts.monoOf(context), @@ -145,7 +167,10 @@ class _SectionCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - for (var i = 0; i < section.fields.length; i++) ...[if (i > 0) const ClideDivider(), _FieldRow(field: section.fields[i], store: store)], + for (var i = 0; i < section.fields.length; i++) ...[ + if (i > 0) const ClideDivider(), + _FieldRow(field: section.fields[i], store: store, i18nNamespace: i18nNamespace), + ], ], ), ), @@ -157,22 +182,27 @@ class _SectionCard extends StatelessWidget { /// A label/help block + the field's control + a reset affordance. class _FieldRow extends StatelessWidget { - const _FieldRow({required this.field, required this.store}); + const _FieldRow({required this.field, required this.store, this.i18nNamespace}); final SettingsField field; final SettingsStore store; + /// Owning category's i18n namespace (T-462); null for key-less categories. + final String? i18nNamespace; + @override Widget build(BuildContext context) { final tokens = ClideSettings.theme.of(context).surface; + final fieldLabel = _localized(context, field.labelKey, i18nNamespace, field.label); + final fieldHelp = field.help == null ? null : _localized(context, field.helpKey, i18nNamespace, field.help!); final label = Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - ClideText(field.label, color: tokens.globalForeground), - if (field.help != null && field.help!.isNotEmpty) + ClideText(fieldLabel, color: tokens.globalForeground), + if (fieldHelp != null && fieldHelp.isNotEmpty) Padding( padding: const EdgeInsets.only(top: 2), - child: ClideText(field.help!, fontSize: clideFontCaption, color: tokens.globalTextMuted), + child: ClideText(fieldHelp, fontSize: clideFontCaption, color: tokens.globalTextMuted), ), ], ); @@ -187,7 +217,7 @@ class _FieldRow extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - if (field.label.isNotEmpty) ...[label, const SizedBox(height: 10)], + if (fieldLabel.isNotEmpty) ...[label, const SizedBox(height: 10)], builder?.call(context) ?? const SizedBox.shrink(), ], ), @@ -203,9 +233,9 @@ class _FieldRow extends StatelessWidget { children: [ Expanded(child: label), const SizedBox(width: 16), - _Control(field: field, value: effective, store: store), + _Control(field: field, value: effective, store: store, i18nNamespace: i18nNamespace), const SizedBox(width: 10), - _ScopeTag(field: field, store: store, effectiveValue: effective), + _ScopeTag(field: field, store: store, effectiveValue: effective, i18nNamespace: i18nNamespace), ], ), ); @@ -214,12 +244,15 @@ class _FieldRow extends StatelessWidget { /// Dispatches to the control widget for the field's [SettingsFieldKind]. class _Control extends StatelessWidget { - const _Control({required this.field, required this.value, required this.store}); + const _Control({required this.field, required this.value, required this.store, this.i18nNamespace}); final SettingsField field; final Object? value; final SettingsStore store; + /// Owning category's i18n namespace (T-462); null for key-less categories. + final String? i18nNamespace; + void _set(Object? v) => store.set(field.key, v); @override @@ -231,6 +264,7 @@ class _Control extends StatelessWidget { return _SelectControl( field: field, value: value?.toString(), + i18nNamespace: i18nNamespace, onPick: (v) { final prefix = field.applyCommandPrefix; if (prefix != null) { @@ -242,11 +276,11 @@ class _Control extends StatelessWidget { }, ); case SettingsFieldKind.text: - return _EditControl(field: field, value: value?.toString() ?? '', numeric: false, onCommit: _set); + return _EditControl(field: field, value: value?.toString() ?? '', numeric: false, i18nNamespace: i18nNamespace, onCommit: _set); case SettingsFieldKind.number: - return _EditControl(field: field, value: value?.toString() ?? '', numeric: true, onCommit: _set); + return _EditControl(field: field, value: value?.toString() ?? '', numeric: true, i18nNamespace: i18nNamespace, onCommit: _set); case SettingsFieldKind.file: - return _FileControl(field: field); + return _FileControl(field: field, i18nNamespace: i18nNamespace); case SettingsFieldKind.custom: // Custom fields are rendered full-width by _FieldRow; never here. return const SizedBox.shrink(); @@ -287,12 +321,15 @@ class _ToggleControl extends StatelessWidget { /// Enum picker — current value on an anchored popover of options. class _SelectControl extends StatefulWidget { - const _SelectControl({required this.field, required this.value, required this.onPick}); + const _SelectControl({required this.field, required this.value, required this.onPick, this.i18nNamespace}); final SettingsField field; final String? value; final void Function(String value) onPick; + /// Owning category's i18n namespace (T-462); null for key-less categories. + final String? i18nNamespace; + @override State<_SelectControl> createState() => _SelectControlState(); } @@ -306,9 +343,10 @@ class _SelectControlState extends State<_SelectControl> { super.dispose(); } - String get _label { + /// Localized label for the currently selected option (or the raw value). + String _selectedLabel(BuildContext context) { for (final o in widget.field.options) { - if (o.value == widget.value) return o.label; + if (o.value == widget.value) return _localized(context, o.labelKey, widget.i18nNamespace, o.label); } return widget.value ?? ''; } @@ -316,6 +354,8 @@ class _SelectControlState extends State<_SelectControl> { @override Widget build(BuildContext context) { final tokens = ClideSettings.theme.of(context).surface; + final fieldLabel = _localized(context, widget.field.labelKey, widget.i18nNamespace, widget.field.label); + final selectedLabel = _selectedLabel(context); return ClideAnchoredOverlay( controller: _overlay, align: ClideAnchorAlign.end, @@ -324,16 +364,16 @@ class _SelectControlState extends State<_SelectControl> { entries: [ for (final o in widget.field.options) ClideMenuItem( - label: o.label, + label: _localized(context, o.labelKey, widget.i18nNamespace, o.label), active: o.value == widget.value, - semanticLabel: '${widget.field.label}: ${o.label}', + semanticLabel: '$fieldLabel: ${_localized(context, o.labelKey, widget.i18nNamespace, o.label)}', onSelect: () => widget.onPick(o.value), ), ], ), anchor: Semantics( button: true, - label: '${widget.field.label}: $_label. Click to change.', + label: '$fieldLabel: $selectedLabel. Click to change.', excludeSemantics: true, onTap: _overlay.toggle, child: ClideTappable( @@ -349,7 +389,7 @@ class _SelectControlState extends State<_SelectControl> { child: Row( mainAxisSize: MainAxisSize.min, children: [ - ClideText(_label, color: tokens.globalForeground), + ClideText(selectedLabel, color: tokens.globalForeground), const SizedBox(width: 6), ClideIcon(PhosphorIcons.byName('caret-down'), size: 10, color: tokens.globalTextMuted), ], @@ -364,13 +404,16 @@ class _SelectControlState extends State<_SelectControl> { /// Inline text / numeric editor. Commits on Enter and on focus loss; numeric /// fields parse + clamp to the field's bounds, ignoring unparseable input. class _EditControl extends StatefulWidget { - const _EditControl({required this.field, required this.value, required this.numeric, required this.onCommit}); + const _EditControl({required this.field, required this.value, required this.numeric, required this.onCommit, this.i18nNamespace}); final SettingsField field; final String value; final bool numeric; final void Function(Object value) onCommit; + /// Owning category's i18n namespace (T-462); null for key-less categories. + final String? i18nNamespace; + @override State<_EditControl> createState() => _EditControlState(); } @@ -430,7 +473,7 @@ class _EditControlState extends State<_EditControl> { final tokens = ClideSettings.theme.of(context).surface; return Semantics( textField: true, - label: widget.field.label, + label: _localized(context, widget.field.labelKey, widget.i18nNamespace, widget.field.label), excludeSemantics: true, child: Container( width: widget.numeric ? 88 : 180, @@ -458,15 +501,18 @@ class _EditControlState extends State<_EditControl> { /// "Opens external file" affordance — a button that runs the field's command. class _FileControl extends StatelessWidget { - const _FileControl({required this.field}); + const _FileControl({required this.field, this.i18nNamespace}); final SettingsField field; + /// Owning category's i18n namespace (T-462); null for key-less categories. + final String? i18nNamespace; + @override Widget build(BuildContext context) { final commands = ClideKernel.of(context).commands; return ClideButton( - label: field.label, + label: _localized(context, field.labelKey, i18nNamespace, field.label), variant: ClideButtonVariant.subtle, onPressed: field.fileCommand == null ? null : () => commands.execute(field.fileCommand!), ); @@ -482,12 +528,15 @@ class _FileControl extends StatelessWidget { /// live in either file (project overrides app); `app.*`/`project.*` keys live /// only in their prefix's layer, so their menu offers that one scope + reset. class _ScopeTag extends StatefulWidget { - const _ScopeTag({required this.field, required this.store, required this.effectiveValue}); + const _ScopeTag({required this.field, required this.store, required this.effectiveValue, this.i18nNamespace}); final SettingsField field; final SettingsStore store; final Object? effectiveValue; + /// Owning category's i18n namespace (T-462); null for key-less categories. + final String? i18nNamespace; + @override State<_ScopeTag> createState() => _ScopeTagState(); } @@ -547,6 +596,7 @@ class _ScopeTagState extends State<_ScopeTag> { final current = widget.store.effectiveLayer(widget.field.key); final look = _appearance(current); final layers = widget.store.writableLayers(widget.field.key); + final fieldLabel = _localized(context, widget.field.labelKey, widget.i18nNamespace, widget.field.label); return ClideAnchoredOverlay( controller: _overlay, align: ClideAnchorAlign.end, @@ -560,7 +610,7 @@ class _ScopeTagState extends State<_ScopeTag> { ), anchor: Semantics( button: true, - label: '${widget.field.label} scope: ${look.label}', + label: '$fieldLabel scope: ${look.label}', excludeSemantics: true, onTap: _overlay.toggle, child: ClideTappable( diff --git a/lib/builtin/settings_ui/src/settings_modal.dart b/lib/builtin/settings_ui/src/settings_modal.dart index f28310f9..2bb587d4 100644 --- a/lib/builtin/settings_ui/src/settings_modal.dart +++ b/lib/builtin/settings_ui/src/settings_modal.dart @@ -244,10 +244,15 @@ class _RailRow extends StatelessWidget { final tokens = ClideSettings.theme.of(context).surface; final dim = matchCount == 0; final fg = dim ? tokens.globalTextMuted : (selected ? tokens.globalForeground : tokens.sidebarForeground); + // Localize the rail label via the owning category's namespace (T-462); + // key-less categories fall back to the English title unchanged. + final title = (category.titleKey == null || category.i18nNamespace == null || category.i18nNamespace!.isEmpty) + ? category.title + : ClideSettings.i18n.string(context, category.titleKey!, namespace: category.i18nNamespace!, placeholder: category.title); return Semantics( button: true, selected: selected, - label: category.title, + label: title, excludeSemantics: true, child: ClideTappable( cursor: SystemMouseCursors.click, @@ -267,7 +272,7 @@ class _RailRow extends StatelessWidget { children: [ if (category.iconName != null) ...[ClideIcon(PhosphorIcons.byName(category.iconName!), size: 15, color: fg), const SizedBox(width: 8)], Expanded( - child: ClideText(category.title, color: fg, maxLines: 1, overflow: TextOverflow.ellipsis), + child: ClideText(title, color: fg, maxLines: 1, overflow: TextOverflow.ellipsis), ), if (matchCount != null) ClideText('$matchCount', fontSize: clideFontCaption, color: tokens.globalTextMuted), ], diff --git a/lib/builtin/theme_picker/src/extension.dart b/lib/builtin/theme_picker/src/extension.dart index 9d09e20f..e08da28d 100644 --- a/lib/builtin/theme_picker/src/extension.dart +++ b/lib/builtin/theme_picker/src/extension.dart @@ -28,7 +28,15 @@ class ThemePickerExtension extends ClideExtension { // "Theme…" to disambiguate from the schema-driven Settings panel's // `settings.open` (T-444); the theme picker folds into that panel's // Appearance category in T-452. - CommandContribution(id: 'theme.pick', command: 'theme.pick', title: 'Theme…', defaultBinding: 'ctrl+k', run: _pick), + CommandContribution( + id: 'theme.pick', + command: 'theme.pick', + title: 'Theme…', + titleKey: 'command.pick', + i18nNamespace: id, + defaultBinding: 'ctrl+k', + run: _pick, + ), // Always-visible switcher in the far-right status bar (T-234). // priority >= 100 places it in the right group; registered after // ipc-status so it sits to its right. @@ -36,49 +44,59 @@ class ThemePickerExtension extends ClideExtension { // Appearance settings category (T-452) — the theme picker as the schema // engine's one custom control, registered against the control registry. SettingsControlContribution(id: 'theme-picker.appearance-control', customId: 'theme.picker', builder: (_) => const AppearanceThemeControl()), - const SettingsCategoryContribution( + SettingsCategoryContribution( id: 'appearance', category: SettingsCategory( id: 'appearance', title: 'Appearance', + titleKey: 'settings.appearance.title', + i18nNamespace: id, iconName: 'palette', priority: 10, sections: [ SettingsSection( label: 'Theme', + labelKey: 'settings.appearance.section.theme', fields: [ SettingsField( key: 'app.theme', kind: SettingsFieldKind.custom, label: 'Theme', + labelKey: 'settings.appearance.field.theme.label', help: 'Color theme; high contrast switches to the accessible variant.', + helpKey: 'settings.appearance.field.theme.help', customId: 'theme.picker', ), ], ), SettingsSection( label: 'Typography', + labelKey: 'settings.appearance.section.typography', fields: [ SettingsField( key: kUiFontSettingKey, kind: SettingsFieldKind.select, label: 'UI font', + labelKey: 'settings.appearance.field.uiFont.label', help: 'Typeface for the app interface; applies live.', + helpKey: 'settings.appearance.field.uiFont.help', defaultValue: 'JosefinSans', options: [ - SettingsOption(value: 'JosefinSans', label: 'Josefin Sans'), - SettingsOption(value: 'Inter', label: 'Inter'), + SettingsOption(value: 'JosefinSans', label: 'Josefin Sans', labelKey: 'settings.appearance.field.uiFont.option.josefinSans'), + SettingsOption(value: 'Inter', label: 'Inter', labelKey: 'settings.appearance.field.uiFont.option.inter'), ], ), SettingsField( key: kMonoFontSettingKey, kind: SettingsFieldKind.select, label: 'Monospace font', + labelKey: 'settings.appearance.field.monoFont.label', help: 'Terminal, diffs, code, and IDs; applies live.', + helpKey: 'settings.appearance.field.monoFont.help', defaultValue: 'JetBrainsMono', options: [ - SettingsOption(value: 'JetBrainsMono', label: 'JetBrains Mono'), - SettingsOption(value: 'FiraMono', label: 'Fira Mono'), + SettingsOption(value: 'JetBrainsMono', label: 'JetBrains Mono', labelKey: 'settings.appearance.field.monoFont.option.jetBrainsMono'), + SettingsOption(value: 'FiraMono', label: 'Fira Mono', labelKey: 'settings.appearance.field.monoFont.option.firaMono'), ], ), ], diff --git a/lib/builtin/view/src/extension.dart b/lib/builtin/view/src/extension.dart index 83174a73..7a73379d 100644 --- a/lib/builtin/view/src/extension.dart +++ b/lib/builtin/view/src/extension.dart @@ -30,6 +30,8 @@ class ViewExtension extends ClideExtension { id: 'view.zoomIn', command: 'view.zoomIn', title: 'View: Zoom In', + titleKey: 'command.view.zoomIn', + i18nNamespace: id, run: (_) async { textZoom.increase(); return IpcResponse.ok(id: '', data: {'scale': textZoom.scale}); @@ -39,6 +41,8 @@ class ViewExtension extends ClideExtension { id: 'view.zoomOut', command: 'view.zoomOut', title: 'View: Zoom Out', + titleKey: 'command.view.zoomOut', + i18nNamespace: id, run: (_) async { textZoom.decrease(); return IpcResponse.ok(id: '', data: {'scale': textZoom.scale}); @@ -48,6 +52,8 @@ class ViewExtension extends ClideExtension { id: 'view.zoomReset', command: 'view.zoomReset', title: 'View: Reset Zoom', + titleKey: 'command.view.zoomReset', + i18nNamespace: id, run: (_) async { textZoom.reset(); return IpcResponse.ok(id: '', data: {'scale': textZoom.scale}); diff --git a/lib/builtin/vim/src/extension.dart b/lib/builtin/vim/src/extension.dart index 06a441fd..80987f94 100644 --- a/lib/builtin/vim/src/extension.dart +++ b/lib/builtin/vim/src/extension.dart @@ -80,6 +80,8 @@ class VimExtension extends ClideExtension { id: id, command: id, title: title, + titleKey: 'command.$id', + i18nNamespace: this.id, run: (_) async { apply(); return IpcResponse.ok(id: '', data: {'mode': _mode?.mode.name ?? 'disabled'}); diff --git a/lib/builtin/welcome/src/extension.dart b/lib/builtin/welcome/src/extension.dart index 146b4e48..e2b6b52b 100644 --- a/lib/builtin/welcome/src/extension.dart +++ b/lib/builtin/welcome/src/extension.dart @@ -26,6 +26,8 @@ class WelcomeExtension extends ClideExtension { id: 'workspace.open-project', command: 'workspace.open-project', title: 'Workspace: Open project…', + titleKey: 'command.workspace.open-project', + i18nNamespace: id, run: (_) async => IpcResponse.ok(id: '', data: const {'note': 'project picker lands in a later tier'}), ), ]; diff --git a/lib/kernel/src/i18n/catalog/builtin.claude_en_us.json b/lib/kernel/src/i18n/catalog/builtin.claude_en_us.json index 063be1cd..1dec9037 100644 --- a/lib/kernel/src/i18n/catalog/builtin.claude_en_us.json +++ b/lib/kernel/src/i18n/catalog/builtin.claude_en_us.json @@ -165,5 +165,37 @@ "teamChat.composer.placeholder": { "translation": "@name or @team …" }, "teamChat.pane.title": { "translation": "Team Chat" }, "teamChat.interrupt.semantics": { "translation": "Interrupt target session" }, - "teamChat.interrupt.label": { "translation": "Interrupt" } + "teamChat.interrupt.label": { "translation": "Interrupt" }, + "command.newSecondary": { "translation": "Claude: open a secondary session" }, + "command.killAllSessions": { "translation": "Claude: kill all sessions for this repo" }, + "command.sessionStorage": { "translation": "Claude: session storage (disk usage + cleanup)" }, + "command.activity.foldLevel": { "translation": "Claude: cycle activity fold level" }, + "command.agent.show": { "translation": "Claude: show an agent session pane" }, + "command.agent.hide": { "translation": "Claude: hide an agent session pane" }, + "command.agent.close": { "translation": "Claude: close (kill) an agent session" }, + "command.agent.mute": { "translation": "Claude: mute broker delivery to an agent session" }, + "command.agent.unmute": { "translation": "Claude: unmute broker delivery to an agent session" }, + "command.agent.injectMessage": { "translation": "Claude: inject a text turn into an agent session" }, + "command.agent.setPermissionMode": { "translation": "Claude: set permission mode for an agent session" }, + "command.mode.cycle": { "translation": "Claude: Cycle permission mode" }, + "command.task.reassign": { "translation": "Claude: reassign a shared task to an agent" }, + "command.teamChat.open": { "translation": "Claude: open the team chat pane" }, + "command.teamChat.post": { "translation": "Claude: post a message into the team channel as the user" }, + "command.agent.fork": { "translation": "Claude: fork a managed session into a new branch session" }, + "settings.activity.title": { "translation": "Activity" }, + "settings.activity.conversation.label": { "translation": "Conversation" }, + "settings.activity.foldLevel.label": { "translation": "Fold level" }, + "settings.activity.foldLevel.help": { "translation": "How aggressively the conversation folds tool calls, thinking, and results." }, + "settings.activity.opt.none": { "translation": "Show everything" }, + "settings.activity.opt.tools": { "translation": "Fold tool calls" }, + "settings.activity.opt.thinking": { "translation": "Fold tools + thinking" }, + "settings.activity.opt.everything": { "translation": "Fold all but prose" }, + "settings.claude.title": { "translation": "Claude" }, + "settings.claude.newSessionDefaults.label": { "translation": "New session defaults" }, + "settings.claude.model.label": { "translation": "Model" }, + "settings.claude.model.help": { "translation": "Model for new sessions." }, + "settings.claude.effort.label": { "translation": "Effort" }, + "settings.claude.effort.help": { "translation": "Reasoning effort for new sessions (applied via --effort at spawn)." }, + "settings.claude.permissionMode.label": { "translation": "Permission mode" }, + "settings.claude.permissionMode.help": { "translation": "Starting permission mode for new sessions." } } diff --git a/lib/kernel/src/i18n/catalog/builtin.cli-install_en_us.json b/lib/kernel/src/i18n/catalog/builtin.cli-install_en_us.json new file mode 100644 index 00000000..4c5b794e --- /dev/null +++ b/lib/kernel/src/i18n/catalog/builtin.cli-install_en_us.json @@ -0,0 +1,3 @@ +{ + "command.clide.installCli": { "translation": "clide: Install 'clide' command in PATH" } +} diff --git a/lib/kernel/src/i18n/catalog/builtin.deeplink_en_us.json b/lib/kernel/src/i18n/catalog/builtin.deeplink_en_us.json index ee25f878..79fba19a 100644 --- a/lib/kernel/src/i18n/catalog/builtin.deeplink_en_us.json +++ b/lib/kernel/src/i18n/catalog/builtin.deeplink_en_us.json @@ -3,5 +3,6 @@ "dialog.body": { "translation": "A clide:// link from outside the app is asking to:" }, "dialog.warning": { "translation": "Only allow this if you trust where the link came from." }, "button.cancel": { "translation": "Cancel" }, - "button.open": { "translation": "Open" } + "button.open": { "translation": "Open" }, + "command.deeplink.invoke": { "translation": "Open a clide:// deep link" } } diff --git a/lib/kernel/src/i18n/catalog/builtin.default-layout_en_us.json b/lib/kernel/src/i18n/catalog/builtin.default-layout_en_us.json index 290f3418..a46ea8b2 100644 --- a/lib/kernel/src/i18n/catalog/builtin.default-layout_en_us.json +++ b/lib/kernel/src/i18n/catalog/builtin.default-layout_en_us.json @@ -1,4 +1,21 @@ { "command.reset": { "translation": "Layout: Reset to Classic" }, - "preset.classic": { "translation": "Classic" } + "preset.classic": { "translation": "Classic" }, + "command.palette.toggle": { "translation": "Command Palette" }, + "command.sidebar.collapse": { "translation": "Toggle Sidebar Collapse" }, + "command.context.collapse": { "translation": "Toggle Context Panel Collapse" }, + "command.panel.focus.left": { "translation": "Focus Left Panel" }, + "command.panel.focus.middle": { "translation": "Focus Middle Panel" }, + "command.panel.focus.right": { "translation": "Focus Right Panel" }, + "command.panel.focusMode": { "translation": "Toggle Focus Mode" }, + "command.panel.focusMode.exit": { "translation": "Exit Focus Mode" }, + "command.editor.open": { "translation": "Open Editor" }, + "command.editor.close": { "translation": "Close Editor" }, + "command.workspace.tab.next": { "translation": "Next Workspace Tab" }, + "command.workspace.tab.previous": { "translation": "Previous Workspace Tab" }, + "command.sidebar.section.1": { "translation": "Sidebar: Section 1" }, + "command.sidebar.section.2": { "translation": "Sidebar: Section 2" }, + "command.sidebar.section.3": { "translation": "Sidebar: Section 3" }, + "command.sidebar.section.4": { "translation": "Sidebar: Section 4" }, + "command.sidebar.section.5": { "translation": "Sidebar: Section 5" } } diff --git a/lib/kernel/src/i18n/catalog/builtin.extensions-ui_en_us.json b/lib/kernel/src/i18n/catalog/builtin.extensions-ui_en_us.json index 20e4aade..b013af09 100644 --- a/lib/kernel/src/i18n/catalog/builtin.extensions-ui_en_us.json +++ b/lib/kernel/src/i18n/catalog/builtin.extensions-ui_en_us.json @@ -1,5 +1,8 @@ { "notice.title": { "translation": "Extension management is coming" }, "notice.body": { "translation": "Installing, enabling, and disabling extensions arrives with third-party (Lua) extension support. For now the built-in extensions are always on." }, - "notice.tracked": { "translation": "Tracked in T-8 (Tier 6) · D-16" } + "notice.tracked": { "translation": "Tracked in T-8 (Tier 6) · D-16" }, + "settings.extensions.title": { "translation": "Extensions" }, + "settings.extensions.section.notice": { "translation": "" }, + "settings.extensions.field.notice.label": { "translation": "" } } diff --git a/lib/kernel/src/i18n/catalog/builtin.keybindings-ui_en_us.json b/lib/kernel/src/i18n/catalog/builtin.keybindings-ui_en_us.json new file mode 100644 index 00000000..c95ea3e9 --- /dev/null +++ b/lib/kernel/src/i18n/catalog/builtin.keybindings-ui_en_us.json @@ -0,0 +1,14 @@ +{ + "command.preset.default": { "translation": "Keymap: Default" }, + "command.preset.vim": { "translation": "Keymap: Vim" }, + "command.preset.vscode": { "translation": "Keymap: VS Code" }, + "command.preset.jetbrains": { "translation": "Keymap: JetBrains" }, + "settings.keymap.title": { "translation": "Keymap" }, + "settings.keymap.section.preset": { "translation": "Preset" }, + "settings.keymap.field.preset.label": { "translation": "Active preset" }, + "settings.keymap.field.preset.help": { "translation": "Keyboard layout for the whole app." }, + "settings.keymap.field.preset.option.default": { "translation": "Default" }, + "settings.keymap.field.preset.option.vim": { "translation": "Vim" }, + "settings.keymap.field.preset.option.vscode": { "translation": "VS Code" }, + "settings.keymap.field.preset.option.jetbrains": { "translation": "JetBrains" } +} diff --git a/lib/kernel/src/i18n/catalog/builtin.menubar_en_us.json b/lib/kernel/src/i18n/catalog/builtin.menubar_en_us.json index ee6ae904..5654cdc5 100644 --- a/lib/kernel/src/i18n/catalog/builtin.menubar_en_us.json +++ b/lib/kernel/src/i18n/catalog/builtin.menubar_en_us.json @@ -18,5 +18,9 @@ "dialog.openProject.body": { "translation": "Enter the path to a git repository." }, "dialog.openProject.error": { "translation": "Not a git repository" }, "dialog.notRepo.title": { "translation": "No git repo found" }, - "dialog.notRepo.body": { "translation": "A clide project root requires a git repository." } + "dialog.notRepo.body": { "translation": "A clide project root requires a git repository." }, + "command.file.openFolder": { "translation": "File: Open Folder…" }, + "command.file.newWindow": { "translation": "File: New Window" }, + "command.file.closeWorkspace": { "translation": "File: Close Project" }, + "command.help.about": { "translation": "Help: About clide" } } diff --git a/lib/kernel/src/i18n/catalog/builtin.output_en_us.json b/lib/kernel/src/i18n/catalog/builtin.output_en_us.json index c967109e..b6207f9c 100644 --- a/lib/kernel/src/i18n/catalog/builtin.output_en_us.json +++ b/lib/kernel/src/i18n/catalog/builtin.output_en_us.json @@ -10,5 +10,6 @@ "a11y.jumpToLatest": { "translation": "jump to latest" }, "jump.label": { "translation": "Jump to latest ↓" }, "a11y.toggleDock": { "translation": "toggle output dock" }, - "dock.label": { "translation": "Output" } + "dock.label": { "translation": "Output" }, + "command.dock.toggle": { "translation": "Toggle output dock" } } diff --git a/lib/kernel/src/i18n/catalog/builtin.settings-ui_en_us.json b/lib/kernel/src/i18n/catalog/builtin.settings-ui_en_us.json index 75f51022..3ae88624 100644 --- a/lib/kernel/src/i18n/catalog/builtin.settings-ui_en_us.json +++ b/lib/kernel/src/i18n/catalog/builtin.settings-ui_en_us.json @@ -1,4 +1,5 @@ { + "command.open": { "translation": "Settings…" }, "modal.title": { "translation": "Settings" }, "modal.close": { "translation": "Close" }, "modal.close.hint": { "translation": "Close settings without changing anything" }, diff --git a/lib/kernel/src/i18n/catalog/builtin.theme-picker_en_us.json b/lib/kernel/src/i18n/catalog/builtin.theme-picker_en_us.json index b78437de..973429ea 100644 --- a/lib/kernel/src/i18n/catalog/builtin.theme-picker_en_us.json +++ b/lib/kernel/src/i18n/catalog/builtin.theme-picker_en_us.json @@ -1,9 +1,22 @@ { - "command.pick": { "translation": "Theme: Pick…" }, + "command.pick": { "translation": "Theme…" }, "modal.title": { "translation": "Select theme" }, "modal.cancel": { "translation": "Cancel" }, "modal.cancel.hint": { "translation": "Close the theme picker without changing the current theme" }, "row.select.hint": { "translation": "Activate this theme" }, "section.appearance": { "translation": "Appearance" }, - "toggle.highContrast": { "translation": "High contrast" } + "toggle.highContrast": { "translation": "High contrast" }, + "settings.appearance.title": { "translation": "Appearance" }, + "settings.appearance.section.theme": { "translation": "Theme" }, + "settings.appearance.section.typography": { "translation": "Typography" }, + "settings.appearance.field.theme.label": { "translation": "Theme" }, + "settings.appearance.field.theme.help": { "translation": "Color theme; high contrast switches to the accessible variant." }, + "settings.appearance.field.uiFont.label": { "translation": "UI font" }, + "settings.appearance.field.uiFont.help": { "translation": "Typeface for the app interface; applies live." }, + "settings.appearance.field.uiFont.option.josefinSans": { "translation": "Josefin Sans" }, + "settings.appearance.field.uiFont.option.inter": { "translation": "Inter" }, + "settings.appearance.field.monoFont.label": { "translation": "Monospace font" }, + "settings.appearance.field.monoFont.help": { "translation": "Terminal, diffs, code, and IDs; applies live." }, + "settings.appearance.field.monoFont.option.jetBrainsMono": { "translation": "JetBrains Mono" }, + "settings.appearance.field.monoFont.option.firaMono": { "translation": "Fira Mono" } } diff --git a/lib/kernel/src/i18n/catalog/builtin.view_en_us.json b/lib/kernel/src/i18n/catalog/builtin.view_en_us.json new file mode 100644 index 00000000..7997bbdd --- /dev/null +++ b/lib/kernel/src/i18n/catalog/builtin.view_en_us.json @@ -0,0 +1,5 @@ +{ + "command.view.zoomIn": { "translation": "View: Zoom In" }, + "command.view.zoomOut": { "translation": "View: Zoom Out" }, + "command.view.zoomReset": { "translation": "View: Reset Zoom" } +} diff --git a/lib/kernel/src/i18n/catalog/builtin.vim_en_us.json b/lib/kernel/src/i18n/catalog/builtin.vim_en_us.json index 2196f7af..83273bfa 100644 --- a/lib/kernel/src/i18n/catalog/builtin.vim_en_us.json +++ b/lib/kernel/src/i18n/catalog/builtin.vim_en_us.json @@ -1,5 +1,8 @@ { "mode.normal": { "translation": "NORMAL" }, "mode.insert": { "translation": "INSERT" }, - "mode.visual": { "translation": "VISUAL" } + "mode.visual": { "translation": "VISUAL" }, + "command.vim.mode.normal": { "translation": "Vim: Normal mode" }, + "command.vim.mode.insert": { "translation": "Vim: Insert mode" }, + "command.vim.mode.visual": { "translation": "Vim: Visual mode" } } diff --git a/lib/kernel/src/i18n/catalog/builtin.welcome_en_us.json b/lib/kernel/src/i18n/catalog/builtin.welcome_en_us.json index dedf4cd3..67e6430e 100644 --- a/lib/kernel/src/i18n/catalog/builtin.welcome_en_us.json +++ b/lib/kernel/src/i18n/catalog/builtin.welcome_en_us.json @@ -30,5 +30,6 @@ "button.opening": { "translation": "Opening…" }, "button.ok": { "translation": "OK" }, "dialog.notRepo.title": { "translation": "No git repo found" }, - "dialog.notRepo.body": { "translation": "A clide project root requires a git repository." } + "dialog.notRepo.body": { "translation": "A clide project root requires a git repository." }, + "command.workspace.open-project": { "translation": "Workspace: Open project…" } }