refactor(i18n): route command + settings manifest labels through the catalog (T-462)

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) <noreply@anthropic.com>
This commit is contained in:
2026-06-19 10:20:19 +02:00
co-authored by Claude Opus 4.8
parent 198c8b18b5
commit 37fa255a63
28 changed files with 419 additions and 72 deletions
+51 -5
View File
@@ -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: <taskId> <sessionId>');
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] <text>');
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) {
@@ -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;
+8 -1
View File
@@ -24,7 +24,14 @@ class DeepLinkExtension extends ClideExtension {
@override
List<ContributionPoint> 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
+83 -8
View File
@@ -16,13 +16,30 @@ class DefaultLayoutExtension extends ClideExtension {
@override
List<ContributionPoint> 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),
),
+13 -2
View File
@@ -20,17 +20,28 @@ class ExtensionsUiExtension extends ClideExtension {
@override
List<ContributionPoint> 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',
),
],
),
],
),
+12 -5
View File
@@ -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.<value>`
// (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'),
],
),
],
+8
View File
@@ -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<Object>((ctx, dismiss) => AboutDialog(onDismiss: () => dismiss()));
return IpcResponse.ok(id: '', data: const {});
+2
View File
@@ -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;
+9 -1
View File
@@ -27,7 +27,15 @@ class SettingsUiExtension extends ClideExtension {
List<ContributionPoint> 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<IpcResponse> _open(List<String> args) async {
@@ -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<SettingsSection> settingsMatchingSections(SettingsCategory category, String
final out = <SettingsSection>[];
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<Object?>(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(
@@ -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),
],
+24 -6
View File
@@ -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'),
],
),
],
+6
View File
@@ -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});
+2
View File
@@ -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'});
+2
View File
@@ -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'}),
),
];
@@ -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." }
}
@@ -0,0 +1,3 @@
{
"command.clide.installCli": { "translation": "clide: Install 'clide' command in PATH" }
}
@@ -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" }
}
@@ -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" }
}
@@ -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": "" }
}
@@ -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" }
}
@@ -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" }
}
@@ -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" }
}
@@ -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" },
@@ -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" }
}
@@ -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" }
}
@@ -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" }
}
@@ -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…" }
}