Files
tolaria/src/hooks/commands/localizeCommands.ts
2026-04-29 02:35:52 +02:00

163 lines
6.7 KiB
TypeScript

import { createTranslator, type AppLocale, type TranslationKey } from '../../lib/i18n'
import type { CommandAction, CommandGroup } from './types'
type Translate = ReturnType<typeof createTranslator>
type CommandLabeler = (command: CommandAction, t: Translate) => string
const GROUP_LABEL_KEYS = {
Navigation: 'command.group.navigation',
Note: 'command.group.note',
Git: 'command.group.git',
View: 'command.group.view',
Settings: 'command.group.settings',
} satisfies Record<CommandGroup, TranslationKey>
const STATIC_LABEL_KEYS: Partial<Record<string, TranslationKey>> = {
'search-notes': 'command.navigation.searchNotes',
'go-all': 'command.navigation.goAllNotes',
'go-archived': 'command.navigation.goArchived',
'go-changes': 'command.navigation.goChanges',
'go-pulse': 'command.navigation.goHistory',
'go-back': 'command.navigation.goBack',
'go-forward': 'command.navigation.goForward',
'go-inbox': 'command.navigation.goInbox',
'rename-folder': 'command.navigation.renameFolder',
'delete-folder': 'command.navigation.deleteFolder',
'filter-open': 'command.navigation.showOpenNotes',
'filter-archived': 'command.navigation.showArchivedNotes',
'create-note': 'command.note.newNote',
'create-type': 'command.note.newType',
'save-note': 'command.note.saveNote',
'find-in-note': 'command.note.findInNote',
'replace-in-note': 'command.note.replaceInNote',
'delete-note': 'command.note.deleteNote',
'restore-deleted-note': 'command.note.restoreDeleted',
'set-note-icon': 'command.note.setIcon',
'change-note-type': 'command.note.changeType',
'move-note-to-folder': 'command.note.moveToFolder',
'remove-note-icon': 'command.note.removeIcon',
'open-in-new-window': 'command.note.openNewWindow',
'initialize-git': 'command.git.initialize',
'commit-push': 'command.git.commitPush',
'add-remote': 'command.git.addRemote',
'git-pull': 'command.git.pull',
'resolve-conflicts': 'command.git.resolveConflicts',
'view-changes': 'command.git.viewChanges',
'view-editor': 'command.view.editorOnly',
'view-editor-list': 'command.view.editorNoteList',
'view-all': 'command.view.fullLayout',
'toggle-inspector': 'command.view.toggleProperties',
'toggle-diff': 'command.view.toggleDiff',
'toggle-raw-editor': 'command.view.toggleRaw',
'toggle-ai-panel': 'command.view.toggleAiPanel',
'new-ai-chat': 'command.view.newAiChat',
'toggle-backlinks': 'command.view.toggleBacklinks',
'zoom-reset': 'command.view.resetZoom',
'create-empty-vault': 'command.settings.createEmptyVault',
'open-vault': 'command.settings.openVault',
'remove-vault': 'command.settings.removeVault',
'restore-getting-started': 'command.settings.restoreGettingStarted',
'reload-vault': 'command.settings.reloadVault',
'repair-vault': 'command.settings.repairVault',
'use-light-mode': 'command.settings.useLightMode',
'use-dark-mode': 'command.settings.useDarkMode',
'toggle-gitignored-files-visibility': 'command.settings.toggleGitignoredFilesVisibility',
'open-ai-agents': 'command.ai.openAgents',
'restore-vault-ai-guidance': 'command.ai.restoreGuidance',
}
function stripKnownPrefix(label: string, prefix: string): string {
return label.startsWith(prefix) ? label.slice(prefix.length) : label
}
function parenthesizedSuffix(label: string): string | null {
return label.match(/\(([^)]+)\)$/)?.[1] ?? null
}
function localizeNoteStateCommand(command: CommandAction, t: Translate): string | null {
if (command.id === 'archive-note') {
return t(command.label === 'Unarchive Note' ? 'command.note.unarchiveNote' : 'command.note.archiveNote')
}
if (command.id === 'toggle-favorite') {
return t(command.label === 'Remove from Favorites' ? 'command.note.removeFavorite' : 'command.note.addFavorite')
}
if (command.id === 'toggle-organized') {
return t(command.label === 'Mark as Unorganized' ? 'command.note.markUnorganized' : 'command.note.markOrganized')
}
return null
}
function localizeColumnsCommand(command: CommandAction, t: Translate): string {
if (command.label === 'Customize All Notes columns') return t('noteList.properties.customizeAllColumns')
if (command.label === 'Customize Inbox columns') return t('noteList.properties.customizeInboxColumns')
return t('noteList.properties.customizeColumns')
}
const VIEW_STATE_LABELERS: Partial<Record<string, CommandLabeler>> = {
'toggle-note-layout': (command, t) => t(command.label === 'Use Left-Aligned Note Layout' ? 'command.view.leftLayout' : 'command.view.centerLayout'),
'zoom-in': (command, t) => t('command.view.zoomIn', { zoom: parenthesizedSuffix(command.label)?.replace('%', '') ?? '' }),
'zoom-out': (command, t) => t('command.view.zoomOut', { zoom: parenthesizedSuffix(command.label)?.replace('%', '') ?? '' }),
'customize-note-list-columns': localizeColumnsCommand,
}
function localizeViewStateCommand(command: CommandAction, t: Translate): string | null {
return VIEW_STATE_LABELERS[command.id]?.(command, t) ?? null
}
function localizeSettingsStateCommand(command: CommandAction, t: Translate): string | null {
if (command.id === 'install-mcp') {
return t(command.label === 'Manage External AI Tools…'
? 'command.settings.manageExternalAi'
: 'command.settings.setupExternalAi')
}
if (command.id === 'switch-default-ai-agent') {
const agent = parenthesizedSuffix(command.label)
return agent
? t('command.ai.switchDefaultWithAgent', { agent })
: t('command.ai.switchDefault')
}
if (command.id.startsWith('switch-ai-agent-')) {
return t('command.ai.switchToAgent', {
agent: stripKnownPrefix(command.label, 'Switch AI Agent to '),
})
}
return null
}
function localizeTypeCommand(command: CommandAction, t: Translate): string | null {
if (command.id.startsWith('new-') && command.group === 'Note') {
return t('command.note.newTypedNote', { type: stripKnownPrefix(command.label, 'New ') })
}
if (command.id.startsWith('list-') && command.group === 'Navigation') {
return t('command.navigation.listType', { type: stripKnownPrefix(command.label, 'List ') })
}
return null
}
export function localizeCommandGroup(group: CommandGroup, locale: AppLocale = 'en'): string {
return createTranslator(locale)(GROUP_LABEL_KEYS[group])
}
export function localizeCommandActions(commands: CommandAction[], locale: AppLocale = 'en'): CommandAction[] {
const t = createTranslator(locale)
return commands.map((command) => {
const key = STATIC_LABEL_KEYS[command.id]
const label = key
? t(key)
: localizeNoteStateCommand(command, t)
?? localizeViewStateCommand(command, t)
?? localizeSettingsStateCommand(command, t)
?? localizeTypeCommand(command, t)
?? command.label
return label === command.label ? command : { ...command, label }
})
}