diff --git a/.changeset/cuddly-vans-judge.md b/.changeset/cuddly-vans-judge.md
new file mode 100644
index 00000000000..ba9a25dc3e7
--- /dev/null
+++ b/.changeset/cuddly-vans-judge.md
@@ -0,0 +1,5 @@
+---
+"roo-cline": patch
+---
+
+Add a preferred language dropdown
diff --git a/.clinerules b/.clinerules
index 9eaef00abef..8c679894987 100644
--- a/.clinerules
+++ b/.clinerules
@@ -1 +1,162 @@
-- Before attempting completion, always make sure that any code changes have test coverage and that the tests pass.
\ No newline at end of file
+# Code Quality Rules
+
+1. Test Coverage:
+ - Before attempting completion, always make sure that any code changes have test coverage
+ - Ensure all tests pass before submitting changes
+
+2. Git Commits:
+ - When finishing a task, always output a git commit command
+ - Include a descriptive commit message that follows conventional commit format
+
+3. Documentation:
+ - Update README.md when making significant changes, such as:
+ * Adding new features or settings
+ * Changing existing functionality
+ * Updating system requirements
+ * Adding new dependencies
+ - Include clear descriptions of new features and how to use them
+ - Keep the documentation in sync with the codebase
+ - Add examples where appropriate
+
+# Adding a New Setting
+
+To add a new setting that persists its state, follow these steps:
+
+## For All Settings
+
+1. Add the setting to ExtensionMessage.ts:
+ - Add the setting to the ExtensionState interface
+ - Make it required if it has a default value, optional if it can be undefined
+ - Example: `preferredLanguage: string`
+
+2. Add test coverage:
+ - Add the setting to mockState in ClineProvider.test.ts
+ - Add test cases for setting persistence and state updates
+ - Ensure all tests pass before submitting changes
+
+## For Checkbox Settings
+
+1. Add the message type to WebviewMessage.ts:
+ - Add the setting name to the WebviewMessage type's type union
+ - Example: `| "multisearchDiffEnabled"`
+
+2. Add the setting to ExtensionStateContext.tsx:
+ - Add the setting to the ExtensionStateContextType interface
+ - Add the setter function to the interface
+ - Add the setting to the initial state in useState
+ - Add the setting to the contextValue object
+ - Example:
+ ```typescript
+ interface ExtensionStateContextType {
+ multisearchDiffEnabled: boolean;
+ setMultisearchDiffEnabled: (value: boolean) => void;
+ }
+ ```
+
+3. Add the setting to ClineProvider.ts:
+ - Add the setting name to the GlobalStateKey type union
+ - Add the setting to the Promise.all array in getState
+ - Add the setting to the return value in getState with a default value
+ - Add the setting to the destructured variables in getStateToPostToWebview
+ - Add the setting to the return value in getStateToPostToWebview
+ - Add a case in setWebviewMessageListener to handle the setting's message type
+ - Example:
+ ```typescript
+ case "multisearchDiffEnabled":
+ await this.updateGlobalState("multisearchDiffEnabled", message.bool)
+ await this.postStateToWebview()
+ break
+ ```
+
+4. Add the checkbox UI to SettingsView.tsx:
+ - Import the setting and its setter from ExtensionStateContext
+ - Add the VSCodeCheckbox component with the setting's state and onChange handler
+ - Add appropriate labels and description text
+ - Example:
+ ```typescript
+ setMultisearchDiffEnabled(e.target.checked)}
+ >
+ Enable multi-search diff matching
+
+ ```
+
+5. Add the setting to handleSubmit in SettingsView.tsx:
+ - Add a vscode.postMessage call to send the setting's value when clicking Done
+ - Example:
+ ```typescript
+ vscode.postMessage({ type: "multisearchDiffEnabled", bool: multisearchDiffEnabled })
+ ```
+
+## For Select/Dropdown Settings
+
+1. Add the message type to WebviewMessage.ts:
+ - Add the setting name to the WebviewMessage type's type union
+ - Example: `| "preferredLanguage"`
+
+2. Add the setting to ExtensionStateContext.tsx:
+ - Add the setting to the ExtensionStateContextType interface
+ - Add the setter function to the interface
+ - Add the setting to the initial state in useState with a default value
+ - Add the setting to the contextValue object
+ - Example:
+ ```typescript
+ interface ExtensionStateContextType {
+ preferredLanguage: string;
+ setPreferredLanguage: (value: string) => void;
+ }
+ ```
+
+3. Add the setting to ClineProvider.ts:
+ - Add the setting name to the GlobalStateKey type union
+ - Add the setting to the Promise.all array in getState
+ - Add the setting to the return value in getState with a default value
+ - Add the setting to the destructured variables in getStateToPostToWebview
+ - Add the setting to the return value in getStateToPostToWebview
+ - Add a case in setWebviewMessageListener to handle the setting's message type
+ - Example:
+ ```typescript
+ case "preferredLanguage":
+ await this.updateGlobalState("preferredLanguage", message.text)
+ await this.postStateToWebview()
+ break
+ ```
+
+4. Add the select UI to SettingsView.tsx:
+ - Import the setting and its setter from ExtensionStateContext
+ - Add the select element with appropriate styling to match VSCode's theme
+ - Add options for the dropdown
+ - Add appropriate labels and description text
+ - Example:
+ ```typescript
+
+ ```
+
+5. Add the setting to handleSubmit in SettingsView.tsx:
+ - Add a vscode.postMessage call to send the setting's value when clicking Done
+ - Example:
+ ```typescript
+ vscode.postMessage({ type: "preferredLanguage", text: preferredLanguage })
+ ```
+
+These steps ensure that:
+- The setting's state is properly typed throughout the application
+- The setting persists between sessions
+- The setting's value is properly synchronized between the webview and extension
+- The setting has a proper UI representation in the settings view
+- Test coverage is maintained for the new setting
diff --git a/README.md b/README.md
index 81561419409..1c81e98fef3 100644
--- a/README.md
+++ b/README.md
@@ -13,6 +13,7 @@ A fork of Cline, an autonomous coding agent, tweaked for more speed and flexibil
- Option to use a larger 1280x800 browser
- Quick prompt copying from history
- OpenRouter compression support
+- Language selection for Cline's communication (English, Japanese, Spanish, French, German, and more)
- Support for newer Gemini models (gemini-exp-1206, gemini-2.0-flash-exp, gemini-2.0-flash-thinking-exp-1219) and Meta 3, 3.1, and 3.2 models via AWS Bedrock
- Runs alongside the original Cline
diff --git a/src/core/Cline.ts b/src/core/Cline.ts
index 63704c22f8b..cc1062dc02a 100644
--- a/src/core/Cline.ts
+++ b/src/core/Cline.ts
@@ -769,8 +769,8 @@ export class Cline {
throw new Error("MCP hub not available")
}
- const { browserLargeViewport } = await this.providerRef.deref()?.getState() ?? {}
- const systemPrompt = await SYSTEM_PROMPT(cwd, this.api.getModel().info.supportsComputerUse ?? false, mcpHub, this.diffStrategy, browserLargeViewport) + await addCustomInstructions(this.customInstructions ?? '', cwd)
+ const { browserLargeViewport, preferredLanguage } = await this.providerRef.deref()?.getState() ?? {}
+ const systemPrompt = await SYSTEM_PROMPT(cwd, this.api.getModel().info.supportsComputerUse ?? false, mcpHub, this.diffStrategy, browserLargeViewport) + await addCustomInstructions(this.customInstructions ?? '', cwd, preferredLanguage)
// If the previous API request's total token usage is close to the context window, truncate the conversation history to free up space for the new request
if (previousApiReqIndex >= 0) {
diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts
index e7ae452f00c..1e6920ed6d1 100644
--- a/src/core/prompts/system.ts
+++ b/src/core/prompts/system.ts
@@ -772,9 +772,17 @@ async function loadRuleFiles(cwd: string): Promise {
return combinedRules
}
-export async function addCustomInstructions(customInstructions: string, cwd: string): Promise {
+export async function addCustomInstructions(customInstructions: string, cwd: string, preferredLanguage?: string): Promise {
const ruleFileContent = await loadRuleFiles(cwd)
- const allInstructions = [customInstructions.trim()]
+ const allInstructions = []
+
+ if (preferredLanguage) {
+ allInstructions.push(`You should always speak and think in the ${preferredLanguage} language.`)
+ }
+
+ if (customInstructions.trim()) {
+ allInstructions.push(customInstructions.trim())
+ }
if (ruleFileContent && ruleFileContent.trim()) {
allInstructions.push(ruleFileContent.trim())
diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts
index 6789d9abe3a..e502d08df0e 100644
--- a/src/core/webview/ClineProvider.ts
+++ b/src/core/webview/ClineProvider.ts
@@ -71,6 +71,7 @@ type GlobalStateKey =
| "alwaysAllowMcp"
| "browserLargeViewport"
| "fuzzyMatchThreshold"
+ | "preferredLanguage" // Language setting for Cline's communication
export const GlobalFileNames = {
apiConversationHistory: "api_conversation_history.json",
@@ -622,6 +623,10 @@ export class ClineProvider implements vscode.WebviewViewProvider {
await this.updateGlobalState("fuzzyMatchThreshold", message.value)
await this.postStateToWebview()
break
+ case "preferredLanguage":
+ await this.updateGlobalState("preferredLanguage", message.text)
+ await this.postStateToWebview()
+ break
}
},
null,
@@ -951,6 +956,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
taskHistory,
soundVolume,
browserLargeViewport,
+ preferredLanguage,
} = await this.getState()
const allowedCommands = vscode.workspace
@@ -977,6 +983,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
allowedCommands,
soundVolume: soundVolume ?? 0.5,
browserLargeViewport: browserLargeViewport ?? false,
+ preferredLanguage: preferredLanguage ?? 'English',
}
}
@@ -1072,6 +1079,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
soundVolume,
browserLargeViewport,
fuzzyMatchThreshold,
+ preferredLanguage,
] = await Promise.all([
this.getGlobalState("apiProvider") as Promise,
this.getGlobalState("apiModelId") as Promise,
@@ -1112,6 +1120,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
this.getGlobalState("soundVolume") as Promise,
this.getGlobalState("browserLargeViewport") as Promise,
this.getGlobalState("fuzzyMatchThreshold") as Promise,
+ this.getGlobalState("preferredLanguage") as Promise,
])
let apiProvider: ApiProvider
@@ -1170,6 +1179,27 @@ export class ClineProvider implements vscode.WebviewViewProvider {
soundVolume,
browserLargeViewport: browserLargeViewport ?? false,
fuzzyMatchThreshold: fuzzyMatchThreshold ?? 1.0,
+ preferredLanguage: 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',
+ 'es': 'Spanish',
+ 'fr': 'French',
+ 'de': 'German',
+ 'it': 'Italian',
+ 'pt': 'Portuguese',
+ 'zh': 'Chinese',
+ 'ja': 'Japanese',
+ 'ko': 'Korean',
+ 'ru': 'Russian',
+ 'ar': 'Arabic',
+ 'hi': 'Hindi'
+ };
+ // Return mapped language or default to English
+ return langMap[vscodeLang.split('-')[0]] ?? 'English';
+ })(),
}
}
diff --git a/src/core/webview/__tests__/ClineProvider.test.ts b/src/core/webview/__tests__/ClineProvider.test.ts
index b2ab983bc04..d3d2c025296 100644
--- a/src/core/webview/__tests__/ClineProvider.test.ts
+++ b/src/core/webview/__tests__/ClineProvider.test.ts
@@ -73,7 +73,8 @@ jest.mock('vscode', () => ({
onDidCloseTextDocument: jest.fn(() => ({ dispose: jest.fn() }))
},
env: {
- uriScheme: 'vscode'
+ uriScheme: 'vscode',
+ language: 'en'
}
}))
@@ -235,6 +236,7 @@ describe('ClineProvider', () => {
const mockState: ExtensionState = {
version: '1.0.0',
+ preferredLanguage: 'English',
clineMessages: [],
taskHistory: [],
shouldShowAnnouncement: false,
@@ -248,7 +250,7 @@ describe('ClineProvider', () => {
alwaysAllowBrowser: false,
uriScheme: 'vscode',
soundEnabled: false,
- diffEnabled: false
+ diffEnabled: false,
}
const message: ExtensionMessage = {
@@ -300,6 +302,22 @@ describe('ClineProvider', () => {
expect(state).toHaveProperty('diffEnabled')
})
+ test('preferredLanguage defaults to VSCode language when not set', 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');
+ });
+
test('diffEnabled defaults to true when not set', async () => {
// Mock globalState.get to return undefined for diffEnabled
(mockContext.globalState.get as jest.Mock).mockReturnValue(undefined)
diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts
index dcd352f8154..faef35c6394 100644
--- a/src/shared/ExtensionMessage.ts
+++ b/src/shared/ExtensionMessage.ts
@@ -55,6 +55,7 @@ export interface ExtensionState {
diffEnabled?: boolean
browserLargeViewport?: boolean
fuzzyMatchThreshold?: number
+ preferredLanguage: string
}
export interface ClineMessage {
diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts
index 7a0983afdba..d662475435a 100644
--- a/src/shared/WebviewMessage.ts
+++ b/src/shared/WebviewMessage.ts
@@ -40,6 +40,7 @@ export interface WebviewMessage {
| "toggleToolAlwaysAllow"
| "toggleMcpServer"
| "fuzzyMatchThreshold"
+ | "preferredLanguage"
text?: string
disabled?: boolean
askResponse?: ClineAskResponse
diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx
index 465fd5a9750..9a124a21394 100644
--- a/webview-ui/src/components/settings/SettingsView.tsx
+++ b/webview-ui/src/components/settings/SettingsView.tsx
@@ -40,6 +40,8 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
allowedCommands,
fuzzyMatchThreshold,
setFuzzyMatchThreshold,
+ preferredLanguage,
+ setPreferredLanguage,
} = useExtensionState()
const [apiErrorMessage, setApiErrorMessage] = useState(undefined)
const [modelIdErrorMessage, setModelIdErrorMessage] = useState(undefined)
@@ -67,6 +69,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
vscode.postMessage({ type: "diffEnabled", bool: diffEnabled })
vscode.postMessage({ type: "browserLargeViewport", bool: browserLargeViewport })
vscode.postMessage({ type: "fuzzyMatchThreshold", value: fuzzyMatchThreshold ?? 1.0 })
+ vscode.postMessage({ type: "preferredLanguage", text: preferredLanguage })
onDone()
}
}
@@ -136,6 +139,42 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
+
+
+
+
+ Select the language that Cline should use for communication.
+