diff --git a/.roomodes b/.roomodes index f10ca32056..8dc2492f64 100644 --- a/.roomodes +++ b/.roomodes @@ -1,15 +1,5 @@ { "customModes": [ - { - "slug": "translate", - "name": "Translate", - "roleDefinition": "You are Roo, a linguistic specialist focused on translating and managing localization files. Your responsibility is to help maintain and update translation files for the application, ensuring consistency and accuracy across all language resources.", - "groups": [ - "read", - ["edit", { "fileRegex": "src/i18n/locales/", "description": "Translation files only" }] - ], - "customInstructions": "When translating content:\n- Maintain consistent terminology across all translations\n- Respect the JSON structure of translation files\n- Consider context when translating UI strings\n- Watch for placeholders (like {{variable}}) and preserve them in translations\n- Be mindful of text length in UI elements when translating to languages that might require more characters\n- If you need context for a translation, use read_file to examine the components using these strings" - }, { "slug": "test", "name": "Test", @@ -18,12 +8,32 @@ "read", "browser", "command", - ["edit", { - "fileRegex": "(__tests__/.*|__mocks__/.*|\\.test\\.(ts|tsx|js|jsx)$|/test/.*|jest\\.config\\.(js|ts)$)", - "description": "Test files, mocks, and Jest configuration" - }] + [ + "edit", + { + "fileRegex": "(__tests__/.*|__mocks__/.*|\\.test\\.(ts|tsx|js|jsx)$|/test/.*|jest\\.config\\.(js|ts)$)", + "description": "Test files, mocks, and Jest configuration" + } + ] ], "customInstructions": "When writing tests:\n- Always use describe/it blocks for clear test organization\n- Include meaningful test descriptions\n- Use beforeEach/afterEach for proper test isolation\n- Implement proper error cases\n- Add JSDoc comments for complex test scenarios\n- Ensure mocks are properly typed\n- Verify both positive and negative test cases" + }, + { + "slug": "translate", + "name": "Translate", + "roleDefinition": "You are Roo, a linguistic specialist focused on translating and managing localization files. Your responsibility is to help maintain and update translation files for the application, ensuring consistency and accuracy across all language resources.", + "customInstructions": "When internationalizing and translating content:\n\n# Translation Style and Tone\n- Maintain a direct and concise style that mirrors the tone of the original text\n- Carefully account for colloquialisms and idiomatic expressions in both source and target languages\n- Aim for culturally relevant and meaningful translations rather than literal translations\n- Adapt the formality level to match the original content (whether formal or informal)\n- Preserve the personality and voice of the original content\n- Use natural-sounding language that feels native to speakers of the target language\n- Don't translate the word \"token\" as it means something specific in English that all languages will understand\n\n# Technical Implementation\n- Use namespaces to organize translations logically\n- Handle pluralization using i18next's built-in capabilities\n- Implement proper interpolation for variables using {{variable}} syntax\n- Don't include defaultValue. The `en` translations are the fallback.\n\n# Quality Assurance\n- Maintain consistent terminology across all translations\n- Respect the JSON structure of translation files\n- Watch for placeholders and preserve them in translations\n- Be mindful of text length in UI elements when translating to languages that might require more characters\n- Use context-aware translations when the same string has different meanings\n\n# Supported Languages\n- Localize all strings into the following locale files: ar, ca, cs, de, en, es, fr, hi, hu, it, ja, ko, pl, pt, pt-BR, ru, tr, zh-CN, zh-TW", + "groups": [ + "read", + [ + "edit", + { + "fileRegex": "(.*\\.(md|ts|tsx|js|jsx)$|.*\\.json$)", + "description": "Source code, translation files, and documentation" + } + ] + ], + "source": "project" } ] } \ No newline at end of file diff --git a/webview-ui/src/components/chat/AutoApproveMenu.tsx b/webview-ui/src/components/chat/AutoApproveMenu.tsx index 692cf1d44c..b3a55c94ec 100644 --- a/webview-ui/src/components/chat/AutoApproveMenu.tsx +++ b/webview-ui/src/components/chat/AutoApproveMenu.tsx @@ -1,6 +1,7 @@ import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react" import { useCallback, useState } from "react" import { useExtensionState } from "../../context/ExtensionStateContext" +import { useAppTranslation } from "../../i18n/TranslationContext" import { vscode } from "../../utils/vscode" interface AutoApproveAction { @@ -38,63 +39,64 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => { setAutoApprovalEnabled, } = useExtensionState() + const { t } = useAppTranslation() + const actions: AutoApproveAction[] = [ { id: "readFiles", - label: "Read files and directories", - shortName: "Read", + label: t("chat:autoApprove.actions.readFiles.label"), + shortName: t("chat:autoApprove.actions.readFiles.shortName"), enabled: alwaysAllowReadOnly ?? false, - description: "Allows access to read any file on your computer.", + description: t("chat:autoApprove.actions.readFiles.description"), }, { id: "editFiles", - label: "Edit files", - shortName: "Edit", + label: t("chat:autoApprove.actions.editFiles.label"), + shortName: t("chat:autoApprove.actions.editFiles.shortName"), enabled: alwaysAllowWrite ?? false, - description: "Allows modification of any files on your computer.", + description: t("chat:autoApprove.actions.editFiles.description"), }, { id: "executeCommands", - label: "Execute approved commands", - shortName: "Commands", + label: t("chat:autoApprove.actions.executeCommands.label"), + shortName: t("chat:autoApprove.actions.executeCommands.shortName"), enabled: alwaysAllowExecute ?? false, - description: - "Allows execution of approved terminal commands. You can configure this in the settings panel.", + description: t("chat:autoApprove.actions.executeCommands.description"), }, { id: "useBrowser", - label: "Use the browser", - shortName: "Browser", + label: t("chat:autoApprove.actions.useBrowser.label"), + shortName: t("chat:autoApprove.actions.useBrowser.shortName"), enabled: alwaysAllowBrowser ?? false, - description: "Allows ability to launch and interact with any website in a headless browser.", + description: t("chat:autoApprove.actions.useBrowser.description"), }, { id: "useMcp", - label: "Use MCP servers", - shortName: "MCP", + label: t("chat:autoApprove.actions.useMcp.label"), + shortName: t("chat:autoApprove.actions.useMcp.shortName"), enabled: alwaysAllowMcp ?? false, - description: "Allows use of configured MCP servers which may modify filesystem or interact with APIs.", + description: t("chat:autoApprove.actions.useMcp.description"), }, { id: "switchModes", - label: "Switch modes", - shortName: "Modes", + label: t("chat:autoApprove.actions.switchModes.label"), + shortName: t("chat:autoApprove.actions.switchModes.shortName"), enabled: alwaysAllowModeSwitch ?? false, - description: "Allows automatic switching between different modes without requiring approval.", + description: t("chat:autoApprove.actions.switchModes.description"), }, { id: "subtasks", - label: "Create & complete subtasks", - shortName: "Subtasks", + label: t("chat:autoApprove.actions.subtasks.label"), + shortName: t("chat:autoApprove.actions.subtasks.shortName"), enabled: alwaysAllowSubtasks ?? false, - description: "Allow creation and completion of subtasks without requiring approval.", + description: t("chat:autoApprove.actions.subtasks.description"), }, { id: "retryRequests", - label: "Retry failed requests", - shortName: "Retries", + label: t("chat:autoApprove.actions.retryRequests.label"), + shortName: t("chat:autoApprove.actions.retryRequests.shortName"), enabled: alwaysApproveResubmit ?? false, - description: "Automatically retry failed API requests when the provider returns an error response.", + description: t("chat:autoApprove.actions.retryRequests.description"), }, ] @@ -211,7 +213,7 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => { color: "var(--vscode-foreground)", flexShrink: 0, }}> - Auto-approve: + {t("chat:autoApprove.title")} { flex: 1, minWidth: 0, }}> - {enabledActionsList || "None"} + {enabledActionsList || t("chat:autoApprove.none")} { color: "var(--vscode-descriptionForeground)", fontSize: "12px", }}> - Auto-approve allows Roo Code to perform actions without asking for permission. Only enable for - actions you fully trust. + {t("chat:autoApprove.description")} {actions.map((action) => (
diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index 1002788dbc..be0ddd0b0e 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -20,6 +20,7 @@ import Thumbnails from "../common/Thumbnails" import { convertToMentionPath } from "../../utils/path-mentions" import { MAX_IMAGES_PER_MESSAGE } from "./ChatView" import ContextMenu from "./ContextMenu" +import { useAppTranslation } from "../../i18n/TranslationContext" interface ChatTextAreaProps { inputValue: string @@ -56,6 +57,7 @@ const ChatTextArea = forwardRef( }, ref, ) => { + const { t } = useAppTranslation() const { filePaths, openedTabs, currentApiConfigName, listApiConfigMeta, customModes, cwd } = useExtensionState() const [gitCommits, setGitCommits] = useState([]) const [showDropdown, setShowDropdown] = useState(false) @@ -133,12 +135,11 @@ const ChatTextArea = forwardRef( } vscode.postMessage(message) } else { - const promptDescription = - "The 'Enhance Prompt' button helps improve your prompt by providing additional context, clarification, or rephrasing. Try typing a prompt in here and clicking the button again to see how it works." + const promptDescription = t("chat:enhancePromptDescription") setInputValue(promptDescription) } } - }, [inputValue, textAreaDisabled, setInputValue]) + }, [inputValue, textAreaDisabled, setInputValue, t]) const queryItems = useMemo(() => { return [ @@ -475,7 +476,7 @@ const ChatTextArea = forwardRef( const reader = new FileReader() reader.onloadend = () => { if (reader.error) { - console.error("Error reading file:", reader.error) + console.error(t("chat:errorReadingFile"), reader.error) resolve(null) } else { const result = reader.result @@ -490,11 +491,11 @@ const ChatTextArea = forwardRef( if (dataUrls.length > 0) { setSelectedImages((prevImages) => [...prevImages, ...dataUrls].slice(0, MAX_IMAGES_PER_MESSAGE)) } else { - console.warn("No valid images were processed") + console.warn(t("chat:noValidImages")) } } }, - [shouldDisableImages, setSelectedImages, cursorPosition, setInputValue, inputValue], + [shouldDisableImages, setSelectedImages, cursorPosition, setInputValue, inputValue, t], ) const handleThumbnailsHeightChange = useCallback((height: number) => { @@ -611,7 +612,7 @@ const ChatTextArea = forwardRef( const reader = new FileReader() reader.onloadend = () => { if (reader.error) { - console.error("Error reading file:", reader.error) + console.error(t("chat:errorReadingFile"), reader.error) resolve(null) } else { const result = reader.result @@ -634,7 +635,7 @@ const ChatTextArea = forwardRef( }) } } else { - console.warn("No valid images were processed") + console.warn(t("chat:noValidImages")) } } }} @@ -779,7 +780,7 @@ const ChatTextArea = forwardRef( ( // Add separator { value: "sep-1", - label: "Separator", + label: t("chat:separator"), type: DropdownOptionType.SEPARATOR, }, // Add Edit option { value: "promptsButtonClicked", - label: "Edit...", + label: t("chat:edit"), type: DropdownOptionType.ACTION, }, ]} @@ -829,7 +830,7 @@ const ChatTextArea = forwardRef( ({ @@ -840,13 +841,13 @@ const ChatTextArea = forwardRef( // Add separator { value: "sep-2", - label: "Separator", + label: t("chat:separator"), type: DropdownOptionType.SEPARATOR, }, // Add Edit option { value: "settingsButtonClicked", - label: "Edit...", + label: t("chat:edit"), type: DropdownOptionType.ACTION, }, ]} @@ -886,7 +887,7 @@ const ChatTextArea = forwardRef( role="button" aria-label="enhance prompt" data-testid="enhance-prompt-button" - title="Enhance prompt with additional context" + title={t("chat:enhancePrompt")} className={`input-icon-button ${ textAreaDisabled ? "disabled" : "" } codicon codicon-sparkle`} @@ -899,13 +900,13 @@ const ChatTextArea = forwardRef( className={`input-icon-button ${ shouldDisableImages ? "disabled" : "" } codicon codicon-device-camera`} - title="Add images to message" + title={t("chat:addImages")} onClick={() => !shouldDisableImages && onSelectImages()} style={{ fontSize: 16.5 }} /> !textAreaDisabled && onSend()} style={{ fontSize: 15 }} /> diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index fc38a6f5f2..d0569e6f91 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -42,9 +42,10 @@ interface ChatViewProps { export const MAX_IMAGES_PER_MESSAGE = 20 // Anthropic limits to 20 images const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0 -const modeShortcutText = `${isMac ? "⌘" : "Ctrl"} + . for next mode` const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryView }: ChatViewProps) => { + const { t } = useAppTranslation() + const modeShortcutText = `${isMac ? "⌘" : "Ctrl"} + . ${t("chat:forNextMode")}` const { version, clineMessages: messages, @@ -67,8 +68,6 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie telemetrySetting, } = useExtensionState() - const { t } = useAppTranslation() - //const task = messages.length > 0 ? (messages[0].say === "task" ? messages[0] : undefined) : undefined) : undefined const task = useMemo(() => messages.at(0), [messages]) // leaving this less safe version here since if the first message is not a task, then the extension is in a bad state and needs to be debugged (see Cline.abort) const modifiedMessages = useMemo(() => combineApiRequests(combineCommandSequences(messages.slice(1))), [messages]) @@ -941,12 +940,10 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie [], ) - const placeholderText = useMemo(() => { - const baseText = task ? t("chat:typeMessage") : t("chat:typeTask") - const contextText = t("chat:addContext") - const imageText = shouldDisableImages ? `, ${t("chat:dragFiles")}` : `, ${t("chat:dragFilesImages")}` - return baseText + `\n(${contextText}${imageText})` - }, [task, shouldDisableImages, t]) + const baseText = task ? t("chat:typeMessage") : t("chat:typeTask") + const placeholderText = + baseText + + `\n(${t("chat:addContext")}${shouldDisableImages ? `, ${t("chat:dragFiles")}` : `, ${t("chat:dragFilesImages")}`})` const itemContent = useCallback( (index: number, messageOrGroup: ClineMessage | ClineMessage[]) => { diff --git a/webview-ui/src/components/history/CopyButton.tsx b/webview-ui/src/components/history/CopyButton.tsx index 0e693b4470..17964cc37e 100644 --- a/webview-ui/src/components/history/CopyButton.tsx +++ b/webview-ui/src/components/history/CopyButton.tsx @@ -3,6 +3,7 @@ import { useCallback } from "react" import { useClipboard } from "@/components/ui/hooks" import { Button } from "@/components/ui" import { cn } from "@/lib/utils" +import { useAppTranslation } from "@/i18n/TranslationContext" type CopyButtonProps = { itemTask: string @@ -10,6 +11,7 @@ type CopyButtonProps = { export const CopyButton = ({ itemTask }: CopyButtonProps) => { const { isCopied, copy } = useClipboard() + const { t } = useAppTranslation() const onCopy = useCallback( (e: React.MouseEvent) => { @@ -23,7 +25,7 @@ export const CopyButton = ({ itemTask }: CopyButtonProps) => { + diff --git a/webview-ui/src/components/history/ExportButton.tsx b/webview-ui/src/components/history/ExportButton.tsx index 6617e475bd..14b312470b 100644 --- a/webview-ui/src/components/history/ExportButton.tsx +++ b/webview-ui/src/components/history/ExportButton.tsx @@ -1,16 +1,21 @@ import { vscode } from "@/utils/vscode" import { Button } from "@/components/ui" +import { useAppTranslation } from "@/i18n/TranslationContext" -export const ExportButton = ({ itemId }: { itemId: string }) => ( - -) +export const ExportButton = ({ itemId }: { itemId: string }) => { + const { t } = useAppTranslation() + + return ( + + ) +} diff --git a/webview-ui/src/components/history/HistoryPreview.tsx b/webview-ui/src/components/history/HistoryPreview.tsx index f81d8ddacf..64af37ed64 100644 --- a/webview-ui/src/components/history/HistoryPreview.tsx +++ b/webview-ui/src/components/history/HistoryPreview.tsx @@ -5,24 +5,25 @@ import { formatLargeNumber, formatDate } from "@/utils/format" import { Button } from "@/components/ui" import { useExtensionState } from "../../context/ExtensionStateContext" +import { useAppTranslation } from "../../i18n/TranslationContext" import { CopyButton } from "./CopyButton" type HistoryPreviewProps = { showHistoryView: () => void } - const HistoryPreview = ({ showHistoryView }: HistoryPreviewProps) => { const { taskHistory } = useExtensionState() + const { t } = useAppTranslation() return (
- Recent Tasks + {t("history:recentTasks")}
{taskHistory.slice(0, 3).map((item) => ( @@ -50,22 +51,26 @@ const HistoryPreview = ({ showHistoryView }: HistoryPreviewProps) => {
- Tokens: ↑{formatLargeNumber(item.tokensIn || 0)} ↓ - {formatLargeNumber(item.tokensOut || 0)} + {t("history:tokens", { + in: formatLargeNumber(item.tokensIn || 0), + out: formatLargeNumber(item.tokensOut || 0), + })} {!!item.cacheWrites && ( <> {" • "} - Cache: +{formatLargeNumber(item.cacheWrites || 0)} →{" "} - {formatLargeNumber(item.cacheReads || 0)} + {t("history:cache", { + writes: formatLargeNumber(item.cacheWrites || 0), + reads: formatLargeNumber(item.cacheReads || 0), + })} )} {!!item.totalCost && ( <> {" • "} - API Cost: ${item.totalCost?.toFixed(4)} + {t("history:apiCost", { cost: item.totalCost?.toFixed(4) })} )}
diff --git a/webview-ui/src/components/history/HistoryView.tsx b/webview-ui/src/components/history/HistoryView.tsx index ec44f8eaca..c82fd0b92a 100644 --- a/webview-ui/src/components/history/HistoryView.tsx +++ b/webview-ui/src/components/history/HistoryView.tsx @@ -8,6 +8,7 @@ import { vscode } from "@/utils/vscode" import { formatLargeNumber, formatDate } from "@/utils/format" import { cn } from "@/lib/utils" import { Button } from "@/components/ui" +import { useAppTranslation } from "@/i18n/TranslationContext" import { Tab, TabContent, TabHeader } from "../common/Tab" import { useTaskSearch } from "./useTaskSearch" @@ -22,6 +23,7 @@ type SortOption = "newest" | "oldest" | "mostExpensive" | "mostTokens" | "mostRe const HistoryView = ({ onDone }: HistoryViewProps) => { const { tasks, searchQuery, setSearchQuery, sortOption, setSortOption, setLastNonRelevantSort } = useTaskSearch() + const { t } = useAppTranslation() const [deleteTaskId, setDeleteTaskId] = useState(null) @@ -29,13 +31,13 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
-

History

- Done +

{t("history:history")}

+ {t("history:done")}
{ const newValue = (e.target as HTMLInputElement)?.value @@ -70,15 +72,15 @@ const HistoryView = ({ onDone }: HistoryViewProps) => { value={sortOption} role="radiogroup" onChange={(e) => setSortOption((e.target as HTMLInputElement).value as SortOption)}> - Newest - Oldest - Most Expensive - Most Tokens + {t("history:newest")} + {t("history:oldest")} + {t("history:mostExpensive")} + {t("history:mostTokens")} - Most Relevant + {t("history:mostRelevant")}
@@ -132,7 +134,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {