diff --git a/packages/ui/src/components/chat/composer/ui/FocusModeButton.tsx b/packages/ui/src/components/chat/composer/ui/FocusModeButton.tsx index 74251911..d740de34 100644 --- a/packages/ui/src/components/chat/composer/ui/FocusModeButton.tsx +++ b/packages/ui/src/components/chat/composer/ui/FocusModeButton.tsx @@ -5,7 +5,12 @@ import React from 'react'; import { Icon } from '@/components/icon/Icon'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { useI18n } from '@/lib/i18n'; -import { cn, isMacOS } from '@/lib/utils'; +import { + formatShortcutForDisplay, + getEffectiveShortcutCombo, +} from '@/lib/shortcuts'; +import { cn } from '@/lib/utils'; +import { useUIStore } from '@/stores/useUIStore'; type FocusModeButtonProps = { footerIconButtonClass: string; @@ -17,6 +22,12 @@ type FocusModeButtonProps = { export const FocusModeButton = React.memo(function FocusModeButton(props: FocusModeButtonProps) { const { footerIconButtonClass, iconSizeClass, isExpandedInput, onToggle } = props; const { t } = useI18n(); + const expandInputShortcutOverride = useUIStore((state) => state.shortcutOverrides.expand_input); + const expandInputCombo = getEffectiveShortcutCombo( + 'expand_input', + expandInputShortcutOverride === undefined ? undefined : { expand_input: expandInputShortcutOverride }, + ); + const shortcut = expandInputCombo ? formatShortcutForDisplay(expandInputCombo) : null; return ( @@ -43,9 +54,7 @@ export const FocusModeButton = React.memo(function FocusModeButton(props: FocusM {t('chat.chatInput.focusMode.label')} - - {isMacOS() ? '⌘⇧E' : 'Ctrl+Shift+E'} - + {shortcut ? {shortcut} : null} diff --git a/packages/ui/src/components/comments/InlineCommentInput.tsx b/packages/ui/src/components/comments/InlineCommentInput.tsx index 7b76b5aa..bfe8a573 100644 --- a/packages/ui/src/components/comments/InlineCommentInput.tsx +++ b/packages/ui/src/components/comments/InlineCommentInput.tsx @@ -5,6 +5,7 @@ import { Textarea } from '@/components/ui/textarea'; import { cn } from '@/lib/utils'; import { useDeviceInfo } from '@/lib/device'; import { useI18n } from '@/lib/i18n'; +import { formatShortcutForDisplay } from '@/lib/shortcuts'; export interface InlineCommentInputProps { initialText?: string; @@ -35,6 +36,7 @@ export function InlineCommentInput({ const { isMobile } = useDeviceInfo(); const [text, setText] = React.useState(initialText); const textareaRef = useRef(null); + const saveShortcut = formatShortcutForDisplay('mod+enter'); const handleTextChange = (value: string) => { setText(value); @@ -154,7 +156,9 @@ export function InlineCommentInput({ value={text} onChange={(e) => handleTextChange(e.target.value)} onKeyDown={handleKeyDown} - placeholder={isMobile ? t('inlineComment.input.placeholderShort') : t('inlineComment.input.placeholder')} + placeholder={isMobile + ? t('inlineComment.input.placeholderShort') + : t('inlineComment.input.placeholder', { shortcut: saveShortcut })} outerClassName="rounded-[var(--radius-xl)] bg-[var(--surface-subtle)] ring-1 ring-inset ring-border/60 focus-within:ring-2 focus-within:ring-[var(--interactive-focus-ring)]" className="min-h-[80px] px-3 py-2.5 text-sm resize-y" /> diff --git a/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx b/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx index 73d76470..820fde9d 100644 --- a/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx +++ b/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx @@ -64,6 +64,7 @@ import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs'; import type { TerminalShellOption } from '@/lib/api/types'; import { isTerminalShell } from '@/lib/terminalShell'; import { subscribeRuntimeEndpointChanged } from '@/lib/runtime-switch'; +import { formatShortcutForDisplay } from '@/lib/shortcuts'; interface Option { id: T; @@ -1543,7 +1544,10 @@ export const OpenChamberVisualSettings: React.FC label={t('settings.openchamber.visual.field.terminalQuickKeys')} ariaLabel={t('settings.openchamber.visual.field.terminalQuickKeysAria')} settingsItem="appearance.terminal-quick-keys" - info={t('settings.openchamber.visual.field.terminalQuickKeysTooltip')} + info={t('settings.openchamber.visual.field.terminalQuickKeysTooltip', { + control: formatShortcutForDisplay('ctrl'), + alt: formatShortcutForDisplay('alt'), + })} /> )} {showTerminalShellSetting && ( diff --git a/packages/ui/src/components/session/DirectoryExplorerDialog.tsx b/packages/ui/src/components/session/DirectoryExplorerDialog.tsx index 81d998a4..0035e44b 100644 --- a/packages/ui/src/components/session/DirectoryExplorerDialog.tsx +++ b/packages/ui/src/components/session/DirectoryExplorerDialog.tsx @@ -22,6 +22,7 @@ import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel'; import { Icon } from "@/components/icon/Icon"; import { opencodeClient } from '@/lib/opencode/client'; import { useI18n } from '@/lib/i18n'; +import { formatShortcutForDisplay } from '@/lib/shortcuts'; interface DirectoryExplorerDialogProps { open: boolean; @@ -338,9 +339,7 @@ export const DirectoryExplorerDialog: React.FC = ( const hasHighlightedBrowseItem = Boolean( highlightedRow && (highlightedRow.type === 'up' || (highlightedRow.type === 'directory' && !highlightedRow.disabled)) ); - const submitModifierLabel = typeof navigator !== 'undefined' && /Mac|iPhone|iPad/.test(navigator.platform) - ? '⌘' - : 'Ctrl'; + const submitModifierLabel = formatShortcutForDisplay('mod'); const submitActionLabel = isAlreadyAdded ? t('directoryExplorerDialog.actions.alreadyAdded') : isCloneMode diff --git a/packages/ui/src/components/ui/HelpDialog.tsx b/packages/ui/src/components/ui/HelpDialog.tsx index 7ce7df29..6080ac29 100644 --- a/packages/ui/src/components/ui/HelpDialog.tsx +++ b/packages/ui/src/components/ui/HelpDialog.tsx @@ -11,7 +11,6 @@ import { useUIStore } from "@/stores/useUIStore"; import { getEffectiveShortcutCombo, getShortcutAction, - getModifierLabel, formatShortcutForDisplay, type ShortcutActionId, } from "@/lib/shortcuts"; @@ -44,7 +43,6 @@ export const HelpDialog: React.FC = () => { const isHelpDialogOpen = useUIStore((state) => state.isHelpDialogOpen); const setHelpDialogOpen = useUIStore((state) => state.setHelpDialogOpen); const shortcutOverrides = useUIStore((state) => state.shortcutOverrides); - const mod = getModifierLabel(); const isVSCode = isVSCodeRuntime(); const shortcuts: ShortcutSection[] = [ @@ -104,7 +102,7 @@ export const HelpDialog: React.FC = () => { keys: '', }, { - keys: [`Shift + Alt + ${mod} + N`], + keys: [formatShortcutForDisplay('mod+shift+alt+n')], descriptionKey: "helpDialog.item.newWindow", icon: "window", }, @@ -195,7 +193,7 @@ export const HelpDialog: React.FC = () => { keys: '', }, { - keys: [`${mod} + 1...0`], + keys: [`${formatShortcutForDisplay('mod')} + 1...0`], descriptionKey: "helpDialog.item.switchContextSurface", icon: "layout-right", }, diff --git a/packages/ui/src/components/views/FilesView.tsx b/packages/ui/src/components/views/FilesView.tsx index 4de4f8f2..8ac76a40 100644 --- a/packages/ui/src/components/views/FilesView.tsx +++ b/packages/ui/src/components/views/FilesView.tsx @@ -53,7 +53,7 @@ import { getOutsideFileGrant } from '@/lib/outsideFileGrants'; import { DiagramEditor } from '@/components/diagram'; import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs'; import { useKeybind, useKeybinds } from '@/hooks/useKeybind'; -import { getModifierLabel } from '@/lib/shortcuts'; +import { formatShortcutForDisplay, getEffectiveShortcutCombo } from '@/lib/shortcuts'; import { EditorView } from '@codemirror/view'; import type { Extension } from '@codemirror/state'; import { useThemeSystem } from '@/contexts/useThemeSystem'; @@ -3167,6 +3167,7 @@ export const FilesView: React.FC = ({ mode = 'full' }) => { } const docked = layout === 'docked'; + const saveShortcut = formatShortcutForDisplay(getEffectiveShortcutCombo('save_file')); const wrapperCls = docked ? 'pointer-events-auto flex flex-wrap items-center gap-1' : 'pointer-events-auto flex items-center gap-1 rounded-lg border border-[var(--interactive-border)] bg-[var(--surface-elevated)] p-1 shadow-sm'; @@ -3196,14 +3197,14 @@ export const FilesView: React.FC = ({ mode = 'full' }) => { {t('filesView.editor.saved')} - ) : isDirty ? withTooltip(t(autoSaveEnabled ? 'filesView.editor.saveNowTitle' : 'filesView.editor.saveNowManualTitle', { shortcut: `${getModifierLabel()}+S` }), + ) : isDirty ? withTooltip(t(autoSaveEnabled ? 'filesView.editor.saveNowTitle' : 'filesView.editor.saveNowManualTitle', { shortcut: saveShortcut }), void saveDraft()} className="h-6 gap-1 px-1 text-muted-foreground opacity-80 hover:bg-transparent hover:opacity-100 focus-visible:bg-transparent active:bg-transparent" - title={t(autoSaveEnabled ? 'filesView.editor.saveNowTitle' : 'filesView.editor.saveNowManualTitle', { shortcut: `${getModifierLabel()}+S` })} - aria-label={t('filesView.editor.saveAria', { shortcut: `${getModifierLabel()}+S` })} + title={t(autoSaveEnabled ? 'filesView.editor.saveNowTitle' : 'filesView.editor.saveNowManualTitle', { shortcut: saveShortcut })} + aria-label={t('filesView.editor.saveAria', { shortcut: saveShortcut })} > diff --git a/packages/ui/src/components/views/SettingsView.tsx b/packages/ui/src/components/views/SettingsView.tsx index 325e73c3..9d706cc1 100644 --- a/packages/ui/src/components/views/SettingsView.tsx +++ b/packages/ui/src/components/views/SettingsView.tsx @@ -1,6 +1,9 @@ import React from 'react'; import { cn } from '@/lib/utils'; -import { getModifierLabel } from '@/lib/shortcuts'; +import { + formatShortcutForDisplay, + getEffectiveShortcutCombo, +} from '@/lib/shortcuts'; import { useUIStore } from '@/stores/useUIStore'; import { useProjectsStore } from '@/stores/useProjectsStore'; import { useAgentsStore } from '@/stores/useAgentsStore'; @@ -240,6 +243,7 @@ export const SettingsView: React.FC = ({ onClose, forceMobile const settingsPageRaw = useUIStore((state) => state.settingsPage); const isSettingsDialogOpen = useUIStore((state) => state.isSettingsDialogOpen); const setSettingsPage = useUIStore((state) => state.setSettingsPage); + const openSettingsShortcutOverride = useUIStore((state) => state.shortcutOverrides.open_settings); const settingsSlug = resolveSettingsSlug(settingsPageRaw); const [mobileStage, setMobileStage] = React.useState(initialMobileStage); @@ -751,7 +755,15 @@ export const SettingsView: React.FC = ({ onClose, forceMobile : showBackButton ? t('settings.view.actions.backToSettings') : t('settings.view.actions.closeSettings'); - const shortcutKey = getModifierLabel(); + const openSettingsCombo = getEffectiveShortcutCombo( + 'open_settings', + openSettingsShortcutOverride === undefined ? undefined : { open_settings: openSettingsShortcutOverride }, + ); + const closeSettingsTitle = openSettingsCombo + ? t('settings.view.actions.closeSettingsWithShortcut', { + shortcut: formatShortcutForDisplay(openSettingsCombo), + }) + : t('settings.view.actions.closeSettings'); const pushMobileSplitDetailHistory = React.useCallback((slug: SettingsPageSlug) => { if (typeof window === 'undefined' || runtimeCtx.isVSCode) { @@ -1119,7 +1131,7 @@ export const SettingsView: React.FC = ({ onClose, forceMobile type="button" onClick={onClose} aria-label={t('settings.view.actions.closeSettings')} - title={t('settings.view.actions.closeSettingsWithShortcut', { shortcut: shortcutKey })} + title={closeSettingsTitle} className="inline-flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg p-2 text-muted-foreground hover:text-foreground hover:bg-interactive-hover/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary" > @@ -1147,7 +1159,7 @@ export const SettingsView: React.FC = ({ onClose, forceMobile type="button" onClick={onClose} aria-label={t('settings.view.actions.closeSettings')} - title={t('settings.view.actions.closeSettingsWithShortcut', { shortcut: shortcutKey })} + title={closeSettingsTitle} className="inline-flex h-7 w-7 items-center justify-center rounded-md p-0.5 text-muted-foreground hover:text-foreground hover:bg-interactive-hover/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary" > diff --git a/packages/ui/src/components/views/TerminalView.tsx b/packages/ui/src/components/views/TerminalView.tsx index c97f608e..fce15637 100644 --- a/packages/ui/src/components/views/TerminalView.tsx +++ b/packages/ui/src/components/views/TerminalView.tsx @@ -21,6 +21,7 @@ import { useI18n } from '@/lib/i18n'; import { PROJECT_ACTION_ICON_MAP, type ProjectActionIconKey } from '@/lib/projectActions'; import { useInlineCommentDraftStore } from '@/stores/useInlineCommentDraftStore'; import { applyTerminalModifier, terminalControlCharacter, terminalSequenceForKey, type TerminalModifier as Modifier, type TerminalQuickKey as MobileKey } from '@/lib/terminalInput'; +import { formatShortcutForDisplay } from '@/lib/shortcuts'; type TerminalViewProps = { visible?: boolean; @@ -924,7 +925,7 @@ export const TerminalView: React.FC = ({ visible }) => { onClick={() => handleModifierToggle('ctrl')} disabled={quickKeysDisabled} > - {t('terminalView.quickKeys.controlLabel')} + {formatShortcutForDisplay('ctrl')} {t('terminalView.quickKeys.controlModifierAria')} = ({ visible }) => { onClick={() => handleModifierToggle('alt')} disabled={quickKeysDisabled} > - {t('terminalView.quickKeys.altLabel')} + {formatShortcutForDisplay('alt')} {t('terminalView.quickKeys.altModifierAria')} = { "rightSidebar.contextNotesTodo.toast.planImported": "Plan importado", "rightSidebar.contextNotesTodo.toast.readPlanFileFailed": "No se pudo leer el archivo del plan", "inlineComment.range.lines": "Líneas {start}-{end}", - "inlineComment.input.placeholder": "Añadir un comentario... (Cmd+Enter para guardar)", + "inlineComment.input.placeholder": "Añadir un comentario... ({shortcut} para guardar)", "inlineComment.input.placeholderShort": "Añadir un comentario...", "inlineComment.actions.cancel": "Cancelar", "inlineComment.actions.save": "Guardar", diff --git a/packages/ui/src/lib/i18n/messages/fr.settings.ts b/packages/ui/src/lib/i18n/messages/fr.settings.ts index 064ab90f..fb1d6ae4 100644 --- a/packages/ui/src/lib/i18n/messages/fr.settings.ts +++ b/packages/ui/src/lib/i18n/messages/fr.settings.ts @@ -43,7 +43,7 @@ export const settingsDict = { 'settings.view.actions.backToSettings': 'Retour aux paramètres', 'settings.view.actions.closeSettings': 'Fermer les paramètres', 'settings.view.actions.openSectionList': 'Ouvrir la liste des sections', - 'settings.view.actions.closeSettingsWithShortcut': 'Fermer les paramètres ({shortcut}+,)', + 'settings.view.actions.closeSettingsWithShortcut': 'Fermer les paramètres ({shortcut})', 'settings.view.actions.back': 'Retour', 'settings.view.actions.resizeNavigation': 'Redimensionner la navigation dans les paramètres', 'settings.page.projects.title': 'Projets', @@ -1822,7 +1822,7 @@ export const settingsDict = { 'settings.openchamber.visual.actions.resetInputBarOffsetAria': 'Réinitialiser le décalage de la barre d\'entrée', 'settings.openchamber.visual.field.terminalQuickKeysAria': 'Touches rapides du terminal', 'settings.openchamber.visual.field.terminalQuickKeys': 'Touches rapides du terminal', - 'settings.openchamber.visual.field.terminalQuickKeysTooltip': 'Afficher Esc, Ctrl, Flèches dans la vue du terminal', + 'settings.openchamber.visual.field.terminalQuickKeysTooltip': 'Afficher Échap, {control}, {alt} et les flèches dans la vue du terminal', 'settings.openchamber.visual.field.activityDefaultModeAria': 'Mode d\'activité par défaut : {option}', 'settings.openchamber.visual.field.showExpandedBashToolsAria': 'Afficher les outils bash étendus', 'settings.openchamber.visual.field.showExpandedEditToolsAria': 'Afficher les outils d\'édition étendus', diff --git a/packages/ui/src/lib/i18n/messages/fr.ts b/packages/ui/src/lib/i18n/messages/fr.ts index a94c8ff7..82b01cc4 100644 --- a/packages/ui/src/lib/i18n/messages/fr.ts +++ b/packages/ui/src/lib/i18n/messages/fr.ts @@ -1341,7 +1341,7 @@ export const dict = { 'rightSidebar.contextNotesTodo.toast.planImported': 'Forfait importé', 'rightSidebar.contextNotesTodo.toast.readPlanFileFailed': 'Échec de la lecture du fichier de plan', 'inlineComment.range.lines': 'Lignes {start}-{end}', - 'inlineComment.input.placeholder': 'Ajouter un commentaire... (Cmd+Entrée pour enregistrer)', + 'inlineComment.input.placeholder': 'Ajouter un commentaire... ({shortcut} pour enregistrer)', 'inlineComment.actions.cancel': 'Annuler', 'inlineComment.actions.save': 'Sauvegarder', 'inlineComment.actions.comment': 'Commentaire', diff --git a/packages/ui/src/lib/i18n/messages/ja.settings.ts b/packages/ui/src/lib/i18n/messages/ja.settings.ts index 668e643d..edcd61cf 100644 --- a/packages/ui/src/lib/i18n/messages/ja.settings.ts +++ b/packages/ui/src/lib/i18n/messages/ja.settings.ts @@ -43,7 +43,7 @@ export const settingsDict = { 'settings.view.actions.backToSettings': '設定に戻る', 'settings.view.actions.closeSettings': '設定を閉じる', 'settings.view.actions.openSectionList': 'セクション一覧を開く', - 'settings.view.actions.closeSettingsWithShortcut': '設定を閉じる ({shortcut}+,)', + 'settings.view.actions.closeSettingsWithShortcut': '設定を閉じる ({shortcut})', 'settings.view.actions.back': '戻る', 'settings.view.actions.resizeNavigation': '設定ナビゲーションのサイズ変更', 'settings.view.search.placeholder': '設定を検索', @@ -1947,7 +1947,7 @@ export const settingsDict = { 'settings.openchamber.visual.actions.resetInputBarOffsetAria': '入力バーオフセットをリセット', 'settings.openchamber.visual.field.terminalQuickKeysAria': 'ターミナルクイックキー', 'settings.openchamber.visual.field.terminalQuickKeys': 'ターミナルクイックキー', - 'settings.openchamber.visual.field.terminalQuickKeysTooltip': 'ターミナルビューに Esc、Ctrl、矢印を表示', + 'settings.openchamber.visual.field.terminalQuickKeysTooltip': 'ターミナルビューに Esc、{control}、{alt}、矢印キーを表示', 'settings.openchamber.visual.field.fileEditorKeymap': 'ファイルエディターキーマップ', 'settings.openchamber.visual.option.fileEditorKeymap.default': 'デフォルト', 'settings.openchamber.visual.option.fileEditorKeymap.vim': 'Vim', diff --git a/packages/ui/src/lib/i18n/messages/ja.ts b/packages/ui/src/lib/i18n/messages/ja.ts index a135d9e5..33599091 100644 --- a/packages/ui/src/lib/i18n/messages/ja.ts +++ b/packages/ui/src/lib/i18n/messages/ja.ts @@ -1517,7 +1517,7 @@ export const dict: Record = { 'rightSidebar.contextNotesTodo.toast.planImported': '計画をインポートしました', 'rightSidebar.contextNotesTodo.toast.readPlanFileFailed': '計画ファイルの読み込みに失敗しました', 'inlineComment.range.lines': '{start}行目~{end}行目', - 'inlineComment.input.placeholder': 'コメントを追加...(Cmd+Enterで保存)', + 'inlineComment.input.placeholder': 'コメントを追加...({shortcut}で保存)', 'inlineComment.input.placeholderShort': 'コメントを追加...', 'inlineComment.actions.cancel': 'キャンセル', 'inlineComment.actions.save': '保存', diff --git a/packages/ui/src/lib/i18n/messages/ko.settings.ts b/packages/ui/src/lib/i18n/messages/ko.settings.ts index d98e2e53..b71963ca 100644 --- a/packages/ui/src/lib/i18n/messages/ko.settings.ts +++ b/packages/ui/src/lib/i18n/messages/ko.settings.ts @@ -43,7 +43,7 @@ export const settingsDict = { 'settings.view.actions.backToSettings': '설정으로 돌아가기', 'settings.view.actions.closeSettings': '설정 닫기', 'settings.view.actions.openSectionList': '섹션 목록 열기', - 'settings.view.actions.closeSettingsWithShortcut': '설정 닫기 ({shortcut}+,)', + 'settings.view.actions.closeSettingsWithShortcut': '설정 닫기 ({shortcut})', 'settings.view.actions.back': '뒤로', 'settings.view.actions.resizeNavigation': '설정 내비게이션 크기 조정', 'settings.view.search.placeholder': '설정 검색', @@ -1914,7 +1914,7 @@ export const settingsDict = { 'settings.openchamber.visual.actions.resetInputBarOffsetAria': '입력 바 오프셋 초기화', 'settings.openchamber.visual.field.terminalQuickKeysAria': '터미널 빠른 키', 'settings.openchamber.visual.field.terminalQuickKeys': '터미널 빠른 키', - 'settings.openchamber.visual.field.terminalQuickKeysTooltip': '터미널 보기에서 Esc, Ctrl, 화살표를 표시합니다', + 'settings.openchamber.visual.field.terminalQuickKeysTooltip': '터미널 보기에서 Esc, {control}, {alt}, 화살표 키를 표시합니다', 'settings.openchamber.visual.field.fileEditorKeymap': '파일 편집기 키맵', 'settings.openchamber.visual.option.fileEditorKeymap.default': '기본값', 'settings.openchamber.visual.option.fileEditorKeymap.vim': 'Vim', diff --git a/packages/ui/src/lib/i18n/messages/ko.ts b/packages/ui/src/lib/i18n/messages/ko.ts index 84c41bcc..45a8d859 100644 --- a/packages/ui/src/lib/i18n/messages/ko.ts +++ b/packages/ui/src/lib/i18n/messages/ko.ts @@ -1524,7 +1524,7 @@ export const dict: Record = { 'rightSidebar.contextNotesTodo.toast.planImported': '플랜 가져옴', 'rightSidebar.contextNotesTodo.toast.readPlanFileFailed': '플랜 파일 읽기 실패', 'inlineComment.range.lines': '줄 {start}-{end}', - 'inlineComment.input.placeholder': '댓글 추가… (Cmd+Enter로 저장)', + 'inlineComment.input.placeholder': '댓글 추가… ({shortcut}로 저장)', 'inlineComment.input.placeholderShort': '댓글 추가…', 'inlineComment.actions.cancel': '취소', 'inlineComment.actions.save': '저장', diff --git a/packages/ui/src/lib/i18n/messages/pl.settings.ts b/packages/ui/src/lib/i18n/messages/pl.settings.ts index 0b10ccb3..b35fdd0b 100644 --- a/packages/ui/src/lib/i18n/messages/pl.settings.ts +++ b/packages/ui/src/lib/i18n/messages/pl.settings.ts @@ -1109,7 +1109,7 @@ export const settingsDict = { 'settings.openchamber.visual.field.editorFontSize': 'Rozmiar czcionki edytora', 'settings.openchamber.visual.field.terminalQuickKeys': 'Szybkie klawisze terminala', 'settings.openchamber.visual.field.terminalQuickKeysAria': 'Szybkie klawisze terminala', - 'settings.openchamber.visual.field.terminalQuickKeysTooltip': 'Pokaż Esc, Ctrl i strzałki w widoku terminala', + 'settings.openchamber.visual.field.terminalQuickKeysTooltip': 'Pokaż Esc, {control}, {alt} i klawisze strzałek w widoku terminala', 'settings.openchamber.visual.field.fileEditorKeymap': 'Mapa klawiszy edytora plików', 'settings.openchamber.visual.option.fileEditorKeymap.default': 'Domyślna', 'settings.openchamber.visual.option.fileEditorKeymap.vim': 'Vim', @@ -2004,7 +2004,7 @@ export const settingsDict = { 'settings.view.actions.back': 'Wstecz', 'settings.view.actions.backToSettings': 'Powrót do ustawień', 'settings.view.actions.closeSettings': 'Zamknij ustawienia', - 'settings.view.actions.closeSettingsWithShortcut': 'Zamknij ustawienia ({shortcut}+,)', + 'settings.view.actions.closeSettingsWithShortcut': 'Zamknij ustawienia ({shortcut})', 'settings.view.actions.openSectionList': 'Otwórz listę sekcji', 'settings.view.actions.reloadOpenCode': 'Przeładuj OpenCode', 'settings.view.actions.reloadOpenCodeTooltip': 'Uruchom ponownie OpenCode i przeładuj jego konfigurację.', diff --git a/packages/ui/src/lib/i18n/messages/pl.ts b/packages/ui/src/lib/i18n/messages/pl.ts index 3e1cb4e4..d8bbeff1 100644 --- a/packages/ui/src/lib/i18n/messages/pl.ts +++ b/packages/ui/src/lib/i18n/messages/pl.ts @@ -2364,7 +2364,7 @@ export const dict: Record = { 'inlineComment.actions.save': 'Zapisz', 'inlineComment.actions.showLess': 'Show less', 'inlineComment.actions.showMore': 'Show more', - 'inlineComment.input.placeholder': 'Add a comment... (Cmd+Enter to save)', + 'inlineComment.input.placeholder': 'Dodaj komentarz... ({shortcut}, aby zapisać)', 'inlineComment.input.placeholderShort': 'Dodaj komentarz...', 'inlineComment.range.lines': 'Lines {start}-{end}', 'inlineComment.toast.selectSessionToSave': 'Select a session to save comment', diff --git a/packages/ui/src/lib/i18n/messages/pt-BR.settings.ts b/packages/ui/src/lib/i18n/messages/pt-BR.settings.ts index d9283647..75f70a7f 100644 --- a/packages/ui/src/lib/i18n/messages/pt-BR.settings.ts +++ b/packages/ui/src/lib/i18n/messages/pt-BR.settings.ts @@ -43,7 +43,7 @@ export const settingsDict = { "settings.view.actions.backToSettings": "Voltar às configurações", "settings.view.actions.closeSettings": "Fechar configurações", "settings.view.actions.openSectionList": "Abrir lista de seções", - "settings.view.actions.closeSettingsWithShortcut": "Fechar configurações ({shortcut}+,)", + "settings.view.actions.closeSettingsWithShortcut": "Fechar configurações ({shortcut})", "settings.view.actions.back": "Voltar", "settings.view.actions.resizeNavigation": "Ajustar tamanho da navegação", "settings.view.search.placeholder": "Buscar configurações", @@ -1914,7 +1914,7 @@ export const settingsDict = { "settings.openchamber.visual.actions.resetInputBarOffsetAria": "Redefinir deslocamento da barra de entrada", "settings.openchamber.visual.field.terminalQuickKeysAria": "Teclas rápidas do terminal", "settings.openchamber.visual.field.terminalQuickKeys": "Teclas rápidas do terminal", - "settings.openchamber.visual.field.terminalQuickKeysTooltip": "Mostrar Esc, Ctrl e flechas na vista do terminal", + "settings.openchamber.visual.field.terminalQuickKeysTooltip": "Mostrar Esc, {control}, {alt} e as setas na vista do terminal", "settings.openchamber.visual.field.fileEditorKeymap": "Mapa de teclas do editor de arquivos", "settings.openchamber.visual.option.fileEditorKeymap.default": "Padrão", "settings.openchamber.visual.option.fileEditorKeymap.vim": "Vim", diff --git a/packages/ui/src/lib/i18n/messages/pt-BR.ts b/packages/ui/src/lib/i18n/messages/pt-BR.ts index 333666ce..b03d7a8b 100644 --- a/packages/ui/src/lib/i18n/messages/pt-BR.ts +++ b/packages/ui/src/lib/i18n/messages/pt-BR.ts @@ -1499,7 +1499,7 @@ export const dict: Record = { "rightSidebar.contextNotesTodo.toast.planImported": "Plano importado", "rightSidebar.contextNotesTodo.toast.readPlanFileFailed": "Não foi possível ler o arquivo do plano", "inlineComment.range.lines": "Linhas {start}-{end}", - "inlineComment.input.placeholder": "Adicionar um comentário... (Cmd+Enter para salvar)", + "inlineComment.input.placeholder": "Adicionar um comentário... ({shortcut} para salvar)", "inlineComment.input.placeholderShort": "Adicionar um comentário...", "inlineComment.actions.cancel": "Cancelar", "inlineComment.actions.save": "Salvar", diff --git a/packages/ui/src/lib/i18n/messages/uk.settings.ts b/packages/ui/src/lib/i18n/messages/uk.settings.ts index c443f195..99f64d8d 100644 --- a/packages/ui/src/lib/i18n/messages/uk.settings.ts +++ b/packages/ui/src/lib/i18n/messages/uk.settings.ts @@ -43,7 +43,7 @@ export const settingsDict = { "settings.view.actions.backToSettings": "Назад до налаштувань", "settings.view.actions.closeSettings": "Закрити налаштування", "settings.view.actions.openSectionList": "Відкрити список розділів", - "settings.view.actions.closeSettingsWithShortcut": "Закрити налаштування ({shortcut}+,)", + "settings.view.actions.closeSettingsWithShortcut": "Закрити налаштування ({shortcut})", "settings.view.actions.back": "Назад", "settings.view.actions.resizeNavigation": "Змінити розмір навігації налаштувань", "settings.view.search.placeholder": "Пошук налаштувань", @@ -1914,7 +1914,7 @@ export const settingsDict = { "settings.openchamber.visual.actions.resetInputBarOffsetAria": "Скинути зміщення панелі вводу", "settings.openchamber.visual.field.terminalQuickKeysAria": "Швидкі клавіші терміналу", "settings.openchamber.visual.field.terminalQuickKeys": "Швидкі клавіші терміналу", - "settings.openchamber.visual.field.terminalQuickKeysTooltip": "Показати Esc, Ctrl, стрілки в поданні терміналу", + "settings.openchamber.visual.field.terminalQuickKeysTooltip": "Показувати Esc, {control}, {alt} і клавіші зі стрілками в поданні термінала", "settings.openchamber.visual.field.fileEditorKeymap": "Розкладка клавіш редактора файлів", "settings.openchamber.visual.option.fileEditorKeymap.default": "Типова", "settings.openchamber.visual.option.fileEditorKeymap.vim": "Vim", diff --git a/packages/ui/src/lib/i18n/messages/uk.ts b/packages/ui/src/lib/i18n/messages/uk.ts index 2680f0c3..2a6775fd 100644 --- a/packages/ui/src/lib/i18n/messages/uk.ts +++ b/packages/ui/src/lib/i18n/messages/uk.ts @@ -1499,7 +1499,7 @@ export const dict: Record = { "rightSidebar.contextNotesTodo.toast.planImported": "План імпортовано", "rightSidebar.contextNotesTodo.toast.readPlanFileFailed": "Не вдалося прочитати файл плану", "inlineComment.range.lines": "Рядки {start}-{end}", - "inlineComment.input.placeholder": "Додайте коментар... (Cmd+Enter, щоб зберегти)", + "inlineComment.input.placeholder": "Додайте коментар... ({shortcut}, щоб зберегти)", "inlineComment.input.placeholderShort": "Додайте коментар...", "inlineComment.actions.cancel": "Скасувати", "inlineComment.actions.save": "Зберегти", diff --git a/packages/ui/src/lib/i18n/messages/zh-CN.settings.ts b/packages/ui/src/lib/i18n/messages/zh-CN.settings.ts index 4ded7fef..94f49f81 100644 --- a/packages/ui/src/lib/i18n/messages/zh-CN.settings.ts +++ b/packages/ui/src/lib/i18n/messages/zh-CN.settings.ts @@ -43,7 +43,7 @@ export const settingsDict = { 'settings.view.actions.backToSettings': '返回设置', 'settings.view.actions.closeSettings': '关闭设置', 'settings.view.actions.openSectionList': '打开分组列表', - 'settings.view.actions.closeSettingsWithShortcut': '关闭设置({shortcut}+,)', + 'settings.view.actions.closeSettingsWithShortcut': '关闭设置({shortcut})', 'settings.view.actions.back': '返回', 'settings.view.actions.resizeNavigation': '调整设置导航宽度', 'settings.view.search.placeholder': '搜索设置', @@ -1914,7 +1914,7 @@ export const settingsDict = { 'settings.openchamber.visual.actions.resetInputBarOffsetAria': '重置输入栏偏移', 'settings.openchamber.visual.field.terminalQuickKeysAria': '终端快捷键', 'settings.openchamber.visual.field.terminalQuickKeys': '终端快捷键', - 'settings.openchamber.visual.field.terminalQuickKeysTooltip': '在终端视图显示 Esc、Ctrl、方向键', + 'settings.openchamber.visual.field.terminalQuickKeysTooltip': '在终端视图显示 Esc、{control}、{alt} 和方向键', 'settings.openchamber.visual.field.fileEditorKeymap': '文件编辑器键位映射', 'settings.openchamber.visual.option.fileEditorKeymap.default': '默认', 'settings.openchamber.visual.option.fileEditorKeymap.vim': 'Vim', diff --git a/packages/ui/src/lib/i18n/messages/zh-CN.ts b/packages/ui/src/lib/i18n/messages/zh-CN.ts index 5a36af20..62914d69 100644 --- a/packages/ui/src/lib/i18n/messages/zh-CN.ts +++ b/packages/ui/src/lib/i18n/messages/zh-CN.ts @@ -1487,7 +1487,7 @@ export const dict: Record = { 'rightSidebar.contextNotesTodo.toast.planImported': '计划已导入', 'rightSidebar.contextNotesTodo.toast.readPlanFileFailed': '读取计划文件失败', 'inlineComment.range.lines': '行 {start}-{end}', - 'inlineComment.input.placeholder': '添加评论...(Cmd+Enter 保存)', + 'inlineComment.input.placeholder': '添加评论...({shortcut} 保存)', 'inlineComment.input.placeholderShort': '添加评论…', 'inlineComment.actions.cancel': '取消', 'inlineComment.actions.save': '保存', diff --git a/packages/ui/src/lib/i18n/messages/zh-TW.settings.ts b/packages/ui/src/lib/i18n/messages/zh-TW.settings.ts index d959d3bf..3381286e 100644 --- a/packages/ui/src/lib/i18n/messages/zh-TW.settings.ts +++ b/packages/ui/src/lib/i18n/messages/zh-TW.settings.ts @@ -41,7 +41,7 @@ 'settings.view.actions.backToSettings': '返回設定頁', 'settings.view.actions.closeSettings': '關閉設定', 'settings.view.actions.openSectionList': '開啟群組清單', - 'settings.view.actions.closeSettingsWithShortcut': '關閉設定({shortcut}+,)', + 'settings.view.actions.closeSettingsWithShortcut': '關閉設定({shortcut})', 'settings.view.actions.back': '返回', 'settings.view.actions.resizeNavigation': '調整設定導覽寬度', 'settings.view.search.placeholder': '搜尋設定', @@ -1820,7 +1820,7 @@ 'settings.openchamber.visual.actions.resetInputBarOffsetAria': '重設輸入列偏移', 'settings.openchamber.visual.field.terminalQuickKeysAria': '終端機快速鍵', 'settings.openchamber.visual.field.terminalQuickKeys': '終端機快速鍵', - 'settings.openchamber.visual.field.terminalQuickKeysTooltip': '在終端機檢視顯示 Esc、Ctrl、方向鍵', + 'settings.openchamber.visual.field.terminalQuickKeysTooltip': '在終端機檢視顯示 Esc、{control}、{alt} 和方向鍵', 'settings.openchamber.visual.field.fileEditorKeymap': '檔案編輯器鍵位映射', 'settings.openchamber.visual.option.fileEditorKeymap.default': '預設', 'settings.openchamber.visual.option.fileEditorKeymap.vim': 'Vim', diff --git a/packages/ui/src/lib/i18n/messages/zh-TW.ts b/packages/ui/src/lib/i18n/messages/zh-TW.ts index 846aa35c..257701f8 100644 --- a/packages/ui/src/lib/i18n/messages/zh-TW.ts +++ b/packages/ui/src/lib/i18n/messages/zh-TW.ts @@ -1497,7 +1497,7 @@ export const dict: Record = { 'rightSidebar.contextNotesTodo.toast.planImported': '計畫已匯入', 'rightSidebar.contextNotesTodo.toast.readPlanFileFailed': '讀取計畫檔案失敗', 'inlineComment.range.lines': '行 {start}-{end}', - 'inlineComment.input.placeholder': '新增留言...(Cmd+Enter 儲存)', + 'inlineComment.input.placeholder': '新增留言...({shortcut} 儲存)', 'inlineComment.input.placeholderShort': '新增留言…', 'inlineComment.actions.cancel': '取消', 'inlineComment.actions.save': '儲存', diff --git a/packages/ui/src/lib/shortcuts/DOCUMENTATION.md b/packages/ui/src/lib/shortcuts/DOCUMENTATION.md index 855ac7b8..e1623e59 100644 --- a/packages/ui/src/lib/shortcuts/DOCUMENTATION.md +++ b/packages/ui/src/lib/shortcuts/DOCUMENTATION.md @@ -25,7 +25,7 @@ Component interaction keys that are not application commands, such as list navig # Binding rules -Bindings remain persisted as `Record`. Each binding has one chord or at most two space-separated chords, such as `mod+s p`. `normalizeCombo`, `parseShortcut`, `formatShortcutForDisplay`, and `getShortcutConflict` provide the shared parsing and validation behavior. A single chord conflicts with a sequence sharing its first chord; sibling sequences are valid. +Bindings remain persisted as `Record`. Each binding has one chord or at most two space-separated chords, such as `mod+s p`. `mod` is the platform-neutral primary modifier (Command on macOS, Control elsewhere), while `alt` is the platform-neutral alternate modifier (Option on macOS, Alt elsewhere); `command`, `cmd`, `meta`, and `option` are accepted input aliases but normalize to those canonical tokens. `normalizeCombo`, `parseShortcut`, `formatShortcutForDisplay`, and `getShortcutConflict` provide the shared parsing and validation behavior. Display formatting uses macOS keyboard symbols (`⌘`, `⌥`, `⌃`, `⇧`) on macOS and named modifiers (`Ctrl`, `Alt`, `Shift`) elsewhere, including tooltip and accessible text consumers. A single chord conflicts with a sequence sharing its first chord; sibling sequences are valid. Contextual internal commands may deliberately share a sequence leader. The single-chord handler gets the first chance to handle the event; returning `false` lets the dispatcher start the sequence. The active file editor therefore owns `mod+s` for saving, while a mounted but unfocused editor yields `mod+s p`, `mod+s g`, and `mod+s l` to the draft target pickers and session list. diff --git a/packages/ui/src/lib/shortcuts/bindings.test.ts b/packages/ui/src/lib/shortcuts/bindings.test.ts index b16d9e84..be955841 100644 --- a/packages/ui/src/lib/shortcuts/bindings.test.ts +++ b/packages/ui/src/lib/shortcuts/bindings.test.ts @@ -106,3 +106,23 @@ describe('shortcut sequences', () => { expect(isRiskyBrowserShortcut('mod+s p')).toBe(true); }); }); + +describe('platform shortcut labels', () => { + test('normalizes Command and Option to platform-neutral modifiers', () => { + expect(normalizeCombo('command+option+n')).toBe('mod+alt+n'); + }); + + test('uses macOS modifier symbols', () => { + expect(formatShortcutForDisplay('mod+ctrl+shift+alt+n', 'Unassigned', 'macos')).toBe( + '⌘ + ⌃ + ⇧ + ⌥ + N', + ); + expect(formatShortcutForDisplay('alt', 'Unassigned', 'macos')).toBe('⌥'); + }); + + test('uses named modifiers on other platforms', () => { + expect(formatShortcutForDisplay('mod+shift+alt+n', 'Unassigned', 'other')).toBe( + 'Ctrl + Shift + Alt + N', + ); + expect(formatShortcutForDisplay('alt', 'Unassigned', 'other')).toBe('Alt'); + }); +}); diff --git a/packages/ui/src/lib/shortcuts/bindings.ts b/packages/ui/src/lib/shortcuts/bindings.ts index 6c53b2d1..d8265a98 100644 --- a/packages/ui/src/lib/shortcuts/bindings.ts +++ b/packages/ui/src/lib/shortcuts/bindings.ts @@ -3,6 +3,7 @@ import { isDesktopShell } from '@/lib/desktop'; import { isMacOS } from '@/lib/utils'; type ShortcutModifier = 'mod' | 'shift' | 'alt' | 'ctrl'; +type ShortcutDisplayPlatform = 'macos' | 'other'; type ShortcutKey = string; export type ShortcutCombo = string; @@ -30,11 +31,19 @@ const MODIFIER_KEY_MAP: Record = { command: 'mod', }; -const DISPLAY_LABEL_MAP: Record = { - mod: isMacOS() && isDesktopShell() ? '⌘' : 'Ctrl', - shift: '⇧', - alt: '⌥', - ctrl: '⌃', +const MODIFIER_LABELS: Record> = { + macos: { + mod: '⌘', + shift: '⇧', + alt: '⌥', + ctrl: '⌃', + }, + other: { + mod: 'Ctrl', + shift: 'Shift', + alt: 'Alt', + ctrl: 'Ctrl', + }, }; const KEY_LABEL_MAP: Record = { @@ -176,19 +185,31 @@ export function parseShortcut(combo: ShortcutCombo): ParsedShortcut | undefined }; } -export function formatShortcutForDisplay(combo: ShortcutCombo, unassignedLabel = 'Unassigned'): string { +function getShortcutDisplayPlatform(): ShortcutDisplayPlatform { + return isMacOS() ? 'macos' : 'other'; +} + +export function formatShortcutForDisplay( + combo: ShortcutCombo, + unassignedLabel = 'Unassigned', + platform = getShortcutDisplayPlatform(), +): string { if (isUnassignedShortcut(combo)) return unassignedLabel; const parsed = parseShortcut(combo); if (!parsed || parsed.chords.some((chord) => !chord.key && chord.modifiers.size === 0)) { return unassignedLabel; } - return parsed.chords.map(formatChordForDisplay).join(', '); + return parsed.chords.map((chord) => formatChordForDisplay(chord, platform)).join(', '); } -function formatChordForDisplay(parsed: ParsedShortcutChord): string { +function formatChordForDisplay( + parsed: ParsedShortcutChord, + platform: ShortcutDisplayPlatform, +): string { + const modifierLabels = MODIFIER_LABELS[platform]; const parts = MODIFIER_PRIORITY .filter((modifier) => parsed.modifiers.has(modifier)) - .map((modifier) => DISPLAY_LABEL_MAP[modifier]); + .map((modifier) => modifierLabels[modifier]); if (parsed.key) { parts.push(KEY_LABEL_MAP[parsed.key.toLowerCase()] || parsed.key.toUpperCase()); } @@ -308,7 +329,3 @@ export function eventMatchesShortcutPrefix( return !chord.key || Boolean(heldKeys?.has(chord.key.toLowerCase())); } - -export function getModifierLabel(): string { - return isMacOS() && isDesktopShell() ? '⌘' : 'Ctrl'; -} diff --git a/packages/ui/src/lib/shortcuts/index.ts b/packages/ui/src/lib/shortcuts/index.ts index 53f0f1b0..b75c3200 100644 --- a/packages/ui/src/lib/shortcuts/index.ts +++ b/packages/ui/src/lib/shortcuts/index.ts @@ -2,7 +2,6 @@ export { eventMatchesShortcut, eventMatchesShortcutPrefix, formatShortcutForDisplay, - getModifierLabel, getShortcutConflict, isRiskyBrowserShortcut, isShortcutPrefixHeld,