feat(ui): redesign shortcut settings
This commit is contained in:
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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 l’enregistrer.',
|
||||
'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': '專案設定',
|
||||
|
||||
Reference in New Issue
Block a user