mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-10-09 03:17:58 +00:00
commit
0d81c0d801
56 changed files with 2280 additions and 790 deletions
29
.roomodes
Normal file
29
.roomodes
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"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",
|
||||
"roleDefinition": "You are Roo, a Jest testing specialist with deep expertise in:\n- Writing and maintaining Jest test suites\n- Test-driven development (TDD) practices\n- Mocking and stubbing with Jest\n- Integration testing strategies\n- TypeScript testing patterns\n- Code coverage analysis\n- Test performance optimization\n\nYour focus is on maintaining high test quality and coverage across the codebase, working primarily with:\n- Test files in __tests__ directories\n- Mock implementations in __mocks__\n- Test utilities and helpers\n- Jest configuration and setup\n\nYou ensure tests are:\n- Well-structured and maintainable\n- Following Jest best practices\n- Properly typed with TypeScript\n- Providing meaningful coverage\n- Using appropriate mocking strategies",
|
||||
"groups": [
|
||||
"read",
|
||||
"browser",
|
||||
"command",
|
||||
["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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,4 +1,13 @@
|
|||
const vscode = {
|
||||
env: {
|
||||
language: "en", // Default language for tests
|
||||
appName: "Visual Studio Code Test",
|
||||
appHost: "desktop",
|
||||
appRoot: "/test/path",
|
||||
machineId: "test-machine-id",
|
||||
sessionId: "test-session-id",
|
||||
shell: "/bin/zsh",
|
||||
},
|
||||
window: {
|
||||
showInformationMessage: jest.fn(),
|
||||
showErrorMessage: jest.fn(),
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ import { truncateConversationIfNeeded } from "./sliding-window"
|
|||
import { ClineProvider } from "./webview/ClineProvider"
|
||||
import { detectCodeOmission } from "../integrations/editor/detect-omission"
|
||||
import { BrowserSession } from "../services/browser/BrowserSession"
|
||||
import { formatLanguage } from "../shared/language"
|
||||
import { McpHub } from "../services/mcp/McpHub"
|
||||
import crypto from "crypto"
|
||||
import { insertGroups } from "./diff/insert-groups"
|
||||
|
|
@ -1102,7 +1103,6 @@ export class Cline {
|
|||
browserViewportSize,
|
||||
mode,
|
||||
customModePrompts,
|
||||
preferredLanguage,
|
||||
experiments,
|
||||
enableMcpServerCreation,
|
||||
browserToolEnabled,
|
||||
|
|
@ -1124,7 +1124,6 @@ export class Cline {
|
|||
customModePrompts,
|
||||
customModes,
|
||||
this.customInstructions,
|
||||
preferredLanguage,
|
||||
this.diffEnabled,
|
||||
experiments,
|
||||
enableMcpServerCreation,
|
||||
|
|
@ -3665,13 +3664,12 @@ export class Cline {
|
|||
customModePrompts,
|
||||
experiments = {} as Record<ExperimentId, boolean>,
|
||||
customInstructions: globalCustomInstructions,
|
||||
preferredLanguage,
|
||||
} = (await this.providerRef.deref()?.getState()) ?? {}
|
||||
const currentMode = mode ?? defaultModeSlug
|
||||
const modeDetails = await getFullModeDetails(currentMode, customModes, customModePrompts, {
|
||||
cwd,
|
||||
globalCustomInstructions,
|
||||
preferredLanguage,
|
||||
language: formatLanguage(vscode.env.language),
|
||||
})
|
||||
details += `\n\n# Current Mode\n`
|
||||
details += `<slug>${currentMode}</slug>\n`
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -46,8 +46,6 @@ const mockContext = {
|
|||
} as unknown as vscode.ExtensionContext
|
||||
|
||||
describe("File-Based Custom System Prompt", () => {
|
||||
const experiments = {}
|
||||
|
||||
beforeEach(() => {
|
||||
// Reset mocks before each test
|
||||
jest.clearAllMocks()
|
||||
|
|
@ -66,18 +64,17 @@ describe("File-Based Custom System Prompt", () => {
|
|||
const prompt = await SYSTEM_PROMPT(
|
||||
mockContext,
|
||||
"test/path", // Using a relative path without leading slash
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
defaultModeSlug,
|
||||
customModePrompts,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
experiments,
|
||||
true,
|
||||
false, // supportsComputerUse
|
||||
undefined, // mcpHub
|
||||
undefined, // diffStrategy
|
||||
undefined, // browserViewportSize
|
||||
defaultModeSlug, // mode
|
||||
customModePrompts, // customModePrompts
|
||||
undefined, // customModes
|
||||
undefined, // globalCustomInstructions
|
||||
undefined, // diffEnabled
|
||||
undefined, // experiments
|
||||
true, // enableMcpServerCreation
|
||||
)
|
||||
|
||||
// Should contain default sections
|
||||
|
|
@ -101,18 +98,17 @@ describe("File-Based Custom System Prompt", () => {
|
|||
const prompt = await SYSTEM_PROMPT(
|
||||
mockContext,
|
||||
"test/path", // Using a relative path without leading slash
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
defaultModeSlug,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
experiments,
|
||||
true,
|
||||
false, // supportsComputerUse
|
||||
undefined, // mcpHub
|
||||
undefined, // diffStrategy
|
||||
undefined, // browserViewportSize
|
||||
defaultModeSlug, // mode
|
||||
undefined, // customModePrompts
|
||||
undefined, // customModes
|
||||
undefined, // globalCustomInstructions
|
||||
undefined, // diffEnabled
|
||||
undefined, // experiments
|
||||
true, // enableMcpServerCreation
|
||||
)
|
||||
|
||||
// Should contain role definition and file-based system prompt
|
||||
|
|
@ -120,7 +116,6 @@ describe("File-Based Custom System Prompt", () => {
|
|||
expect(prompt).toContain(fileCustomSystemPrompt)
|
||||
|
||||
// Should not contain any of the default sections
|
||||
expect(prompt).not.toContain("TOOL USE")
|
||||
expect(prompt).not.toContain("CAPABILITIES")
|
||||
expect(prompt).not.toContain("MODES")
|
||||
})
|
||||
|
|
@ -146,18 +141,17 @@ describe("File-Based Custom System Prompt", () => {
|
|||
const prompt = await SYSTEM_PROMPT(
|
||||
mockContext,
|
||||
"test/path", // Using a relative path without leading slash
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
defaultModeSlug,
|
||||
customModePrompts,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
experiments,
|
||||
true,
|
||||
false, // supportsComputerUse
|
||||
undefined, // mcpHub
|
||||
undefined, // diffStrategy
|
||||
undefined, // browserViewportSize
|
||||
defaultModeSlug, // mode
|
||||
customModePrompts, // customModePrompts
|
||||
undefined, // customModes
|
||||
undefined, // globalCustomInstructions
|
||||
undefined, // diffEnabled
|
||||
undefined, // experiments
|
||||
true, // enableMcpServerCreation
|
||||
)
|
||||
|
||||
// Should contain custom role definition and file-based system prompt
|
||||
|
|
@ -165,7 +159,6 @@ describe("File-Based Custom System Prompt", () => {
|
|||
expect(prompt).toContain(fileCustomSystemPrompt)
|
||||
|
||||
// Should not contain any of the default sections
|
||||
expect(prompt).not.toContain("TOOL USE")
|
||||
expect(prompt).not.toContain("CAPABILITIES")
|
||||
expect(prompt).not.toContain("MODES")
|
||||
})
|
||||
|
|
|
|||
|
|
@ -3,20 +3,20 @@ import { getCapabilitiesSection } from "../sections/capabilities"
|
|||
import { DiffStrategy, DiffResult } from "../../diff/types"
|
||||
|
||||
describe("addCustomInstructions", () => {
|
||||
test("adds preferred language to custom instructions", async () => {
|
||||
test("adds vscode language to custom instructions", async () => {
|
||||
const result = await addCustomInstructions(
|
||||
"mode instructions",
|
||||
"global instructions",
|
||||
"/test/path",
|
||||
"test-mode",
|
||||
{ preferredLanguage: "French" },
|
||||
{ language: "fr" },
|
||||
)
|
||||
|
||||
expect(result).toContain("Language Preference:")
|
||||
expect(result).toContain("You should always speak and think in the French language")
|
||||
expect(result).toContain('You should always speak and think in the "fr" language')
|
||||
})
|
||||
|
||||
test("works without preferred language", async () => {
|
||||
test("works without vscode language", async () => {
|
||||
const result = await addCustomInstructions(
|
||||
"mode instructions",
|
||||
"global instructions",
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { SearchReplaceDiffStrategy } from "../../../core/diff/strategies/search-
|
|||
import * as vscode from "vscode"
|
||||
import fs from "fs/promises"
|
||||
import os from "os"
|
||||
import { defaultModeSlug, modes } from "../../../shared/modes"
|
||||
import { defaultModeSlug, modes, Mode, isToolAllowedForMode } from "../../../shared/modes"
|
||||
// Import path utils to get access to toPosix string extension
|
||||
import "../../../utils/path"
|
||||
import { addCustomInstructions } from "../sections/custom-instructions"
|
||||
|
|
@ -18,46 +18,63 @@ jest.mock("../sections/modes", () => ({
|
|||
getModesSection: jest.fn().mockImplementation(async () => `====\n\nMODES\n\n- Test modes section`),
|
||||
}))
|
||||
|
||||
jest.mock("../sections/custom-instructions", () => ({
|
||||
addCustomInstructions: jest
|
||||
.fn()
|
||||
.mockImplementation(async (modeCustomInstructions, globalCustomInstructions, cwd, mode, options) => {
|
||||
const sections = []
|
||||
// Mock the custom instructions
|
||||
jest.mock("../sections/custom-instructions", () => {
|
||||
const addCustomInstructions = jest.fn()
|
||||
return {
|
||||
addCustomInstructions,
|
||||
__setMockImplementation: (impl: any) => {
|
||||
addCustomInstructions.mockImplementation(impl)
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
// Add language preference if provided
|
||||
if (options?.preferredLanguage) {
|
||||
sections.push(
|
||||
`Language Preference:\nYou should always speak and think in the ${options.preferredLanguage} language.`,
|
||||
)
|
||||
}
|
||||
// Set up default mock implementation
|
||||
const { __setMockImplementation } = jest.requireMock("../sections/custom-instructions")
|
||||
__setMockImplementation(
|
||||
async (
|
||||
modeCustomInstructions: string,
|
||||
globalCustomInstructions: string,
|
||||
cwd: string,
|
||||
mode: string,
|
||||
options?: { language?: string },
|
||||
) => {
|
||||
const sections = []
|
||||
|
||||
// Add global instructions first
|
||||
if (globalCustomInstructions?.trim()) {
|
||||
sections.push(`Global Instructions:\n${globalCustomInstructions.trim()}`)
|
||||
}
|
||||
// Add language preference if provided
|
||||
if (options?.language) {
|
||||
sections.push(
|
||||
`Language Preference:\nYou should always speak and think in the "${options.language}" language.`,
|
||||
)
|
||||
}
|
||||
|
||||
// Add mode-specific instructions after
|
||||
if (modeCustomInstructions?.trim()) {
|
||||
sections.push(`Mode-specific Instructions:\n${modeCustomInstructions}`)
|
||||
}
|
||||
// Add global instructions first
|
||||
if (globalCustomInstructions?.trim()) {
|
||||
sections.push(`Global Instructions:\n${globalCustomInstructions.trim()}`)
|
||||
}
|
||||
|
||||
// Add rules
|
||||
const rules = []
|
||||
if (mode) {
|
||||
rules.push(`# Rules from .clinerules-${mode}:\nMock mode-specific rules`)
|
||||
}
|
||||
rules.push(`# Rules from .clinerules:\nMock generic rules`)
|
||||
// Add mode-specific instructions after
|
||||
if (modeCustomInstructions?.trim()) {
|
||||
sections.push(`Mode-specific Instructions:\n${modeCustomInstructions}`)
|
||||
}
|
||||
|
||||
if (rules.length > 0) {
|
||||
sections.push(`Rules:\n${rules.join("\n")}`)
|
||||
}
|
||||
// Add rules
|
||||
const rules = []
|
||||
if (mode) {
|
||||
rules.push(`# Rules from .clinerules-${mode}:\nMock mode-specific rules`)
|
||||
}
|
||||
rules.push(`# Rules from .clinerules:\nMock generic rules`)
|
||||
|
||||
const joinedSections = sections.join("\n\n")
|
||||
return joinedSections
|
||||
? `\n====\n\nUSER'S CUSTOM INSTRUCTIONS\n\nThe following additional instructions are provided by the user, and should be followed to the best of your ability without interfering with the TOOL USE guidelines.\n\n${joinedSections}`
|
||||
: ""
|
||||
}),
|
||||
}))
|
||||
if (rules.length > 0) {
|
||||
sections.push(`Rules:\n${rules.join("\n")}`)
|
||||
}
|
||||
|
||||
const joinedSections = sections.join("\n\n")
|
||||
return joinedSections
|
||||
? `\n====\n\nUSER'S CUSTOM INSTRUCTIONS\n\nThe following additional instructions are provided by the user, and should be followed to the best of your ability without interfering with the TOOL USE guidelines.\n\n${joinedSections}`
|
||||
: ""
|
||||
},
|
||||
)
|
||||
|
||||
// Mock environment-specific values for consistent tests
|
||||
jest.mock("os", () => ({
|
||||
|
|
@ -69,6 +86,13 @@ jest.mock("default-shell", () => "/bin/zsh")
|
|||
|
||||
jest.mock("os-name", () => () => "Linux")
|
||||
|
||||
// Mock vscode language
|
||||
jest.mock("vscode", () => ({
|
||||
env: {
|
||||
language: "en",
|
||||
},
|
||||
}))
|
||||
|
||||
jest.mock("../../../utils/shell", () => ({
|
||||
getShell: () => "/bin/zsh",
|
||||
}))
|
||||
|
|
@ -126,7 +150,7 @@ const createMockMcpHub = (): McpHub =>
|
|||
|
||||
describe("SYSTEM_PROMPT", () => {
|
||||
let mockMcpHub: McpHub
|
||||
let experiments: Record<string, boolean>
|
||||
let experiments: Record<string, boolean> | undefined
|
||||
|
||||
beforeAll(() => {
|
||||
// Ensure fs mock is properly initialized
|
||||
|
|
@ -146,6 +170,10 @@ describe("SYSTEM_PROMPT", () => {
|
|||
"/mock/mcp/path",
|
||||
]
|
||||
dirs.forEach((dir) => mockFs._mockDirectories.add(dir))
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
// Reset experiments before each test to ensure they're disabled by default
|
||||
experiments = {
|
||||
[EXPERIMENT_IDS.SEARCH_AND_REPLACE]: false,
|
||||
[EXPERIMENT_IDS.INSERT_BLOCK]: false,
|
||||
|
|
@ -175,7 +203,6 @@ describe("SYSTEM_PROMPT", () => {
|
|||
undefined, // customModePrompts
|
||||
undefined, // customModes
|
||||
undefined, // globalCustomInstructions
|
||||
undefined, // preferredLanguage
|
||||
undefined, // diffEnabled
|
||||
experiments,
|
||||
true, // enableMcpServerCreation
|
||||
|
|
@ -196,7 +223,6 @@ describe("SYSTEM_PROMPT", () => {
|
|||
undefined, // customModePrompts
|
||||
undefined, // customModes,
|
||||
undefined, // globalCustomInstructions
|
||||
undefined, // preferredLanguage
|
||||
undefined, // diffEnabled
|
||||
experiments,
|
||||
true, // enableMcpServerCreation
|
||||
|
|
@ -219,7 +245,6 @@ describe("SYSTEM_PROMPT", () => {
|
|||
undefined, // customModePrompts
|
||||
undefined, // customModes,
|
||||
undefined, // globalCustomInstructions
|
||||
undefined, // preferredLanguage
|
||||
undefined, // diffEnabled
|
||||
experiments,
|
||||
true, // enableMcpServerCreation
|
||||
|
|
@ -240,7 +265,6 @@ describe("SYSTEM_PROMPT", () => {
|
|||
undefined, // customModePrompts
|
||||
undefined, // customModes,
|
||||
undefined, // globalCustomInstructions
|
||||
undefined, // preferredLanguage
|
||||
undefined, // diffEnabled
|
||||
experiments,
|
||||
true, // enableMcpServerCreation
|
||||
|
|
@ -261,7 +285,6 @@ describe("SYSTEM_PROMPT", () => {
|
|||
undefined, // customModePrompts
|
||||
undefined, // customModes,
|
||||
undefined, // globalCustomInstructions
|
||||
undefined, // preferredLanguage
|
||||
undefined, // diffEnabled
|
||||
experiments,
|
||||
true, // enableMcpServerCreation
|
||||
|
|
@ -282,7 +305,6 @@ describe("SYSTEM_PROMPT", () => {
|
|||
undefined, // customModePrompts
|
||||
undefined, // customModes
|
||||
undefined, // globalCustomInstructions
|
||||
undefined, // preferredLanguage
|
||||
true, // diffEnabled
|
||||
experiments,
|
||||
true, // enableMcpServerCreation
|
||||
|
|
@ -304,7 +326,6 @@ describe("SYSTEM_PROMPT", () => {
|
|||
undefined, // customModePrompts
|
||||
undefined, // customModes
|
||||
undefined, // globalCustomInstructions
|
||||
undefined, // preferredLanguage
|
||||
false, // diffEnabled
|
||||
experiments,
|
||||
true, // enableMcpServerCreation
|
||||
|
|
@ -326,7 +347,6 @@ describe("SYSTEM_PROMPT", () => {
|
|||
undefined, // customModePrompts
|
||||
undefined, // customModes
|
||||
undefined, // globalCustomInstructions
|
||||
undefined, // preferredLanguage
|
||||
undefined, // diffEnabled
|
||||
experiments,
|
||||
true, // enableMcpServerCreation
|
||||
|
|
@ -336,7 +356,11 @@ describe("SYSTEM_PROMPT", () => {
|
|||
expect(prompt).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it("should include preferred language in custom instructions", async () => {
|
||||
it("should include vscode language in custom instructions", async () => {
|
||||
// Mock vscode.env.language
|
||||
const vscode = jest.requireMock("vscode")
|
||||
vscode.env = { language: "es" }
|
||||
|
||||
const prompt = await SYSTEM_PROMPT(
|
||||
mockContext,
|
||||
"/test/path",
|
||||
|
|
@ -348,14 +372,16 @@ describe("SYSTEM_PROMPT", () => {
|
|||
undefined, // customModePrompts
|
||||
undefined, // customModes
|
||||
undefined, // globalCustomInstructions
|
||||
"Spanish", // preferredLanguage
|
||||
undefined, // diffEnabled
|
||||
experiments,
|
||||
undefined, // experiments
|
||||
true, // enableMcpServerCreation
|
||||
)
|
||||
|
||||
expect(prompt).toContain("Language Preference:")
|
||||
expect(prompt).toContain("You should always speak and think in the Spanish language")
|
||||
expect(prompt).toContain('You should always speak and think in the "es" language')
|
||||
|
||||
// Reset mock
|
||||
vscode.env = { language: "en" }
|
||||
})
|
||||
|
||||
it("should include custom mode role definition at top and instructions at bottom", async () => {
|
||||
|
|
@ -381,7 +407,6 @@ describe("SYSTEM_PROMPT", () => {
|
|||
undefined, // customModePrompts
|
||||
customModes, // customModes
|
||||
"Global instructions", // globalCustomInstructions
|
||||
undefined, // preferredLanguage
|
||||
undefined, // diffEnabled
|
||||
experiments,
|
||||
true, // enableMcpServerCreation
|
||||
|
|
@ -409,18 +434,17 @@ describe("SYSTEM_PROMPT", () => {
|
|||
const prompt = await SYSTEM_PROMPT(
|
||||
mockContext,
|
||||
"/test/path",
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
defaultModeSlug,
|
||||
customModePrompts,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
experiments,
|
||||
true, // enableMcpServerCreation
|
||||
false, // supportsComputerUse
|
||||
undefined, // mcpHub
|
||||
undefined, // diffStrategy
|
||||
undefined, // browserViewportSize
|
||||
defaultModeSlug as Mode, // mode
|
||||
customModePrompts, // customModePrompts
|
||||
undefined, // customModes
|
||||
undefined, // globalCustomInstructions
|
||||
undefined, // diffEnabled
|
||||
undefined, // experiments
|
||||
false, // enableMcpServerCreation
|
||||
)
|
||||
|
||||
// Role definition from promptComponent should be at the top
|
||||
|
|
@ -440,18 +464,17 @@ describe("SYSTEM_PROMPT", () => {
|
|||
const prompt = await SYSTEM_PROMPT(
|
||||
mockContext,
|
||||
"/test/path",
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
defaultModeSlug,
|
||||
customModePrompts,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
experiments,
|
||||
true, // enableMcpServerCreation
|
||||
false, // supportsComputerUse
|
||||
undefined, // mcpHub
|
||||
undefined, // diffStrategy
|
||||
undefined, // browserViewportSize
|
||||
defaultModeSlug as Mode, // mode
|
||||
customModePrompts, // customModePrompts
|
||||
undefined, // customModes
|
||||
undefined, // globalCustomInstructions
|
||||
undefined, // diffEnabled
|
||||
undefined, // experiments
|
||||
false, // enableMcpServerCreation
|
||||
)
|
||||
|
||||
// Should use the default mode's role definition
|
||||
|
|
@ -460,6 +483,15 @@ describe("SYSTEM_PROMPT", () => {
|
|||
|
||||
describe("experimental tools", () => {
|
||||
it("should disable experimental tools by default", async () => {
|
||||
// Set experiments to explicitly disable experimental tools
|
||||
const experimentsConfig = {
|
||||
[EXPERIMENT_IDS.SEARCH_AND_REPLACE]: false,
|
||||
[EXPERIMENT_IDS.INSERT_BLOCK]: false,
|
||||
}
|
||||
|
||||
// Reset experiments
|
||||
experiments = experimentsConfig
|
||||
|
||||
const prompt = await SYSTEM_PROMPT(
|
||||
mockContext,
|
||||
"/test/path",
|
||||
|
|
@ -471,23 +503,29 @@ describe("SYSTEM_PROMPT", () => {
|
|||
undefined, // customModePrompts
|
||||
undefined, // customModes
|
||||
undefined, // globalCustomInstructions
|
||||
undefined, // preferredLanguage
|
||||
undefined, // diffEnabled
|
||||
experiments, // experiments - undefined should disable all experimental tools
|
||||
experimentsConfig, // Explicitly disable experimental tools
|
||||
true, // enableMcpServerCreation
|
||||
)
|
||||
|
||||
// Verify experimental tools are not included in the prompt
|
||||
expect(prompt).not.toContain(EXPERIMENT_IDS.SEARCH_AND_REPLACE)
|
||||
expect(prompt).not.toContain(EXPERIMENT_IDS.INSERT_BLOCK)
|
||||
// Check that experimental tool sections are not included
|
||||
const toolSections = prompt.split("\n## ").slice(1)
|
||||
const toolNames = toolSections.map((section) => section.split("\n")[0].trim())
|
||||
expect(toolNames).not.toContain("search_and_replace")
|
||||
expect(toolNames).not.toContain("insert_content")
|
||||
expect(prompt).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it("should enable experimental tools when explicitly enabled", async () => {
|
||||
const experiments = {
|
||||
// Set experiments for testing experimental features
|
||||
const experimentsEnabled = {
|
||||
[EXPERIMENT_IDS.SEARCH_AND_REPLACE]: true,
|
||||
[EXPERIMENT_IDS.INSERT_BLOCK]: true,
|
||||
}
|
||||
|
||||
// Reset default experiments
|
||||
experiments = undefined
|
||||
|
||||
const prompt = await SYSTEM_PROMPT(
|
||||
mockContext,
|
||||
"/test/path",
|
||||
|
|
@ -499,23 +537,31 @@ describe("SYSTEM_PROMPT", () => {
|
|||
undefined, // customModePrompts
|
||||
undefined, // customModes
|
||||
undefined, // globalCustomInstructions
|
||||
undefined, // preferredLanguage
|
||||
undefined, // diffEnabled
|
||||
experiments,
|
||||
experimentsEnabled, // Use the enabled experiments
|
||||
true, // enableMcpServerCreation
|
||||
)
|
||||
|
||||
// Get all tool sections
|
||||
const toolSections = prompt.split("## ").slice(1) // Split by section headers and remove first non-tool part
|
||||
const toolNames = toolSections.map((section) => section.split("\n")[0].trim())
|
||||
|
||||
// Verify experimental tools are included in the prompt when enabled
|
||||
expect(prompt).toContain(EXPERIMENT_IDS.SEARCH_AND_REPLACE)
|
||||
expect(prompt).toContain(EXPERIMENT_IDS.INSERT_BLOCK)
|
||||
expect(toolNames).toContain("search_and_replace")
|
||||
expect(toolNames).toContain("insert_content")
|
||||
expect(prompt).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it("should selectively enable experimental tools", async () => {
|
||||
const experiments = {
|
||||
// Set experiments for testing selective enabling
|
||||
const experimentsSelective = {
|
||||
[EXPERIMENT_IDS.SEARCH_AND_REPLACE]: true,
|
||||
[EXPERIMENT_IDS.INSERT_BLOCK]: false,
|
||||
}
|
||||
|
||||
// Reset default experiments
|
||||
experiments = undefined
|
||||
|
||||
const prompt = await SYSTEM_PROMPT(
|
||||
mockContext,
|
||||
"/test/path",
|
||||
|
|
@ -527,15 +573,19 @@ describe("SYSTEM_PROMPT", () => {
|
|||
undefined, // customModePrompts
|
||||
undefined, // customModes
|
||||
undefined, // globalCustomInstructions
|
||||
undefined, // preferredLanguage
|
||||
undefined, // diffEnabled
|
||||
experiments,
|
||||
experimentsSelective, // Use the selective experiments
|
||||
true, // enableMcpServerCreation
|
||||
)
|
||||
|
||||
// Get all tool sections
|
||||
const toolSections = prompt.split("## ").slice(1) // Split by section headers and remove first non-tool part
|
||||
const toolNames = toolSections.map((section) => section.split("\n")[0].trim())
|
||||
|
||||
// Verify only enabled experimental tools are included
|
||||
expect(prompt).toContain(EXPERIMENT_IDS.SEARCH_AND_REPLACE)
|
||||
expect(prompt).not.toContain(EXPERIMENT_IDS.INSERT_BLOCK)
|
||||
expect(toolNames).toContain("search_and_replace")
|
||||
expect(toolNames).not.toContain("insert_content")
|
||||
expect(prompt).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it("should list all available editing tools in base instruction", async () => {
|
||||
|
|
@ -555,9 +605,8 @@ describe("SYSTEM_PROMPT", () => {
|
|||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
true, // diffEnabled
|
||||
experiments,
|
||||
experiments, // experiments
|
||||
true, // enableMcpServerCreation
|
||||
)
|
||||
|
||||
|
|
@ -567,7 +616,6 @@ describe("SYSTEM_PROMPT", () => {
|
|||
expect(prompt).toContain("insert_content (for adding lines to existing files)")
|
||||
expect(prompt).toContain("search_and_replace (for finding and replacing individual pieces of text)")
|
||||
})
|
||||
|
||||
it("should provide detailed instructions for each enabled tool", async () => {
|
||||
const experiments = {
|
||||
[EXPERIMENT_IDS.SEARCH_AND_REPLACE]: true,
|
||||
|
|
@ -585,8 +633,7 @@ describe("SYSTEM_PROMPT", () => {
|
|||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
true,
|
||||
true, // diffEnabled
|
||||
experiments,
|
||||
true, // enableMcpServerCreation
|
||||
)
|
||||
|
|
@ -606,7 +653,7 @@ describe("SYSTEM_PROMPT", () => {
|
|||
})
|
||||
|
||||
describe("addCustomInstructions", () => {
|
||||
let experiments: Record<string, boolean>
|
||||
let experiments: Record<string, boolean> | undefined
|
||||
beforeAll(() => {
|
||||
// Ensure fs mock is properly initialized
|
||||
const mockFs = jest.requireMock("fs/promises")
|
||||
|
|
@ -619,10 +666,8 @@ describe("addCustomInstructions", () => {
|
|||
throw new Error(`ENOENT: no such file or directory, mkdir '${path}'`)
|
||||
})
|
||||
|
||||
experiments = {
|
||||
[EXPERIMENT_IDS.SEARCH_AND_REPLACE]: false,
|
||||
[EXPERIMENT_IDS.INSERT_BLOCK]: false,
|
||||
}
|
||||
// Initialize experiments as undefined by default
|
||||
experiments = undefined
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
@ -640,10 +685,9 @@ describe("addCustomInstructions", () => {
|
|||
"architect", // mode
|
||||
undefined, // customModePrompts
|
||||
undefined, // customModes
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
experiments,
|
||||
undefined, // globalCustomInstructions
|
||||
undefined, // diffEnabled
|
||||
undefined, // experiments
|
||||
true, // enableMcpServerCreation
|
||||
)
|
||||
|
||||
|
|
@ -661,10 +705,9 @@ describe("addCustomInstructions", () => {
|
|||
"ask", // mode
|
||||
undefined, // customModePrompts
|
||||
undefined, // customModes
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
experiments,
|
||||
undefined, // globalCustomInstructions
|
||||
undefined, // diffEnabled
|
||||
undefined, // experiments
|
||||
true, // enableMcpServerCreation
|
||||
)
|
||||
|
||||
|
|
@ -685,9 +728,8 @@ describe("addCustomInstructions", () => {
|
|||
undefined, // customModePrompts
|
||||
undefined, // customModes,
|
||||
undefined, // globalCustomInstructions
|
||||
undefined, // preferredLanguage
|
||||
undefined, // diffEnabled
|
||||
experiments,
|
||||
undefined, // experiments
|
||||
true, // enableMcpServerCreation
|
||||
)
|
||||
|
||||
|
|
@ -709,9 +751,8 @@ describe("addCustomInstructions", () => {
|
|||
undefined, // customModePrompts
|
||||
undefined, // customModes,
|
||||
undefined, // globalCustomInstructions
|
||||
undefined, // preferredLanguage
|
||||
undefined, // diffEnabled
|
||||
experiments,
|
||||
undefined, // experiments
|
||||
false, // enableMcpServerCreation
|
||||
)
|
||||
|
||||
|
|
@ -751,7 +792,7 @@ describe("addCustomInstructions", () => {
|
|||
|
||||
it("should include preferred language when provided", async () => {
|
||||
const instructions = await addCustomInstructions("", "", "/test/path", defaultModeSlug, {
|
||||
preferredLanguage: "Spanish",
|
||||
language: "es",
|
||||
})
|
||||
expect(instructions).toMatchSnapshot()
|
||||
})
|
||||
|
|
@ -767,7 +808,7 @@ describe("addCustomInstructions", () => {
|
|||
"",
|
||||
"/test/path",
|
||||
defaultModeSlug,
|
||||
{ preferredLanguage: "French" },
|
||||
{ language: "fr" },
|
||||
)
|
||||
expect(instructions).toMatchSnapshot()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -113,11 +113,11 @@ describe("addCustomInstructions", () => {
|
|||
"global instructions",
|
||||
"/fake/path",
|
||||
"test-mode",
|
||||
{ preferredLanguage: "Spanish" },
|
||||
{ language: "es" },
|
||||
)
|
||||
|
||||
expect(result).toContain("Language Preference:")
|
||||
expect(result).toContain("Spanish")
|
||||
expect(result).toContain("es")
|
||||
expect(result).toContain("Global Instructions:\nglobal instructions")
|
||||
expect(result).toContain("Mode-specific Instructions:\nmode instructions")
|
||||
expect(result).toContain("Rules from .clinerules-test-mode:\nmode specific rules")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import fs from "fs/promises"
|
||||
import path from "path"
|
||||
import * as vscode from "vscode"
|
||||
|
||||
async function safeReadFile(filePath: string): Promise<string> {
|
||||
try {
|
||||
|
|
@ -33,7 +34,7 @@ export async function addCustomInstructions(
|
|||
globalCustomInstructions: string,
|
||||
cwd: string,
|
||||
mode: string,
|
||||
options: { preferredLanguage?: string; rooIgnoreInstructions?: string } = {},
|
||||
options: { language?: string; rooIgnoreInstructions?: string } = {},
|
||||
): Promise<string> {
|
||||
const sections = []
|
||||
|
||||
|
|
@ -45,9 +46,9 @@ export async function addCustomInstructions(
|
|||
}
|
||||
|
||||
// Add language preference if provided
|
||||
if (options.preferredLanguage) {
|
||||
if (options.language) {
|
||||
sections.push(
|
||||
`Language Preference:\nYou should always speak and think in the ${options.preferredLanguage} language.`,
|
||||
`Language Preference:\nYou should always speak and think in the "${options.language}" language unless the user gives you instructions below to do otherwise.`,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import {
|
|||
addCustomInstructions,
|
||||
} from "./sections"
|
||||
import { loadSystemPromptFile } from "./sections/custom-system-prompt"
|
||||
import { formatLanguage } from "../../shared/language"
|
||||
|
||||
async function generatePrompt(
|
||||
context: vscode.ExtensionContext,
|
||||
|
|
@ -37,7 +38,6 @@ async function generatePrompt(
|
|||
promptComponent?: PromptComponent,
|
||||
customModeConfigs?: ModeConfig[],
|
||||
globalCustomInstructions?: string,
|
||||
preferredLanguage?: string,
|
||||
diffEnabled?: boolean,
|
||||
experiments?: Record<string, boolean>,
|
||||
enableMcpServerCreation?: boolean,
|
||||
|
|
@ -90,7 +90,7 @@ ${getSystemInfoSection(cwd, mode, customModeConfigs)}
|
|||
|
||||
${getObjectiveSection()}
|
||||
|
||||
${await addCustomInstructions(promptComponent?.customInstructions || modeConfig.customInstructions || "", globalCustomInstructions || "", cwd, mode, { preferredLanguage, rooIgnoreInstructions })}`
|
||||
${await addCustomInstructions(promptComponent?.customInstructions || modeConfig.customInstructions || "", globalCustomInstructions || "", cwd, mode, { language: formatLanguage(vscode.env.language), rooIgnoreInstructions })}`
|
||||
|
||||
return basePrompt
|
||||
}
|
||||
|
|
@ -106,7 +106,6 @@ export const SYSTEM_PROMPT = async (
|
|||
customModePrompts?: CustomModePrompts,
|
||||
customModes?: ModeConfig[],
|
||||
globalCustomInstructions?: string,
|
||||
preferredLanguage?: string,
|
||||
diffEnabled?: boolean,
|
||||
experiments?: Record<string, boolean>,
|
||||
enableMcpServerCreation?: boolean,
|
||||
|
|
@ -135,11 +134,19 @@ export const SYSTEM_PROMPT = async (
|
|||
// If a file-based custom system prompt exists, use it
|
||||
if (fileCustomSystemPrompt) {
|
||||
const roleDefinition = promptComponent?.roleDefinition || currentMode.roleDefinition
|
||||
const customInstructions = await addCustomInstructions(
|
||||
promptComponent?.customInstructions || currentMode.customInstructions || "",
|
||||
globalCustomInstructions || "",
|
||||
cwd,
|
||||
mode,
|
||||
{ language: formatLanguage(vscode.env.language), rooIgnoreInstructions },
|
||||
)
|
||||
// For file-based prompts, don't include the tool sections
|
||||
return `${roleDefinition}
|
||||
|
||||
${fileCustomSystemPrompt}
|
||||
|
||||
${await addCustomInstructions(promptComponent?.customInstructions || currentMode.customInstructions || "", globalCustomInstructions || "", cwd, mode, { preferredLanguage, rooIgnoreInstructions })}`
|
||||
${customInstructions}`
|
||||
}
|
||||
|
||||
// If diff is disabled, don't pass the diffStrategy
|
||||
|
|
@ -156,7 +163,6 @@ ${await addCustomInstructions(promptComponent?.customInstructions || currentMode
|
|||
promptComponent,
|
||||
customModes,
|
||||
globalCustomInstructions,
|
||||
preferredLanguage,
|
||||
diffEnabled,
|
||||
experiments,
|
||||
enableMcpServerCreation,
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import { checkoutDiffPayloadSchema, checkoutRestorePayloadSchema, WebviewMessage
|
|||
import { Mode, CustomModePrompts, PromptComponent, defaultModeSlug, ModeConfig } from "../../shared/modes"
|
||||
import { checkExistKey } from "../../shared/checkExistApiConfig"
|
||||
import { EXPERIMENT_IDS, experiments as Experiments, experimentDefault, ExperimentId } from "../../shared/experiments"
|
||||
import { formatLanguage } from "../../shared/language"
|
||||
import { downloadTask } from "../../integrations/misc/export-markdown"
|
||||
import { openFile, openImage } from "../../integrations/misc/open-file"
|
||||
import { selectImages } from "../../integrations/misc/process-images"
|
||||
|
|
@ -1389,10 +1390,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
await this.updateGlobalState("rateLimitSeconds", message.value ?? 0)
|
||||
await this.postStateToWebview()
|
||||
break
|
||||
case "preferredLanguage":
|
||||
await this.updateGlobalState("preferredLanguage", message.text)
|
||||
await this.postStateToWebview()
|
||||
break
|
||||
case "writeDelayMs":
|
||||
await this.updateGlobalState("writeDelayMs", message.value)
|
||||
await this.postStateToWebview()
|
||||
|
|
@ -1930,7 +1927,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
apiConfiguration,
|
||||
customModePrompts,
|
||||
customInstructions,
|
||||
preferredLanguage,
|
||||
browserViewportSize,
|
||||
diffEnabled,
|
||||
mcpEnabled,
|
||||
|
|
@ -1968,7 +1964,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
customModePrompts,
|
||||
customModes,
|
||||
customInstructions,
|
||||
preferredLanguage,
|
||||
diffEnabled,
|
||||
experiments,
|
||||
enableMcpServerCreation,
|
||||
|
|
@ -2344,7 +2339,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
screenshotQuality,
|
||||
remoteBrowserHost,
|
||||
remoteBrowserEnabled,
|
||||
preferredLanguage,
|
||||
writeDelayMs,
|
||||
terminalOutputLineLimit,
|
||||
fuzzyMatchThreshold,
|
||||
|
|
@ -2365,6 +2359,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
browserToolEnabled,
|
||||
telemetrySetting,
|
||||
showRooIgnoredFiles,
|
||||
language,
|
||||
} = await this.getState()
|
||||
const telemetryKey = process.env.POSTHOG_API_KEY
|
||||
const machineId = vscode.env.machineId
|
||||
|
|
@ -2404,7 +2399,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
screenshotQuality: screenshotQuality ?? 75,
|
||||
remoteBrowserHost,
|
||||
remoteBrowserEnabled: remoteBrowserEnabled ?? false,
|
||||
preferredLanguage: preferredLanguage ?? "English",
|
||||
writeDelayMs: writeDelayMs ?? 1000,
|
||||
terminalOutputLineLimit: terminalOutputLineLimit ?? 500,
|
||||
fuzzyMatchThreshold: fuzzyMatchThreshold ?? 1.0,
|
||||
|
|
@ -2430,6 +2424,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
telemetryKey,
|
||||
machineId,
|
||||
showRooIgnoredFiles: showRooIgnoredFiles ?? true,
|
||||
language,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2563,37 +2558,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
writeDelayMs: stateValues.writeDelayMs ?? 1000,
|
||||
terminalOutputLineLimit: stateValues.terminalOutputLineLimit ?? 500,
|
||||
mode: stateValues.mode ?? defaultModeSlug,
|
||||
preferredLanguage:
|
||||
stateValues.preferredLanguage ??
|
||||
(() => {
|
||||
// Get VSCode's locale setting
|
||||
const vscodeLang = vscode.env.language
|
||||
// Map VSCode locale to our supported languages
|
||||
const langMap: { [key: string]: string } = {
|
||||
en: "English",
|
||||
ar: "Arabic",
|
||||
"pt-br": "Brazilian Portuguese",
|
||||
ca: "Catalan",
|
||||
cs: "Czech",
|
||||
fr: "French",
|
||||
de: "German",
|
||||
hi: "Hindi",
|
||||
hu: "Hungarian",
|
||||
it: "Italian",
|
||||
ja: "Japanese",
|
||||
ko: "Korean",
|
||||
pl: "Polish",
|
||||
pt: "Portuguese",
|
||||
ru: "Russian",
|
||||
zh: "Simplified Chinese",
|
||||
"zh-cn": "Simplified Chinese",
|
||||
es: "Spanish",
|
||||
"zh-tw": "Traditional Chinese",
|
||||
tr: "Turkish",
|
||||
}
|
||||
// Return mapped language or default to English
|
||||
return langMap[vscodeLang] ?? langMap[vscodeLang.split("-")[0]] ?? "English"
|
||||
})(),
|
||||
// Pass the VSCode language code directly
|
||||
language: formatLanguage(vscode.env.language),
|
||||
mcpEnabled: stateValues.mcpEnabled ?? true,
|
||||
enableMcpServerCreation: stateValues.enableMcpServerCreation ?? true,
|
||||
alwaysApproveResubmit: stateValues.alwaysApproveResubmit ?? false,
|
||||
|
|
|
|||
|
|
@ -420,7 +420,6 @@ describe("ClineProvider", () => {
|
|||
|
||||
const mockState: ExtensionState = {
|
||||
version: "1.0.0",
|
||||
preferredLanguage: "English",
|
||||
clineMessages: [],
|
||||
taskHistory: [],
|
||||
shouldShowAnnouncement: false,
|
||||
|
|
@ -535,20 +534,12 @@ describe("ClineProvider", () => {
|
|||
expect(state).toHaveProperty("writeDelayMs")
|
||||
})
|
||||
|
||||
test("preferredLanguage defaults to VSCode language when not set", async () => {
|
||||
test("language is set to VSCode language", async () => {
|
||||
// Mock VSCode language as Spanish
|
||||
;(vscode.env as any).language = "es-ES"
|
||||
|
||||
const state = await provider.getState()
|
||||
expect(state.preferredLanguage).toBe("Spanish")
|
||||
})
|
||||
|
||||
test("preferredLanguage defaults to English for unsupported VSCode language", async () => {
|
||||
// Mock VSCode language as an unsupported language
|
||||
;(vscode.env as any).language = "unsupported-LANG"
|
||||
|
||||
const state = await provider.getState()
|
||||
expect(state.preferredLanguage).toBe("English")
|
||||
expect(state.language).toBe("es-ES")
|
||||
})
|
||||
|
||||
test("diffEnabled defaults to true when not set", async () => {
|
||||
|
|
@ -1216,7 +1207,7 @@ describe("ClineProvider", () => {
|
|||
expect(callArgs[4]).toHaveProperty("getToolDescription") // diffStrategy
|
||||
expect(callArgs[5]).toBe("900x600") // browserViewportSize
|
||||
expect(callArgs[6]).toBe("code") // mode
|
||||
expect(callArgs[11]).toBe(true) // diffEnabled
|
||||
expect(callArgs[10]).toBe(true) // diffEnabled
|
||||
|
||||
// Run the test again to verify it's consistent
|
||||
await handler({ type: "getSystemPrompt", mode: "code" })
|
||||
|
|
@ -1274,7 +1265,7 @@ describe("ClineProvider", () => {
|
|||
expect(callArgs[4]).toHaveProperty("getToolDescription") // diffStrategy
|
||||
expect(callArgs[5]).toBe("900x600") // browserViewportSize
|
||||
expect(callArgs[6]).toBe("code") // mode
|
||||
expect(callArgs[11]).toBe(false) // diffEnabled should be false
|
||||
expect(callArgs[10]).toBe(false) // diffEnabled should be false
|
||||
})
|
||||
|
||||
test("uses correct mode-specific instructions when mode is specified", async () => {
|
||||
|
|
|
|||
1
src/exports/roo-code.d.ts
vendored
1
src/exports/roo-code.d.ts
vendored
|
|
@ -164,7 +164,6 @@ export type GlobalStateKey =
|
|||
| "screenshotQuality"
|
||||
| "remoteBrowserHost"
|
||||
| "fuzzyMatchThreshold"
|
||||
| "preferredLanguage" // Language setting for Cline's communication
|
||||
| "writeDelayMs"
|
||||
| "terminalOutputLineLimit"
|
||||
| "mcpEnabled"
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ export interface ExtensionState {
|
|||
remoteBrowserHost?: string
|
||||
remoteBrowserEnabled?: boolean
|
||||
fuzzyMatchThreshold?: number
|
||||
preferredLanguage: string
|
||||
language?: string
|
||||
writeDelayMs: number
|
||||
terminalOutputLineLimit?: number
|
||||
mcpEnabled: boolean
|
||||
|
|
|
|||
|
|
@ -64,7 +64,6 @@ export interface WebviewMessage {
|
|||
| "toggleMcpServer"
|
||||
| "updateMcpTimeout"
|
||||
| "fuzzyMatchThreshold"
|
||||
| "preferredLanguage"
|
||||
| "writeDelayMs"
|
||||
| "enhancePrompt"
|
||||
| "enhancedPrompt"
|
||||
|
|
|
|||
19
src/shared/__tests__/language.test.ts
Normal file
19
src/shared/__tests__/language.test.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { formatLanguage } from "../language"
|
||||
|
||||
describe("formatLanguage", () => {
|
||||
it("should uppercase region code in locale string", () => {
|
||||
expect(formatLanguage("en-us")).toBe("en-US")
|
||||
expect(formatLanguage("fr-ca")).toBe("fr-CA")
|
||||
expect(formatLanguage("de-de")).toBe("de-DE")
|
||||
})
|
||||
|
||||
it("should return original string if no region code present", () => {
|
||||
expect(formatLanguage("en")).toBe("en")
|
||||
expect(formatLanguage("fr")).toBe("fr")
|
||||
})
|
||||
|
||||
it("should handle empty or undefined input", () => {
|
||||
expect(formatLanguage("")).toBe("en")
|
||||
expect(formatLanguage(undefined as unknown as string)).toBe("en")
|
||||
})
|
||||
})
|
||||
|
|
@ -6,7 +6,6 @@ jest.mock("../../core/prompts/sections/custom-instructions", () => ({
|
|||
}))
|
||||
|
||||
import { isToolAllowedForMode, FileRestrictionError, ModeConfig, getFullModeDetails, modes } from "../modes"
|
||||
import * as vscode from "vscode"
|
||||
import { addCustomInstructions } from "../../core/prompts/sections/custom-instructions"
|
||||
|
||||
describe("isToolAllowedForMode", () => {
|
||||
|
|
@ -402,7 +401,7 @@ describe("FileRestrictionError", () => {
|
|||
const options = {
|
||||
cwd: "/test/path",
|
||||
globalCustomInstructions: "Global instructions",
|
||||
preferredLanguage: "en",
|
||||
language: "en",
|
||||
}
|
||||
|
||||
await getFullModeDetails("debug", undefined, undefined, options)
|
||||
|
|
@ -412,7 +411,7 @@ describe("FileRestrictionError", () => {
|
|||
"Global instructions",
|
||||
"/test/path",
|
||||
"debug",
|
||||
{ preferredLanguage: "en" },
|
||||
{ language: "en" },
|
||||
)
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -82,7 +82,6 @@ export const GLOBAL_STATE_KEYS = [
|
|||
"screenshotQuality",
|
||||
"remoteBrowserHost",
|
||||
"fuzzyMatchThreshold",
|
||||
"preferredLanguage", // Language setting for Cline's communication.
|
||||
"writeDelayMs",
|
||||
"terminalOutputLineLimit",
|
||||
"mcpEnabled",
|
||||
|
|
|
|||
14
src/shared/language.ts
Normal file
14
src/shared/language.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* Formats a VSCode locale string to ensure the region code is uppercase.
|
||||
* For example, transforms "en-us" to "en-US" or "fr-ca" to "fr-CA".
|
||||
*
|
||||
* @param vscodeLocale - The VSCode locale string to format (e.g., "en-us", "fr-ca")
|
||||
* @returns The formatted locale string with uppercase region code
|
||||
*/
|
||||
export function formatLanguage(vscodeLocale: string): string {
|
||||
if (!vscodeLocale) {
|
||||
return "en" // Default to English if no locale is provided
|
||||
}
|
||||
|
||||
return vscodeLocale.replace(/-(\w+)$/, (_, region) => `-${region.toUpperCase()}`)
|
||||
}
|
||||
|
|
@ -195,10 +195,13 @@ export function isToolAllowedForMode(
|
|||
}
|
||||
|
||||
// Check tool requirements if any exist
|
||||
if (toolRequirements && tool in toolRequirements) {
|
||||
if (!toolRequirements[tool]) {
|
||||
if (toolRequirements && typeof toolRequirements === "object") {
|
||||
if (tool in toolRequirements && !toolRequirements[tool]) {
|
||||
return false
|
||||
}
|
||||
} else if (toolRequirements === false) {
|
||||
// If toolRequirements is a boolean false, all tools are disabled
|
||||
return false
|
||||
}
|
||||
|
||||
const mode = getModeBySlug(modeSlug, customModes)
|
||||
|
|
@ -275,7 +278,7 @@ export async function getFullModeDetails(
|
|||
options?: {
|
||||
cwd?: string
|
||||
globalCustomInstructions?: string
|
||||
preferredLanguage?: string
|
||||
language?: string
|
||||
},
|
||||
): Promise<ModeConfig> {
|
||||
// First get the base mode config from custom modes or built-in modes
|
||||
|
|
@ -295,7 +298,7 @@ export async function getFullModeDetails(
|
|||
options.globalCustomInstructions || "",
|
||||
options.cwd,
|
||||
modeSlug,
|
||||
{ preferredLanguage: options.preferredLanguage },
|
||||
{ language: options.language },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
2
webview-ui/.gitignore
vendored
2
webview-ui/.gitignore
vendored
|
|
@ -23,3 +23,5 @@ yarn-debug.log*
|
|||
yarn-error.log*
|
||||
|
||||
*storybook.log
|
||||
|
||||
tsconfig.tsbuildinfo
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ module.exports = {
|
|||
testEnvironment: "jsdom",
|
||||
injectGlobals: true,
|
||||
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
|
||||
transform: { "^.+\\.(ts|tsx)$": ["ts-jest", { tsconfig: { jsx: "react-jsx" } }] },
|
||||
transform: { "^.+\\.(ts|tsx)$": ["ts-jest", { tsconfig: { jsx: "react-jsx", module: "ESNext" } }] },
|
||||
testMatch: ["<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}", "<rootDir>/src/**/*.{spec,test}.{js,jsx,ts,tsx}"],
|
||||
setupFilesAfterEnv: ["<rootDir>/src/setupTests.ts"],
|
||||
moduleNameMapper: {
|
||||
|
|
@ -12,6 +12,12 @@ module.exports = {
|
|||
"^vscrui$": "<rootDir>/src/__mocks__/vscrui.ts",
|
||||
"^@vscode/webview-ui-toolkit/react$": "<rootDir>/src/__mocks__/@vscode/webview-ui-toolkit/react.ts",
|
||||
"^@/(.*)$": "<rootDir>/src/$1",
|
||||
"^src/i18n/setup$": "<rootDir>/src/__mocks__/i18n/setup.ts",
|
||||
"^\\.\\./setup$": "<rootDir>/src/__mocks__/i18n/setup.ts",
|
||||
"^\\./setup$": "<rootDir>/src/__mocks__/i18n/setup.ts",
|
||||
"^src/i18n/TranslationContext$": "<rootDir>/src/__mocks__/i18n/TranslationContext.tsx",
|
||||
"^\\.\\./TranslationContext$": "<rootDir>/src/__mocks__/i18n/TranslationContext.tsx",
|
||||
"^\\./TranslationContext$": "<rootDir>/src/__mocks__/i18n/TranslationContext.tsx"
|
||||
},
|
||||
reporters: [["jest-simple-dot-reporter", {}]],
|
||||
transformIgnorePatterns: [
|
||||
|
|
|
|||
463
webview-ui/package-lock.json
generated
463
webview-ui/package-lock.json
generated
|
|
@ -28,11 +28,14 @@
|
|||
"debounce": "^2.1.1",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fzf": "^0.5.2",
|
||||
"i18next": "^24.2.2",
|
||||
"i18next-http-backend": "^3.0.2",
|
||||
"lucide-react": "^0.475.0",
|
||||
"mermaid": "^11.4.1",
|
||||
"posthog-js": "^1.227.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-i18next": "^15.4.1",
|
||||
"react-markdown": "^9.0.3",
|
||||
"react-remark": "^2.1.0",
|
||||
"react-textarea-autosize": "^8.5.3",
|
||||
|
|
@ -78,7 +81,7 @@
|
|||
"storybook": "^8.5.6",
|
||||
"storybook-dark-mode": "^4.0.2",
|
||||
"ts-jest": "^29.2.5",
|
||||
"typescript": "^4.9.5",
|
||||
"typescript": "^5.4.5",
|
||||
"vite": "6.0.11"
|
||||
}
|
||||
},
|
||||
|
|
@ -6165,26 +6168,6 @@
|
|||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta"
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/instrumenter": {
|
||||
"version": "8.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/instrumenter/-/instrumenter-8.5.6.tgz",
|
||||
"integrity": "sha512-uMOOiq/9dFoFhSl3IxuQ+yq4lClkcRtEuB6cPzD/rVCmlh+i//VkHTqFCNrDvpVA21Lsy9NLmnxLHJpBGN3Avg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@storybook/global": "^5.0.0",
|
||||
"@vitest/utils": "^2.1.1"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/storybook"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"storybook": "^8.5.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/manager-api": {
|
||||
"version": "8.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-8.5.6.tgz",
|
||||
|
|
@ -6303,96 +6286,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/test": {
|
||||
"version": "8.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/test/-/test-8.5.6.tgz",
|
||||
"integrity": "sha512-U4HdyAcCwc/ictwq0HWKI6j2NAUggB9ENfyH3baEWaLEI+mp4pzQMuTnOIF9TvqU7K1D5UqOyfs/hlbFxUFysg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@storybook/csf": "0.1.12",
|
||||
"@storybook/global": "^5.0.0",
|
||||
"@storybook/instrumenter": "8.5.6",
|
||||
"@testing-library/dom": "10.4.0",
|
||||
"@testing-library/jest-dom": "6.5.0",
|
||||
"@testing-library/user-event": "14.5.2",
|
||||
"@vitest/expect": "2.0.5",
|
||||
"@vitest/spy": "2.0.5"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/storybook"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"storybook": "^8.5.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/test/node_modules/@testing-library/jest-dom": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.5.0.tgz",
|
||||
"integrity": "sha512-xGGHpBXYSHUUr6XsKBfs85TWlYKpTc37cSBBVrXcib2MkHLboWlkClhWF37JKlDb9KEq3dHs+f2xR7XJEWGBxA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@adobe/css-tools": "^4.4.0",
|
||||
"aria-query": "^5.0.0",
|
||||
"chalk": "^3.0.0",
|
||||
"css.escape": "^1.5.1",
|
||||
"dom-accessibility-api": "^0.6.3",
|
||||
"lodash": "^4.17.21",
|
||||
"redent": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
"npm": ">=6",
|
||||
"yarn": ">=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/test/node_modules/@testing-library/user-event": {
|
||||
"version": "14.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.5.2.tgz",
|
||||
"integrity": "sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12",
|
||||
"npm": ">=6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@testing-library/dom": ">=7.21.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/test/node_modules/chalk": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
|
||||
"integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/test/node_modules/dom-accessibility-api": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz",
|
||||
"integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@storybook/theming": {
|
||||
"version": "8.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-8.5.6.tgz",
|
||||
|
|
@ -6645,7 +6538,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz",
|
||||
"integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.10.4",
|
||||
|
|
@ -6666,7 +6558,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz",
|
||||
"integrity": "sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@adobe/css-tools": "^4.4.0",
|
||||
"aria-query": "^5.0.0",
|
||||
|
|
@ -6708,7 +6599,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.2.0.tgz",
|
||||
"integrity": "sha512-2cSskAvA1QNtKc8Y9VJQRv0tm3hLVgxRGDB+KYhIaPQJ1I+RHbhIXcM+zClKXzMes/wshsMVzf4B9vS4IZpqDQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5"
|
||||
},
|
||||
|
|
@ -6760,7 +6650,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
|
||||
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/babel__core": {
|
||||
|
|
@ -7771,116 +7660,6 @@
|
|||
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/expect": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.0.5.tgz",
|
||||
"integrity": "sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vitest/spy": "2.0.5",
|
||||
"@vitest/utils": "2.0.5",
|
||||
"chai": "^5.1.1",
|
||||
"tinyrainbow": "^1.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/expect/node_modules/@vitest/pretty-format": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.0.5.tgz",
|
||||
"integrity": "sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tinyrainbow": "^1.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/expect/node_modules/@vitest/utils": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.0.5.tgz",
|
||||
"integrity": "sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "2.0.5",
|
||||
"estree-walker": "^3.0.3",
|
||||
"loupe": "^3.1.1",
|
||||
"tinyrainbow": "^1.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/expect/node_modules/estree-walker": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
|
||||
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/pretty-format": {
|
||||
"version": "2.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz",
|
||||
"integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tinyrainbow": "^1.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/spy": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.0.5.tgz",
|
||||
"integrity": "sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tinyspy": "^3.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/utils": {
|
||||
"version": "2.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz",
|
||||
"integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "2.1.9",
|
||||
"loupe": "^3.1.2",
|
||||
"tinyrainbow": "^1.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vscode/webview-ui-toolkit": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@vscode/webview-ui-toolkit/-/webview-ui-toolkit-1.4.0.tgz",
|
||||
|
|
@ -8239,18 +8018,6 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/assertion-error": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
|
||||
"integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/ast-types": {
|
||||
"version": "0.16.1",
|
||||
"resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz",
|
||||
|
|
@ -8770,25 +8537,6 @@
|
|||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/chai": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz",
|
||||
"integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"assertion-error": "^2.0.1",
|
||||
"check-error": "^2.1.1",
|
||||
"deep-eql": "^5.0.1",
|
||||
"loupe": "^3.1.0",
|
||||
"pathval": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
|
|
@ -8856,18 +8604,6 @@
|
|||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/check-error": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz",
|
||||
"integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
}
|
||||
},
|
||||
"node_modules/chevrotain": {
|
||||
"version": "11.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz",
|
||||
|
|
@ -9568,6 +9304,14 @@
|
|||
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-fetch": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz",
|
||||
"integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==",
|
||||
"dependencies": {
|
||||
"node-fetch": "^2.6.12"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
|
|
@ -10277,18 +10021,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/deep-eql": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
|
||||
"integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/deep-is": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
||||
|
|
@ -10461,7 +10193,6 @@
|
|||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
|
||||
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/domexception": {
|
||||
|
|
@ -13070,6 +12801,14 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/html-parse-stringify": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
|
||||
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
|
||||
"dependencies": {
|
||||
"void-elements": "3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html-url-attributes": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
|
||||
|
|
@ -13136,6 +12875,44 @@
|
|||
"integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/i18next": {
|
||||
"version": "24.2.2",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-24.2.2.tgz",
|
||||
"integrity": "sha512-NE6i86lBCKRYZa5TaUDkU5S4HFgLIEJRLr3Whf2psgaxBleQ2LC1YW1Vc+SCgkAW7VEzndT6al6+CzegSUHcTQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://locize.com"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://locize.com/i18next.html"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/i18next-http-backend": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-3.0.2.tgz",
|
||||
"integrity": "sha512-PdlvPnvIp4E1sYi46Ik4tBYh/v/NbYfFFgTjkwFl0is8A18s7/bx9aXqsrOax9WUbeNS6mD2oix7Z0yGGf6m5g==",
|
||||
"dependencies": {
|
||||
"cross-fetch": "4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
|
|
@ -15602,15 +15379,6 @@
|
|||
"loose-envify": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/loupe": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz",
|
||||
"integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/lowlight": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lowlight/-/lowlight-3.3.0.tgz",
|
||||
|
|
@ -15650,7 +15418,6 @@
|
|||
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
|
||||
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"lz-string": "bin/bin.js"
|
||||
|
|
@ -17793,6 +17560,44 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "4.x || >=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"encoding": "^0.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"encoding": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch/node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||
},
|
||||
"node_modules/node-fetch/node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
|
||||
},
|
||||
"node_modules/node-fetch/node_modules/whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||
"dependencies": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-int64": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
|
||||
|
|
@ -18252,18 +18057,6 @@
|
|||
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
|
||||
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="
|
||||
},
|
||||
"node_modules/pathval": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz",
|
||||
"integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 14.16"
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
|
|
@ -18725,6 +18518,27 @@
|
|||
"react": "^18.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-i18next": {
|
||||
"version": "15.4.1",
|
||||
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.4.1.tgz",
|
||||
"integrity": "sha512-ahGab+IaSgZmNPYXdV1n+OYky95TGpFwnKRflX/16dY04DsYYKHtVLjeny7sBSCREEcoMbAgSkFiGLF5g5Oofw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.25.0",
|
||||
"html-parse-stringify": "^3.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"i18next": ">= 23.2.3",
|
||||
"react": ">= 16.8.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
|
|
@ -20783,30 +20597,6 @@
|
|||
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
|
||||
"integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="
|
||||
},
|
||||
"node_modules/tinyrainbow": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz",
|
||||
"integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyspy": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz",
|
||||
"integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tmpl": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
|
||||
|
|
@ -21141,17 +20931,16 @@
|
|||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "4.9.5",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
|
||||
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"version": "5.7.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
|
||||
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
|
||||
"devOptional": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.2.0"
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/ufo": {
|
||||
|
|
@ -21742,6 +21531,14 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/void-elements": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
|
||||
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vscode-jsonrpc": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz",
|
||||
|
|
|
|||
|
|
@ -35,11 +35,14 @@
|
|||
"debounce": "^2.1.1",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fzf": "^0.5.2",
|
||||
"i18next": "^24.2.2",
|
||||
"i18next-http-backend": "^3.0.2",
|
||||
"lucide-react": "^0.475.0",
|
||||
"mermaid": "^11.4.1",
|
||||
"posthog-js": "^1.227.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-i18next": "^15.4.1",
|
||||
"react-markdown": "^9.0.3",
|
||||
"react-remark": "^2.1.0",
|
||||
"react-textarea-autosize": "^8.5.3",
|
||||
|
|
@ -85,7 +88,7 @@
|
|||
"storybook": "^8.5.6",
|
||||
"storybook-dark-mode": "^4.0.2",
|
||||
"ts-jest": "^29.2.5",
|
||||
"typescript": "^4.9.5",
|
||||
"typescript": "^5.4.5",
|
||||
"vite": "6.0.11"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { useCallback, useEffect, useRef, useState } from "react"
|
||||
import { useEvent } from "react-use"
|
||||
|
||||
import { ExtensionMessage } from "../../src/shared/ExtensionMessage"
|
||||
import { ShowHumanRelayDialogMessage } from "../../src/shared/ExtensionMessage"
|
||||
import TranslationProvider from "./i18n/TranslationContext"
|
||||
|
||||
import { vscode } from "./utils/vscode"
|
||||
import { telemetryClient } from "./utils/TelemetryClient"
|
||||
|
|
@ -131,7 +131,9 @@ const App = () => {
|
|||
|
||||
const AppWithProviders = () => (
|
||||
<ExtensionStateContextProvider>
|
||||
<App />
|
||||
<TranslationProvider>
|
||||
<App />
|
||||
</TranslationProvider>
|
||||
</ExtensionStateContextProvider>
|
||||
)
|
||||
|
||||
|
|
|
|||
47
webview-ui/src/__mocks__/i18n/TranslationContext.tsx
Normal file
47
webview-ui/src/__mocks__/i18n/TranslationContext.tsx
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import React, { ReactNode } from "react"
|
||||
import i18next from "./setup"
|
||||
|
||||
// Create a mock context
|
||||
export const TranslationContext = React.createContext<{
|
||||
t: (key: string, options?: Record<string, any>) => string
|
||||
i18n: typeof i18next
|
||||
}>({
|
||||
t: (key: string, options?: Record<string, any>) => {
|
||||
// Handle specific test cases
|
||||
if (key === "settings.autoApprove.title") {
|
||||
return "Auto-Approve"
|
||||
}
|
||||
if (key === "notifications.error" && options?.message) {
|
||||
return `Operation failed: ${options.message}`
|
||||
}
|
||||
return key // Default fallback
|
||||
},
|
||||
i18n: i18next,
|
||||
})
|
||||
|
||||
// Mock translation provider
|
||||
export const TranslationProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||
return (
|
||||
<TranslationContext.Provider
|
||||
value={{
|
||||
t: (key: string, options?: Record<string, any>) => {
|
||||
// Handle specific test cases
|
||||
if (key === "settings.autoApprove.title") {
|
||||
return "Auto-Approve"
|
||||
}
|
||||
if (key === "notifications.error" && options?.message) {
|
||||
return `Operation failed: ${options.message}`
|
||||
}
|
||||
return key // Default fallback
|
||||
},
|
||||
i18n: i18next,
|
||||
}}>
|
||||
{children}
|
||||
</TranslationContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
// Custom hook for easy translations
|
||||
export const useAppTranslation = () => React.useContext(TranslationContext)
|
||||
|
||||
export default TranslationProvider
|
||||
62
webview-ui/src/__mocks__/i18n/setup.ts
Normal file
62
webview-ui/src/__mocks__/i18n/setup.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import i18next from "i18next"
|
||||
import { initReactI18next } from "react-i18next"
|
||||
|
||||
// Mock translations for testing
|
||||
const translations: Record<string, Record<string, any>> = {
|
||||
en: {
|
||||
chat: {
|
||||
greeting: "What can Roo do for you?",
|
||||
},
|
||||
settings: {
|
||||
autoApprove: {
|
||||
title: "Auto-Approve",
|
||||
},
|
||||
},
|
||||
common: {
|
||||
notifications: {
|
||||
error: "Operation failed: {{message}}",
|
||||
},
|
||||
},
|
||||
},
|
||||
es: {
|
||||
chat: {
|
||||
greeting: "¿Qué puede hacer Roo por ti?",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Initialize i18next for React
|
||||
i18next.use(initReactI18next).init({
|
||||
lng: "en",
|
||||
fallbackLng: "en",
|
||||
debug: false,
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
resources: {
|
||||
en: {
|
||||
chat: translations.en.chat,
|
||||
settings: translations.en.settings,
|
||||
common: translations.en.common,
|
||||
},
|
||||
es: {
|
||||
chat: translations.es.chat,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export function loadTranslations() {
|
||||
// Translations are already loaded in the mock
|
||||
}
|
||||
|
||||
export function addTranslation(language: string, namespace: string, resources: any) {
|
||||
if (!translations[language]) {
|
||||
translations[language] = {}
|
||||
}
|
||||
translations[language][namespace] = resources
|
||||
|
||||
// Also add to i18next
|
||||
i18next.addResourceBundle(language, namespace, resources, true, true)
|
||||
}
|
||||
|
||||
export default i18next
|
||||
|
|
@ -30,6 +30,7 @@ import { AudioType } from "../../../../src/shared/WebviewMessage"
|
|||
import { validateCommand } from "../../utils/command-validation"
|
||||
import { getAllModes } from "../../../../src/shared/modes"
|
||||
import TelemetryBanner from "../common/TelemetryBanner"
|
||||
import { useAppTranslation } from "@/i18n/TranslationContext"
|
||||
|
||||
interface ChatViewProps {
|
||||
isHidden: boolean
|
||||
|
|
@ -66,6 +67,8 @@ 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])
|
||||
|
|
@ -1100,7 +1103,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
|||
{telemetrySetting === "unset" && <TelemetryBanner />}
|
||||
{showAnnouncement && <Announcement version={version} hideAnnouncement={hideAnnouncement} />}
|
||||
<div style={{ padding: "0 20px", flexShrink: 0 }}>
|
||||
<h2>What can Roo do for you?</h2>
|
||||
<h2>{t("chat:greeting")}</h2>
|
||||
<p>
|
||||
Thanks to the latest breakthroughs in agentic coding capabilities, I can handle complex
|
||||
software development tasks step-by-step. With tools that let me create & edit files, explore
|
||||
|
|
|
|||
|
|
@ -9,19 +9,6 @@ import {
|
|||
VSCodeRadioGroup,
|
||||
VSCodeRadio,
|
||||
} from "@vscode/webview-ui-toolkit/react"
|
||||
import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons"
|
||||
import {
|
||||
Button,
|
||||
Command,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { useExtensionState } from "../../context/ExtensionStateContext"
|
||||
import {
|
||||
Mode,
|
||||
|
|
@ -43,6 +30,7 @@ import {
|
|||
import { TOOL_GROUPS, GROUP_DISPLAY_NAMES, ToolGroup } from "../../../../src/shared/tool-groups"
|
||||
import { vscode } from "../../utils/vscode"
|
||||
import { Tab, TabContent, TabHeader } from "../common/Tab"
|
||||
import i18next from "i18next"
|
||||
|
||||
// Get all available groups that should show in prompts view
|
||||
const availableGroups = (Object.keys(TOOL_GROUPS) as ToolGroup[]).filter((group) => !TOOL_GROUPS[group].alwaysAvailable)
|
||||
|
|
@ -69,8 +57,6 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
mode,
|
||||
customInstructions,
|
||||
setCustomInstructions,
|
||||
preferredLanguage,
|
||||
setPreferredLanguage,
|
||||
customModes,
|
||||
enableCustomModeCreation,
|
||||
setEnableCustomModeCreation,
|
||||
|
|
@ -82,9 +68,6 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
const [testPrompt, setTestPrompt] = useState("")
|
||||
const [isEnhancing, setIsEnhancing] = useState(false)
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false)
|
||||
const [open, setOpen] = useState(false)
|
||||
const [isCustomLanguage, setIsCustomLanguage] = useState(false)
|
||||
const [customLanguage, setCustomLanguage] = useState("")
|
||||
const [selectedPromptContent, setSelectedPromptContent] = useState("")
|
||||
const [selectedPromptTitle, setSelectedPromptTitle] = useState("")
|
||||
const [isToolsEditMode, setIsToolsEditMode] = useState(false)
|
||||
|
|
@ -427,136 +410,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
</TabHeader>
|
||||
|
||||
<TabContent>
|
||||
<div className="pb-5 border-b border-vscode-input-border">
|
||||
<div className="mb-5">
|
||||
<div className="font-bold mb-1">Preferred Language</div>
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="combobox"
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
className="w-full justify-between">
|
||||
{preferredLanguage ?? "Select language..."}
|
||||
<CaretSortIcon className="opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="start" className="p-0">
|
||||
<Command>
|
||||
<CommandInput placeholder="Search language..." className="h-9" />
|
||||
<CommandList>
|
||||
<CommandGroup>
|
||||
{[
|
||||
{ value: "English", label: "English" },
|
||||
{ value: "Arabic", label: "Arabic - العربية" },
|
||||
{
|
||||
value: "Brazilian Portuguese",
|
||||
label: "Portuguese - Português (Brasil)",
|
||||
},
|
||||
{ value: "Catalan", label: "Catalan - Català" },
|
||||
{ value: "Czech", label: "Czech - Čeština" },
|
||||
{ value: "French", label: "French - Français" },
|
||||
{ value: "German", label: "German - Deutsch" },
|
||||
{ value: "Hindi", label: "Hindi - हिन्दी" },
|
||||
{ value: "Hungarian", label: "Hungarian - Magyar" },
|
||||
{ value: "Italian", label: "Italian - Italiano" },
|
||||
{ value: "Japanese", label: "Japanese - 日本語" },
|
||||
{ value: "Korean", label: "Korean - 한국어" },
|
||||
{ value: "Polish", label: "Polish - Polski" },
|
||||
{ value: "Portuguese", label: "Portuguese - Português (Portugal)" },
|
||||
{ value: "Russian", label: "Russian - Русский" },
|
||||
{ value: "Simplified Chinese", label: "Simplified Chinese - 简体中文" },
|
||||
{ value: "Spanish", label: "Spanish - Español" },
|
||||
{
|
||||
value: "Traditional Chinese",
|
||||
label: "Traditional Chinese - 繁體中文",
|
||||
},
|
||||
{ value: "Turkish", label: "Turkish - Türkçe" },
|
||||
].map((language) => (
|
||||
<CommandItem
|
||||
key={language.value}
|
||||
value={language.value}
|
||||
onSelect={(value) => {
|
||||
setPreferredLanguage(value)
|
||||
vscode.postMessage({
|
||||
type: "preferredLanguage",
|
||||
text: value,
|
||||
})
|
||||
setOpen(false)
|
||||
}}>
|
||||
{language.label}
|
||||
<CheckIcon
|
||||
className={cn(
|
||||
"ml-auto",
|
||||
preferredLanguage === language.value
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
)}
|
||||
/>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
<div className="border-t border-[var(--vscode-input-border)]">
|
||||
<button
|
||||
className="w-full px-2 py-1.5 text-sm text-left hover:bg-[var(--vscode-list-hoverBackground)]"
|
||||
onClick={() => {
|
||||
setIsCustomLanguage(true)
|
||||
setOpen(false)
|
||||
}}>
|
||||
+ Choose another language
|
||||
</button>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<p className="text-xs mt-1.5 text-vscode-descriptionForeground">
|
||||
Select the language that Roo should use for communication.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="font-bold mb-1">Custom Instructions for All Modes</div>
|
||||
<div className="text-sm text-vscode-descriptionForeground mb-2">
|
||||
These instructions apply to all modes. They provide a base set of behaviors that can be enhanced
|
||||
by mode-specific instructions below.
|
||||
</div>
|
||||
<VSCodeTextArea
|
||||
value={customInstructions ?? ""}
|
||||
onChange={(e) => {
|
||||
const value =
|
||||
(e as CustomEvent)?.detail?.target?.value ||
|
||||
((e as any).target as HTMLTextAreaElement).value
|
||||
setCustomInstructions(value || undefined)
|
||||
vscode.postMessage({
|
||||
type: "customInstructions",
|
||||
text: value.trim() || undefined,
|
||||
})
|
||||
}}
|
||||
rows={4}
|
||||
resize="vertical"
|
||||
className="w-full"
|
||||
data-testid="global-custom-instructions-textarea"
|
||||
/>
|
||||
<div className="text-xs text-vscode-descriptionForeground mt-1.5 mb-10">
|
||||
Instructions can also be loaded from{" "}
|
||||
<span
|
||||
className="text-vscode-textLink-foreground cursor-pointer underline"
|
||||
onClick={() =>
|
||||
vscode.postMessage({
|
||||
type: "openFile",
|
||||
text: "./.clinerules",
|
||||
values: {
|
||||
create: true,
|
||||
content: "",
|
||||
},
|
||||
})
|
||||
}>
|
||||
.clinerules
|
||||
</span>{" "}
|
||||
in your workspace.
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<div>
|
||||
<div onClick={(e) => e.stopPropagation()} className="flex justify-between items-center mb-3">
|
||||
<h3 className="text-vscode-foreground m-0">Modes</h3>
|
||||
<div className="flex gap-2">
|
||||
|
|
@ -987,8 +841,37 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
</VSCodeButton>
|
||||
</div>
|
||||
|
||||
{/*
|
||||
NOTE: This setting is placed in PromptsView rather than SettingsView since it
|
||||
directly affects the functionality related to modes and custom mode creation,
|
||||
which are managed in this component. This is an intentional deviation from
|
||||
the standard pattern described in cline_docs/settings.md.
|
||||
*/}
|
||||
<div className="mt-12">
|
||||
<VSCodeCheckbox
|
||||
checked={enableCustomModeCreation ?? true}
|
||||
onChange={(e: any) => {
|
||||
// Just update the local state through React context
|
||||
// The React context will update the global state
|
||||
setEnableCustomModeCreation(e.target.checked)
|
||||
}}>
|
||||
<span style={{ fontWeight: "500" }}>Enable Custom Mode Creation Through Prompts</span>
|
||||
</VSCodeCheckbox>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: "5px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
When enabled, Roo allows you to create custom modes using prompts like ‘Make me a custom
|
||||
mode that…’. Disabling this reduces your system prompt by about 700 tokens when this feature
|
||||
isn’t needed. When disabled you can still manually create custom modes using the + button
|
||||
above or by editing the related config JSON.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Custom System Prompt Disclosure */}
|
||||
<div className="mb-3 mt-12">
|
||||
<div className="mt-12">
|
||||
<button
|
||||
onClick={() => setIsSystemPromptDisclosureOpen(!isSystemPromptDisclosureOpen)}
|
||||
className="flex items-center text-xs text-vscode-foreground hover:text-vscode-textLink-foreground focus:outline-none"
|
||||
|
|
@ -1025,34 +908,54 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/*
|
||||
NOTE: This setting is placed in PromptsView rather than SettingsView since it
|
||||
directly affects the functionality related to modes and custom mode creation,
|
||||
which are managed in this component. This is an intentional deviation from
|
||||
the standard pattern described in cline_docs/settings.md.
|
||||
*/}
|
||||
<div className="mb-4 mt-4">
|
||||
<VSCodeCheckbox
|
||||
checked={enableCustomModeCreation ?? true}
|
||||
onChange={(e: any) => {
|
||||
// Just update the local state through React context
|
||||
// The React context will update the global state
|
||||
setEnableCustomModeCreation(e.target.checked)
|
||||
}}>
|
||||
<span style={{ fontWeight: "500" }}>Enable Custom Mode Creation Through Prompts</span>
|
||||
</VSCodeCheckbox>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: "5px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
When enabled, Roo allows you to create custom modes using prompts like ‘Make me a custom
|
||||
mode that…’. Disabling this reduces your system prompt by about 700 tokens when this feature
|
||||
isn’t needed. When disabled you can still manually create custom modes using the + button
|
||||
above or by editing the related config JSON.
|
||||
</p>
|
||||
<div className="pb-5 border-b border-vscode-input-border">
|
||||
<h3 style={{ color: "var(--vscode-foreground)", marginBottom: "12px" }}>
|
||||
Custom Instructions for All Modes
|
||||
</h3>
|
||||
|
||||
<div className="text-sm text-vscode-descriptionForeground mb-2">
|
||||
These instructions apply to all modes. They provide a base set of behaviors that can be enhanced
|
||||
by mode-specific instructions below.
|
||||
<br />
|
||||
If you would like Roo to think and speak in a different language than your editor display
|
||||
language ({i18next.language}), you can specify it here.
|
||||
</div>
|
||||
<VSCodeTextArea
|
||||
value={customInstructions ?? ""}
|
||||
onChange={(e) => {
|
||||
const value =
|
||||
(e as CustomEvent)?.detail?.target?.value ||
|
||||
((e as any).target as HTMLTextAreaElement).value
|
||||
setCustomInstructions(value || undefined)
|
||||
vscode.postMessage({
|
||||
type: "customInstructions",
|
||||
text: value.trim() || undefined,
|
||||
})
|
||||
}}
|
||||
rows={4}
|
||||
resize="vertical"
|
||||
className="w-full"
|
||||
data-testid="global-custom-instructions-textarea"
|
||||
/>
|
||||
<div className="text-xs text-vscode-descriptionForeground mt-1.5 mb-10">
|
||||
Instructions can also be loaded from{" "}
|
||||
<span
|
||||
className="text-vscode-textLink-foreground cursor-pointer underline"
|
||||
onClick={() =>
|
||||
vscode.postMessage({
|
||||
type: "openFile",
|
||||
text: "./.clinerules",
|
||||
values: {
|
||||
create: true,
|
||||
content: "",
|
||||
},
|
||||
})
|
||||
}>
|
||||
.clinerules
|
||||
</span>{" "}
|
||||
in your workspace.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -1509,41 +1412,6 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isCustomLanguage && (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
||||
<div className="bg-[var(--vscode-editor-background)] p-6 rounded-lg w-96">
|
||||
<h3 className="text-lg font-semibold mb-4">Add Custom Language</h3>
|
||||
<input
|
||||
type="text"
|
||||
className="w-full p-2 mb-4 bg-[var(--vscode-input-background)] text-[var(--vscode-input-foreground)] border border-[var(--vscode-input-border)] rounded"
|
||||
placeholder="Enter language name"
|
||||
value={customLanguage}
|
||||
onChange={(e) => setCustomLanguage(e.target.value)}
|
||||
/>
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button variant="secondary" onClick={() => setIsCustomLanguage(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (customLanguage.trim()) {
|
||||
setPreferredLanguage(customLanguage.trim())
|
||||
vscode.postMessage({
|
||||
type: "preferredLanguage",
|
||||
text: customLanguage.trim(),
|
||||
})
|
||||
setIsCustomLanguage(false)
|
||||
setCustomLanguage("")
|
||||
}
|
||||
}}
|
||||
disabled={!customLanguage.trim()}>
|
||||
Add
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Tab>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ export type ComboboxType = "single" | "multiple"
|
|||
|
||||
export interface ComboboxBaseProps
|
||||
extends React.ComponentProps<typeof PopoverPrimitive.Root>,
|
||||
Omit<React.ComponentProps<typeof CommandPrimitive>, "value" | "defaultValue" | "onValueChange"> {
|
||||
Omit<React.ComponentProps<typeof CommandPrimitive>, "value" | "defaultValue" | "onValueChange" | "children"> {
|
||||
type?: ComboboxType | undefined
|
||||
inputValue?: string
|
||||
defaultInputValue?: string
|
||||
|
|
|
|||
|
|
@ -39,8 +39,6 @@ export interface ExtensionStateContextType extends ExtensionState {
|
|||
setEnableCheckpoints: (value: boolean) => void
|
||||
setBrowserViewportSize: (value: string) => void
|
||||
setFuzzyMatchThreshold: (value: number) => void
|
||||
preferredLanguage: string
|
||||
setPreferredLanguage: (value: string) => void
|
||||
setWriteDelayMs: (value: number) => void
|
||||
screenshotQuality?: number
|
||||
setScreenshotQuality: (value: number) => void
|
||||
|
|
@ -118,7 +116,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
|||
enableCheckpoints: true,
|
||||
checkpointStorage: "task",
|
||||
fuzzyMatchThreshold: 1.0,
|
||||
preferredLanguage: "English",
|
||||
language: "en", // Default language code
|
||||
enableCustomModeCreation: true,
|
||||
writeDelayMs: 1000,
|
||||
browserViewportSize: "900x600",
|
||||
|
|
@ -260,7 +258,6 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
|||
setBrowserViewportSize: (value: string) =>
|
||||
setState((prevState) => ({ ...prevState, browserViewportSize: value })),
|
||||
setFuzzyMatchThreshold: (value) => setState((prevState) => ({ ...prevState, fuzzyMatchThreshold: value })),
|
||||
setPreferredLanguage: (value) => setState((prevState) => ({ ...prevState, preferredLanguage: value })),
|
||||
setWriteDelayMs: (value) => setState((prevState) => ({ ...prevState, writeDelayMs: value })),
|
||||
setScreenshotQuality: (value) => setState((prevState) => ({ ...prevState, screenshotQuality: value })),
|
||||
setTerminalOutputLineLimit: (value) =>
|
||||
|
|
|
|||
|
|
@ -109,7 +109,6 @@ describe("mergeExtensionState", () => {
|
|||
shouldShowAnnouncement: false,
|
||||
enableCheckpoints: true,
|
||||
checkpointStorage: "task",
|
||||
preferredLanguage: "English",
|
||||
writeDelayMs: 1000,
|
||||
requestDelaySeconds: 5,
|
||||
rateLimitSeconds: 0,
|
||||
|
|
|
|||
57
webview-ui/src/i18n/TranslationContext.tsx
Normal file
57
webview-ui/src/i18n/TranslationContext.tsx
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import React, { createContext, useContext, ReactNode, useEffect, useCallback } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import i18next, { loadTranslations } from "./setup"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
|
||||
// Create context for translations
|
||||
export const TranslationContext = createContext<{
|
||||
t: (key: string, options?: Record<string, any>) => string
|
||||
i18n: typeof i18next
|
||||
}>({
|
||||
t: (key: string) => key,
|
||||
i18n: i18next,
|
||||
})
|
||||
|
||||
// Translation provider component
|
||||
export const TranslationProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||
// Initialize with default configuration
|
||||
const { i18n } = useTranslation()
|
||||
// Get the extension state directly - it already contains all state properties
|
||||
const extensionState = useExtensionState()
|
||||
|
||||
// Load translations once when the component mounts
|
||||
useEffect(() => {
|
||||
try {
|
||||
loadTranslations()
|
||||
} catch (error) {
|
||||
console.error("Failed to load translations:", error)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
i18n.changeLanguage(extensionState.language)
|
||||
}, [i18n, extensionState.language])
|
||||
|
||||
// Memoize the translation function to prevent unnecessary re-renders
|
||||
const translate = useCallback(
|
||||
(key: string, options?: Record<string, any>) => {
|
||||
return i18n.t(key, options)
|
||||
},
|
||||
[i18n],
|
||||
)
|
||||
|
||||
return (
|
||||
<TranslationContext.Provider
|
||||
value={{
|
||||
t: translate,
|
||||
i18n,
|
||||
}}>
|
||||
{children}
|
||||
</TranslationContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
// Custom hook for easy translations
|
||||
export const useAppTranslation = () => useContext(TranslationContext)
|
||||
|
||||
export default TranslationProvider
|
||||
52
webview-ui/src/i18n/__tests__/TranslationContext.test.tsx
Normal file
52
webview-ui/src/i18n/__tests__/TranslationContext.test.tsx
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import React from "react"
|
||||
import { render } from "@testing-library/react"
|
||||
import "@testing-library/jest-dom"
|
||||
import TranslationProvider, { useAppTranslation } from "../TranslationContext"
|
||||
import { setupI18nForTests } from "../test-utils"
|
||||
|
||||
// Mock the useExtensionState hook
|
||||
jest.mock("@/context/ExtensionStateContext", () => ({
|
||||
useExtensionState: () => ({
|
||||
language: "en",
|
||||
}),
|
||||
}))
|
||||
|
||||
// Mock component that uses the translation context
|
||||
const TestComponent = () => {
|
||||
const { t } = useAppTranslation()
|
||||
return (
|
||||
<div>
|
||||
<h1 data-testid="translation-test">{t("settings.autoApprove.title")}</h1>
|
||||
<p data-testid="translation-interpolation">{t("notifications.error", { message: "Test error" })}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
describe("TranslationContext", () => {
|
||||
beforeAll(() => {
|
||||
// Initialize i18next with test translations
|
||||
setupI18nForTests()
|
||||
})
|
||||
|
||||
it("should provide translations via context", () => {
|
||||
const { getByTestId } = render(
|
||||
<TranslationProvider>
|
||||
<TestComponent />
|
||||
</TranslationProvider>,
|
||||
)
|
||||
|
||||
// Check if translation is provided correctly
|
||||
expect(getByTestId("translation-test")).toHaveTextContent("Auto-Approve")
|
||||
})
|
||||
|
||||
it("should handle interpolation correctly", () => {
|
||||
const { getByTestId } = render(
|
||||
<TranslationProvider>
|
||||
<TestComponent />
|
||||
</TranslationProvider>,
|
||||
)
|
||||
|
||||
// Check if interpolation works
|
||||
expect(getByTestId("translation-interpolation")).toHaveTextContent("Operation failed: Test error")
|
||||
})
|
||||
})
|
||||
0
webview-ui/src/i18n/locales/ar/.gitkeep
Normal file
0
webview-ui/src/i18n/locales/ar/.gitkeep
Normal file
0
webview-ui/src/i18n/locales/ca/.gitkeep
Normal file
0
webview-ui/src/i18n/locales/ca/.gitkeep
Normal file
0
webview-ui/src/i18n/locales/cs/.gitkeep
Normal file
0
webview-ui/src/i18n/locales/cs/.gitkeep
Normal file
0
webview-ui/src/i18n/locales/de/.gitkeep
Normal file
0
webview-ui/src/i18n/locales/de/.gitkeep
Normal file
0
webview-ui/src/i18n/locales/en/.gitkeep
Normal file
0
webview-ui/src/i18n/locales/en/.gitkeep
Normal file
3
webview-ui/src/i18n/locales/en/chat.json
Normal file
3
webview-ui/src/i18n/locales/en/chat.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"greeting": "What can Roo do for you?"
|
||||
}
|
||||
0
webview-ui/src/i18n/locales/es/.gitkeep
Normal file
0
webview-ui/src/i18n/locales/es/.gitkeep
Normal file
0
webview-ui/src/i18n/locales/fr/.gitkeep
Normal file
0
webview-ui/src/i18n/locales/fr/.gitkeep
Normal file
0
webview-ui/src/i18n/locales/hi/.gitkeep
Normal file
0
webview-ui/src/i18n/locales/hi/.gitkeep
Normal file
0
webview-ui/src/i18n/locales/hu/.gitkeep
Normal file
0
webview-ui/src/i18n/locales/hu/.gitkeep
Normal file
0
webview-ui/src/i18n/locales/it/.gitkeep
Normal file
0
webview-ui/src/i18n/locales/it/.gitkeep
Normal file
0
webview-ui/src/i18n/locales/ja/.gitkeep
Normal file
0
webview-ui/src/i18n/locales/ja/.gitkeep
Normal file
0
webview-ui/src/i18n/locales/ko/.gitkeep
Normal file
0
webview-ui/src/i18n/locales/ko/.gitkeep
Normal file
0
webview-ui/src/i18n/locales/pl/.gitkeep
Normal file
0
webview-ui/src/i18n/locales/pl/.gitkeep
Normal file
0
webview-ui/src/i18n/locales/pt-BR/.gitkeep
Normal file
0
webview-ui/src/i18n/locales/pt-BR/.gitkeep
Normal file
0
webview-ui/src/i18n/locales/pt/.gitkeep
Normal file
0
webview-ui/src/i18n/locales/pt/.gitkeep
Normal file
0
webview-ui/src/i18n/locales/ru/.gitkeep
Normal file
0
webview-ui/src/i18n/locales/ru/.gitkeep
Normal file
0
webview-ui/src/i18n/locales/tr/.gitkeep
Normal file
0
webview-ui/src/i18n/locales/tr/.gitkeep
Normal file
0
webview-ui/src/i18n/locales/zh-CN/.gitkeep
Normal file
0
webview-ui/src/i18n/locales/zh-CN/.gitkeep
Normal file
0
webview-ui/src/i18n/locales/zh-TW/.gitkeep
Normal file
0
webview-ui/src/i18n/locales/zh-TW/.gitkeep
Normal file
54
webview-ui/src/i18n/setup.ts
Normal file
54
webview-ui/src/i18n/setup.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import i18next from "i18next"
|
||||
import { initReactI18next } from "react-i18next"
|
||||
|
||||
// Build translations object
|
||||
const translations: Record<string, Record<string, any>> = {}
|
||||
|
||||
// Dynamically load locale files
|
||||
const localeFiles = import.meta.glob("./locales/**/*.json", { eager: true })
|
||||
|
||||
// Process all locale files
|
||||
Object.entries(localeFiles).forEach(([path, module]) => {
|
||||
// Extract language and namespace from path
|
||||
// Example path: './locales/en/common.json' -> language: 'en', namespace: 'common'
|
||||
const match = path.match(/\.\/locales\/([^/]+)\/([^/]+)\.json/)
|
||||
|
||||
if (match) {
|
||||
const [, language, namespace] = match
|
||||
|
||||
// Initialize language object if it doesn't exist
|
||||
if (!translations[language]) {
|
||||
translations[language] = {}
|
||||
}
|
||||
|
||||
// Add namespace resources to language
|
||||
translations[language][namespace] = (module as any).default || module
|
||||
}
|
||||
})
|
||||
|
||||
console.log("Dynamically loaded translations:", Object.keys(translations))
|
||||
|
||||
// Initialize i18next for React
|
||||
// This will be initialized with the VSCode language in TranslationProvider
|
||||
i18next.use(initReactI18next).init({
|
||||
lng: "en", // Default language (will be overridden)
|
||||
fallbackLng: "en",
|
||||
debug: false,
|
||||
interpolation: {
|
||||
escapeValue: false, // React already escapes by default
|
||||
},
|
||||
})
|
||||
|
||||
export function loadTranslations() {
|
||||
Object.entries(translations).forEach(([lang, namespaces]) => {
|
||||
try {
|
||||
Object.entries(namespaces).forEach(([namespace, resources]) => {
|
||||
i18next.addResourceBundle(lang, namespace, resources, true, true)
|
||||
})
|
||||
} catch (error) {
|
||||
console.warn(`Could not load ${lang} translations:`, error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default i18next
|
||||
37
webview-ui/src/i18n/test-utils.ts
Normal file
37
webview-ui/src/i18n/test-utils.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import i18next from "i18next"
|
||||
import { initReactI18next } from "react-i18next"
|
||||
|
||||
/**
|
||||
* Sets up i18next for testing with pre-defined translations.
|
||||
* Use this in test files to ensure consistent translation handling.
|
||||
*/
|
||||
export const setupI18nForTests = () => {
|
||||
i18next.use(initReactI18next).init({
|
||||
lng: "en",
|
||||
fallbackLng: "en",
|
||||
debug: false,
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
// Pre-define all translations needed for tests
|
||||
resources: {
|
||||
en: {
|
||||
settings: {
|
||||
autoApprove: {
|
||||
title: "Auto-Approve",
|
||||
},
|
||||
},
|
||||
common: {
|
||||
notifications: {
|
||||
error: "Operation failed: {{message}}",
|
||||
},
|
||||
},
|
||||
chat: {
|
||||
test: "Test",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return i18next
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue