feat(ui): redesign shortcut settings

This commit is contained in:
ChangeHow
2026-08-06 14:18:15 +08:00
parent 1b9da5f9fa
commit 4c5421a7af
13 changed files with 583 additions and 348 deletions
@@ -1,12 +1,7 @@
import React from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import {
SettingsSection,
SettingsFieldRow,
} from '@/components/sections/shared/SettingsSection';
import { SettingsFieldRow, SettingsSection } from '@/components/sections/shared/SettingsSection';
import { useUIStore } from '@/stores/useUIStore';
import { cn } from '@/lib/utils';
import { updateDesktopSettings } from '@/lib/persistence';
import { isVSCodeRuntime } from '@/lib/desktop';
import {
@@ -14,314 +9,133 @@ import {
getCustomizableShortcutActions,
getEffectiveShortcutCombo,
getEffectiveShortcutPrefix,
isRiskyBrowserShortcut,
keyToShortcutToken,
normalizeCombo,
getShortcutCategory,
UNASSIGNED_SHORTCUT,
type ShortcutAction,
type ShortcutActionId,
type ShortcutCategory,
type ShortcutCombo,
} from '@/lib/shortcuts';
import { useI18n } from '@/lib/i18n';
import { ShortcutRecordingDialog } from './ShortcutRecordingDialog';
const MODIFIER_KEYS = new Set(['shift', 'control', 'alt', 'meta']);
const keyboardEventToCombo = (event: React.KeyboardEvent<HTMLInputElement>): ShortcutCombo | null => {
if (MODIFIER_KEYS.has(event.key.toLowerCase())) {
return null;
}
const parts: string[] = [];
if (event.metaKey || event.ctrlKey) {
parts.push('mod');
}
if (event.shiftKey) {
parts.push('shift');
}
if (event.altKey) {
parts.push('alt');
}
const keyToken = keyToShortcutToken(event.key);
if (!keyToken) {
return null;
}
parts.push(keyToken);
return normalizeCombo(parts.join('+'));
};
// Prefix capture for chord-style shortcuts (e.g. "switch context panel
// surface"): a bare modifier press is accepted so the prefix can be just the
// primary modifier (default) or a modifier + key chord like `mod+p`.
const keyboardEventToPrefixCombo = (event: React.KeyboardEvent<HTMLInputElement>): ShortcutCombo | null => {
const parts: string[] = [];
if (event.metaKey || event.ctrlKey) {
parts.push('mod');
}
if (event.shiftKey) {
parts.push('shift');
}
if (event.altKey) {
parts.push('alt');
}
if (MODIFIER_KEYS.has(event.key.toLowerCase())) {
return parts.length > 0 ? normalizeCombo(parts.join('+')) : null;
}
const keyToken = keyToShortcutToken(event.key);
if (!keyToken) {
return null;
}
parts.push(keyToken);
return parts.length > 0 ? normalizeCombo(parts.join('+')) : null;
};
const CATEGORIES: ShortcutCategory[] = ['session', 'models', 'panels', 'navigation', 'application'];
export const KeyboardShortcutsSettings: React.FC = () => {
const { t } = useI18n();
const tUnsafe = React.useCallback((key: string) => t(key as Parameters<typeof t>[0]), [t]);
const tUnsafe = (key: string) => t(key as Parameters<typeof t>[0]);
const shortcutOverrides = useUIStore((state) => state.shortcutOverrides);
const setShortcutOverride = useUIStore((state) => state.setShortcutOverride);
const clearShortcutOverride = useUIStore((state) => state.clearShortcutOverride);
const resetAllShortcutOverrides = useUIStore((state) => state.resetAllShortcutOverrides);
const [editingAction, setEditingAction] = React.useState<ShortcutAction | null>(null);
const actions = React.useMemo(() => {
const all = getCustomizableShortcutActions();
if (!isVSCodeRuntime()) {
return all;
}
return all.filter((action) => action.id !== 'toggle_prompt_navigator');
return isVSCodeRuntime() ? all.filter((action) => action.id !== 'toggle_prompt_navigator') : all;
}, []);
const actionLabel = React.useCallback((id: string, fallbackLabel: string): string => {
const key = `settings.openchamber.keyboardShortcuts.action.${id}.label`;
const actionLabel = (action: ShortcutAction): string => {
const key = `settings.openchamber.keyboardShortcuts.action.${action.id}.label`;
const translated = tUnsafe(key);
return translated === key ? fallbackLabel : translated;
}, [tUnsafe]);
const [capturingActionId, setCapturingActionId] = React.useState<string | null>(null);
const [draftByAction, setDraftByAction] = React.useState<Record<string, ShortcutCombo>>({});
const [errorText, setErrorText] = React.useState<string>('');
const [warningText, setWarningText] = React.useState<string>('');
const [pendingOverwrite, setPendingOverwrite] = React.useState<{
actionId: string;
combo: ShortcutCombo;
conflictActionId: string;
} | null>(null);
const persistShortcutOverrides = React.useCallback((nextOverrides: Record<string, ShortcutCombo>) => {
return translated === key ? action.label : translated;
};
const persist = (nextOverrides: Record<string, ShortcutCombo>) => {
void updateDesktopSettings({ shortcutOverrides: nextOverrides });
}, []);
const findConflict = React.useCallback((actionId: string, combo: ShortcutCombo): string | null => {
const normalized = normalizeCombo(combo);
for (const action of actions) {
if (action.id === actionId) {
continue;
}
const existing = getEffectiveShortcutCombo(action.id, shortcutOverrides);
if (normalizeCombo(existing) === normalized) {
return action.id;
}
}
return null;
}, [actions, shortcutOverrides]);
const saveCombo = React.useCallback((actionId: string, combo: ShortcutCombo) => {
const normalized = normalizeCombo(combo);
const conflictActionId = findConflict(actionId, normalized);
if (conflictActionId) {
setPendingOverwrite({ actionId, combo: normalized, conflictActionId });
setErrorText('');
return;
}
const nextOverrides = { ...shortcutOverrides, [actionId]: normalized };
setShortcutOverride(actionId, normalized);
persistShortcutOverrides(nextOverrides);
setPendingOverwrite(null);
setErrorText('');
setWarningText(isRiskyBrowserShortcut(normalized) ? t('settings.openchamber.keyboardShortcuts.warning.riskyBrowserShortcut') : '');
setDraftByAction((current) => {
const rest = { ...current };
delete rest[actionId];
return rest;
});
}, [findConflict, persistShortcutOverrides, setShortcutOverride, shortcutOverrides, t]);
const confirmOverwrite = React.useCallback(() => {
if (!pendingOverwrite) {
return;
}
const nextOverrides = {
...shortcutOverrides,
[pendingOverwrite.conflictActionId]: UNASSIGNED_SHORTCUT,
[pendingOverwrite.actionId]: pendingOverwrite.combo,
};
setShortcutOverride(pendingOverwrite.conflictActionId, UNASSIGNED_SHORTCUT);
setShortcutOverride(pendingOverwrite.actionId, pendingOverwrite.combo);
persistShortcutOverrides(nextOverrides);
setPendingOverwrite(null);
setErrorText('');
setWarningText(isRiskyBrowserShortcut(pendingOverwrite.combo) ? t('settings.openchamber.keyboardShortcuts.warning.riskyBrowserShortcut') : '');
setDraftByAction((current) => {
const rest = { ...current };
delete rest[pendingOverwrite.actionId];
return rest;
});
}, [pendingOverwrite, persistShortcutOverrides, setShortcutOverride, shortcutOverrides, t]);
const resetOne = React.useCallback((actionId: string) => {
};
const save = (
actionId: ShortcutActionId,
combo: ShortcutCombo,
replaceActionId?: ShortcutActionId,
) => {
const nextOverrides = { ...shortcutOverrides, [actionId]: combo };
if (replaceActionId) nextOverrides[replaceActionId] = UNASSIGNED_SHORTCUT;
setShortcutOverride(actionId, combo);
if (replaceActionId) setShortcutOverride(replaceActionId, UNASSIGNED_SHORTCUT);
persist(nextOverrides);
};
const resetOne = (actionId: ShortcutActionId) => {
const nextOverrides = { ...shortcutOverrides };
delete nextOverrides[actionId];
clearShortcutOverride(actionId);
persistShortcutOverrides(nextOverrides);
setDraftByAction((current) => {
const rest = { ...current };
delete rest[actionId];
return rest;
});
setPendingOverwrite(null);
setErrorText('');
setWarningText('');
}, [clearShortcutOverride, persistShortcutOverrides, shortcutOverrides]);
persist(nextOverrides);
};
const shortcutDisplay = (action: ShortcutAction): string => {
const isSurfaceSwitch = action.id === 'switch_context_surface';
const combo = isSurfaceSwitch
? getEffectiveShortcutPrefix(action.id, shortcutOverrides)
: getEffectiveShortcutCombo(action.id, shortcutOverrides);
const formatted = formatShortcutForDisplay(
combo,
t('settings.openchamber.keyboardShortcuts.unassigned'),
);
return isSurfaceSwitch && combo && combo !== UNASSIGNED_SHORTCUT
? `${formatted}${t('settings.openchamber.keyboardShortcuts.action.switch_context_surface.suffix')}`
: formatted;
};
return (
<SettingsSection
settingsItem="shortcuts.keyboard-shortcuts"
title={t('settings.openchamber.keyboardShortcuts.title')}
divider={false}
info={t('settings.openchamber.keyboardShortcuts.tooltip')}
headerAction={(
<Button
type="button"
variant="outline"
size="xs"
className="!font-normal"
onClick={() => {
resetAllShortcutOverrides();
persistShortcutOverrides({});
setDraftByAction({});
setPendingOverwrite(null);
setErrorText('');
setWarningText('');
}}
>
{t('settings.openchamber.keyboardShortcuts.actions.resetAll')}
</Button>
)}
>
{(errorText || warningText || pendingOverwrite) && (
<div className="mb-2 space-y-2">
{pendingOverwrite && (
<div className="rounded-lg border border-[var(--status-warning-border)] bg-[var(--status-warning-background)] p-3 flex flex-col @xl:flex-row @xl:items-center justify-between gap-3">
<span className="typography-meta text-foreground">
{t('settings.openchamber.keyboardShortcuts.overwritePrompt')}
</span>
<div className="flex gap-2 shrink-0">
<Button type="button" size="xs" className="!font-normal" onClick={confirmOverwrite}>{t('settings.openchamber.keyboardShortcuts.actions.overwrite')}</Button>
<Button type="button" size="xs" className="!font-normal" variant="ghost" onClick={() => setPendingOverwrite(null)}>{t('settings.common.actions.cancel')}</Button>
</div>
<>
{CATEGORIES.map((category, categoryIndex) => {
const categoryActions = actions.filter((action) => getShortcutCategory(action) === category);
if (categoryActions.length === 0) return null;
return (
<SettingsSection
key={category}
settingsItem={categoryIndex === 0 ? 'shortcuts.keyboard-shortcuts' : undefined}
title={t(`settings.openchamber.keyboardShortcuts.category.${category}`)}
divider={categoryIndex !== 0}
info={categoryIndex === 0 ? t('settings.openchamber.keyboardShortcuts.tooltip') : undefined}
headerAction={categoryIndex === 0 ? (
<Button type="button" variant="outline" size="xs" className="!font-normal" onClick={() => {
resetAllShortcutOverrides();
persist({});
}}>
{t('settings.openchamber.keyboardShortcuts.actions.resetAll')}
</Button>
) : undefined}
>
<div className="space-y-2">
{categoryActions.map((action) => (
<SettingsFieldRow key={action.id} label={actionLabel(action)}>
<kbd
className="min-w-32 rounded-md border border-border bg-muted px-2 py-1 text-center typography-meta font-mono text-foreground"
>
{shortcutDisplay(action)}
</kbd>
<Button
type="button"
variant="secondary"
size="xs"
className="!font-normal"
onClick={() => setEditingAction(action)}
>
{t('settings.openchamber.keyboardShortcuts.actions.edit')}
</Button>
<Button
type="button"
variant="ghost"
size="xs"
className="!font-normal"
onClick={() => resetOne(action.id)}
>
{t('settings.common.actions.reset')}
</Button>
</SettingsFieldRow>
))}
</div>
)}
{errorText && (
<div className="rounded-lg border border-[var(--status-error-border)] bg-[var(--status-error-background)] p-3 typography-meta text-foreground">
{errorText}
</div>
)}
{warningText && (
<div className="rounded-lg border border-[var(--status-warning-border)] bg-[var(--status-warning-background)] p-3 typography-meta text-foreground">
{warningText}
</div>
)}
</div>
)}
<div>
{actions.map((action, index) => {
const isSurfaceSwitch = action.id === 'switch_context_surface';
const effective = isSurfaceSwitch
? getEffectiveShortcutPrefix(action.id, shortcutOverrides)
: getEffectiveShortcutCombo(action.id, shortcutOverrides);
const draft = draftByAction[action.id];
const displayCombo = draft ?? effective;
const hasDraft = typeof draft === 'string' && normalizeCombo(draft) !== normalizeCombo(effective);
const isUnassignedDisplay = displayCombo === '' || normalizeCombo(displayCombo) === UNASSIGNED_SHORTCUT;
const displayValue = capturingActionId === action.id
? t('settings.openchamber.keyboardShortcuts.field.pressKeys')
: isSurfaceSwitch && !isUnassignedDisplay
? `${formatShortcutForDisplay(displayCombo)}${t('settings.openchamber.keyboardShortcuts.action.switch_context_surface.suffix')}`
: formatShortcutForDisplay(displayCombo);
return (
<div key={action.id} className={cn("py-1.5", index > 0 && "border-t border-border/40")}>
<SettingsFieldRow
label={actionLabel(action.id, action.label)}
alignEnd={false}
>
<Input
readOnly
value={displayValue}
onFocus={() => {
setCapturingActionId(action.id);
setErrorText('');
}}
onBlur={() => {
if (capturingActionId === action.id) {
setCapturingActionId(null);
}
}}
onKeyDown={(event) => {
event.preventDefault();
event.stopPropagation();
if (event.key === 'Escape') {
setCapturingActionId(null);
return;
}
const combo = isSurfaceSwitch ? keyboardEventToPrefixCombo(event) : keyboardEventToCombo(event);
if (!combo) {
return;
}
setDraftByAction((current) => ({
...current,
[action.id]: combo,
}));
setCapturingActionId(null);
setPendingOverwrite(null);
setErrorText('');
}}
className="h-7 w-40 min-w-0 typography-ui-label text-center"
/>
<Button
type="button"
variant="secondary"
size="xs"
className="!font-normal"
onClick={() => {
const next = draftByAction[action.id];
if (!next) {
setErrorText(t('settings.openchamber.keyboardShortcuts.error.captureFirst'));
return;
}
saveCombo(action.id, next);
}}
disabled={!hasDraft}
>
{t('settings.common.actions.saveChanges')}
</Button>
<Button type="button" size="xs" className="!font-normal" variant="ghost" onClick={() => resetOne(action.id)}>
{t('settings.common.actions.reset')}
</Button>
</SettingsFieldRow>
</div>
);
})}
</div>
</SettingsSection>
</SettingsSection>
);
})}
<ShortcutRecordingDialog
action={editingAction}
actions={actions}
overrides={shortcutOverrides}
actionLabel={actionLabel}
onSave={save}
onOpenChange={(open) => {
if (!open) setEditingAction(null);
}}
/>
</>
);
};
@@ -0,0 +1,209 @@
import React from 'react';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import {
formatShortcutForDisplay,
getEffectiveShortcutCombo,
getEffectiveShortcutPrefix,
getShortcutConflict,
isRiskyBrowserShortcut,
keyToShortcutToken,
normalizeCombo,
type ShortcutAction,
type ShortcutActionId,
type ShortcutCombo,
} from '@/lib/shortcuts';
import { useI18n } from '@/lib/i18n';
const MODIFIER_KEYS = new Set(['shift', 'control', 'alt', 'meta']);
interface ShortcutRecordingDialogProps {
action: ShortcutAction | null;
actions: ReadonlyArray<ShortcutAction>;
overrides: Record<string, string>;
actionLabel: (action: ShortcutAction) => string;
onSave: (
actionId: ShortcutActionId,
combo: ShortcutCombo,
replaceActionId?: ShortcutActionId,
) => void;
onOpenChange: (open: boolean) => void;
}
function keyboardEventToCombo(event: React.KeyboardEvent<HTMLDivElement>): ShortcutCombo | null {
if (MODIFIER_KEYS.has(event.key.toLowerCase())) return null;
const key = keyToShortcutToken(event.key);
if (!key) return null;
const parts: string[] = [];
if (event.metaKey || event.ctrlKey) parts.push('mod');
if (event.shiftKey) parts.push('shift');
if (event.altKey) parts.push('alt');
parts.push(key);
return normalizeCombo(parts.join('+'));
}
function modifierKeyUpToCombo(event: React.KeyboardEvent<HTMLDivElement>): ShortcutCombo | null {
const key = event.key.toLowerCase();
if (!MODIFIER_KEYS.has(key)) return null;
const parts: string[] = [];
if (event.metaKey || event.ctrlKey || key === 'meta' || key === 'control') parts.push('mod');
if (event.shiftKey || key === 'shift') parts.push('shift');
if (event.altKey || key === 'alt') parts.push('alt');
return parts.length > 0 ? normalizeCombo(parts.join('+')) : null;
}
export const ShortcutRecordingDialog: React.FC<ShortcutRecordingDialogProps> = ({
action,
actions,
overrides,
actionLabel,
onSave,
onOpenChange,
}) => {
const { t } = useI18n();
const [chords, setChords] = React.useState<ShortcutCombo[]>([]);
const recordingRef = React.useRef<HTMLDivElement>(null);
React.useEffect(() => {
if (!action) return;
setChords([]);
}, [action]);
const combo = normalizeCombo(chords.join(' '));
const conflicts = React.useMemo(() => {
if (!action || !combo) return [];
const result: Array<{ action: ShortcutAction; kind: 'exact' | 'prefix' }> = [];
for (const candidate of actions) {
if (candidate.id === action.id) continue;
const candidateCombo = candidate.id === 'switch_context_surface'
? getEffectiveShortcutPrefix(candidate.id, overrides)
: getEffectiveShortcutCombo(candidate.id, overrides);
const kind = getShortcutConflict(combo, candidateCombo);
if (kind) result.push({ action: candidate, kind });
}
return result;
}, [action, actions, combo, overrides]);
const prefixConflict = conflicts.find((conflict) => conflict.kind === 'prefix');
const exactConflict = conflicts.find((conflict) => conflict.kind === 'exact');
const close = () => onOpenChange(false);
return (
<Dialog open={action !== null} onOpenChange={onOpenChange}>
<DialogContent
className="max-w-md"
initialFocus={recordingRef}
>
<DialogHeader>
<DialogTitle>
{action
? t('settings.openchamber.keyboardShortcuts.dialog.title', {
action: actionLabel(action),
})
: ''}
</DialogTitle>
<DialogDescription>{t('settings.openchamber.keyboardShortcuts.dialog.instructions')}</DialogDescription>
</DialogHeader>
<div
className="space-y-3 rounded-lg outline-none focus-visible:ring-2 focus-visible:ring-ring"
tabIndex={0}
ref={recordingRef}
onKeyDown={(event) => {
event.preventDefault();
event.stopPropagation();
if (event.key === 'Escape') {
close();
return;
}
if (event.key === 'Backspace') {
setChords((current) => current.slice(0, -1));
return;
}
const chord = keyboardEventToCombo(event);
if (chord) {
setChords((current) => action?.id === 'switch_context_surface'
? [chord]
: current.length < 2 ? [...current, chord] : current);
}
}}
onKeyUp={(event) => {
if (action?.id !== 'switch_context_surface' || chords.length > 0) return;
const combo = modifierKeyUpToCombo(event);
if (!combo) return;
event.preventDefault();
event.stopPropagation();
setChords([combo]);
}}
>
{[0, 1].map((index) => (
<div key={index} className="flex items-center justify-between gap-3">
<span className="typography-ui-label text-foreground">
{t(index === 0
? 'settings.openchamber.keyboardShortcuts.dialog.firstChord'
: 'settings.openchamber.keyboardShortcuts.dialog.secondChord')}
</span>
<kbd
className="min-w-32 rounded-md border border-border bg-muted px-3 py-2 text-center typography-meta font-mono text-foreground"
>
{chords[index]
? formatShortcutForDisplay(chords[index])
: t('settings.openchamber.keyboardShortcuts.dialog.recording')}
</kbd>
</div>
))}
{prefixConflict ? (
<p className="typography-meta text-[var(--status-error)]">
{t('settings.openchamber.keyboardShortcuts.error.prefixConflict', { action: actionLabel(prefixConflict.action) })}
</p>
) : null}
{exactConflict && !prefixConflict ? (
<p className="typography-meta text-[var(--status-warning)]">
{t('settings.openchamber.keyboardShortcuts.error.exactConflict', { action: actionLabel(exactConflict.action) })}
</p>
) : null}
{combo && isRiskyBrowserShortcut(combo) ? (
<p className="typography-meta text-[var(--status-warning)]">
{t('settings.openchamber.keyboardShortcuts.warning.riskyBrowserShortcut')}
</p>
) : null}
</div>
<DialogFooter>
<Button type="button" variant="ghost" size="sm" onClick={close}>
{t('settings.common.actions.cancel')}
</Button>
{exactConflict && !prefixConflict ? (
<Button type="button" size="sm" onClick={() => {
if (!action) return;
onSave(action.id, combo, exactConflict.action.id);
close();
}}>
{t('settings.openchamber.keyboardShortcuts.actions.replaceAndSave')}
</Button>
) : (
<Button type="button" size="sm" disabled={!combo || Boolean(prefixConflict)} onClick={() => {
if (!action) return;
onSave(action.id, combo);
close();
}}>
{t('settings.common.actions.saveChanges')}
</Button>
)}
</DialogFooter>
</DialogContent>
</Dialog>
);
};
+68 -37
View File
@@ -13,13 +13,14 @@ import {
getShortcutAction,
getModifierLabel,
formatShortcutForDisplay,
type ShortcutActionId,
} from "@/lib/shortcuts";
import { useI18n, type I18nKey } from "@/lib/i18n";
import { isVSCodeRuntime } from "@/lib/desktop";
import type { IconName } from "@/components/icon/icons";
type ShortcutItem = {
id?: string;
id?: ShortcutActionId;
keys: string | string[];
descriptionKey: I18nKey;
icon: IconName | null;
@@ -30,9 +31,14 @@ type ShortcutSection = {
items: ShortcutItem[];
};
const renderShortcut = (id: string, fallbackCombo: string, overrides: Record<string, string>) => {
const renderShortcut = (
id: ShortcutActionId,
fallbackCombo: string,
overrides: Record<string, string>,
unassignedLabel: string,
) => {
const action = getShortcutAction(id);
return action ? formatShortcutForDisplay(getEffectiveShortcutCombo(id, overrides)) : fallbackCombo;
return action ? formatShortcutForDisplay(getEffectiveShortcutCombo(id, overrides), unassignedLabel) : fallbackCombo;
};
export const HelpDialog: React.FC = () => {
@@ -121,6 +127,18 @@ export const HelpDialog: React.FC = () => {
icon: "git-branch",
keys: '',
},
{
id: 'open_draft_project_picker',
descriptionKey: 'settings.openchamber.keyboardShortcuts.action.open_draft_project_picker.label',
icon: 'folder',
keys: '',
},
{
id: 'open_draft_worktree_picker',
descriptionKey: 'settings.openchamber.keyboardShortcuts.action.open_draft_worktree_picker.label',
icon: 'git-branch',
keys: '',
},
{ id: 'focus_input', descriptionKey: "helpDialog.item.focusChatInput", icon: "text", keys: '' },
{
id: 'toggle_prompt_navigator',
@@ -214,7 +232,7 @@ export const HelpDialog: React.FC = () => {
];
return (
<Dialog open={isHelpDialogOpen} onOpenChange={setHelpDialogOpen}>
<Dialog open={isHelpDialogOpen} onOpenChange={setHelpDialogOpen}>
<DialogContent className="max-w-2xl w-[min(42rem,calc(100vw-1.5rem))] max-h-[calc(100dvh-2rem)] flex flex-col overflow-hidden">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
@@ -237,40 +255,48 @@ export const HelpDialog: React.FC = () => {
{section.items
.filter((shortcut) => !(isVSCode && shortcut.id === 'toggle_prompt_navigator'))
.map((shortcut) => {
const displayKeys = shortcut.id
? renderShortcut(shortcut.id, Array.isArray(shortcut.keys) ? shortcut.keys[0] : shortcut.keys, shortcutOverrides)
: (Array.isArray(shortcut.keys) ? shortcut.keys : shortcut.keys.split(" / "));
const fallbackKeys = Array.isArray(shortcut.keys)
? shortcut.keys[0]
: shortcut.keys;
const displayKeys = shortcut.id
? renderShortcut(
shortcut.id,
fallbackKeys,
shortcutOverrides,
t('settings.openchamber.keyboardShortcuts.unassigned'),
)
: (Array.isArray(shortcut.keys) ? shortcut.keys : shortcut.keys.split(" / "));
return (
<div
key={shortcut.id || shortcut.descriptionKey}
className="flex items-center justify-between py-1 px-2"
>
<div className="flex items-center gap-2">
{shortcut.icon && (
<Icon name={shortcut.icon} className="h-3.5 w-3.5 text-muted-foreground" />
)}
<span className="typography-meta">
{t(shortcut.descriptionKey)}
</span>
return (
<div
key={shortcut.id || shortcut.descriptionKey}
className="flex items-center justify-between py-1 px-2"
>
<div className="flex items-center gap-2">
{shortcut.icon && (
<Icon name={shortcut.icon} className="h-3.5 w-3.5 text-muted-foreground" />
)}
<span className="typography-meta">
{t(shortcut.descriptionKey)}
</span>
</div>
<div className="flex items-center gap-1">
{(Array.isArray(displayKeys) ? displayKeys : [displayKeys]).map((keyCombo: string, i: number) => (
<React.Fragment key={`${keyCombo}-${i}`}>
{i > 0 && (
<span className="typography-meta text-muted-foreground mx-1">
{t('helpDialog.keyCombiner.or')}
</span>
)}
<kbd className="inline-flex items-center gap-1 px-1.5 py-0.5 typography-meta font-mono bg-muted rounded border border-border/20">
{keyCombo}
</kbd>
</React.Fragment>
))}
</div>
</div>
<div className="flex items-center gap-1">
{(Array.isArray(displayKeys) ? displayKeys : [displayKeys]).map((keyCombo: string, i: number) => (
<React.Fragment key={`${keyCombo}-${i}`}>
{i > 0 && (
<span className="typography-meta text-muted-foreground mx-1">
{t('helpDialog.keyCombiner.or')}
</span>
)}
<kbd className="inline-flex items-center gap-1 px-1.5 py-0.5 typography-meta font-mono bg-muted rounded border border-border/20">
{keyCombo}
</kbd>
</React.Fragment>
))}
</div>
</div>
);
})}
);
})}
</div>
</div>
))}
@@ -284,7 +310,12 @@ export const HelpDialog: React.FC = () => {
<ul className="space-y-0.5 typography-meta">
<li>
{t('helpDialog.proTips.commandPalette', {
shortcut: renderShortcut('open_command_palette', `${mod} P`, shortcutOverrides),
shortcut: renderShortcut(
'open_command_palette',
`${mod} P`,
shortcutOverrides,
t('settings.openchamber.keyboardShortcuts.unassigned'),
),
})}
</li>
<li>
@@ -1096,7 +1096,7 @@ export const settingsDict = {
'settings.openchamber.keyboardShortcuts.overwritePrompt': 'This combo is already used by another shortcut. Overwrite and clear that other mapping?',
'settings.openchamber.keyboardShortcuts.field.pressKeys': 'Press keys...',
'settings.openchamber.keyboardShortcuts.error.captureFirst': 'Capture a shortcut first.',
'settings.openchamber.keyboardShortcuts.warning.riskyBrowserShortcut': 'This shortcut can conflict with browser defaults. It is still saved.',
'settings.openchamber.keyboardShortcuts.warning.riskyBrowserShortcut': 'This shortcut can conflict with browser defaults. You can still save it.',
'settings.openchamber.keyboardShortcuts.action.open_go_to_line.label': 'Go to line (files editor)',
'settings.openchamber.keyboardShortcuts.action.open_command_palette.label': 'Open command palette',
'settings.openchamber.keyboardShortcuts.action.focus_input.label': 'Focus input',
@@ -1125,7 +1125,25 @@ export const settingsDict = {
'settings.openchamber.keyboardShortcuts.action.expand_input.label': 'Expand input',
'settings.openchamber.keyboardShortcuts.action.open_timeline_dialog.label': 'Open conversation timeline',
'settings.openchamber.keyboardShortcuts.action.toggle_prompt_navigator.label': 'Toggle prompt navigator',
'settings.projects.sidebar.total': 'Total {count}',
'settings.openchamber.keyboardShortcuts.category.session': 'Session Controls',
'settings.openchamber.keyboardShortcuts.category.models': 'Models & Agents',
'settings.openchamber.keyboardShortcuts.category.panels': 'Panels & Tools',
'settings.openchamber.keyboardShortcuts.category.navigation': 'Navigation',
'settings.openchamber.keyboardShortcuts.category.application': 'Application',
'settings.openchamber.keyboardShortcuts.actions.edit': 'Edit',
'settings.openchamber.keyboardShortcuts.actions.replaceAndSave': 'Replace and Save',
'settings.openchamber.keyboardShortcuts.dialog.title': 'Edit {action}',
'settings.openchamber.keyboardShortcuts.dialog.instructions': 'Press up to two key combinations. Press Backspace to remove the last one.',
'settings.openchamber.keyboardShortcuts.dialog.firstChord': 'First combination',
'settings.openchamber.keyboardShortcuts.dialog.secondChord': 'Second combination',
'settings.openchamber.keyboardShortcuts.dialog.recording': 'Press keys…',
'settings.openchamber.keyboardShortcuts.unassigned': 'Unassigned',
'settings.openchamber.keyboardShortcuts.error.prefixConflict': 'This conflicts with the sequence used by {action}. Choose a different combination.',
'settings.openchamber.keyboardShortcuts.error.exactConflict': 'This combination is already used by {action}.',
'settings.openchamber.keyboardShortcuts.action.open_draft_project_picker.label': 'Open draft project picker',
'settings.openchamber.keyboardShortcuts.action.open_draft_worktree_picker.label': 'Open draft worktree picker',
'settings.openchamber.keyboardShortcuts.action.toggle_dictation.label': 'Voice input',
'settings.projects.sidebar.total': 'Total {count}',
'settings.projects.sidebar.actions.addProject': 'Add project',
'settings.projects.page.empty.noProjects': 'No projects available.',
'settings.projects.page.title.default': 'Project Settings',
@@ -1063,7 +1063,7 @@ export const settingsDict = {
"settings.openchamber.keyboardShortcuts.overwritePrompt": "Esta combinación ya está usada por otro atajo. ¿Sobrescribir y limpiar esa otra asignación?",
"settings.openchamber.keyboardShortcuts.field.pressKeys": "Pulsa las teclas...",
"settings.openchamber.keyboardShortcuts.error.captureFirst": "Captura un atajo primero.",
"settings.openchamber.keyboardShortcuts.warning.riskyBrowserShortcut": "Este atajo puede entrar en conflicto con los predeterminados del navegador. Todavía se guarda.",
"settings.openchamber.keyboardShortcuts.warning.riskyBrowserShortcut": "Este atajo puede entrar en conflicto con los predeterminados del navegador. Aun así, puedes guardarlo.",
"settings.openchamber.keyboardShortcuts.action.open_go_to_line.label": "Ir a línea (editor de archivos)",
"settings.openchamber.keyboardShortcuts.action.open_command_palette.label": "Abrir paleta de comandos",
"settings.openchamber.keyboardShortcuts.action.focus_input.label": "Enfocar entrada",
@@ -1092,7 +1092,25 @@ export const settingsDict = {
"settings.openchamber.keyboardShortcuts.action.expand_input.label": "Expandir entrada",
"settings.openchamber.keyboardShortcuts.action.open_timeline_dialog.label": "Abrir línea de tiempo de conversación",
"settings.openchamber.keyboardShortcuts.action.toggle_prompt_navigator.label": "Mostrar u ocultar navegador de prompts",
"settings.projects.sidebar.total": "Total {count}",
"settings.openchamber.keyboardShortcuts.category.session": "Controles de sesión",
"settings.openchamber.keyboardShortcuts.category.models": "Modelos y agentes",
"settings.openchamber.keyboardShortcuts.category.panels": "Paneles y herramientas",
"settings.openchamber.keyboardShortcuts.category.navigation": "Navegación",
"settings.openchamber.keyboardShortcuts.category.application": "Aplicación",
"settings.openchamber.keyboardShortcuts.actions.edit": "Editar",
"settings.openchamber.keyboardShortcuts.actions.replaceAndSave": "Reemplazar y guardar",
"settings.openchamber.keyboardShortcuts.dialog.title": "Editar {action}",
"settings.openchamber.keyboardShortcuts.dialog.instructions": "Pulse hasta dos combinaciones de teclas. Pulse Retroceso para quitar la última.",
"settings.openchamber.keyboardShortcuts.dialog.firstChord": "Primera combinación",
"settings.openchamber.keyboardShortcuts.dialog.secondChord": "Segunda combinación",
"settings.openchamber.keyboardShortcuts.dialog.recording": "Pulse las teclas…",
"settings.openchamber.keyboardShortcuts.unassigned": "Sin asignar",
"settings.openchamber.keyboardShortcuts.error.prefixConflict": "Esto entra en conflicto con la secuencia usada por {action}. Elija otra combinación.",
"settings.openchamber.keyboardShortcuts.error.exactConflict": "Esta combinación ya la usa {action}.",
"settings.openchamber.keyboardShortcuts.action.open_draft_project_picker.label": "Abrir selector de proyecto de borrador",
"settings.openchamber.keyboardShortcuts.action.open_draft_worktree_picker.label": "Abrir selector de árbol de trabajo de borrador",
"settings.openchamber.keyboardShortcuts.action.toggle_dictation.label": "Entrada de voz",
"settings.projects.sidebar.total": "Total {count}",
"settings.projects.sidebar.actions.addProject": "Añadir proyecto",
"settings.projects.page.empty.noProjects": "No hay proyectos disponibles.",
"settings.projects.page.title.default": "Configuración del proyecto",
@@ -984,7 +984,7 @@ export const settingsDict = {
'settings.openchamber.keyboardShortcuts.overwritePrompt': 'Ce combo est déjà utilisé par un autre raccourci. Écraser et effacer cet autre mappage ?',
'settings.openchamber.keyboardShortcuts.field.pressKeys': 'Appuyez sur les touches...',
'settings.openchamber.keyboardShortcuts.error.captureFirst': 'Capturez d\'abord un raccourci.',
'settings.openchamber.keyboardShortcuts.warning.riskyBrowserShortcut': 'Ce raccourci peut entrer en conflit avec les paramètres par défaut du navigateur. Il est toujours sauvegardé.',
'settings.openchamber.keyboardShortcuts.warning.riskyBrowserShortcut': 'Ce raccourci peut entrer en conflit avec les paramètres par défaut du navigateur. Vous pouvez tout de même lenregistrer.',
'settings.openchamber.keyboardShortcuts.action.open_go_to_line.label': 'Aller à la ligne (éditeur de fichiers)',
'settings.openchamber.keyboardShortcuts.action.open_command_palette.label': 'Ouvrir la palette de commandes',
'settings.openchamber.keyboardShortcuts.action.focus_input.label': 'Entrée de mise au point',
@@ -1013,7 +1013,25 @@ export const settingsDict = {
'settings.openchamber.keyboardShortcuts.action.expand_input.label': 'Développer l\'entrée',
'settings.openchamber.keyboardShortcuts.action.open_timeline_dialog.label': 'Chronologie de la conversation ouverte',
'settings.openchamber.keyboardShortcuts.action.toggle_prompt_navigator.label': 'Afficher ou masquer le navigateur de prompts',
'settings.projects.sidebar.total': 'Total {count}',
'settings.openchamber.keyboardShortcuts.category.session': 'Commandes de session',
'settings.openchamber.keyboardShortcuts.category.models': 'Modèles et agents',
'settings.openchamber.keyboardShortcuts.category.panels': 'Panneaux et outils',
'settings.openchamber.keyboardShortcuts.category.navigation': 'Navigation',
'settings.openchamber.keyboardShortcuts.category.application': 'Application',
'settings.openchamber.keyboardShortcuts.actions.edit': 'Modifier',
'settings.openchamber.keyboardShortcuts.actions.replaceAndSave': 'Remplacer et enregistrer',
'settings.openchamber.keyboardShortcuts.dialog.title': 'Modifier {action}',
'settings.openchamber.keyboardShortcuts.dialog.instructions': 'Appuyez sur deux combinaisons de touches au maximum. Appuyez sur Retour arrière pour supprimer la dernière.',
'settings.openchamber.keyboardShortcuts.dialog.firstChord': 'Première combinaison',
'settings.openchamber.keyboardShortcuts.dialog.secondChord': 'Deuxième combinaison',
'settings.openchamber.keyboardShortcuts.dialog.recording': 'Appuyez sur les touches…',
'settings.openchamber.keyboardShortcuts.unassigned': 'Non attribué',
'settings.openchamber.keyboardShortcuts.error.prefixConflict': 'Cela entre en conflit avec la séquence utilisée par {action}. Choisissez une autre combinaison.',
'settings.openchamber.keyboardShortcuts.error.exactConflict': 'Cette combinaison est déjà utilisée par {action}.',
'settings.openchamber.keyboardShortcuts.action.open_draft_project_picker.label': 'Ouvrir le sélecteur de projet de brouillon',
'settings.openchamber.keyboardShortcuts.action.open_draft_worktree_picker.label': 'Ouvrir le sélecteur de worktree de brouillon',
'settings.openchamber.keyboardShortcuts.action.toggle_dictation.label': 'Saisie vocale',
'settings.projects.sidebar.total': 'Total {count}',
'settings.projects.sidebar.actions.addProject': 'Ajouter un projet',
'settings.projects.page.empty.noProjects': 'Aucun projet disponible.',
'settings.projects.page.title.default': 'Paramètres du projet',
@@ -1096,7 +1096,7 @@ export const settingsDict = {
'settings.openchamber.keyboardShortcuts.overwritePrompt': 'このキーコンボは別のショートカットで既に使用されています。上書きしてそのマッピングをクリアしますか?',
'settings.openchamber.keyboardShortcuts.field.pressKeys': 'キーを押してください...',
'settings.openchamber.keyboardShortcuts.error.captureFirst': '最初にショートカットを設定してください。',
'settings.openchamber.keyboardShortcuts.warning.riskyBrowserShortcut': 'このショートカットはブラウザのデフォルトと競合する可能性があります。それでも保存されます。',
'settings.openchamber.keyboardShortcuts.warning.riskyBrowserShortcut': 'このショートカットはブラウザのデフォルトと競合する可能性がありますが、そのまま保存できます。',
'settings.openchamber.keyboardShortcuts.action.open_go_to_line.label': '指定行に移動(ファイルエディター)',
'settings.openchamber.keyboardShortcuts.action.open_command_palette.label': 'コマンドパレットを開く',
'settings.openchamber.keyboardShortcuts.action.focus_input.label': '入力をフォーカス',
@@ -1125,7 +1125,25 @@ export const settingsDict = {
'settings.openchamber.keyboardShortcuts.action.open_model_selector.label': 'モデルセレクターを開く',
'settings.openchamber.keyboardShortcuts.action.open_timeline_dialog.label': '会話タイムラインを開く',
'settings.openchamber.keyboardShortcuts.action.toggle_prompt_navigator.label': 'プロンプトナビゲーターの表示切替',
'settings.projects.sidebar.total': '合計 {count}',
'settings.openchamber.keyboardShortcuts.category.session': 'セッション操作',
'settings.openchamber.keyboardShortcuts.category.models': 'モデルとエージェント',
'settings.openchamber.keyboardShortcuts.category.panels': 'パネルとツール',
'settings.openchamber.keyboardShortcuts.category.navigation': 'ナビゲーション',
'settings.openchamber.keyboardShortcuts.category.application': 'アプリケーション',
'settings.openchamber.keyboardShortcuts.actions.edit': '編集',
'settings.openchamber.keyboardShortcuts.actions.replaceAndSave': '置き換えて保存',
'settings.openchamber.keyboardShortcuts.dialog.title': '{action} を編集',
'settings.openchamber.keyboardShortcuts.dialog.instructions': 'キーの組み合わせを最大2つ入力できます。Backspace で最後の組み合わせを削除します。',
'settings.openchamber.keyboardShortcuts.dialog.firstChord': '最初の組み合わせ',
'settings.openchamber.keyboardShortcuts.dialog.secondChord': '2番目の組み合わせ',
'settings.openchamber.keyboardShortcuts.dialog.recording': 'キーを押してください…',
'settings.openchamber.keyboardShortcuts.unassigned': '未割り当て',
'settings.openchamber.keyboardShortcuts.error.prefixConflict': '{action} のシーケンスと競合しています。別の組み合わせを選択してください。',
'settings.openchamber.keyboardShortcuts.error.exactConflict': 'この組み合わせは {action} で使用されています。',
'settings.openchamber.keyboardShortcuts.action.open_draft_project_picker.label': '下書きプロジェクト選択を開く',
'settings.openchamber.keyboardShortcuts.action.open_draft_worktree_picker.label': '下書きワークツリー選択を開く',
'settings.openchamber.keyboardShortcuts.action.toggle_dictation.label': '音声入力',
'settings.projects.sidebar.total': '合計 {count}',
'settings.projects.sidebar.actions.addProject': 'プロジェクトを追加',
'settings.projects.page.empty.noProjects': '利用可能なプロジェクトがありません。',
'settings.projects.page.title.default': 'プロジェクト設定',
@@ -1063,7 +1063,7 @@ export const settingsDict = {
'settings.openchamber.keyboardShortcuts.overwritePrompt': '이 조합은 이미 다른 단축키에서 사용 중입니다. 덮어쓰고 기존 매핑을 지울까요?',
'settings.openchamber.keyboardShortcuts.field.pressKeys': '키를 누르세요...',
'settings.openchamber.keyboardShortcuts.error.captureFirst': '먼저 단축키를 입력하세요.',
'settings.openchamber.keyboardShortcuts.warning.riskyBrowserShortcut': '이 단축키는 브라우저 기본값과 충돌할 수 있습니다. 그래도 저장니다.',
'settings.openchamber.keyboardShortcuts.warning.riskyBrowserShortcut': '이 단축키는 브라우저 기본값과 충돌할 수 있지만 그래도 저장할 수 있습니다.',
'settings.openchamber.keyboardShortcuts.action.open_go_to_line.label': '줄로 이동(파일 편집기)',
'settings.openchamber.keyboardShortcuts.action.open_command_palette.label': '명령 팔레트 열기',
'settings.openchamber.keyboardShortcuts.action.focus_input.label': '입력에 포커스',
@@ -1092,7 +1092,25 @@ export const settingsDict = {
'settings.openchamber.keyboardShortcuts.action.expand_input.label': '입력 확장',
'settings.openchamber.keyboardShortcuts.action.open_timeline_dialog.label': '대화 타임라인 열기',
'settings.openchamber.keyboardShortcuts.action.toggle_prompt_navigator.label': '프롬프트 탐색기 표시/숨기기',
'settings.projects.sidebar.total': '총 {count}개',
'settings.openchamber.keyboardShortcuts.category.session': '세션 제어',
'settings.openchamber.keyboardShortcuts.category.models': '모델 및 에이전트',
'settings.openchamber.keyboardShortcuts.category.panels': '패널 및 도구',
'settings.openchamber.keyboardShortcuts.category.navigation': '탐색',
'settings.openchamber.keyboardShortcuts.category.application': '애플리케이션',
'settings.openchamber.keyboardShortcuts.actions.edit': '편집',
'settings.openchamber.keyboardShortcuts.actions.replaceAndSave': '바꾸고 저장',
'settings.openchamber.keyboardShortcuts.dialog.title': '{action} 편집',
'settings.openchamber.keyboardShortcuts.dialog.instructions': '키 조합을 최대 두 개까지 누르세요. Backspace를 누르면 마지막 조합이 삭제됩니다.',
'settings.openchamber.keyboardShortcuts.dialog.firstChord': '첫 번째 조합',
'settings.openchamber.keyboardShortcuts.dialog.secondChord': '두 번째 조합',
'settings.openchamber.keyboardShortcuts.dialog.recording': '키를 누르세요…',
'settings.openchamber.keyboardShortcuts.unassigned': '할당되지 않음',
'settings.openchamber.keyboardShortcuts.error.prefixConflict': '{action}에서 사용하는 시퀀스와 충돌합니다. 다른 조합을 선택하세요.',
'settings.openchamber.keyboardShortcuts.error.exactConflict': '이 조합은 이미 {action}에서 사용합니다.',
'settings.openchamber.keyboardShortcuts.action.open_draft_project_picker.label': '초안 프로젝트 선택기 열기',
'settings.openchamber.keyboardShortcuts.action.open_draft_worktree_picker.label': '초안 워크트리 선택기 열기',
'settings.openchamber.keyboardShortcuts.action.toggle_dictation.label': '음성 입력',
'settings.projects.sidebar.total': '총 {count}개',
'settings.projects.sidebar.actions.addProject': '프로젝트 추가',
'settings.projects.page.empty.noProjects': '사용 가능한 프로젝트가 없습니다.',
'settings.projects.page.title.default': '프로젝트 설정',
@@ -838,7 +838,7 @@ export const settingsDict = {
'settings.openchamber.keyboardShortcuts.overwritePrompt': 'Ta kombinacja jest już używana przez inny skrót. Nadpisać i wyczyścić to inne przypisanie?',
'settings.openchamber.keyboardShortcuts.title': 'Skróty klawiszowe',
'settings.openchamber.keyboardShortcuts.tooltip': 'Przechwyć nową kombinację klawiszy, zapisz ją, a przypisania zostaną natychmiast zaktualizowane.',
'settings.openchamber.keyboardShortcuts.warning.riskyBrowserShortcut': 'Ten skrót może kolidować z domyślnymi skrótami przeglądarki. Został jednak zapisany.',
'settings.openchamber.keyboardShortcuts.warning.riskyBrowserShortcut': 'Ten skrót może kolidować z domyślnymi skrótami przeglądarki. Nadal możesz go zapisać.',
'settings.openchamber.opencodeCli.actions.browse': 'Przeglądaj',
'settings.openchamber.opencodeCli.actions.browseAria': 'Przeglądaj ścieżkę do pliku binarnego OpenCode',
'settings.openchamber.opencodeCli.actions.restartingOpenCode': 'Restartowanie OpenCode...',
@@ -1360,7 +1360,26 @@ export const settingsDict = {
'settings.projects.page.toast.removeIconFailed': 'Nie udało się usunąć ikony projektu',
'settings.projects.page.toast.uploadIconFailed': 'Nie udało się przesłać ikony projektu',
'settings.projects.sidebar.actions.addProject': 'Dodaj projekt',
'settings.projects.sidebar.total': 'Suma: {count}',
'settings.openchamber.keyboardShortcuts.category.session': 'Sterowanie sesją',
'settings.openchamber.keyboardShortcuts.category.models': 'Modele i agenci',
'settings.openchamber.keyboardShortcuts.category.panels': 'Panele i narzędzia',
'settings.openchamber.keyboardShortcuts.category.navigation': 'Nawigacja',
'settings.openchamber.keyboardShortcuts.category.application': 'Aplikacja',
'settings.openchamber.keyboardShortcuts.actions.edit': 'Edytuj',
'settings.openchamber.keyboardShortcuts.actions.replaceAndSave': 'Zastąp i zapisz',
'settings.openchamber.keyboardShortcuts.dialog.title': 'Edytuj: {action}',
'settings.openchamber.keyboardShortcuts.dialog.instructions': 'Naciśnij maksymalnie dwie kombinacje klawiszy. Naciśnij Backspace, aby usunąć ostatnią.',
'settings.openchamber.keyboardShortcuts.dialog.firstChord': 'Pierwsza kombinacja',
'settings.openchamber.keyboardShortcuts.dialog.secondChord': 'Druga kombinacja',
'settings.openchamber.keyboardShortcuts.dialog.recording': 'Naciśnij klawisze…',
'settings.openchamber.keyboardShortcuts.unassigned': 'Nieprzypisany',
'settings.openchamber.keyboardShortcuts.error.prefixConflict': 'To koliduje z sekwencją używaną przez {action}. Wybierz inną kombinację.',
'settings.openchamber.keyboardShortcuts.error.exactConflict': 'Ta kombinacja jest już używana przez {action}.',
'settings.openchamber.keyboardShortcuts.action.open_draft_project_picker.label': 'Otwórz wybór projektu szkicu',
'settings.openchamber.keyboardShortcuts.action.open_draft_worktree_picker.label': 'Otwórz wybór worktree szkicu',
'settings.openchamber.keyboardShortcuts.action.open_timeline_dialog.label': 'Otwórz oś czasu rozmowy',
'settings.openchamber.keyboardShortcuts.action.toggle_dictation.label': 'Wprowadzanie głosowe',
'settings.projects.sidebar.total': 'Suma: {count}',
'settings.providers.page.actions.complete': 'Zakończ',
'settings.providers.page.actions.continue': 'Kontynuuj',
'settings.providers.page.actions.cancel': 'Anuluj',
@@ -1063,7 +1063,7 @@ export const settingsDict = {
"settings.openchamber.keyboardShortcuts.overwritePrompt": "Esta combinação já está sendo usada por outro atalho. Sobrescrever e limpar essa outra atribuição?",
"settings.openchamber.keyboardShortcuts.field.pressKeys": "Pressione as teclas...",
"settings.openchamber.keyboardShortcuts.error.captureFirst": "Captura um atalho primeiro.",
"settings.openchamber.keyboardShortcuts.warning.riskyBrowserShortcut": "Este atalho pode entrar em conflito com os padrões do navegador. Ainda assim, ele será salvo.",
"settings.openchamber.keyboardShortcuts.warning.riskyBrowserShortcut": "Este atalho pode entrar em conflito com os padrões do navegador. Ainda assim, você pode salvá-lo.",
"settings.openchamber.keyboardShortcuts.action.open_go_to_line.label": "Ir para linha (editor de arquivos)",
"settings.openchamber.keyboardShortcuts.action.open_command_palette.label": "Abrir paleta de comandos",
"settings.openchamber.keyboardShortcuts.action.focus_input.label": "Focar entrada",
@@ -1092,7 +1092,25 @@ export const settingsDict = {
"settings.openchamber.keyboardShortcuts.action.expand_input.label": "Expandir entrada",
"settings.openchamber.keyboardShortcuts.action.open_timeline_dialog.label": "Abrir linha do tempo da conversa",
"settings.openchamber.keyboardShortcuts.action.toggle_prompt_navigator.label": "Mostrar ou ocultar navegador de prompts",
"settings.projects.sidebar.total": "Total {count}",
"settings.openchamber.keyboardShortcuts.category.session": "Controles de sessão",
"settings.openchamber.keyboardShortcuts.category.models": "Modelos e agentes",
"settings.openchamber.keyboardShortcuts.category.panels": "Painéis e ferramentas",
"settings.openchamber.keyboardShortcuts.category.navigation": "Navegação",
"settings.openchamber.keyboardShortcuts.category.application": "Aplicação",
"settings.openchamber.keyboardShortcuts.actions.edit": "Editar",
"settings.openchamber.keyboardShortcuts.actions.replaceAndSave": "Substituir e salvar",
"settings.openchamber.keyboardShortcuts.dialog.title": "Editar {action}",
"settings.openchamber.keyboardShortcuts.dialog.instructions": "Pressione até duas combinações de teclas. Pressione Backspace para remover a última.",
"settings.openchamber.keyboardShortcuts.dialog.firstChord": "Primeira combinação",
"settings.openchamber.keyboardShortcuts.dialog.secondChord": "Segunda combinação",
"settings.openchamber.keyboardShortcuts.dialog.recording": "Pressione as teclas…",
"settings.openchamber.keyboardShortcuts.unassigned": "Não atribuído",
"settings.openchamber.keyboardShortcuts.error.prefixConflict": "Isto entra em conflito com a sequência usada por {action}. Escolha outra combinação.",
"settings.openchamber.keyboardShortcuts.error.exactConflict": "Esta combinação já é usada por {action}.",
"settings.openchamber.keyboardShortcuts.action.open_draft_project_picker.label": "Abrir seletor de projeto do rascunho",
"settings.openchamber.keyboardShortcuts.action.open_draft_worktree_picker.label": "Abrir seletor de worktree do rascunho",
"settings.openchamber.keyboardShortcuts.action.toggle_dictation.label": "Entrada por voz",
"settings.projects.sidebar.total": "Total {count}",
"settings.projects.sidebar.actions.addProject": "Adicionar projeto",
"settings.projects.page.empty.noProjects": "Não há projetos disponíveis.",
"settings.projects.page.title.default": "Configurações do projeto",
@@ -1063,7 +1063,7 @@ export const settingsDict = {
"settings.openchamber.keyboardShortcuts.overwritePrompt": "Ця комбінація вже використовується іншою комбінацією клавіш. Перезаписати та очистити інше зіставлення?",
"settings.openchamber.keyboardShortcuts.field.pressKeys": "Натисніть клавіші...",
"settings.openchamber.keyboardShortcuts.error.captureFirst": "Спочатку запишіть комбінацію клавіш.",
"settings.openchamber.keyboardShortcuts.warning.riskyBrowserShortcut": "Ця комбінація клавіш може конфліктувати зі стандартними скороченнями браузера. Її все одно збережено.",
"settings.openchamber.keyboardShortcuts.warning.riskyBrowserShortcut": "Ця комбінація клавіш може конфліктувати зі стандартними скороченнями браузера. Ви все одно можете її зберегти.",
"settings.openchamber.keyboardShortcuts.action.open_go_to_line.label": "Перейти до рядка (редактор файлів)",
"settings.openchamber.keyboardShortcuts.action.open_command_palette.label": "Відкрити палітру команд",
"settings.openchamber.keyboardShortcuts.action.focus_input.label": "Фокус на полі вводу",
@@ -1092,7 +1092,25 @@ export const settingsDict = {
"settings.openchamber.keyboardShortcuts.action.expand_input.label": "Розгорнути введення",
"settings.openchamber.keyboardShortcuts.action.open_timeline_dialog.label": "Відкрити хронологію розмови",
"settings.openchamber.keyboardShortcuts.action.toggle_prompt_navigator.label": "Показати або приховати навігатор промптів",
"settings.projects.sidebar.total": "Усього {count}",
"settings.openchamber.keyboardShortcuts.category.session": "Керування сесією",
"settings.openchamber.keyboardShortcuts.category.models": "Моделі й агенти",
"settings.openchamber.keyboardShortcuts.category.panels": "Панелі та інструменти",
"settings.openchamber.keyboardShortcuts.category.navigation": "Навігація",
"settings.openchamber.keyboardShortcuts.category.application": "Застосунок",
"settings.openchamber.keyboardShortcuts.actions.edit": "Редагувати",
"settings.openchamber.keyboardShortcuts.actions.replaceAndSave": "Замінити й зберегти",
"settings.openchamber.keyboardShortcuts.dialog.title": "Редагувати {action}",
"settings.openchamber.keyboardShortcuts.dialog.instructions": "Натисніть до двох комбінацій клавіш. Натисніть Backspace, щоб видалити останню.",
"settings.openchamber.keyboardShortcuts.dialog.firstChord": "Перша комбінація",
"settings.openchamber.keyboardShortcuts.dialog.secondChord": "Друга комбінація",
"settings.openchamber.keyboardShortcuts.dialog.recording": "Натисніть клавіші…",
"settings.openchamber.keyboardShortcuts.unassigned": "Не призначено",
"settings.openchamber.keyboardShortcuts.error.prefixConflict": "Це конфліктує з послідовністю, яку використовує {action}. Виберіть іншу комбінацію.",
"settings.openchamber.keyboardShortcuts.error.exactConflict": "Цю комбінацію вже використовує {action}.",
"settings.openchamber.keyboardShortcuts.action.open_draft_project_picker.label": "Відкрити вибір проєкту чернетки",
"settings.openchamber.keyboardShortcuts.action.open_draft_worktree_picker.label": "Відкрити вибір worktree чернетки",
"settings.openchamber.keyboardShortcuts.action.toggle_dictation.label": "Голосове введення",
"settings.projects.sidebar.total": "Усього {count}",
"settings.projects.sidebar.actions.addProject": "Додати проєкт",
"settings.projects.page.empty.noProjects": "Немає доступних проєктів.",
"settings.projects.page.title.default": "Параметри проєкту",
@@ -1063,7 +1063,7 @@ export const settingsDict = {
'settings.openchamber.keyboardShortcuts.overwritePrompt': '该组合已被其他快捷键使用。是否覆盖并清除原映射?',
'settings.openchamber.keyboardShortcuts.field.pressKeys': '按下按键...',
'settings.openchamber.keyboardShortcuts.error.captureFirst': '请先录入一个快捷键。',
'settings.openchamber.keyboardShortcuts.warning.riskyBrowserShortcut': '该快捷键可能与浏览器默认快捷键冲突,但仍保存。',
'settings.openchamber.keyboardShortcuts.warning.riskyBrowserShortcut': '该快捷键可能与浏览器默认快捷键冲突,但仍保存。',
'settings.openchamber.keyboardShortcuts.action.open_go_to_line.label': '跳转到行(文件编辑器)',
'settings.openchamber.keyboardShortcuts.action.open_command_palette.label': '打开命令面板',
'settings.openchamber.keyboardShortcuts.action.focus_input.label': '聚焦输入框',
@@ -1092,7 +1092,25 @@ export const settingsDict = {
'settings.openchamber.keyboardShortcuts.action.expand_input.label': '展开输入框',
'settings.openchamber.keyboardShortcuts.action.open_timeline_dialog.label': '打开对话时间线',
'settings.openchamber.keyboardShortcuts.action.toggle_prompt_navigator.label': '显示或隐藏提示词导航',
'settings.projects.sidebar.total': '总计 {count}',
'settings.openchamber.keyboardShortcuts.category.session': '会话控制',
'settings.openchamber.keyboardShortcuts.category.models': '模型和智能体',
'settings.openchamber.keyboardShortcuts.category.panels': '面板和工具',
'settings.openchamber.keyboardShortcuts.category.navigation': '导航',
'settings.openchamber.keyboardShortcuts.category.application': '应用程序',
'settings.openchamber.keyboardShortcuts.actions.edit': '编辑',
'settings.openchamber.keyboardShortcuts.actions.replaceAndSave': '替换并保存',
'settings.openchamber.keyboardShortcuts.dialog.title': '编辑{action}',
'settings.openchamber.keyboardShortcuts.dialog.instructions': '最多按下两个按键组合。按 Backspace 删除最后一个。',
'settings.openchamber.keyboardShortcuts.dialog.firstChord': '第一个组合',
'settings.openchamber.keyboardShortcuts.dialog.secondChord': '第二个组合',
'settings.openchamber.keyboardShortcuts.dialog.recording': '按下按键…',
'settings.openchamber.keyboardShortcuts.unassigned': '未分配',
'settings.openchamber.keyboardShortcuts.error.prefixConflict': '这与 {action} 使用的序列冲突。请选择其他组合。',
'settings.openchamber.keyboardShortcuts.error.exactConflict': '此组合已被 {action} 使用。',
'settings.openchamber.keyboardShortcuts.action.open_draft_project_picker.label': '打开草稿项目选择器',
'settings.openchamber.keyboardShortcuts.action.open_draft_worktree_picker.label': '打开草稿工作树选择器',
'settings.openchamber.keyboardShortcuts.action.toggle_dictation.label': '语音输入',
'settings.projects.sidebar.total': '总计 {count}',
'settings.projects.sidebar.actions.addProject': '添加项目',
'settings.projects.page.empty.noProjects': '暂无项目。',
'settings.projects.page.title.default': '项目设置',
@@ -970,7 +970,7 @@
'settings.openchamber.keyboardShortcuts.overwritePrompt': '該組合已被其他快速鍵使用。是否覆寫並清除原對應?',
'settings.openchamber.keyboardShortcuts.field.pressKeys': '按下按鍵...',
'settings.openchamber.keyboardShortcuts.error.captureFirst': '請先錄入一個快速鍵。',
'settings.openchamber.keyboardShortcuts.warning.riskyBrowserShortcut': '該快速鍵可能與瀏覽器預設快速鍵衝突,但仍儲存。',
'settings.openchamber.keyboardShortcuts.warning.riskyBrowserShortcut': '該快速鍵可能與瀏覽器預設快速鍵衝突,但仍儲存。',
'settings.openchamber.keyboardShortcuts.action.open_go_to_line.label': '跳轉到行(檔案編輯器)',
'settings.openchamber.keyboardShortcuts.action.open_command_palette.label': '開啟命令面板',
'settings.openchamber.keyboardShortcuts.action.focus_input.label': '聚焦輸入方塊',
@@ -999,7 +999,25 @@
'settings.openchamber.keyboardShortcuts.action.expand_input.label': '展開輸入方塊',
'settings.openchamber.keyboardShortcuts.action.open_timeline_dialog.label': '開啟對話時間軸',
'settings.openchamber.keyboardShortcuts.action.toggle_prompt_navigator.label': '顯示或隱藏提示詞導覽',
'settings.projects.sidebar.total': '總計 {count}',
'settings.openchamber.keyboardShortcuts.category.session': '工作階段控制',
'settings.openchamber.keyboardShortcuts.category.models': '模型與代理',
'settings.openchamber.keyboardShortcuts.category.panels': '面板與工具',
'settings.openchamber.keyboardShortcuts.category.navigation': '導覽',
'settings.openchamber.keyboardShortcuts.category.application': '應用程式',
'settings.openchamber.keyboardShortcuts.actions.edit': '編輯',
'settings.openchamber.keyboardShortcuts.actions.replaceAndSave': '取代並儲存',
'settings.openchamber.keyboardShortcuts.dialog.title': '編輯 {action}',
'settings.openchamber.keyboardShortcuts.dialog.instructions': '最多按下兩個按鍵組合。按 Backspace 可刪除最後一個。',
'settings.openchamber.keyboardShortcuts.dialog.firstChord': '第一個組合',
'settings.openchamber.keyboardShortcuts.dialog.secondChord': '第二個組合',
'settings.openchamber.keyboardShortcuts.dialog.recording': '按下按鍵…',
'settings.openchamber.keyboardShortcuts.unassigned': '未指派',
'settings.openchamber.keyboardShortcuts.error.prefixConflict': '這與 {action} 使用的序列衝突。請選擇其他組合。',
'settings.openchamber.keyboardShortcuts.error.exactConflict': '此組合已由 {action} 使用。',
'settings.openchamber.keyboardShortcuts.action.open_draft_project_picker.label': '開啟草稿專案選擇器',
'settings.openchamber.keyboardShortcuts.action.open_draft_worktree_picker.label': '開啟草稿 worktree 選擇器',
'settings.openchamber.keyboardShortcuts.action.toggle_dictation.label': '語音輸入',
'settings.projects.sidebar.total': '總計 {count}',
'settings.projects.sidebar.actions.addProject': '新增專案',
'settings.projects.page.empty.noProjects': '暫無專案。',
'settings.projects.page.title.default': '專案設定',