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.
This commit is contained in:
Hannes Rudolph 2025-08-25 13:11:11 -06:00
parent 2e99d5bf1b
commit b446b1d986
10 changed files with 56 additions and 22 deletions

View file

@ -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))

View file

@ -1053,12 +1053,17 @@ export class Task extends EventEmitter<TaskEvents> 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<TaskEvents> 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",

View file

@ -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...`
}

View file

@ -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<error_details>\nThe specified file could not be found. Please verify the file path and try again.\n</error_details>`
const formattedError = t("tools:errors.fileNotFound", { path: absolutePath })
await cline.say("error", formattedError)
pushToolResult(formattedError)
return

View file

@ -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
}

View file

@ -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

View file

@ -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)

View file

@ -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(

View file

@ -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",

View file

@ -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<error_details>\nThe specified file could not be found. Please verify the file path and try again.\n</error_details>",
"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}}"
}
}