Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/cuddly-vans-judge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"roo-cline": patch
---

Add a preferred language dropdown
163 changes: 162 additions & 1 deletion .clinerules
Original file line number Diff line number Diff line change
@@ -1 +1,162 @@
- Before attempting completion, always make sure that any code changes have test coverage and that the tests pass.
# 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
<VSCodeCheckbox
checked={multisearchDiffEnabled}
onChange={(e: any) => setMultisearchDiffEnabled(e.target.checked)}
>
<span style={{ fontWeight: "500" }}>Enable multi-search diff matching</span>
</VSCodeCheckbox>
```

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
<select
value={preferredLanguage}
onChange={(e) => setPreferredLanguage(e.target.value)}
style={{
width: "100%",
padding: "4px 8px",
backgroundColor: "var(--vscode-input-background)",
color: "var(--vscode-input-foreground)",
border: "1px solid var(--vscode-input-border)",
borderRadius: "2px"
}}>
<option value="English">English</option>
<option value="Spanish">Spanish</option>
...
</select>
```

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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions src/core/Cline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
12 changes: 10 additions & 2 deletions src/core/prompts/system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -772,9 +772,17 @@ async function loadRuleFiles(cwd: string): Promise<string> {
return combinedRules
}

export async function addCustomInstructions(customInstructions: string, cwd: string): Promise<string> {
export async function addCustomInstructions(customInstructions: string, cwd: string, preferredLanguage?: string): Promise<string> {
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())
Expand Down
30 changes: 30 additions & 0 deletions src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ type GlobalStateKey =
| "alwaysAllowMcp"
| "browserLargeViewport"
| "fuzzyMatchThreshold"
| "preferredLanguage" // Language setting for Cline's communication

export const GlobalFileNames = {
apiConversationHistory: "api_conversation_history.json",
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -951,6 +956,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
taskHistory,
soundVolume,
browserLargeViewport,
preferredLanguage,
} = await this.getState()

const allowedCommands = vscode.workspace
Expand All @@ -977,6 +983,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
allowedCommands,
soundVolume: soundVolume ?? 0.5,
browserLargeViewport: browserLargeViewport ?? false,
preferredLanguage: preferredLanguage ?? 'English',
}
}

Expand Down Expand Up @@ -1072,6 +1079,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
soundVolume,
browserLargeViewport,
fuzzyMatchThreshold,
preferredLanguage,
] = await Promise.all([
this.getGlobalState("apiProvider") as Promise<ApiProvider | undefined>,
this.getGlobalState("apiModelId") as Promise<string | undefined>,
Expand Down Expand Up @@ -1112,6 +1120,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
this.getGlobalState("soundVolume") as Promise<number | undefined>,
this.getGlobalState("browserLargeViewport") as Promise<boolean | undefined>,
this.getGlobalState("fuzzyMatchThreshold") as Promise<number | undefined>,
this.getGlobalState("preferredLanguage") as Promise<string | undefined>,
])

let apiProvider: ApiProvider
Expand Down Expand Up @@ -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';
})(),
}
}

Expand Down
22 changes: 20 additions & 2 deletions src/core/webview/__tests__/ClineProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ jest.mock('vscode', () => ({
onDidCloseTextDocument: jest.fn(() => ({ dispose: jest.fn() }))
},
env: {
uriScheme: 'vscode'
uriScheme: 'vscode',
language: 'en'
}
}))

Expand Down Expand Up @@ -235,6 +236,7 @@ describe('ClineProvider', () => {

const mockState: ExtensionState = {
version: '1.0.0',
preferredLanguage: 'English',
clineMessages: [],
taskHistory: [],
shouldShowAnnouncement: false,
Expand All @@ -248,7 +250,7 @@ describe('ClineProvider', () => {
alwaysAllowBrowser: false,
uriScheme: 'vscode',
soundEnabled: false,
diffEnabled: false
diffEnabled: false,
}

const message: ExtensionMessage = {
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions src/shared/ExtensionMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export interface ExtensionState {
diffEnabled?: boolean
browserLargeViewport?: boolean
fuzzyMatchThreshold?: number
preferredLanguage: string
}

export interface ClineMessage {
Expand Down
1 change: 1 addition & 0 deletions src/shared/WebviewMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export interface WebviewMessage {
| "toggleToolAlwaysAllow"
| "toggleMcpServer"
| "fuzzyMatchThreshold"
| "preferredLanguage"
text?: string
disabled?: boolean
askResponse?: ClineAskResponse
Expand Down
Loading
Loading