From b446b1d986909c7924ce9b0260e3d5e984f07162 Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Mon, 25 Aug 2025 13:11:11 -0600 Subject: [PATCH] fix: internationalize hardcoded error messages - Added new error message keys to English locale files (tools.json and common.json) - Replaced hardcoded 'Roo tried to use' messages with i18n calls - Replaced other hardcoded error messages in say('error') calls - Updated test mocks to handle new i18n keys - Affected files: - Task.ts: Lines 1058, 2140 - writeToFileTool.ts: Line 148 - askFollowupQuestionTool.ts: Line 51 - applyDiffTool.ts: Line 86 - searchAndReplaceTool.ts: Lines 140, 159 - insertContentTool.ts: Line 90 - presentAssistantMessage.ts: Line 318 - useMcpToolTool.spec.ts: Test mock update This change ensures all error messages can be properly translated to the 17+ languages supported by Roo Code. --- .../presentAssistantMessage.ts | 6 ++++- src/core/task/Task.ts | 22 ++++++++++--------- .../tools/__tests__/useMcpToolTool.spec.ts | 7 ++++++ src/core/tools/applyDiffTool.ts | 3 ++- src/core/tools/askFollowupQuestionTool.ts | 3 ++- src/core/tools/insertContentTool.ts | 3 ++- src/core/tools/searchAndReplaceTool.ts | 10 +++++---- src/core/tools/writeToFileTool.ts | 8 ++++--- src/i18n/locales/en/common.json | 3 ++- src/i18n/locales/en/tools.json | 13 +++++++++++ 10 files changed, 56 insertions(+), 22 deletions(-) diff --git a/src/core/assistant-message/presentAssistantMessage.ts b/src/core/assistant-message/presentAssistantMessage.ts index a8b90728b1..874b33e1cf 100644 --- a/src/core/assistant-message/presentAssistantMessage.ts +++ b/src/core/assistant-message/presentAssistantMessage.ts @@ -35,6 +35,7 @@ import { Task } from "../task/Task" import { codebaseSearchTool } from "../tools/codebaseSearchTool" import { experiments, EXPERIMENT_IDS } from "../../shared/experiments" import { applyDiffToolLegacy } from "../tools/applyDiffTool" +import { t } from "../../i18n" /** * Processes and presents assistant message content to the user interface. @@ -316,7 +317,10 @@ export async function presentAssistantMessage(cline: Task) { await cline.say( "error", - `Error ${action}:\n${error.message ?? JSON.stringify(serializeError(error), null, 2)}`, + t("tools:errors.toolExecutionError", { + action, + error: error.message ?? JSON.stringify(serializeError(error), null, 2), + }), ) pushToolResult(formatResponse.toolError(errorString)) diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 104cb87206..906ef547e0 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -1053,12 +1053,17 @@ export class Task extends EventEmitter implements TaskLike { } async sayAndCreateMissingParamError(toolName: ToolName, paramName: string, relPath?: string) { - await this.say( - "error", - `Roo tried to use ${toolName}${ - relPath ? ` for '${relPath.toPosix()}'` : "" - } without value for required parameter '${paramName}'. Retrying...`, - ) + const errorMessage = relPath + ? t("tools:errors.missingRequiredParameter.withPath", { + toolName, + relPath: relPath.toPosix(), + paramName, + }) + : t("tools:errors.missingRequiredParameter.withoutPath", { + toolName, + paramName, + }) + await this.say("error", errorMessage) return formatResponse.toolError(formatResponse.missingToolParameterError(paramName)) } @@ -2135,10 +2140,7 @@ export class Task extends EventEmitter implements TaskLike { // If there's no assistant_responses, that means we got no text // or tool_use content blocks from API which we should assume is // an error. - await this.say( - "error", - "Unexpected API Response: The language model did not provide any assistant messages. This may indicate an issue with the API or the model's output.", - ) + await this.say("error", t("common:errors.unexpectedApiResponse")) await this.addToApiConversationHistory({ role: "assistant", diff --git a/src/core/tools/__tests__/useMcpToolTool.spec.ts b/src/core/tools/__tests__/useMcpToolTool.spec.ts index 97893b3a97..37cc8a6d66 100644 --- a/src/core/tools/__tests__/useMcpToolTool.spec.ts +++ b/src/core/tools/__tests__/useMcpToolTool.spec.ts @@ -15,6 +15,13 @@ vi.mock("../../prompts/responses", () => ({ vi.mock("../../../i18n", () => ({ t: vi.fn((key: string, params?: any) => { + // Handle the new tools error messages + if (key === "tools:errors.missingRequiredParameter.withPath" && params) { + return `Roo tried to use ${params.toolName} for '${params.relPath}' without value for required parameter '${params.paramName}'. Retrying...` + } + if (key === "tools:errors.missingRequiredParameter.withoutPath" && params) { + return `Roo tried to use ${params.toolName} without value for required parameter '${params.paramName}'. Retrying...` + } if (key === "mcp:errors.invalidJsonArgument" && params?.toolName) { return `Roo tried to use ${params.toolName} with an invalid JSON argument. Retrying...` } diff --git a/src/core/tools/applyDiffTool.ts b/src/core/tools/applyDiffTool.ts index 903e3c846e..983232dd3a 100644 --- a/src/core/tools/applyDiffTool.ts +++ b/src/core/tools/applyDiffTool.ts @@ -13,6 +13,7 @@ import { fileExistsAtPath } from "../../utils/fs" import { RecordSource } from "../context-tracking/FileContextTrackerTypes" import { unescapeHtmlEntities } from "../../utils/text-normalization" import { EXPERIMENT_IDS, experiments } from "../../shared/experiments" +import { t } from "../../i18n" export async function applyDiffToolLegacy( cline: Task, @@ -82,7 +83,7 @@ export async function applyDiffToolLegacy( if (!fileExists) { cline.consecutiveMistakeCount++ cline.recordToolError("apply_diff") - const formattedError = `File does not exist at path: ${absolutePath}\n\n\nThe specified file could not be found. Please verify the file path and try again.\n` + const formattedError = t("tools:errors.fileNotFound", { path: absolutePath }) await cline.say("error", formattedError) pushToolResult(formattedError) return diff --git a/src/core/tools/askFollowupQuestionTool.ts b/src/core/tools/askFollowupQuestionTool.ts index e736936887..11138a62bb 100644 --- a/src/core/tools/askFollowupQuestionTool.ts +++ b/src/core/tools/askFollowupQuestionTool.ts @@ -2,6 +2,7 @@ import { Task } from "../task/Task" import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" import { formatResponse } from "../prompts/responses" import { parseXml } from "../../utils/xml" +import { t } from "../../i18n" export async function askFollowupQuestionTool( cline: Task, @@ -48,7 +49,7 @@ export async function askFollowupQuestionTool( } catch (error) { cline.consecutiveMistakeCount++ cline.recordToolError("ask_followup_question") - await cline.say("error", `Failed to parse operations: ${error.message}`) + await cline.say("error", t("tools:errors.parseOperationsFailed", { error: error.message })) pushToolResult(formatResponse.toolError("Invalid operations xml format")) return } diff --git a/src/core/tools/insertContentTool.ts b/src/core/tools/insertContentTool.ts index b5e85dea30..e175b37e2f 100644 --- a/src/core/tools/insertContentTool.ts +++ b/src/core/tools/insertContentTool.ts @@ -12,6 +12,7 @@ import { fileExistsAtPath } from "../../utils/fs" import { insertGroups } from "../diff/insert-groups" import { DEFAULT_WRITE_DELAY_MS } from "@roo-code/types" import { EXPERIMENT_IDS, experiments } from "../../shared/experiments" +import { t } from "../../i18n" export async function insertContentTool( cline: Task, @@ -86,7 +87,7 @@ export async function insertContentTool( if (lineNumber > 1) { cline.consecutiveMistakeCount++ cline.recordToolError("insert_content") - const formattedError = `Cannot insert content at line ${lineNumber} into a non-existent file. For new files, 'line' must be 0 (to append) or 1 (to insert at the beginning).` + const formattedError = t("tools:errors.insertContentNewFile", { lineNumber }) await cline.say("error", formattedError) pushToolResult(formattedError) return diff --git a/src/core/tools/searchAndReplaceTool.ts b/src/core/tools/searchAndReplaceTool.ts index 50f4868b50..cd883c85df 100644 --- a/src/core/tools/searchAndReplaceTool.ts +++ b/src/core/tools/searchAndReplaceTool.ts @@ -13,6 +13,7 @@ import { fileExistsAtPath } from "../../utils/fs" import { RecordSource } from "../context-tracking/FileContextTrackerTypes" import { DEFAULT_WRITE_DELAY_MS } from "@roo-code/types" import { EXPERIMENT_IDS, experiments } from "../../shared/experiments" +import { t } from "../../i18n" /** * Tool for performing search and replace operations on files @@ -135,7 +136,7 @@ export async function searchAndReplaceTool( cline.consecutiveMistakeCount++ cline.recordToolError("search_and_replace") const formattedError = formatResponse.toolError( - `File does not exist at path: ${absolutePath}\nThe specified file could not be found. Please verify the file path and try again.`, + t("tools:errors.fileNotFoundSimple", { path: absolutePath }), ) await cline.say("error", formattedError) pushToolResult(formattedError) @@ -152,9 +153,10 @@ export async function searchAndReplaceTool( } catch (error) { cline.consecutiveMistakeCount++ cline.recordToolError("search_and_replace") - const errorMessage = `Error reading file: ${absolutePath}\nFailed to read the file content: ${ - error instanceof Error ? error.message : String(error) - }\nPlease verify file permissions and try again.` + const errorMessage = t("tools:errors.fileReadError", { + path: absolutePath, + error: error instanceof Error ? error.message : String(error), + }) const formattedError = formatResponse.toolError(errorMessage) await cline.say("error", formattedError) pushToolResult(formattedError) diff --git a/src/core/tools/writeToFileTool.ts b/src/core/tools/writeToFileTool.ts index e82eab92bc..5784de291c 100644 --- a/src/core/tools/writeToFileTool.ts +++ b/src/core/tools/writeToFileTool.ts @@ -16,6 +16,7 @@ import { detectCodeOmission } from "../../integrations/editor/detect-omission" import { unescapeHtmlEntities } from "../../utils/text-normalization" import { DEFAULT_WRITE_DELAY_MS } from "@roo-code/types" import { EXPERIMENT_IDS, experiments } from "../../shared/experiments" +import { t } from "../../i18n" export async function writeToFileTool( cline: Task, @@ -145,9 +146,10 @@ export async function writeToFileTool( // Use more specific error message for line_count that provides guidance based on the situation await cline.say( "error", - `Roo tried to use write_to_file${ - relPath ? ` for '${relPath.toPosix()}'` : "" - } but the required parameter 'line_count' was missing or truncated after ${actualLineCount} lines of content were written. Retrying...`, + t("tools:errors.lineCountMissing", { + relPath: relPath ? ` for '${relPath.toPosix()}'` : "", + actualLineCount, + }), ) pushToolResult( diff --git a/src/i18n/locales/en/common.json b/src/i18n/locales/en/common.json index e413bc0890..bbf0f8a181 100644 --- a/src/i18n/locales/en/common.json +++ b/src/i18n/locales/en/common.json @@ -103,7 +103,8 @@ }, "roo": { "authenticationRequired": "Roo provider requires cloud authentication. Please sign in to Roo Code Cloud." - } + }, + "unexpectedApiResponse": "Unexpected API Response: The language model did not provide any assistant messages. This may indicate an issue with the API or the model's output." }, "warnings": { "no_terminal_content": "No terminal content selected", diff --git a/src/i18n/locales/en/tools.json b/src/i18n/locales/en/tools.json index 5b88affae6..e394a54c03 100644 --- a/src/i18n/locales/en/tools.json +++ b/src/i18n/locales/en/tools.json @@ -14,5 +14,18 @@ "errors": { "policy_restriction": "Failed to create new task due to policy restrictions." } + }, + "errors": { + "missingRequiredParameter": { + "withPath": "Roo tried to use {{toolName}} for '{{relPath}}' without value for required parameter '{{paramName}}'. Retrying...", + "withoutPath": "Roo tried to use {{toolName}} without value for required parameter '{{paramName}}'. Retrying..." + }, + "lineCountMissing": "Roo tried to use write_to_file{{relPath}} but the required parameter 'line_count' was missing or truncated after {{actualLineCount}} lines of content were written. Retrying...", + "parseOperationsFailed": "Failed to parse operations: {{error}}", + "fileNotFound": "File does not exist at path: {{path}}\n\n\nThe specified file could not be found. Please verify the file path and try again.\n", + "fileNotFoundSimple": "File does not exist at path: {{path}}\nThe specified file could not be found. Please verify the file path and try again.", + "fileReadError": "Error reading file: {{path}}\nFailed to read the file content: {{error}}\nPlease verify file permissions and try again.", + "insertContentNewFile": "Cannot insert content at line {{lineNumber}} into a non-existent file. For new files, 'line' must be 0 (to append) or 1 (to insert at the beginning).", + "toolExecutionError": "Error {{action}}:\n{{error}}" } }