From 9e4829606614a13807741087bcdc817b959ed2f9 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Mon, 28 Jul 2025 01:03:47 -0400 Subject: [PATCH 1/6] Add a UI for managing slash commands --- src/core/webview/webviewMessageHandler.ts | 166 ++++++++++++++ src/i18n/locales/en/common.json | 3 + src/shared/ExtensionMessage.ts | 2 + src/shared/WebviewMessage.ts | 4 + .../src/components/chat/ChatTextArea.tsx | 34 ++- .../src/components/chat/SlashCommandItem.tsx | 72 +++++++ .../src/components/chat/SlashCommandsList.tsx | 203 ++++++++++++++++++ .../components/chat/SlashCommandsPopover.tsx | 82 +++++++ webview-ui/src/i18n/locales/en/chat.json | 18 ++ 9 files changed, 583 insertions(+), 1 deletion(-) create mode 100644 webview-ui/src/components/chat/SlashCommandItem.tsx create mode 100644 webview-ui/src/components/chat/SlashCommandsList.tsx create mode 100644 webview-ui/src/components/chat/SlashCommandsPopover.tsx diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index da73c569201..7e25c5b5e8e 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -2365,6 +2365,7 @@ export const webviewMessageHandler = async ( const commandList = commands.map((command) => ({ name: command.name, source: command.source, + filePath: command.filePath, })) await provider.postMessageToWebview({ @@ -2381,5 +2382,170 @@ export const webviewMessageHandler = async ( } break } + case "openCommandFile": { + try { + if (message.text) { + const { getCommand } = await import("../../services/command/commands") + const command = await getCommand(provider.cwd || "", message.text) + + if (command && command.filePath) { + openFile(command.filePath) + } else { + vscode.window.showErrorMessage(t("common:errors.command_not_found", { name: message.text })) + } + } + } catch (error) { + provider.log( + `Error opening command file: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`, + ) + vscode.window.showErrorMessage(t("common:errors.open_command_file")) + } + break + } + case "deleteCommand": { + try { + if (message.text && message.values?.source) { + const { getCommand } = await import("../../services/command/commands") + const command = await getCommand(provider.cwd || "", message.text) + + if (command && command.filePath) { + // Delete the command file + await fs.unlink(command.filePath) + provider.log(`Deleted command file: ${command.filePath}`) + } else { + vscode.window.showErrorMessage(t("common:errors.command_not_found", { name: message.text })) + } + } + } catch (error) { + provider.log(`Error deleting command: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`) + vscode.window.showErrorMessage(t("common:errors.delete_command")) + } + break + } + case "createCommand": { + try { + const source = message.values?.source as "global" | "project" + const fileName = message.text // Custom filename from user input + + if (!source) { + provider.log("Missing source for createCommand") + break + } + + // Determine the commands directory based on source + let commandsDir: string + if (source === "global") { + const globalConfigDir = path.join(os.homedir(), ".roo") + commandsDir = path.join(globalConfigDir, "commands") + } else { + // Project commands + const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath + if (!workspaceRoot) { + vscode.window.showErrorMessage("No workspace folder found for project command") + break + } + commandsDir = path.join(workspaceRoot, ".roo", "commands") + } + + // Ensure the commands directory exists + await fs.mkdir(commandsDir, { recursive: true }) + + // Use provided filename or generate a unique one + let commandName: string + if (fileName && fileName.trim()) { + let cleanFileName = fileName.trim() + + // Strip leading slash if present + if (cleanFileName.startsWith("/")) { + cleanFileName = cleanFileName.substring(1) + } + + // Remove .md extension if present BEFORE slugification + if (cleanFileName.toLowerCase().endsWith(".md")) { + cleanFileName = cleanFileName.slice(0, -3) + } + + // Slugify the command name: lowercase, replace spaces with dashes, remove special characters + commandName = cleanFileName + .toLowerCase() + .replace(/\s+/g, "-") // Replace spaces with dashes + .replace(/[^a-z0-9-]/g, "") // Remove special characters except dashes + .replace(/-+/g, "-") // Replace multiple dashes with single dash + .replace(/^-|-$/g, "") // Remove leading/trailing dashes + + // Ensure we have a valid command name + if (!commandName || commandName.length === 0) { + commandName = "new-command" + } + } else { + // Generate a unique command name + commandName = "new-command" + let counter = 1 + let filePath = path.join(commandsDir, `${commandName}.md`) + + while ( + await fs + .access(filePath) + .then(() => true) + .catch(() => false) + ) { + commandName = `new-command-${counter}` + filePath = path.join(commandsDir, `${commandName}.md`) + counter++ + } + } + + const filePath = path.join(commandsDir, `${commandName}.md`) + + // Check if file already exists + if ( + await fs + .access(filePath) + .then(() => true) + .catch(() => false) + ) { + vscode.window.showErrorMessage(`Command "${commandName}" already exists`) + break + } + + // Create the command file with template content + const templateContent = "This is a new slash command. Edit this file to customize the command behavior." + + await fs.writeFile(filePath, templateContent, "utf8") + provider.log(`Created new command file: ${filePath}`) + + // Open the new file in the editor + openFile(filePath) + + // Refresh commands list + const { getCommands } = await import("../../services/command/commands") + const commands = await getCommands(provider.cwd || "") + const commandList = commands.map((command) => ({ + name: command.name, + source: command.source, + filePath: command.filePath, + })) + await provider.postMessageToWebview({ + type: "commands", + commands: commandList, + }) + } catch (error) { + provider.log(`Error creating command: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`) + vscode.window.showErrorMessage("Failed to create command") + } + break + } + + case "insertTextIntoTextarea": { + const text = message.text + if (text) { + // Send message to insert text into the chat textarea + await provider.postMessageToWebview({ + type: "insertTextIntoTextarea", + text: text, + }) + } + break + } } } diff --git a/src/i18n/locales/en/common.json b/src/i18n/locales/en/common.json index 4150e12f848..9d05612d5b2 100644 --- a/src/i18n/locales/en/common.json +++ b/src/i18n/locales/en/common.json @@ -71,6 +71,9 @@ "share_task_not_found": "Task not found or access denied.", "mode_import_failed": "Failed to import mode: {{error}}", "delete_rules_folder_failed": "Failed to delete rules folder: {{rulesFolderPath}}. Error: {{error}}", + "command_not_found": "Command '{{name}}' not found", + "open_command_file": "Failed to open command file", + "delete_command": "Failed to delete command", "claudeCode": { "processExited": "Claude Code process exited with code {{exitCode}}.", "errorOutput": "Error output: {{output}}", diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 816069f91f9..a592c3e885a 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -23,6 +23,7 @@ import type { MarketplaceItem } from "@roo-code/types" export interface Command { name: string source: "global" | "project" + filePath?: string } // Type for marketplace installed metadata @@ -116,6 +117,7 @@ export interface ExtensionMessage { | "showDeleteMessageDialog" | "showEditMessageDialog" | "commands" + | "insertTextIntoTextarea" text?: string payload?: any // Add a generic payload for now, can refine later action?: diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 1304e4c7d51..9c80bc1e7cf 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -202,6 +202,10 @@ export interface WebviewMessage { | "saveCodeIndexSettingsAtomic" | "requestCodeIndexSecretStatus" | "requestCommands" + | "openCommandFile" + | "deleteCommand" + | "createCommand" + | "insertTextIntoTextarea" text?: string editedMessageContent?: string tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "account" diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index e387732197e..f569d807914 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -28,6 +28,7 @@ import { MAX_IMAGES_PER_MESSAGE } from "./ChatView" import ContextMenu from "./ContextMenu" import { VolumeX, Image, WandSparkles, SendHorizontal } from "lucide-react" import { IndexingStatusBadge } from "./IndexingStatusBadge" +import { SlashCommandsPopover } from "./SlashCommandsPopover" import { cn } from "@/lib/utils" import { usePromptHistory } from "./hooks/usePromptHistory" import { EditModeControls } from "./EditModeControls" @@ -145,6 +146,36 @@ const ChatTextArea = forwardRef( } setIsEnhancingPrompt(false) + } else if (message.type === "insertTextIntoTextarea") { + if (message.text && textAreaRef.current) { + // Insert the command text at the current cursor position + const textarea = textAreaRef.current + const currentValue = inputValue + const cursorPos = textarea.selectionStart || 0 + + // Check if we need to add a space before the command + const textBefore = currentValue.slice(0, cursorPos) + const needsSpaceBefore = textBefore.length > 0 && !textBefore.endsWith(" ") + const prefix = needsSpaceBefore ? " " : "" + + // Insert the text at cursor position + const newValue = + currentValue.slice(0, cursorPos) + + prefix + + message.text + + " " + + currentValue.slice(cursorPos) + setInputValue(newValue) + + // Set cursor position after the inserted text + const newCursorPos = cursorPos + prefix.length + message.text.length + 1 + setTimeout(() => { + if (textAreaRef.current) { + textAreaRef.current.focus() + textAreaRef.current.setSelectionRange(newCursorPos, newCursorPos) + } + }, 0) + } } else if (message.type === "commitSearchResults") { const commits = message.commits.map((commit: any) => ({ type: ContextMenuOptionType.Git, @@ -165,7 +196,7 @@ const ChatTextArea = forwardRef( window.addEventListener("message", messageHandler) return () => window.removeEventListener("message", messageHandler) - }, [setInputValue, searchRequestId]) + }, [setInputValue, searchRequestId, inputValue]) const [isDraggingOver, setIsDraggingOver] = useState(false) const [textAreaBaseHeight, setTextAreaBaseHeight] = useState(undefined) @@ -897,6 +928,7 @@ const ChatTextArea = forwardRef( )} + + + + + + + + + ) +} diff --git a/webview-ui/src/components/chat/SlashCommandsList.tsx b/webview-ui/src/components/chat/SlashCommandsList.tsx new file mode 100644 index 00000000000..a80f4477223 --- /dev/null +++ b/webview-ui/src/components/chat/SlashCommandsList.tsx @@ -0,0 +1,203 @@ +import React, { useState } from "react" +import { Plus, Globe, Folder } from "lucide-react" + +import type { Command } from "@roo/ExtensionMessage" + +import { useAppTranslation } from "@/i18n/TranslationContext" +import { useExtensionState } from "@/context/ExtensionStateContext" +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + Button, +} from "@/components/ui" +import { vscode } from "@/utils/vscode" + +import { SlashCommandItem } from "./SlashCommandItem" + +interface SlashCommandsListProps { + commands: Command[] + onRefresh: () => void +} + +export const SlashCommandsList: React.FC = ({ commands, onRefresh }) => { + const { t } = useAppTranslation() + const { cwd } = useExtensionState() + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false) + const [commandToDelete, setCommandToDelete] = useState(null) + const [globalNewName, setGlobalNewName] = useState("") + const [workspaceNewName, setWorkspaceNewName] = useState("") + + // Check if we're in a workspace/project + const hasWorkspace = Boolean(cwd) + + const handleDeleteClick = (command: Command) => { + setCommandToDelete(command) + setDeleteDialogOpen(true) + } + + const handleDeleteConfirm = () => { + if (commandToDelete) { + vscode.postMessage({ + type: "deleteCommand", + text: commandToDelete.name, + values: { source: commandToDelete.source }, + }) + setDeleteDialogOpen(false) + setCommandToDelete(null) + // Refresh the commands list after deletion + setTimeout(onRefresh, 100) + } + } + + const handleDeleteCancel = () => { + setDeleteDialogOpen(false) + setCommandToDelete(null) + } + + const handleCreateCommand = (source: "global" | "project", name: string) => { + if (!name.trim()) return + + // Append .md if not already present + const fileName = name.trim().endsWith(".md") ? name.trim() : `${name.trim()}.md` + + vscode.postMessage({ + type: "createCommand", + text: fileName, + values: { source }, + }) + + // Clear the input and refresh + if (source === "global") { + setGlobalNewName("") + } else { + setWorkspaceNewName("") + } + setTimeout(onRefresh, 500) + } + + const handleCommandClick = (command: Command) => { + // Insert the command into the textarea + vscode.postMessage({ + type: "insertTextIntoTextarea", + text: `/${command.name}`, + }) + } + + // Group commands by source + const globalCommands = commands.filter((cmd) => cmd.source === "global") + const projectCommands = commands.filter((cmd) => cmd.source === "project") + + return ( + <> + {/* Commands list */} +
+
+ {/* Global Commands Section */} +
+ + {t("chat:slashCommands.globalCommands")} +
+ {globalCommands.map((command) => ( + + ))} + {/* New global command input */} +
+ setGlobalNewName(e.target.value)} + placeholder={t("chat:slashCommands.newGlobalCommandPlaceholder")} + className="flex-1 bg-transparent text-vscode-input-foreground placeholder-vscode-input-placeholderForeground border-none outline-none focus:outline-0 text-sm" + tabIndex={-1} + onKeyDown={(e) => { + if (e.key === "Enter") { + handleCreateCommand("global", globalNewName) + } + }} + /> + +
+ + {/* Workspace Commands Section - Only show if in a workspace */} + {hasWorkspace && ( + <> +
+ + {t("chat:slashCommands.workspaceCommands")} +
+ {projectCommands.map((command) => ( + + ))} + {/* New workspace command input */} +
+ setWorkspaceNewName(e.target.value)} + placeholder={t("chat:slashCommands.newWorkspaceCommandPlaceholder")} + className="flex-1 bg-transparent text-vscode-input-foreground placeholder-vscode-input-placeholderForeground border-none outline-none focus:outline-0 text-sm" + tabIndex={-1} + onKeyDown={(e) => { + if (e.key === "Enter") { + handleCreateCommand("project", workspaceNewName) + } + }} + /> + +
+ + )} +
+
+ + + + + {t("chat:slashCommands.deleteDialog.title")} + + {t("chat:slashCommands.deleteDialog.description", { name: commandToDelete?.name })} + + + + + {t("chat:slashCommands.deleteDialog.cancel")} + + + {t("chat:slashCommands.deleteDialog.confirm")} + + + + + + ) +} diff --git a/webview-ui/src/components/chat/SlashCommandsPopover.tsx b/webview-ui/src/components/chat/SlashCommandsPopover.tsx new file mode 100644 index 00000000000..fc17760fc18 --- /dev/null +++ b/webview-ui/src/components/chat/SlashCommandsPopover.tsx @@ -0,0 +1,82 @@ +import React, { useEffect, useState } from "react" +import { Zap } from "lucide-react" + +import { useAppTranslation } from "@/i18n/TranslationContext" +import { useExtensionState } from "@/context/ExtensionStateContext" +import { Button, Popover, PopoverContent, PopoverTrigger, StandardTooltip } from "@/components/ui" +import { useRooPortal } from "@/components/ui/hooks/useRooPortal" +import { cn } from "@/lib/utils" +import { vscode } from "@/utils/vscode" + +import { SlashCommandsList } from "./SlashCommandsList" + +interface SlashCommandsPopoverProps { + className?: string +} + +export const SlashCommandsPopover: React.FC = ({ className }) => { + const { t } = useAppTranslation() + const { commands } = useExtensionState() + const [isOpen, setIsOpen] = useState(false) + const portalContainer = useRooPortal("roo-portal") + + // Request commands when popover opens + useEffect(() => { + if (isOpen && (!commands || commands.length === 0)) { + handleRefresh() + } + }, [isOpen, commands]) + + const handleRefresh = () => { + vscode.postMessage({ type: "requestCommands" }) + } + + const handleOpenChange = (open: boolean) => { + setIsOpen(open) + if (open) { + // Always refresh when opening to get latest commands + handleRefresh() + } + } + + const trigger = ( + + + + ) + + return ( + + {trigger} + + +
+ {/* Header section */} +
+

+ {t("chat:slashCommands.description")} +

+
+ + {/* Commands list */} + +
+
+
+ ) +} diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index 91568f7e36c..828ac657cb5 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -352,6 +352,24 @@ "command": { "triggerDescription": "Trigger the {{name}} command" }, + "slashCommands": { + "tooltip": "Manage slash commands", + "title": "Slash Commands", + "description": "Create custom slash commands for quick access to frequently used prompts and workflows.", + "globalCommands": "Global Commands", + "workspaceCommands": "Workspace Commands", + "globalCommand": "Global command", + "editCommand": "Edit command", + "deleteCommand": "Delete command", + "newGlobalCommandPlaceholder": "New global command...", + "newWorkspaceCommandPlaceholder": "New workspace command...", + "deleteDialog": { + "title": "Delete Command", + "description": "Are you sure you want to delete the command \"{{name}}\"? This action cannot be undone.", + "cancel": "Cancel", + "confirm": "Delete" + } + }, "queuedMessages": { "title": "Queued Messages:", "clickToEdit": "Click to edit message" From aae9cfa76c5380a0cb6b7e9d91916a076232b543 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Mon, 28 Jul 2025 13:41:42 +0000 Subject: [PATCH 2/6] feat: add missing translations for slash commands UI - Added translations for command_not_found, open_command_file, and delete_command in common.json for all languages - Added complete slashCommands section translations in chat.json for all languages - All translations validated with find-missing-translations.js script --- src/i18n/locales/ca/common.json | 3 +++ src/i18n/locales/de/common.json | 3 +++ src/i18n/locales/es/common.json | 3 +++ src/i18n/locales/fr/common.json | 3 +++ src/i18n/locales/hi/common.json | 3 +++ src/i18n/locales/id/common.json | 3 +++ src/i18n/locales/it/common.json | 3 +++ src/i18n/locales/ja/common.json | 3 +++ src/i18n/locales/ko/common.json | 3 +++ src/i18n/locales/nl/common.json | 3 +++ src/i18n/locales/pl/common.json | 3 +++ src/i18n/locales/pt-BR/common.json | 3 +++ src/i18n/locales/ru/common.json | 3 +++ src/i18n/locales/tr/common.json | 3 +++ src/i18n/locales/vi/common.json | 3 +++ src/i18n/locales/zh-CN/common.json | 3 +++ src/i18n/locales/zh-TW/common.json | 3 +++ webview-ui/src/i18n/locales/ca/chat.json | 18 ++++++++++++++++++ webview-ui/src/i18n/locales/de/chat.json | 18 ++++++++++++++++++ webview-ui/src/i18n/locales/es/chat.json | 18 ++++++++++++++++++ webview-ui/src/i18n/locales/fr/chat.json | 18 ++++++++++++++++++ webview-ui/src/i18n/locales/hi/chat.json | 18 ++++++++++++++++++ webview-ui/src/i18n/locales/id/chat.json | 18 ++++++++++++++++++ webview-ui/src/i18n/locales/it/chat.json | 18 ++++++++++++++++++ webview-ui/src/i18n/locales/ja/chat.json | 18 ++++++++++++++++++ webview-ui/src/i18n/locales/ko/chat.json | 18 ++++++++++++++++++ webview-ui/src/i18n/locales/nl/chat.json | 18 ++++++++++++++++++ webview-ui/src/i18n/locales/pl/chat.json | 18 ++++++++++++++++++ webview-ui/src/i18n/locales/pt-BR/chat.json | 18 ++++++++++++++++++ webview-ui/src/i18n/locales/ru/chat.json | 18 ++++++++++++++++++ webview-ui/src/i18n/locales/tr/chat.json | 18 ++++++++++++++++++ webview-ui/src/i18n/locales/vi/chat.json | 18 ++++++++++++++++++ webview-ui/src/i18n/locales/zh-CN/chat.json | 18 ++++++++++++++++++ webview-ui/src/i18n/locales/zh-TW/chat.json | 18 ++++++++++++++++++ 34 files changed, 357 insertions(+) diff --git a/src/i18n/locales/ca/common.json b/src/i18n/locales/ca/common.json index cbe2c032b40..fe2ac216502 100644 --- a/src/i18n/locales/ca/common.json +++ b/src/i18n/locales/ca/common.json @@ -74,6 +74,9 @@ "share_not_enabled": "La compartició de tasques no està habilitada per a aquesta organització.", "share_task_not_found": "Tasca no trobada o accés denegat.", "delete_rules_folder_failed": "Error en eliminar la carpeta de regles: {{rulesFolderPath}}. Error: {{error}}", + "command_not_found": "Ordre '{{name}}' no trobada", + "open_command_file": "Error en obrir el fitxer d'ordres", + "delete_command": "Error en eliminar l'ordre", "claudeCode": { "processExited": "El procés Claude Code ha sortit amb codi {{exitCode}}.", "errorOutput": "Sortida d'error: {{output}}", diff --git a/src/i18n/locales/de/common.json b/src/i18n/locales/de/common.json index 95d315e1038..f68981ed42f 100644 --- a/src/i18n/locales/de/common.json +++ b/src/i18n/locales/de/common.json @@ -71,6 +71,9 @@ "share_task_not_found": "Aufgabe nicht gefunden oder Zugriff verweigert.", "mode_import_failed": "Fehler beim Importieren des Modus: {{error}}", "delete_rules_folder_failed": "Fehler beim Löschen des Regelordners: {{rulesFolderPath}}. Fehler: {{error}}", + "command_not_found": "Befehl '{{name}}' nicht gefunden", + "open_command_file": "Fehler beim Öffnen der Befehlsdatei", + "delete_command": "Fehler beim Löschen des Befehls", "claudeCode": { "processExited": "Claude Code Prozess wurde mit Code {{exitCode}} beendet.", "errorOutput": "Fehlerausgabe: {{output}}", diff --git a/src/i18n/locales/es/common.json b/src/i18n/locales/es/common.json index d47b95be7e6..d033f904d4b 100644 --- a/src/i18n/locales/es/common.json +++ b/src/i18n/locales/es/common.json @@ -71,6 +71,9 @@ "share_task_not_found": "Tarea no encontrada o acceso denegado.", "mode_import_failed": "Error al importar el modo: {{error}}", "delete_rules_folder_failed": "Error al eliminar la carpeta de reglas: {{rulesFolderPath}}. Error: {{error}}", + "command_not_found": "Comando '{{name}}' no encontrado", + "open_command_file": "Error al abrir el archivo de comandos", + "delete_command": "Error al eliminar el comando", "claudeCode": { "processExited": "El proceso de Claude Code terminó con código {{exitCode}}.", "errorOutput": "Salida de error: {{output}}", diff --git a/src/i18n/locales/fr/common.json b/src/i18n/locales/fr/common.json index e239358bbfc..6460f12ceec 100644 --- a/src/i18n/locales/fr/common.json +++ b/src/i18n/locales/fr/common.json @@ -71,6 +71,9 @@ "share_task_not_found": "Tâche non trouvée ou accès refusé.", "mode_import_failed": "Échec de l'importation du mode : {{error}}", "delete_rules_folder_failed": "Échec de la suppression du dossier de règles : {{rulesFolderPath}}. Erreur : {{error}}", + "command_not_found": "Commande '{{name}}' introuvable", + "open_command_file": "Échec de l'ouverture du fichier de commande", + "delete_command": "Échec de la suppression de la commande", "claudeCode": { "processExited": "Le processus Claude Code s'est terminé avec le code {{exitCode}}.", "errorOutput": "Sortie d'erreur : {{output}}", diff --git a/src/i18n/locales/hi/common.json b/src/i18n/locales/hi/common.json index d5ba036f632..6a36ed95c22 100644 --- a/src/i18n/locales/hi/common.json +++ b/src/i18n/locales/hi/common.json @@ -71,6 +71,9 @@ "share_task_not_found": "कार्य नहीं मिला या पहुंच अस्वीकृत।", "mode_import_failed": "मोड आयात करने में विफल: {{error}}", "delete_rules_folder_failed": "नियम फ़ोल्डर हटाने में विफल: {{rulesFolderPath}}। त्रुटि: {{error}}", + "command_not_found": "कमांड '{{name}}' नहीं मिला", + "open_command_file": "कमांड फ़ाइल खोलने में विफल", + "delete_command": "कमांड हटाने में विफल", "claudeCode": { "processExited": "Claude Code प्रक्रिया कोड {{exitCode}} के साथ समाप्त हुई।", "errorOutput": "त्रुटि आउटपुट: {{output}}", diff --git a/src/i18n/locales/id/common.json b/src/i18n/locales/id/common.json index b261f69e242..2b9302ef541 100644 --- a/src/i18n/locales/id/common.json +++ b/src/i18n/locales/id/common.json @@ -71,6 +71,9 @@ "share_task_not_found": "Tugas tidak ditemukan atau akses ditolak.", "mode_import_failed": "Gagal mengimpor mode: {{error}}", "delete_rules_folder_failed": "Gagal menghapus folder aturan: {{rulesFolderPath}}. Error: {{error}}", + "command_not_found": "Perintah '{{name}}' tidak ditemukan", + "open_command_file": "Gagal membuka file perintah", + "delete_command": "Gagal menghapus perintah", "claudeCode": { "processExited": "Proses Claude Code keluar dengan kode {{exitCode}}.", "errorOutput": "Output error: {{output}}", diff --git a/src/i18n/locales/it/common.json b/src/i18n/locales/it/common.json index f5e15398edd..91ea7484cfc 100644 --- a/src/i18n/locales/it/common.json +++ b/src/i18n/locales/it/common.json @@ -71,6 +71,9 @@ "share_task_not_found": "Attività non trovata o accesso negato.", "mode_import_failed": "Importazione della modalità non riuscita: {{error}}", "delete_rules_folder_failed": "Impossibile eliminare la cartella delle regole: {{rulesFolderPath}}. Errore: {{error}}", + "command_not_found": "Comando '{{name}}' non trovato", + "open_command_file": "Impossibile aprire il file di comando", + "delete_command": "Impossibile eliminare il comando", "claudeCode": { "processExited": "Il processo Claude Code è terminato con codice {{exitCode}}.", "errorOutput": "Output di errore: {{output}}", diff --git a/src/i18n/locales/ja/common.json b/src/i18n/locales/ja/common.json index 9b43f64bcf4..3380bfe2e08 100644 --- a/src/i18n/locales/ja/common.json +++ b/src/i18n/locales/ja/common.json @@ -71,6 +71,9 @@ "share_task_not_found": "タスクが見つからないか、アクセスが拒否されました。", "mode_import_failed": "モードのインポートに失敗しました:{{error}}", "delete_rules_folder_failed": "ルールフォルダの削除に失敗しました:{{rulesFolderPath}}。エラー:{{error}}", + "command_not_found": "コマンド '{{name}}' が見つかりません", + "open_command_file": "コマンドファイルを開けませんでした", + "delete_command": "コマンドの削除に失敗しました", "claudeCode": { "processExited": "Claude Code プロセスがコード {{exitCode}} で終了しました。", "errorOutput": "エラー出力:{{output}}", diff --git a/src/i18n/locales/ko/common.json b/src/i18n/locales/ko/common.json index 2bfbacad320..901ad478f1a 100644 --- a/src/i18n/locales/ko/common.json +++ b/src/i18n/locales/ko/common.json @@ -71,6 +71,9 @@ "share_task_not_found": "작업을 찾을 수 없거나 액세스가 거부되었습니다.", "mode_import_failed": "모드 가져오기 실패: {{error}}", "delete_rules_folder_failed": "규칙 폴더 삭제 실패: {{rulesFolderPath}}. 오류: {{error}}", + "command_not_found": "'{{name}}' 명령을 찾을 수 없습니다", + "open_command_file": "명령 파일을 열 수 없습니다", + "delete_command": "명령 삭제 실패", "claudeCode": { "processExited": "Claude Code 프로세스가 코드 {{exitCode}}로 종료되었습니다.", "errorOutput": "오류 출력: {{output}}", diff --git a/src/i18n/locales/nl/common.json b/src/i18n/locales/nl/common.json index 645a371754f..001c8573bd5 100644 --- a/src/i18n/locales/nl/common.json +++ b/src/i18n/locales/nl/common.json @@ -71,6 +71,9 @@ "share_task_not_found": "Taak niet gevonden of toegang geweigerd.", "mode_import_failed": "Importeren van modus mislukt: {{error}}", "delete_rules_folder_failed": "Kan regelmap niet verwijderen: {{rulesFolderPath}}. Fout: {{error}}", + "command_not_found": "Opdracht '{{name}}' niet gevonden", + "open_command_file": "Kan opdrachtbestand niet openen", + "delete_command": "Kan opdracht niet verwijderen", "claudeCode": { "processExited": "Claude Code proces beëindigd met code {{exitCode}}.", "errorOutput": "Foutuitvoer: {{output}}", diff --git a/src/i18n/locales/pl/common.json b/src/i18n/locales/pl/common.json index 45251e1ab4c..bb8f267ef55 100644 --- a/src/i18n/locales/pl/common.json +++ b/src/i18n/locales/pl/common.json @@ -71,6 +71,9 @@ "share_task_not_found": "Zadanie nie znalezione lub dostęp odmówiony.", "mode_import_failed": "Import trybu nie powiódł się: {{error}}", "delete_rules_folder_failed": "Nie udało się usunąć folderu reguł: {{rulesFolderPath}}. Błąd: {{error}}", + "command_not_found": "Polecenie '{{name}}' nie zostało znalezione", + "open_command_file": "Nie udało się otworzyć pliku polecenia", + "delete_command": "Nie udało się usunąć polecenia", "claudeCode": { "processExited": "Proces Claude Code zakończył się kodem {{exitCode}}.", "errorOutput": "Wyjście błędu: {{output}}", diff --git a/src/i18n/locales/pt-BR/common.json b/src/i18n/locales/pt-BR/common.json index 29c951fb392..9f947f7ec7a 100644 --- a/src/i18n/locales/pt-BR/common.json +++ b/src/i18n/locales/pt-BR/common.json @@ -75,6 +75,9 @@ "share_task_not_found": "Tarefa não encontrada ou acesso negado.", "mode_import_failed": "Falha ao importar o modo: {{error}}", "delete_rules_folder_failed": "Falha ao excluir pasta de regras: {{rulesFolderPath}}. Erro: {{error}}", + "command_not_found": "Comando '{{name}}' não encontrado", + "open_command_file": "Falha ao abrir arquivo de comando", + "delete_command": "Falha ao excluir comando", "claudeCode": { "processExited": "O processo Claude Code saiu com código {{exitCode}}.", "errorOutput": "Saída de erro: {{output}}", diff --git a/src/i18n/locales/ru/common.json b/src/i18n/locales/ru/common.json index 5f8fdf34c17..20639106a88 100644 --- a/src/i18n/locales/ru/common.json +++ b/src/i18n/locales/ru/common.json @@ -71,6 +71,9 @@ "share_task_not_found": "Задача не найдена или доступ запрещен.", "mode_import_failed": "Не удалось импортировать режим: {{error}}", "delete_rules_folder_failed": "Не удалось удалить папку правил: {{rulesFolderPath}}. Ошибка: {{error}}", + "command_not_found": "Команда '{{name}}' не найдена", + "open_command_file": "Не удалось открыть файл команды", + "delete_command": "Не удалось удалить команду", "claudeCode": { "processExited": "Процесс Claude Code завершился с кодом {{exitCode}}.", "errorOutput": "Вывод ошибки: {{output}}", diff --git a/src/i18n/locales/tr/common.json b/src/i18n/locales/tr/common.json index c7feb38ef66..6c13467eac0 100644 --- a/src/i18n/locales/tr/common.json +++ b/src/i18n/locales/tr/common.json @@ -71,6 +71,9 @@ "share_task_not_found": "Görev bulunamadı veya erişim reddedildi.", "mode_import_failed": "Mod içe aktarılamadı: {{error}}", "delete_rules_folder_failed": "Kurallar klasörü silinemedi: {{rulesFolderPath}}. Hata: {{error}}", + "command_not_found": "'{{name}}' komutu bulunamadı", + "open_command_file": "Komut dosyası açılamadı", + "delete_command": "Komut silinemedi", "claudeCode": { "processExited": "Claude Code işlemi {{exitCode}} koduyla çıktı.", "errorOutput": "Hata çıktısı: {{output}}", diff --git a/src/i18n/locales/vi/common.json b/src/i18n/locales/vi/common.json index 84b8b409dc1..606ce08b8e2 100644 --- a/src/i18n/locales/vi/common.json +++ b/src/i18n/locales/vi/common.json @@ -71,6 +71,9 @@ "share_task_not_found": "Không tìm thấy nhiệm vụ hoặc truy cập bị từ chối.", "mode_import_failed": "Nhập chế độ thất bại: {{error}}", "delete_rules_folder_failed": "Không thể xóa thư mục quy tắc: {{rulesFolderPath}}. Lỗi: {{error}}", + "command_not_found": "Không tìm thấy lệnh '{{name}}'", + "open_command_file": "Không thể mở tệp lệnh", + "delete_command": "Không thể xóa lệnh", "claudeCode": { "processExited": "Tiến trình Claude Code thoát với mã {{exitCode}}.", "errorOutput": "Đầu ra lỗi: {{output}}", diff --git a/src/i18n/locales/zh-CN/common.json b/src/i18n/locales/zh-CN/common.json index 7798a8bbdb1..c8151786c89 100644 --- a/src/i18n/locales/zh-CN/common.json +++ b/src/i18n/locales/zh-CN/common.json @@ -76,6 +76,9 @@ "share_task_not_found": "未找到任务或访问被拒绝。", "mode_import_failed": "导入模式失败:{{error}}", "delete_rules_folder_failed": "删除规则文件夹失败:{{rulesFolderPath}}。错误:{{error}}", + "command_not_found": "未找到命令 '{{name}}'", + "open_command_file": "打开命令文件失败", + "delete_command": "删除命令失败", "claudeCode": { "processExited": "Claude Code 进程退出,退出码:{{exitCode}}。", "errorOutput": "错误输出:{{output}}", diff --git a/src/i18n/locales/zh-TW/common.json b/src/i18n/locales/zh-TW/common.json index c6105c2bf9f..1b45231a5f0 100644 --- a/src/i18n/locales/zh-TW/common.json +++ b/src/i18n/locales/zh-TW/common.json @@ -70,6 +70,9 @@ "share_not_enabled": "此組織未啟用工作分享功能。", "share_task_not_found": "未找到工作或存取被拒絕。", "delete_rules_folder_failed": "刪除規則資料夾失敗: {{rulesFolderPath}}。錯誤: {{error}}", + "command_not_found": "找不到指令 '{{name}}'", + "open_command_file": "開啟指令檔案失敗", + "delete_command": "刪除指令失敗", "claudeCode": { "processExited": "Claude Code 程序退出,退出碼:{{exitCode}}。", "errorOutput": "錯誤輸出:{{output}}", diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json index 381576ad481..b7dd2b33811 100644 --- a/webview-ui/src/i18n/locales/ca/chat.json +++ b/webview-ui/src/i18n/locales/ca/chat.json @@ -352,6 +352,24 @@ "command": { "triggerDescription": "Activa la comanda {{name}}" }, + "slashCommands": { + "tooltip": "Gestionar ordres de barra", + "title": "Ordres de Barra", + "description": "Crea ordres de barra personalitzades per accedir ràpidament a indicacions i fluxos de treball utilitzats amb freqüència.", + "globalCommands": "Ordres Globals", + "workspaceCommands": "Ordres de l'Espai de Treball", + "globalCommand": "Ordre global", + "editCommand": "Editar ordre", + "deleteCommand": "Eliminar ordre", + "newGlobalCommandPlaceholder": "Nova ordre global...", + "newWorkspaceCommandPlaceholder": "Nova ordre de l'espai de treball...", + "deleteDialog": { + "title": "Eliminar Ordre", + "description": "Estàs segur que vols eliminar l'ordre \"{{name}}\"? Aquesta acció no es pot desfer.", + "cancel": "Cancel·lar", + "confirm": "Eliminar" + } + }, "queuedMessages": { "title": "Missatges en cua:", "clickToEdit": "Feu clic per editar el missatge" diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json index 3d43ad90a1e..9c5ff0fe488 100644 --- a/webview-ui/src/i18n/locales/de/chat.json +++ b/webview-ui/src/i18n/locales/de/chat.json @@ -352,6 +352,24 @@ "editMessage": { "placeholder": "Bearbeite deine Nachricht..." }, + "slashCommands": { + "tooltip": "Slash-Befehle verwalten", + "title": "Slash-Befehle", + "description": "Erstelle benutzerdefinierte Slash-Befehle für schnellen Zugriff auf häufig verwendete Prompts und Workflows.", + "globalCommands": "Globale Befehle", + "workspaceCommands": "Arbeitsbereich-Befehle", + "globalCommand": "Globaler Befehl", + "editCommand": "Befehl bearbeiten", + "deleteCommand": "Befehl löschen", + "newGlobalCommandPlaceholder": "Neuer globaler Befehl...", + "newWorkspaceCommandPlaceholder": "Neuer Arbeitsbereich-Befehl...", + "deleteDialog": { + "title": "Befehl löschen", + "description": "Bist du sicher, dass du den Befehl \"{{name}}\" löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.", + "cancel": "Abbrechen", + "confirm": "Löschen" + } + }, "queuedMessages": { "title": "Warteschlange Nachrichten:", "clickToEdit": "Klicken zum Bearbeiten der Nachricht" diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json index 7effb8f6caf..398c64c879c 100644 --- a/webview-ui/src/i18n/locales/es/chat.json +++ b/webview-ui/src/i18n/locales/es/chat.json @@ -352,6 +352,24 @@ "command": { "triggerDescription": "Activar el comando {{name}}" }, + "slashCommands": { + "tooltip": "Gestionar comandos de barra", + "title": "Comandos de Barra", + "description": "Crea comandos de barra personalizados para acceder rápidamente a prompts y flujos de trabajo utilizados con frecuencia.", + "globalCommands": "Comandos Globales", + "workspaceCommands": "Comandos del Espacio de Trabajo", + "globalCommand": "Comando global", + "editCommand": "Editar comando", + "deleteCommand": "Eliminar comando", + "newGlobalCommandPlaceholder": "Nuevo comando global...", + "newWorkspaceCommandPlaceholder": "Nuevo comando del espacio de trabajo...", + "deleteDialog": { + "title": "Eliminar Comando", + "description": "¿Estás seguro de que quieres eliminar el comando \"{{name}}\"? Esta acción no se puede deshacer.", + "cancel": "Cancelar", + "confirm": "Eliminar" + } + }, "queuedMessages": { "title": "Mensajes en cola:", "clickToEdit": "Haz clic para editar el mensaje" diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json index bc677bc7701..67e6dbcd477 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -352,6 +352,24 @@ "command": { "triggerDescription": "Déclencher la commande {{name}}" }, + "slashCommands": { + "tooltip": "Gérer les commandes slash", + "title": "Commandes Slash", + "description": "Créez des commandes slash personnalisées pour accéder rapidement aux prompts et flux de travail fréquemment utilisés.", + "globalCommands": "Commandes Globales", + "workspaceCommands": "Commandes de l'Espace de Travail", + "globalCommand": "Commande globale", + "editCommand": "Modifier la commande", + "deleteCommand": "Supprimer la commande", + "newGlobalCommandPlaceholder": "Nouvelle commande globale...", + "newWorkspaceCommandPlaceholder": "Nouvelle commande de l'espace de travail...", + "deleteDialog": { + "title": "Supprimer la Commande", + "description": "Êtes-vous sûr de vouloir supprimer la commande \"{{name}}\" ? Cette action ne peut pas être annulée.", + "cancel": "Annuler", + "confirm": "Supprimer" + } + }, "queuedMessages": { "title": "Messages en file d'attente :", "clickToEdit": "Cliquez pour modifier le message" diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json index d050373c469..4b4138f5f71 100644 --- a/webview-ui/src/i18n/locales/hi/chat.json +++ b/webview-ui/src/i18n/locales/hi/chat.json @@ -352,6 +352,24 @@ "command": { "triggerDescription": "{{name}} कमांड को ट्रिगर करें" }, + "slashCommands": { + "tooltip": "स्लैश कमांड प्रबंधित करें", + "title": "स्लैश कमांड", + "description": "बार-बार उपयोग किए जाने वाले प्रॉम्प्ट और वर्कफ़्लो तक त्वरित पहुंच के लिए कस्टम स्लैश कमांड बनाएं।", + "globalCommands": "वैश्विक कमांड", + "workspaceCommands": "कार्यक्षेत्र कमांड", + "globalCommand": "वैश्विक कमांड", + "editCommand": "कमांड संपादित करें", + "deleteCommand": "कमांड हटाएं", + "newGlobalCommandPlaceholder": "नया वैश्विक कमांड...", + "newWorkspaceCommandPlaceholder": "नया कार्यक्षेत्र कमांड...", + "deleteDialog": { + "title": "कमांड हटाएं", + "description": "क्या आप वाकई \"{{name}}\" कमांड को हटाना चाहते हैं? यह क्रिया पूर्ववत नहीं की जा सकती।", + "cancel": "रद्द करें", + "confirm": "हटाएं" + } + }, "queuedMessages": { "title": "कतार में संदेश:", "clickToEdit": "संदेश संपादित करने के लिए क्लिक करें" diff --git a/webview-ui/src/i18n/locales/id/chat.json b/webview-ui/src/i18n/locales/id/chat.json index ed6109e47be..23546c962f0 100644 --- a/webview-ui/src/i18n/locales/id/chat.json +++ b/webview-ui/src/i18n/locales/id/chat.json @@ -358,6 +358,24 @@ "command": { "triggerDescription": "Jalankan perintah {{name}}" }, + "slashCommands": { + "tooltip": "Kelola perintah slash", + "title": "Perintah Slash", + "description": "Buat perintah slash kustom untuk akses cepat ke prompt dan alur kerja yang sering digunakan.", + "globalCommands": "Perintah Global", + "workspaceCommands": "Perintah Workspace", + "globalCommand": "Perintah global", + "editCommand": "Edit perintah", + "deleteCommand": "Hapus perintah", + "newGlobalCommandPlaceholder": "Perintah global baru...", + "newWorkspaceCommandPlaceholder": "Perintah workspace baru...", + "deleteDialog": { + "title": "Hapus Perintah", + "description": "Apakah Anda yakin ingin menghapus perintah \"{{name}}\"? Tindakan ini tidak dapat dibatalkan.", + "cancel": "Batal", + "confirm": "Hapus" + } + }, "queuedMessages": { "title": "Pesan Antrian:", "clickToEdit": "Klik untuk mengedit pesan" diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json index c35209a8cb4..f2d020647be 100644 --- a/webview-ui/src/i18n/locales/it/chat.json +++ b/webview-ui/src/i18n/locales/it/chat.json @@ -352,6 +352,24 @@ "command": { "triggerDescription": "Attiva il comando {{name}}" }, + "slashCommands": { + "tooltip": "Gestisci comandi slash", + "title": "Comandi Slash", + "description": "Crea comandi slash personalizzati per accedere rapidamente a prompt e flussi di lavoro utilizzati frequentemente.", + "globalCommands": "Comandi Globali", + "workspaceCommands": "Comandi dello Spazio di Lavoro", + "globalCommand": "Comando globale", + "editCommand": "Modifica comando", + "deleteCommand": "Elimina comando", + "newGlobalCommandPlaceholder": "Nuovo comando globale...", + "newWorkspaceCommandPlaceholder": "Nuovo comando dello spazio di lavoro...", + "deleteDialog": { + "title": "Elimina Comando", + "description": "Sei sicuro di voler eliminare il comando \"{{name}}\"? Questa azione non può essere annullata.", + "cancel": "Annulla", + "confirm": "Elimina" + } + }, "queuedMessages": { "title": "Messaggi in coda:", "clickToEdit": "Clicca per modificare il messaggio" diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json index 82fded799b5..514bba4f349 100644 --- a/webview-ui/src/i18n/locales/ja/chat.json +++ b/webview-ui/src/i18n/locales/ja/chat.json @@ -352,6 +352,24 @@ "command": { "triggerDescription": "{{name}}コマンドをトリガー" }, + "slashCommands": { + "tooltip": "スラッシュコマンドを管理", + "title": "スラッシュコマンド", + "description": "よく使用するプロンプトやワークフローに素早くアクセスするためのカスタムスラッシュコマンドを作成します。", + "globalCommands": "グローバルコマンド", + "workspaceCommands": "ワークスペースコマンド", + "globalCommand": "グローバルコマンド", + "editCommand": "コマンドを編集", + "deleteCommand": "コマンドを削除", + "newGlobalCommandPlaceholder": "新しいグローバルコマンド...", + "newWorkspaceCommandPlaceholder": "新しいワークスペースコマンド...", + "deleteDialog": { + "title": "コマンドを削除", + "description": "\"{{name}}\" コマンドを削除してもよろしいですか?この操作は元に戻せません。", + "cancel": "キャンセル", + "confirm": "削除" + } + }, "queuedMessages": { "title": "キューメッセージ:", "clickToEdit": "クリックしてメッセージを編集" diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json index 0432ce56522..90f69d5b2c2 100644 --- a/webview-ui/src/i18n/locales/ko/chat.json +++ b/webview-ui/src/i18n/locales/ko/chat.json @@ -352,6 +352,24 @@ "command": { "triggerDescription": "{{name}} 명령 트리거" }, + "slashCommands": { + "tooltip": "슬래시 명령 관리", + "title": "슬래시 명령", + "description": "자주 사용하는 프롬프트와 워크플로우에 빠르게 액세스할 수 있는 사용자 정의 슬래시 명령을 만듭니다.", + "globalCommands": "전역 명령", + "workspaceCommands": "작업 공간 명령", + "globalCommand": "전역 명령", + "editCommand": "명령 편집", + "deleteCommand": "명령 삭제", + "newGlobalCommandPlaceholder": "새 전역 명령...", + "newWorkspaceCommandPlaceholder": "새 작업 공간 명령...", + "deleteDialog": { + "title": "명령 삭제", + "description": "\"{{name}}\" 명령을 삭제하시겠습니까? 이 작업은 취소할 수 없습니다.", + "cancel": "취소", + "confirm": "삭제" + } + }, "queuedMessages": { "title": "대기열 메시지:", "clickToEdit": "클릭하여 메시지 편집" diff --git a/webview-ui/src/i18n/locales/nl/chat.json b/webview-ui/src/i18n/locales/nl/chat.json index 6dae0113af1..c8dbac4f2d5 100644 --- a/webview-ui/src/i18n/locales/nl/chat.json +++ b/webview-ui/src/i18n/locales/nl/chat.json @@ -352,6 +352,24 @@ "command": { "triggerDescription": "Activeer de {{name}} opdracht" }, + "slashCommands": { + "tooltip": "Slash-opdrachten beheren", + "title": "Slash-opdrachten", + "description": "Maak aangepaste slash-opdrachten voor snelle toegang tot veelgebruikte prompts en workflows.", + "globalCommands": "Globale Opdrachten", + "workspaceCommands": "Werkruimte Opdrachten", + "globalCommand": "Globale opdracht", + "editCommand": "Opdracht bewerken", + "deleteCommand": "Opdracht verwijderen", + "newGlobalCommandPlaceholder": "Nieuwe globale opdracht...", + "newWorkspaceCommandPlaceholder": "Nieuwe werkruimte opdracht...", + "deleteDialog": { + "title": "Opdracht Verwijderen", + "description": "Weet je zeker dat je de opdracht \"{{name}}\" wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt.", + "cancel": "Annuleren", + "confirm": "Verwijderen" + } + }, "queuedMessages": { "title": "Berichten in wachtrij:", "clickToEdit": "Klik om bericht te bewerken" diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json index 984fa9a24c8..366a3af09c2 100644 --- a/webview-ui/src/i18n/locales/pl/chat.json +++ b/webview-ui/src/i18n/locales/pl/chat.json @@ -352,6 +352,24 @@ "command": { "triggerDescription": "Uruchom polecenie {{name}}" }, + "slashCommands": { + "tooltip": "Zarządzaj poleceniami slash", + "title": "Polecenia Slash", + "description": "Twórz niestandardowe polecenia slash dla szybkiego dostępu do często używanych promptów i przepływów pracy.", + "globalCommands": "Polecenia Globalne", + "workspaceCommands": "Polecenia Obszaru Roboczego", + "globalCommand": "Polecenie globalne", + "editCommand": "Edytuj polecenie", + "deleteCommand": "Usuń polecenie", + "newGlobalCommandPlaceholder": "Nowe polecenie globalne...", + "newWorkspaceCommandPlaceholder": "Nowe polecenie obszaru roboczego...", + "deleteDialog": { + "title": "Usuń Polecenie", + "description": "Czy na pewno chcesz usunąć polecenie \"{{name}}\"? Tej akcji nie można cofnąć.", + "cancel": "Anuluj", + "confirm": "Usuń" + } + }, "queuedMessages": { "title": "Wiadomości w kolejce:", "clickToEdit": "Kliknij, aby edytować wiadomość" diff --git a/webview-ui/src/i18n/locales/pt-BR/chat.json b/webview-ui/src/i18n/locales/pt-BR/chat.json index cbb1918e749..741056c3f08 100644 --- a/webview-ui/src/i18n/locales/pt-BR/chat.json +++ b/webview-ui/src/i18n/locales/pt-BR/chat.json @@ -352,6 +352,24 @@ "command": { "triggerDescription": "Acionar o comando {{name}}" }, + "slashCommands": { + "tooltip": "Gerenciar comandos de barra", + "title": "Comandos de Barra", + "description": "Crie comandos de barra personalizados para acesso rápido a prompts e fluxos de trabalho usados com frequência.", + "globalCommands": "Comandos Globais", + "workspaceCommands": "Comandos do Espaço de Trabalho", + "globalCommand": "Comando global", + "editCommand": "Editar comando", + "deleteCommand": "Excluir comando", + "newGlobalCommandPlaceholder": "Novo comando global...", + "newWorkspaceCommandPlaceholder": "Novo comando do espaço de trabalho...", + "deleteDialog": { + "title": "Excluir Comando", + "description": "Tem certeza de que deseja excluir o comando \"{{name}}\"? Esta ação não pode ser desfeita.", + "cancel": "Cancelar", + "confirm": "Excluir" + } + }, "queuedMessages": { "title": "Mensagens na fila:", "clickToEdit": "Clique para editar a mensagem" diff --git a/webview-ui/src/i18n/locales/ru/chat.json b/webview-ui/src/i18n/locales/ru/chat.json index 378e749405b..133057c5c86 100644 --- a/webview-ui/src/i18n/locales/ru/chat.json +++ b/webview-ui/src/i18n/locales/ru/chat.json @@ -352,6 +352,24 @@ "command": { "triggerDescription": "Запустить команду {{name}}" }, + "slashCommands": { + "tooltip": "Управление слэш-командами", + "title": "Слэш-команды", + "description": "Создавайте пользовательские слэш-команды для быстрого доступа к часто используемым промптам и рабочим процессам.", + "globalCommands": "Глобальные команды", + "workspaceCommands": "Команды рабочего пространства", + "globalCommand": "Глобальная команда", + "editCommand": "Редактировать команду", + "deleteCommand": "Удалить команду", + "newGlobalCommandPlaceholder": "Новая глобальная команда...", + "newWorkspaceCommandPlaceholder": "Новая команда рабочего пространства...", + "deleteDialog": { + "title": "Удалить команду", + "description": "Вы уверены, что хотите удалить команду \"{{name}}\"? Это действие нельзя отменить.", + "cancel": "Отмена", + "confirm": "Удалить" + } + }, "queuedMessages": { "title": "Сообщения в очереди:", "clickToEdit": "Нажмите, чтобы редактировать сообщение" diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json index e217184a9cc..3dbb34feaf0 100644 --- a/webview-ui/src/i18n/locales/tr/chat.json +++ b/webview-ui/src/i18n/locales/tr/chat.json @@ -352,6 +352,24 @@ "command": { "triggerDescription": "{{name}} komutunu tetikle" }, + "slashCommands": { + "tooltip": "Eğik çizgi komutlarını yönet", + "title": "Eğik Çizgi Komutları", + "description": "Sık kullanılan komut istemleri ve iş akışlarına hızlı erişim için özel eğik çizgi komutları oluşturun.", + "globalCommands": "Genel Komutlar", + "workspaceCommands": "Çalışma Alanı Komutları", + "globalCommand": "Genel komut", + "editCommand": "Komutu düzenle", + "deleteCommand": "Komutu sil", + "newGlobalCommandPlaceholder": "Yeni genel komut...", + "newWorkspaceCommandPlaceholder": "Yeni çalışma alanı komutu...", + "deleteDialog": { + "title": "Komutu Sil", + "description": "\"{{name}}\" komutunu silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.", + "cancel": "İptal", + "confirm": "Sil" + } + }, "queuedMessages": { "title": "Sıradaki Mesajlar:", "clickToEdit": "Mesajı düzenlemek için tıkla" diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json index b6fe59d9b72..e22158adf7d 100644 --- a/webview-ui/src/i18n/locales/vi/chat.json +++ b/webview-ui/src/i18n/locales/vi/chat.json @@ -352,6 +352,24 @@ "command": { "triggerDescription": "Kích hoạt lệnh {{name}}" }, + "slashCommands": { + "tooltip": "Quản lý lệnh gạch chéo", + "title": "Lệnh Gạch Chéo", + "description": "Tạo lệnh gạch chéo tùy chỉnh để truy cập nhanh vào các lời nhắc và quy trình làm việc thường dùng.", + "globalCommands": "Lệnh Toàn Cục", + "workspaceCommands": "Lệnh Không Gian Làm Việc", + "globalCommand": "Lệnh toàn cục", + "editCommand": "Chỉnh sửa lệnh", + "deleteCommand": "Xóa lệnh", + "newGlobalCommandPlaceholder": "Lệnh toàn cục mới...", + "newWorkspaceCommandPlaceholder": "Lệnh không gian làm việc mới...", + "deleteDialog": { + "title": "Xóa Lệnh", + "description": "Bạn có chắc chắn muốn xóa lệnh \"{{name}}\" không? Hành động này không thể hoàn tác.", + "cancel": "Hủy", + "confirm": "Xóa" + } + }, "queuedMessages": { "title": "Tin nhắn trong hàng đợi:", "clickToEdit": "Nhấp để chỉnh sửa tin nhắn" diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index 4f87348a989..036c777dc20 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -352,6 +352,24 @@ "editMessage": { "placeholder": "编辑消息..." }, + "slashCommands": { + "tooltip": "管理斜杠命令", + "title": "斜杠命令", + "description": "创建自定义斜杠命令,快速访问常用提示词和工作流程。", + "globalCommands": "全局命令", + "workspaceCommands": "工作区命令", + "globalCommand": "全局命令", + "editCommand": "编辑命令", + "deleteCommand": "删除命令", + "newGlobalCommandPlaceholder": "新建全局命令...", + "newWorkspaceCommandPlaceholder": "新建工作区命令...", + "deleteDialog": { + "title": "删除命令", + "description": "确定要删除命令 \"{{name}}\" 吗?此操作无法撤销。", + "cancel": "取消", + "confirm": "删除" + } + }, "queuedMessages": { "title": "队列消息:", "clickToEdit": "点击编辑消息" diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index 0671a07f876..733e9bd1c23 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -352,6 +352,24 @@ "command": { "triggerDescription": "觸發 {{name}} 命令" }, + "slashCommands": { + "tooltip": "管理斜線指令", + "title": "斜線指令", + "description": "建立自訂斜線指令,快速存取常用提示詞和工作流程。", + "globalCommands": "全域指令", + "workspaceCommands": "工作區指令", + "globalCommand": "全域指令", + "editCommand": "編輯指令", + "deleteCommand": "刪除指令", + "newGlobalCommandPlaceholder": "新增全域指令...", + "newWorkspaceCommandPlaceholder": "新增工作區指令...", + "deleteDialog": { + "title": "刪除指令", + "description": "確定要刪除指令 \"{{name}}\" 嗎?此動作無法復原。", + "cancel": "取消", + "confirm": "刪除" + } + }, "queuedMessages": { "title": "佇列訊息:", "clickToEdit": "點擊編輯訊息" From dbec3f5dc548759f64ac927fd03eca337f7c677e Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Mon, 28 Jul 2025 12:19:03 -0400 Subject: [PATCH 3/6] UI tweaks --- .../__tests__/command-autocomplete.spec.ts | 103 +++++++----------- .../src/components/chat/ChatTextArea.tsx | 3 - .../src/components/chat/ContextMenu.tsx | 48 ++++++-- webview-ui/src/i18n/locales/ca/chat.json | 2 +- webview-ui/src/i18n/locales/de/chat.json | 2 +- webview-ui/src/i18n/locales/en/chat.json | 2 +- webview-ui/src/i18n/locales/es/chat.json | 2 +- webview-ui/src/i18n/locales/fr/chat.json | 2 +- webview-ui/src/i18n/locales/hi/chat.json | 2 +- webview-ui/src/i18n/locales/id/chat.json | 2 +- webview-ui/src/i18n/locales/it/chat.json | 2 +- webview-ui/src/i18n/locales/ja/chat.json | 2 +- webview-ui/src/i18n/locales/ko/chat.json | 2 +- webview-ui/src/i18n/locales/nl/chat.json | 2 +- webview-ui/src/i18n/locales/pl/chat.json | 2 +- webview-ui/src/i18n/locales/pt-BR/chat.json | 2 +- webview-ui/src/i18n/locales/ru/chat.json | 2 +- webview-ui/src/i18n/locales/tr/chat.json | 2 +- webview-ui/src/i18n/locales/vi/chat.json | 2 +- webview-ui/src/i18n/locales/zh-CN/chat.json | 2 +- webview-ui/src/i18n/locales/zh-TW/chat.json | 2 +- .../utils/__tests__/context-mentions.spec.ts | 48 ++++---- webview-ui/src/utils/context-mentions.ts | 86 ++++++++------- 23 files changed, 162 insertions(+), 162 deletions(-) diff --git a/webview-ui/src/__tests__/command-autocomplete.spec.ts b/webview-ui/src/__tests__/command-autocomplete.spec.ts index d680fe7af2a..3789ad1bf50 100644 --- a/webview-ui/src/__tests__/command-autocomplete.spec.ts +++ b/webview-ui/src/__tests__/command-autocomplete.spec.ts @@ -16,22 +16,18 @@ describe("Command Autocomplete", () => { { type: ContextMenuOptionType.Problems, value: "problems" }, ] - // Mock translation function - const mockT = (key: string, options?: { name?: string }) => { - if (key === "chat:command.triggerDescription") { - return `Trigger the ${options?.name || "command"} command` - } - return key - } - describe("slash command command suggestions", () => { it('should return all commands when query is just "/"', () => { - const options = getContextMenuOptions("/", "/", mockT, null, mockQueryItems, [], [], mockCommands) + const options = getContextMenuOptions("/", "/", null, mockQueryItems, [], [], mockCommands) - expect(options).toHaveLength(5) - expect(options.every((option) => option.type === ContextMenuOptionType.Command)).toBe(true) + // Should have 6 items: 1 section header + 5 commands + expect(options).toHaveLength(6) - const commandNames = options.map((option) => option.value) + // Filter out section headers to check commands + const commandOptions = options.filter((option) => option.type === ContextMenuOptionType.Command) + expect(commandOptions).toHaveLength(5) + + const commandNames = commandOptions.map((option) => option.value) expect(commandNames).toContain("setup") expect(commandNames).toContain("build") expect(commandNames).toContain("deploy") @@ -40,7 +36,7 @@ describe("Command Autocomplete", () => { }) it("should filter commands based on fuzzy search", () => { - const options = getContextMenuOptions("/set", "/set", mockT, null, mockQueryItems, [], [], mockCommands) + const options = getContextMenuOptions("/set", "/set", null, mockQueryItems, [], [], mockCommands) // Should match 'setup' (fuzzy search behavior may vary) expect(options.length).toBeGreaterThan(0) @@ -50,18 +46,17 @@ describe("Command Autocomplete", () => { }) it("should return commands with correct format", () => { - const options = getContextMenuOptions("/setup", "/setup", mockT, null, mockQueryItems, [], [], mockCommands) + const options = getContextMenuOptions("/setup", "/setup", null, mockQueryItems, [], [], mockCommands) const setupOption = options.find((option) => option.value === "setup") expect(setupOption).toBeDefined() expect(setupOption!.type).toBe(ContextMenuOptionType.Command) - expect(setupOption!.label).toBe("setup") - expect(setupOption!.description).toBe("Trigger the setup command") - expect(setupOption!.icon).toBe("$(play)") + expect(setupOption!.slashCommand).toBe("/setup") + expect(setupOption!.value).toBe("setup") }) it("should handle empty command list", () => { - const options = getContextMenuOptions("/setup", "/setup", mockT, null, mockQueryItems, [], [], []) + const options = getContextMenuOptions("/setup", "/setup", null, mockQueryItems, [], [], []) // Should return NoResults when no commands match expect(options).toHaveLength(1) @@ -72,7 +67,6 @@ describe("Command Autocomplete", () => { const options = getContextMenuOptions( "/nonexistent", "/nonexistent", - mockT, null, mockQueryItems, [], @@ -86,7 +80,7 @@ describe("Command Autocomplete", () => { }) it("should not return command suggestions for non-slash queries", () => { - const options = getContextMenuOptions("setup", "setup", mockT, null, mockQueryItems, [], [], mockCommands) + const options = getContextMenuOptions("setup", "setup", null, mockQueryItems, [], [], mockCommands) // Should not contain command options for non-slash queries const commandOptions = options.filter((option) => option.type === ContextMenuOptionType.Command) @@ -100,24 +94,15 @@ describe("Command Autocomplete", () => { { name: "deploy.prod", source: "global" }, ] - const options = getContextMenuOptions( - "/setup", - "/setup", - mockT, - null, - mockQueryItems, - [], - [], - specialCommands, - ) + const options = getContextMenuOptions("/setup", "/setup", null, mockQueryItems, [], [], specialCommands) const setupDevOption = options.find((option) => option.value === "setup-dev") expect(setupDevOption).toBeDefined() - expect(setupDevOption!.label).toBe("setup-dev") + expect(setupDevOption!.slashCommand).toBe("/setup-dev") }) it("should handle case-insensitive fuzzy matching", () => { - const options = getContextMenuOptions("/setup", "/setup", mockT, null, mockQueryItems, [], [], mockCommands) + const options = getContextMenuOptions("/setup", "/setup", null, mockQueryItems, [], [], mockCommands) const commandNames = options.map((option) => option.value) expect(commandNames).toContain("setup") @@ -133,7 +118,6 @@ describe("Command Autocomplete", () => { const options = getContextMenuOptions( "/test", "/test", - mockT, null, mockQueryItems, [], @@ -141,12 +125,13 @@ describe("Command Autocomplete", () => { commandsWithSimilarNames, ) - // 'test' should be first due to exact match - expect(options[0].value).toBe("test") + // Filter out section headers and check the first command + const commandOptions = options.filter((option) => option.type === ContextMenuOptionType.Command) + expect(commandOptions[0].value).toBe("test") }) it("should handle partial matches correctly", () => { - const options = getContextMenuOptions("/te", "/te", mockT, null, mockQueryItems, [], [], mockCommands) + const options = getContextMenuOptions("/te", "/te", null, mockQueryItems, [], [], mockCommands) // Should match 'test-suite' const commandNames = options.map((option) => option.value) @@ -173,7 +158,7 @@ describe("Command Autocomplete", () => { ] as any[] it("should return both modes and commands for slash commands", () => { - const options = getContextMenuOptions("/", "/", mockT, null, mockQueryItems, [], mockModes, mockCommands) + const options = getContextMenuOptions("/", "/", null, mockQueryItems, [], mockModes, mockCommands) const modeOptions = options.filter((option) => option.type === ContextMenuOptionType.Mode) const commandOptions = options.filter((option) => option.type === ContextMenuOptionType.Command) @@ -183,16 +168,7 @@ describe("Command Autocomplete", () => { }) it("should filter both modes and commands based on query", () => { - const options = getContextMenuOptions( - "/co", - "/co", - mockT, - null, - mockQueryItems, - [], - mockModes, - mockCommands, - ) + const options = getContextMenuOptions("/co", "/co", null, mockQueryItems, [], mockModes, mockCommands) // Should match 'code' mode and possibly some commands (fuzzy search may match) const modeOptions = options.filter((option) => option.type === ContextMenuOptionType.Mode) @@ -207,28 +183,30 @@ describe("Command Autocomplete", () => { describe("command source indication", () => { it("should not expose source information in autocomplete", () => { - const options = getContextMenuOptions("/setup", "/setup", mockT, null, mockQueryItems, [], [], mockCommands) + const options = getContextMenuOptions("/setup", "/setup", null, mockQueryItems, [], [], mockCommands) const setupOption = options.find((option) => option.value === "setup") expect(setupOption).toBeDefined() // Source should not be exposed in the UI - expect(setupOption!.description).not.toContain("project") - expect(setupOption!.description).not.toContain("global") - expect(setupOption!.description).toBe("Trigger the setup command") + if (setupOption!.description) { + expect(setupOption!.description).not.toContain("project") + expect(setupOption!.description).not.toContain("global") + expect(setupOption!.description).toBe("Trigger the setup command") + } }) }) describe("edge cases", () => { it("should handle undefined commands gracefully", () => { - const options = getContextMenuOptions("/setup", "/setup", mockT, null, mockQueryItems, [], [], undefined) + const options = getContextMenuOptions("/setup", "/setup", null, mockQueryItems, [], [], undefined) expect(options).toHaveLength(1) expect(options[0].type).toBe(ContextMenuOptionType.NoResults) }) it("should handle empty query with commands", () => { - const options = getContextMenuOptions("", "", mockT, null, mockQueryItems, [], [], mockCommands) + const options = getContextMenuOptions("", "", null, mockQueryItems, [], [], mockCommands) // Should not return command options for empty query const commandOptions = options.filter((option) => option.type === ContextMenuOptionType.Command) @@ -240,19 +218,12 @@ describe("Command Autocomplete", () => { { name: "very-long-command-name-that-exceeds-normal-length", source: "project" }, ] - const options = getContextMenuOptions( - "/very", - "/very", - mockT, - null, - mockQueryItems, - [], - [], - longNameCommands, - ) + const options = getContextMenuOptions("/very", "/very", null, mockQueryItems, [], [], longNameCommands) - expect(options.length).toBe(1) - expect(options[0].value).toBe("very-long-command-name-that-exceeds-normal-length") + // Should have 2 items: 1 section header + 1 command + expect(options.length).toBe(2) + const commandOptions = options.filter((option) => option.type === ContextMenuOptionType.Command) + expect(commandOptions[0].value).toBe("very-long-command-name-that-exceeds-normal-length") }) it("should handle commands with numeric names", () => { @@ -262,7 +233,7 @@ describe("Command Autocomplete", () => { { name: "123test", source: "project" }, ] - const options = getContextMenuOptions("/v", "/v", mockT, null, mockQueryItems, [], [], numericCommands) + const options = getContextMenuOptions("/v", "/v", null, mockQueryItems, [], [], numericCommands) const commandNames = options.map((option) => option.value) expect(commandNames).toContain("v2-setup") diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index f569d807914..29d9fe61a61 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -396,7 +396,6 @@ const ChatTextArea = forwardRef( const options = getContextMenuOptions( searchQuery, inputValue, - t, selectedType, queryItems, fileSearchResults, @@ -435,7 +434,6 @@ const ChatTextArea = forwardRef( const selectedOption = getContextMenuOptions( searchQuery, inputValue, - t, selectedType, queryItems, fileSearchResults, @@ -529,7 +527,6 @@ const ChatTextArea = forwardRef( handleHistoryNavigation, resetHistoryNavigation, commands, - t, ], ) diff --git a/webview-ui/src/components/chat/ContextMenu.tsx b/webview-ui/src/components/chat/ContextMenu.tsx index 372dea088c7..b938e06bef1 100644 --- a/webview-ui/src/components/chat/ContextMenu.tsx +++ b/webview-ui/src/components/chat/ContextMenu.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useMemo, useRef, useState } from "react" import { getIconForFilePath, getIconUrlByName, getIconForDirectoryPath } from "vscode-material-icons" -import { useTranslation } from "react-i18next" import type { ModeConfig } from "@roo-code/types" import type { Command } from "@roo/ExtensionMessage" @@ -43,20 +42,18 @@ const ContextMenu: React.FC = ({ }) => { const [materialIconsBaseUri, setMaterialIconsBaseUri] = useState("") const menuRef = useRef(null) - const { t } = useTranslation() const filteredOptions = useMemo(() => { return getContextMenuOptions( searchQuery, inputValue, - t, selectedType, queryItems, dynamicSearchResults, modes, commands, ) - }, [searchQuery, inputValue, t, selectedType, queryItems, dynamicSearchResults, modes, commands]) + }, [searchQuery, inputValue, selectedType, queryItems, dynamicSearchResults, modes, commands]) useEffect(() => { if (menuRef.current) { @@ -82,10 +79,25 @@ const ContextMenu: React.FC = ({ const renderOptionContent = (option: ContextMenuQueryItem) => { switch (option.type) { + case ContextMenuOptionType.SectionHeader: + return ( + + {option.label} + + ) case ContextMenuOptionType.Mode: return (
- {option.label} +
+ {option.slashCommand} +
{option.description && ( = ({ case ContextMenuOptionType.Command: return (
- {option.label} +
+ {option.slashCommand} +
{option.description && ( = ({ } const isOptionSelectable = (option: ContextMenuQueryItem): boolean => { - return option.type !== ContextMenuOptionType.NoResults && option.type !== ContextMenuOptionType.URL + return ( + option.type !== ContextMenuOptionType.NoResults && + option.type !== ContextMenuOptionType.URL && + option.type !== ContextMenuOptionType.SectionHeader + ) } return ( @@ -252,8 +270,9 @@ const ContextMenu: React.FC = ({ zIndex: 1000, display: "flex", flexDirection: "column", - maxHeight: "200px", + maxHeight: "300px", overflowY: "auto", + overflowX: "hidden", }}> {filteredOptions && filteredOptions.length > 0 ? ( filteredOptions.map((option, index) => ( @@ -261,12 +280,20 @@ const ContextMenu: React.FC = ({ key={`${option.type}-${option.value || index}`} onClick={() => isOptionSelectable(option) && onSelect(option.type, option.value)} style={{ - padding: "4px 6px", + padding: + option.type === ContextMenuOptionType.SectionHeader ? "8px 6px 4px 6px" : "4px 6px", cursor: isOptionSelectable(option) ? "pointer" : "default", color: "var(--vscode-dropdown-foreground)", display: "flex", alignItems: "center", justifyContent: "space-between", + position: "relative", + ...(option.type === ContextMenuOptionType.SectionHeader + ? { + borderBottom: "1px solid var(--vscode-editorGroup-border)", + marginBottom: "2px", + } + : {}), ...(index === selectedIndex && isOptionSelectable(option) ? { backgroundColor: "var(--vscode-list-activeSelectionBackground)", @@ -283,6 +310,7 @@ const ContextMenu: React.FC = ({ minWidth: 0, overflow: "hidden", paddingTop: 0, + position: "relative", }}> {(option.type === ContextMenuOptionType.File || option.type === ContextMenuOptionType.Folder || @@ -299,9 +327,11 @@ const ContextMenu: React.FC = ({ /> )} {option.type !== ContextMenuOptionType.Mode && + option.type !== ContextMenuOptionType.Command && option.type !== ContextMenuOptionType.File && option.type !== ContextMenuOptionType.Folder && option.type !== ContextMenuOptionType.OpenedFile && + option.type !== ContextMenuOptionType.SectionHeader && getIconForOption(option) && ( { { path: "/Users/test/project/assets/", type: "folder", label: "assets/" }, ] - // Mock translation function - const mockT = (key: string, options?: { name?: string }) => { - if (key === "chat:command.triggerDescription" && options?.name) { - return `Trigger command: ${options.name}` - } - return key - } - it("should return all option types for empty query", () => { - const result = getContextMenuOptions("", "", mockT, null, []) + const result = getContextMenuOptions("", "", null, []) expect(result).toHaveLength(6) expect(result.map((item) => item.type)).toEqual([ ContextMenuOptionType.Problems, @@ -216,7 +208,7 @@ describe("getContextMenuOptions", () => { }) it("should filter by selected type when query is empty", () => { - const result = getContextMenuOptions("", "", mockT, ContextMenuOptionType.File, mockQueryItems) + const result = getContextMenuOptions("", "", ContextMenuOptionType.File, mockQueryItems) expect(result).toHaveLength(2) expect(result.map((item) => item.type)).toContain(ContextMenuOptionType.File) expect(result.map((item) => item.type)).toContain(ContextMenuOptionType.OpenedFile) @@ -225,19 +217,19 @@ describe("getContextMenuOptions", () => { }) it("should match git commands", () => { - const result = getContextMenuOptions("git", "git", mockT, null, mockQueryItems) + const result = getContextMenuOptions("git", "git", null, mockQueryItems) expect(result[0].type).toBe(ContextMenuOptionType.Git) expect(result[0].label).toBe("Git Commits") }) it("should match git commit hashes", () => { - const result = getContextMenuOptions("abc1234", "abc1234", mockT, null, mockQueryItems) + const result = getContextMenuOptions("abc1234", "abc1234", null, mockQueryItems) expect(result[0].type).toBe(ContextMenuOptionType.Git) expect(result[0].value).toBe("abc1234") }) it("should return NoResults when no matches found", () => { - const result = getContextMenuOptions("nonexistent", "nonexistent", mockT, null, mockQueryItems) + const result = getContextMenuOptions("nonexistent", "nonexistent", null, mockQueryItems) expect(result).toHaveLength(1) expect(result[0].type).toBe(ContextMenuOptionType.NoResults) }) @@ -258,7 +250,7 @@ describe("getContextMenuOptions", () => { }, ] - const result = getContextMenuOptions("test", "test", mockT, null, testItems, mockDynamicSearchResults) + const result = getContextMenuOptions("test", "test", null, testItems, mockDynamicSearchResults) // Check if opened files and dynamic search results are included expect(result.some((item) => item.type === ContextMenuOptionType.OpenedFile)).toBe(true) @@ -267,7 +259,7 @@ describe("getContextMenuOptions", () => { it("should maintain correct result ordering according to implementation", () => { // Add multiple item types to test ordering - const result = getContextMenuOptions("t", "t", mockT, null, mockQueryItems, mockDynamicSearchResults) + const result = getContextMenuOptions("t", "t", null, mockQueryItems, mockDynamicSearchResults) // Find the different result types const fileResults = result.filter( @@ -298,7 +290,7 @@ describe("getContextMenuOptions", () => { }) it("should include opened files when dynamic search results exist", () => { - const result = getContextMenuOptions("open", "open", mockT, null, mockQueryItems, mockDynamicSearchResults) + const result = getContextMenuOptions("open", "open", null, mockQueryItems, mockDynamicSearchResults) // Verify opened files are included expect(result.some((item) => item.type === ContextMenuOptionType.OpenedFile)).toBe(true) @@ -307,7 +299,7 @@ describe("getContextMenuOptions", () => { }) it("should include git results when dynamic search results exist", () => { - const result = getContextMenuOptions("commit", "commit", mockT, null, mockQueryItems, mockDynamicSearchResults) + const result = getContextMenuOptions("commit", "commit", null, mockQueryItems, mockDynamicSearchResults) // Verify git results are included expect(result.some((item) => item.type === ContextMenuOptionType.Git)).toBe(true) @@ -328,7 +320,7 @@ describe("getContextMenuOptions", () => { }, ] - const result = getContextMenuOptions("test", "test", mockT, null, mockQueryItems, duplicateSearchResults) + const result = getContextMenuOptions("test", "test", null, mockQueryItems, duplicateSearchResults) // Count occurrences of src/test.ts in results const duplicateCount = result.filter( @@ -348,7 +340,6 @@ describe("getContextMenuOptions", () => { const result = getContextMenuOptions( "nonexistentquery123456", "nonexistentquery123456", - mockT, null, mockQueryItems, [], // Empty dynamic search results @@ -396,7 +387,7 @@ describe("getContextMenuOptions", () => { ] // Get results for "test" query - const result = getContextMenuOptions(testQuery, testQuery, mockT, null, testItems, testSearchResults) + const result = getContextMenuOptions(testQuery, testQuery, null, testItems, testSearchResults) // Verify we have results expect(result.length).toBeGreaterThan(0) @@ -442,17 +433,18 @@ describe("getContextMenuOptions", () => { }, ] - const result = getContextMenuOptions("/co", "/co", mockT, null, [], [], mockModes) + const result = getContextMenuOptions("/co", "/co", null, [], [], mockModes) - // Verify mode results are returned - expect(result[0].type).toBe(ContextMenuOptionType.Mode) - expect(result[0].value).toBe("code") + // Should have section header first, then mode results + expect(result[0].type).toBe(ContextMenuOptionType.SectionHeader) + expect(result[1].type).toBe(ContextMenuOptionType.Mode) + expect(result[1].value).toBe("code") }) it("should not process slash commands when query starts with slash but inputValue doesn't", () => { // Use a completely non-matching query to ensure we get NoResults // and provide empty query items to avoid any matches - const result = getContextMenuOptions("/nonexistentquery", "Hello /code", mockT, null, [], []) + const result = getContextMenuOptions("/nonexistentquery", "Hello /code", null, [], []) // Should not process as a mode command expect(result[0].type).not.toBe(ContextMenuOptionType.Mode) @@ -462,7 +454,7 @@ describe("getContextMenuOptions", () => { // --- Tests for Escaped Spaces (Focus on how paths are presented) --- it("should return search results with correct labels/descriptions (no escaping needed here)", () => { - const options = getContextMenuOptions("@search", "search", mockT, null, mockQueryItems, mockSearchResults) + const options = getContextMenuOptions("@search", "search", null, mockQueryItems, mockSearchResults) const fileResult = options.find((o) => o.label === "search result spaces.ts") expect(fileResult).toBeDefined() // Value should be the normalized path, description might be the same or label @@ -475,7 +467,7 @@ describe("getContextMenuOptions", () => { }) it("should return query items (like opened files) with correct labels/descriptions", () => { - const options = getContextMenuOptions("open", "@open", mockT, null, mockQueryItems, []) + const options = getContextMenuOptions("open", "@open", null, mockQueryItems, []) const openedFile = options.find((o) => o.label === "open file.ts") expect(openedFile).toBeDefined() expect(openedFile?.value).toBe("src/open file.ts") @@ -492,7 +484,7 @@ describe("getContextMenuOptions", () => { ] // The formatting happens in getContextMenuOptions when converting search results to menu items - const formattedItems = getContextMenuOptions("spaces", "@spaces", mockT, null, [], searchResults) + const formattedItems = getContextMenuOptions("spaces", "@spaces", null, [], searchResults) // Verify we get some results back that aren't "No Results" expect(formattedItems.length).toBeGreaterThan(0) diff --git a/webview-ui/src/utils/context-mentions.ts b/webview-ui/src/utils/context-mentions.ts index 50ab3cf12b3..fb71e7e2a91 100644 --- a/webview-ui/src/utils/context-mentions.ts +++ b/webview-ui/src/utils/context-mentions.ts @@ -107,6 +107,7 @@ export enum ContextMenuOptionType { NoResults = "noResults", Mode = "mode", // Add mode type Command = "command", // Add command type + SectionHeader = "sectionHeader", // Add section header type } export interface ContextMenuQueryItem { @@ -115,12 +116,13 @@ export interface ContextMenuQueryItem { label?: string description?: string icon?: string + slashCommand?: string + secondaryText?: string } export function getContextMenuOptions( query: string, inputValue: string, - t: (key: string, options?: { name?: string }) => string, selectedType: ContextMenuOptionType | null = null, queryItems: ContextMenuQueryItem[], dynamicSearchResults: SearchResult[] = [], @@ -132,7 +134,42 @@ export function getContextMenuOptions( const slashQuery = query.slice(1) const results: ContextMenuQueryItem[] = [] - // Add mode suggestions + // Add command suggestions first (prioritize commands at the top) + if (commands?.length) { + // Create searchable strings array for fzf + const searchableCommands = commands.map((command) => ({ + original: command, + searchStr: command.name, + })) + + // Initialize fzf instance for fuzzy search + const fzf = new Fzf(searchableCommands, { + selector: (item) => item.searchStr, + }) + + // Get fuzzy matching commands + const matchingCommands = slashQuery + ? fzf.find(slashQuery).map((result) => ({ + type: ContextMenuOptionType.Command, + value: result.item.original.name, + slashCommand: `/${result.item.original.name}`, + })) + : commands.map((command) => ({ + type: ContextMenuOptionType.Command, + value: command.name, + slashCommand: `/${command.name}`, + })) + + if (matchingCommands.length > 0) { + results.push({ + type: ContextMenuOptionType.SectionHeader, + label: "Custom Commands", + }) + results.push(...matchingCommands) + } + } + + // Add mode suggestions second if (modes?.length) { // Create searchable strings array for fzf const searchableItems = modes.map((mode) => ({ @@ -150,50 +187,23 @@ export function getContextMenuOptions( ? fzf.find(slashQuery).map((result) => ({ type: ContextMenuOptionType.Mode, value: result.item.original.slug, - label: result.item.original.name, + slashCommand: `/${result.item.original.slug}`, description: getModeDescription(result.item.original), })) : modes.map((mode) => ({ type: ContextMenuOptionType.Mode, value: mode.slug, - label: mode.name, + slashCommand: `/${mode.slug}`, description: getModeDescription(mode), })) - results.push(...matchingModes) - } - - // Add command suggestions - if (commands?.length) { - // Create searchable strings array for fzf - const searchableCommands = commands.map((command) => ({ - original: command, - searchStr: command.name, - })) - - // Initialize fzf instance for fuzzy search - const fzf = new Fzf(searchableCommands, { - selector: (item) => item.searchStr, - }) - - // Get fuzzy matching commands - const matchingCommands = slashQuery - ? fzf.find(slashQuery).map((result) => ({ - type: ContextMenuOptionType.Command, - value: result.item.original.name, - label: result.item.original.name, - description: t("chat:command.triggerDescription", { name: result.item.original.name }), - icon: "$(play)", - })) - : commands.map((command) => ({ - type: ContextMenuOptionType.Command, - value: command.name, - label: command.name, - description: t("chat:command.triggerDescription", { name: command.name }), - icon: "$(play)", - })) - - results.push(...matchingCommands) + if (matchingModes.length > 0) { + results.push({ + type: ContextMenuOptionType.SectionHeader, + label: "Modes", + }) + results.push(...matchingModes) + } } return results.length > 0 ? results : [{ type: ContextMenuOptionType.NoResults }] From 5da23f9a60e167503e8f90840eff5ba597643eb8 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Mon, 28 Jul 2025 12:42:09 -0400 Subject: [PATCH 4/6] Update webview-ui/src/i18n/locales/ca/chat.json Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> --- webview-ui/src/i18n/locales/ca/chat.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json index 1a7f1745b83..b20b280ce67 100644 --- a/webview-ui/src/i18n/locales/ca/chat.json +++ b/webview-ui/src/i18n/locales/ca/chat.json @@ -108,7 +108,7 @@ "stopTts": "Atura la síntesi de veu", "typeMessage": "Escriu un missatge...", "typeTask": "Escriu la teva tasca aquí...", - "addContext": "@ per afegir context, / per a comandos", + "addContext": "@ per afegir context, / per a comandes", "dragFiles": "manté premut shift per arrossegar fitxers", "dragFilesImages": "manté premut shift per arrossegar fitxers/imatges", "enhancePromptDescription": "El botó 'Millora la sol·licitud' ajuda a millorar la teva sol·licitud proporcionant context addicional, aclariments o reformulacions. Prova d'escriure una sol·licitud aquí i fes clic al botó de nou per veure com funciona.", From eb8abca47ac42e1909064a9f7ee39a8ca83c04c2 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Mon, 28 Jul 2025 12:43:19 -0400 Subject: [PATCH 5/6] Update webview-ui/src/i18n/locales/fr/chat.json Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> --- webview-ui/src/i18n/locales/fr/chat.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json index 73efd55d6f0..a8b022f2398 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -364,7 +364,7 @@ "newGlobalCommandPlaceholder": "Nouvelle commande globale...", "newWorkspaceCommandPlaceholder": "Nouvelle commande de l'espace de travail...", "deleteDialog": { - "title": "Supprimer la Commande", + "title": "Supprimer la commande", "description": "Êtes-vous sûr de vouloir supprimer la commande \"{{name}}\" ? Cette action ne peut pas être annulée.", "cancel": "Annuler", "confirm": "Supprimer" From cd35f757518dd91e35a060a908104952de954f6a Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Mon, 28 Jul 2025 14:59:37 -0400 Subject: [PATCH 6/6] More translations --- src/core/webview/webviewMessageHandler.ts | 8 ++++---- src/i18n/locales/ca/common.json | 4 ++++ src/i18n/locales/de/common.json | 4 ++++ src/i18n/locales/en/common.json | 4 ++++ src/i18n/locales/es/common.json | 4 ++++ src/i18n/locales/fr/common.json | 4 ++++ src/i18n/locales/hi/common.json | 4 ++++ src/i18n/locales/id/common.json | 4 ++++ src/i18n/locales/it/common.json | 4 ++++ src/i18n/locales/ja/common.json | 4 ++++ src/i18n/locales/ko/common.json | 4 ++++ src/i18n/locales/nl/common.json | 4 ++++ src/i18n/locales/pl/common.json | 4 ++++ src/i18n/locales/pt-BR/common.json | 4 ++++ src/i18n/locales/ru/common.json | 4 ++++ src/i18n/locales/tr/common.json | 4 ++++ src/i18n/locales/vi/common.json | 4 ++++ src/i18n/locales/zh-CN/common.json | 4 ++++ src/i18n/locales/zh-TW/common.json | 4 ++++ 19 files changed, 76 insertions(+), 4 deletions(-) diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index 7e25c5b5e8e..63c10ff790b 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -2441,7 +2441,7 @@ export const webviewMessageHandler = async ( // Project commands const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath if (!workspaceRoot) { - vscode.window.showErrorMessage("No workspace folder found for project command") + vscode.window.showErrorMessage(t("common:errors.no_workspace_for_project_command")) break } commandsDir = path.join(workspaceRoot, ".roo", "commands") @@ -2504,12 +2504,12 @@ export const webviewMessageHandler = async ( .then(() => true) .catch(() => false) ) { - vscode.window.showErrorMessage(`Command "${commandName}" already exists`) + vscode.window.showErrorMessage(t("common:errors.command_already_exists", { commandName })) break } // Create the command file with template content - const templateContent = "This is a new slash command. Edit this file to customize the command behavior." + const templateContent = t("common:errors.command_template_content") await fs.writeFile(filePath, templateContent, "utf8") provider.log(`Created new command file: ${filePath}`) @@ -2531,7 +2531,7 @@ export const webviewMessageHandler = async ( }) } catch (error) { provider.log(`Error creating command: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`) - vscode.window.showErrorMessage("Failed to create command") + vscode.window.showErrorMessage(t("common:errors.create_command_failed")) } break } diff --git a/src/i18n/locales/ca/common.json b/src/i18n/locales/ca/common.json index fe2ac216502..6cd97472efd 100644 --- a/src/i18n/locales/ca/common.json +++ b/src/i18n/locales/ca/common.json @@ -77,6 +77,10 @@ "command_not_found": "Ordre '{{name}}' no trobada", "open_command_file": "Error en obrir el fitxer d'ordres", "delete_command": "Error en eliminar l'ordre", + "no_workspace_for_project_command": "No s'ha trobat cap carpeta d'espai de treball per a l'ordre del projecte", + "command_already_exists": "L'ordre \"{{commandName}}\" ja existeix", + "create_command_failed": "Error en crear l'ordre", + "command_template_content": "Aquesta és una nova ordre slash. Edita aquest fitxer per personalitzar el comportament de l'ordre.", "claudeCode": { "processExited": "El procés Claude Code ha sortit amb codi {{exitCode}}.", "errorOutput": "Sortida d'error: {{output}}", diff --git a/src/i18n/locales/de/common.json b/src/i18n/locales/de/common.json index f68981ed42f..92dc790e1f9 100644 --- a/src/i18n/locales/de/common.json +++ b/src/i18n/locales/de/common.json @@ -74,6 +74,10 @@ "command_not_found": "Befehl '{{name}}' nicht gefunden", "open_command_file": "Fehler beim Öffnen der Befehlsdatei", "delete_command": "Fehler beim Löschen des Befehls", + "no_workspace_for_project_command": "Kein Arbeitsbereich-Ordner für Projektbefehl gefunden", + "command_already_exists": "Befehl \"{{commandName}}\" existiert bereits", + "create_command_failed": "Fehler beim Erstellen des Befehls", + "command_template_content": "Dies ist ein neuer Slash-Befehl. Bearbeite diese Datei, um das Befehlsverhalten anzupassen.", "claudeCode": { "processExited": "Claude Code Prozess wurde mit Code {{exitCode}} beendet.", "errorOutput": "Fehlerausgabe: {{output}}", diff --git a/src/i18n/locales/en/common.json b/src/i18n/locales/en/common.json index 9d05612d5b2..37a40d8742c 100644 --- a/src/i18n/locales/en/common.json +++ b/src/i18n/locales/en/common.json @@ -74,6 +74,10 @@ "command_not_found": "Command '{{name}}' not found", "open_command_file": "Failed to open command file", "delete_command": "Failed to delete command", + "no_workspace_for_project_command": "No workspace folder found for project command", + "command_already_exists": "Command \"{{commandName}}\" already exists", + "create_command_failed": "Failed to create command", + "command_template_content": "This is a new slash command. Edit this file to customize the command behavior.", "claudeCode": { "processExited": "Claude Code process exited with code {{exitCode}}.", "errorOutput": "Error output: {{output}}", diff --git a/src/i18n/locales/es/common.json b/src/i18n/locales/es/common.json index d033f904d4b..0972b8a77e1 100644 --- a/src/i18n/locales/es/common.json +++ b/src/i18n/locales/es/common.json @@ -74,6 +74,10 @@ "command_not_found": "Comando '{{name}}' no encontrado", "open_command_file": "Error al abrir el archivo de comandos", "delete_command": "Error al eliminar el comando", + "no_workspace_for_project_command": "No se encontró carpeta de espacio de trabajo para comando de proyecto", + "command_already_exists": "El comando \"{{commandName}}\" ya existe", + "create_command_failed": "Error al crear comando", + "command_template_content": "Este es un nuevo comando slash. Edita este archivo para personalizar el comportamiento del comando.", "claudeCode": { "processExited": "El proceso de Claude Code terminó con código {{exitCode}}.", "errorOutput": "Salida de error: {{output}}", diff --git a/src/i18n/locales/fr/common.json b/src/i18n/locales/fr/common.json index 6460f12ceec..d6b6f1faf3f 100644 --- a/src/i18n/locales/fr/common.json +++ b/src/i18n/locales/fr/common.json @@ -74,6 +74,10 @@ "command_not_found": "Commande '{{name}}' introuvable", "open_command_file": "Échec de l'ouverture du fichier de commande", "delete_command": "Échec de la suppression de la commande", + "no_workspace_for_project_command": "Aucun dossier d'espace de travail trouvé pour la commande de projet", + "command_already_exists": "La commande \"{{commandName}}\" existe déjà", + "create_command_failed": "Échec de la création de la commande", + "command_template_content": "Ceci est une nouvelle commande slash. Modifie ce fichier pour personnaliser le comportement de la commande.", "claudeCode": { "processExited": "Le processus Claude Code s'est terminé avec le code {{exitCode}}.", "errorOutput": "Sortie d'erreur : {{output}}", diff --git a/src/i18n/locales/hi/common.json b/src/i18n/locales/hi/common.json index 6a36ed95c22..935bc3b5f66 100644 --- a/src/i18n/locales/hi/common.json +++ b/src/i18n/locales/hi/common.json @@ -74,6 +74,10 @@ "command_not_found": "कमांड '{{name}}' नहीं मिला", "open_command_file": "कमांड फ़ाइल खोलने में विफल", "delete_command": "कमांड हटाने में विफल", + "no_workspace_for_project_command": "प्रोजेक्ट कमांड के लिए वर्कस्पेस फ़ोल्डर नहीं मिला", + "command_already_exists": "कमांड \"{{commandName}}\" पहले से मौजूद है", + "create_command_failed": "कमांड बनाने में विफल", + "command_template_content": "यह एक नया स्लैश कमांड है। कमांड व्यवहार को कस्टमाइज़ करने के लिए इस फ़ाइल को संपादित करें।", "claudeCode": { "processExited": "Claude Code प्रक्रिया कोड {{exitCode}} के साथ समाप्त हुई।", "errorOutput": "त्रुटि आउटपुट: {{output}}", diff --git a/src/i18n/locales/id/common.json b/src/i18n/locales/id/common.json index 2b9302ef541..71ed70fce86 100644 --- a/src/i18n/locales/id/common.json +++ b/src/i18n/locales/id/common.json @@ -74,6 +74,10 @@ "command_not_found": "Perintah '{{name}}' tidak ditemukan", "open_command_file": "Gagal membuka file perintah", "delete_command": "Gagal menghapus perintah", + "no_workspace_for_project_command": "Tidak ditemukan folder workspace untuk perintah proyek", + "command_already_exists": "Perintah \"{{commandName}}\" sudah ada", + "create_command_failed": "Gagal membuat perintah", + "command_template_content": "Ini adalah perintah slash baru. Edit file ini untuk menyesuaikan perilaku perintah.", "claudeCode": { "processExited": "Proses Claude Code keluar dengan kode {{exitCode}}.", "errorOutput": "Output error: {{output}}", diff --git a/src/i18n/locales/it/common.json b/src/i18n/locales/it/common.json index 91ea7484cfc..bcb5754f9b9 100644 --- a/src/i18n/locales/it/common.json +++ b/src/i18n/locales/it/common.json @@ -74,6 +74,10 @@ "command_not_found": "Comando '{{name}}' non trovato", "open_command_file": "Impossibile aprire il file di comando", "delete_command": "Impossibile eliminare il comando", + "no_workspace_for_project_command": "Nessuna cartella workspace trovata per il comando di progetto", + "command_already_exists": "Il comando \"{{commandName}}\" esiste già", + "create_command_failed": "Errore nella creazione del comando", + "command_template_content": "Questo è un nuovo comando slash. Modifica questo file per personalizzare il comportamento del comando.", "claudeCode": { "processExited": "Il processo Claude Code è terminato con codice {{exitCode}}.", "errorOutput": "Output di errore: {{output}}", diff --git a/src/i18n/locales/ja/common.json b/src/i18n/locales/ja/common.json index 3380bfe2e08..2fb6a76b4cc 100644 --- a/src/i18n/locales/ja/common.json +++ b/src/i18n/locales/ja/common.json @@ -74,6 +74,10 @@ "command_not_found": "コマンド '{{name}}' が見つかりません", "open_command_file": "コマンドファイルを開けませんでした", "delete_command": "コマンドの削除に失敗しました", + "no_workspace_for_project_command": "プロジェクトコマンド用のワークスペースフォルダが見つかりません", + "command_already_exists": "コマンド \"{{commandName}}\" は既に存在します", + "create_command_failed": "コマンドの作成に失敗しました", + "command_template_content": "これは新しいスラッシュコマンドです。このファイルを編集してコマンドの動作をカスタマイズしてください。", "claudeCode": { "processExited": "Claude Code プロセスがコード {{exitCode}} で終了しました。", "errorOutput": "エラー出力:{{output}}", diff --git a/src/i18n/locales/ko/common.json b/src/i18n/locales/ko/common.json index 901ad478f1a..e3ee789b047 100644 --- a/src/i18n/locales/ko/common.json +++ b/src/i18n/locales/ko/common.json @@ -74,6 +74,10 @@ "command_not_found": "'{{name}}' 명령을 찾을 수 없습니다", "open_command_file": "명령 파일을 열 수 없습니다", "delete_command": "명령 삭제 실패", + "no_workspace_for_project_command": "프로젝트 명령용 워크스페이스 폴더를 찾을 수 없습니다", + "command_already_exists": "명령 \"{{commandName}}\"이(가) 이미 존재합니다", + "create_command_failed": "명령 생성에 실패했습니다", + "command_template_content": "이것은 새로운 슬래시 명령입니다. 이 파일을 편집하여 명령 동작을 사용자 정의하세요.", "claudeCode": { "processExited": "Claude Code 프로세스가 코드 {{exitCode}}로 종료되었습니다.", "errorOutput": "오류 출력: {{output}}", diff --git a/src/i18n/locales/nl/common.json b/src/i18n/locales/nl/common.json index 001c8573bd5..8cf11bf4351 100644 --- a/src/i18n/locales/nl/common.json +++ b/src/i18n/locales/nl/common.json @@ -74,6 +74,10 @@ "command_not_found": "Opdracht '{{name}}' niet gevonden", "open_command_file": "Kan opdrachtbestand niet openen", "delete_command": "Kan opdracht niet verwijderen", + "no_workspace_for_project_command": "Geen werkruimtemap gevonden voor projectopdracht", + "command_already_exists": "Opdracht \"{{commandName}}\" bestaat al", + "create_command_failed": "Kan opdracht niet aanmaken", + "command_template_content": "Dit is een nieuwe slash-opdracht. Bewerk dit bestand om het opdrachtgedrag aan te passen.", "claudeCode": { "processExited": "Claude Code proces beëindigd met code {{exitCode}}.", "errorOutput": "Foutuitvoer: {{output}}", diff --git a/src/i18n/locales/pl/common.json b/src/i18n/locales/pl/common.json index bb8f267ef55..d1872ce628b 100644 --- a/src/i18n/locales/pl/common.json +++ b/src/i18n/locales/pl/common.json @@ -74,6 +74,10 @@ "command_not_found": "Polecenie '{{name}}' nie zostało znalezione", "open_command_file": "Nie udało się otworzyć pliku polecenia", "delete_command": "Nie udało się usunąć polecenia", + "no_workspace_for_project_command": "Nie znaleziono folderu obszaru roboczego dla polecenia projektu", + "command_already_exists": "Polecenie \"{{commandName}}\" już istnieje", + "create_command_failed": "Nie udało się utworzyć polecenia", + "command_template_content": "To jest nowe polecenie slash. Edytuj ten plik, aby dostosować zachowanie polecenia.", "claudeCode": { "processExited": "Proces Claude Code zakończył się kodem {{exitCode}}.", "errorOutput": "Wyjście błędu: {{output}}", diff --git a/src/i18n/locales/pt-BR/common.json b/src/i18n/locales/pt-BR/common.json index 9f947f7ec7a..fd16391491f 100644 --- a/src/i18n/locales/pt-BR/common.json +++ b/src/i18n/locales/pt-BR/common.json @@ -78,6 +78,10 @@ "command_not_found": "Comando '{{name}}' não encontrado", "open_command_file": "Falha ao abrir arquivo de comando", "delete_command": "Falha ao excluir comando", + "no_workspace_for_project_command": "Nenhuma pasta de workspace encontrada para comando de projeto", + "command_already_exists": "Comando \"{{commandName}}\" já existe", + "create_command_failed": "Falha ao criar comando", + "command_template_content": "Este é um novo comando slash. Edite este arquivo para personalizar o comportamento do comando.", "claudeCode": { "processExited": "O processo Claude Code saiu com código {{exitCode}}.", "errorOutput": "Saída de erro: {{output}}", diff --git a/src/i18n/locales/ru/common.json b/src/i18n/locales/ru/common.json index 20639106a88..05ad66aedcb 100644 --- a/src/i18n/locales/ru/common.json +++ b/src/i18n/locales/ru/common.json @@ -74,6 +74,10 @@ "command_not_found": "Команда '{{name}}' не найдена", "open_command_file": "Не удалось открыть файл команды", "delete_command": "Не удалось удалить команду", + "no_workspace_for_project_command": "Не найдена папка рабочего пространства для команды проекта", + "command_already_exists": "Команда \"{{commandName}}\" уже существует", + "create_command_failed": "Не удалось создать команду", + "command_template_content": "Это новая slash-команда. Отредактируйте этот файл, чтобы настроить поведение команды.", "claudeCode": { "processExited": "Процесс Claude Code завершился с кодом {{exitCode}}.", "errorOutput": "Вывод ошибки: {{output}}", diff --git a/src/i18n/locales/tr/common.json b/src/i18n/locales/tr/common.json index 6c13467eac0..66d7666d4ad 100644 --- a/src/i18n/locales/tr/common.json +++ b/src/i18n/locales/tr/common.json @@ -74,6 +74,10 @@ "command_not_found": "'{{name}}' komutu bulunamadı", "open_command_file": "Komut dosyası açılamadı", "delete_command": "Komut silinemedi", + "no_workspace_for_project_command": "Proje komutu için çalışma alanı klasörü bulunamadı", + "command_already_exists": "\"{{commandName}}\" komutu zaten mevcut", + "create_command_failed": "Komut oluşturulamadı", + "command_template_content": "Bu yeni bir slash komutudur. Komut davranışını özelleştirmek için bu dosyayı düzenleyin.", "claudeCode": { "processExited": "Claude Code işlemi {{exitCode}} koduyla çıktı.", "errorOutput": "Hata çıktısı: {{output}}", diff --git a/src/i18n/locales/vi/common.json b/src/i18n/locales/vi/common.json index 606ce08b8e2..59e1e3d14b4 100644 --- a/src/i18n/locales/vi/common.json +++ b/src/i18n/locales/vi/common.json @@ -74,6 +74,10 @@ "command_not_found": "Không tìm thấy lệnh '{{name}}'", "open_command_file": "Không thể mở tệp lệnh", "delete_command": "Không thể xóa lệnh", + "no_workspace_for_project_command": "Không tìm thấy thư mục workspace cho lệnh dự án", + "command_already_exists": "Lệnh \"{{commandName}}\" đã tồn tại", + "create_command_failed": "Không thể tạo lệnh", + "command_template_content": "Đây là một lệnh slash mới. Chỉnh sửa tệp này để tùy chỉnh hành vi của lệnh.", "claudeCode": { "processExited": "Tiến trình Claude Code thoát với mã {{exitCode}}.", "errorOutput": "Đầu ra lỗi: {{output}}", diff --git a/src/i18n/locales/zh-CN/common.json b/src/i18n/locales/zh-CN/common.json index c8151786c89..97f7c74bb34 100644 --- a/src/i18n/locales/zh-CN/common.json +++ b/src/i18n/locales/zh-CN/common.json @@ -79,6 +79,10 @@ "command_not_found": "未找到命令 '{{name}}'", "open_command_file": "打开命令文件失败", "delete_command": "删除命令失败", + "no_workspace_for_project_command": "未找到项目命令的工作区文件夹", + "command_already_exists": "命令 \"{{commandName}}\" 已存在", + "create_command_failed": "创建命令失败", + "command_template_content": "这是一个新的斜杠命令。编辑此文件以自定义命令行为。", "claudeCode": { "processExited": "Claude Code 进程退出,退出码:{{exitCode}}。", "errorOutput": "错误输出:{{output}}", diff --git a/src/i18n/locales/zh-TW/common.json b/src/i18n/locales/zh-TW/common.json index 1b45231a5f0..2b15dd40b94 100644 --- a/src/i18n/locales/zh-TW/common.json +++ b/src/i18n/locales/zh-TW/common.json @@ -73,6 +73,10 @@ "command_not_found": "找不到指令 '{{name}}'", "open_command_file": "開啟指令檔案失敗", "delete_command": "刪除指令失敗", + "no_workspace_for_project_command": "找不到專案指令的工作區資料夾", + "command_already_exists": "指令 \"{{commandName}}\" 已存在", + "create_command_failed": "建立指令失敗", + "command_template_content": "這是一個新的斜線指令。編輯此檔案以自訂指令行為。", "claudeCode": { "processExited": "Claude Code 程序退出,退出碼:{{exitCode}}。", "errorOutput": "錯誤輸出:{{output}}",