Skip to content

Commit 3248c9b

Browse files
authored
Merge pull request #192 from RooVetGit/language_chooser
Add a preferred language dropdown
2 parents 6ad6949 + 8b4c52f commit 3248c9b

File tree

11 files changed

+275
-7
lines changed

11 files changed

+275
-7
lines changed

.changeset/cuddly-vans-judge.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"roo-cline": patch
3+
---
4+
5+
Add a preferred language dropdown

.clinerules

Lines changed: 162 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,162 @@
1-
- Before attempting completion, always make sure that any code changes have test coverage and that the tests pass.
1+
# Code Quality Rules
2+
3+
1. Test Coverage:
4+
- Before attempting completion, always make sure that any code changes have test coverage
5+
- Ensure all tests pass before submitting changes
6+
7+
2. Git Commits:
8+
- When finishing a task, always output a git commit command
9+
- Include a descriptive commit message that follows conventional commit format
10+
11+
3. Documentation:
12+
- Update README.md when making significant changes, such as:
13+
* Adding new features or settings
14+
* Changing existing functionality
15+
* Updating system requirements
16+
* Adding new dependencies
17+
- Include clear descriptions of new features and how to use them
18+
- Keep the documentation in sync with the codebase
19+
- Add examples where appropriate
20+
21+
# Adding a New Setting
22+
23+
To add a new setting that persists its state, follow these steps:
24+
25+
## For All Settings
26+
27+
1. Add the setting to ExtensionMessage.ts:
28+
- Add the setting to the ExtensionState interface
29+
- Make it required if it has a default value, optional if it can be undefined
30+
- Example: `preferredLanguage: string`
31+
32+
2. Add test coverage:
33+
- Add the setting to mockState in ClineProvider.test.ts
34+
- Add test cases for setting persistence and state updates
35+
- Ensure all tests pass before submitting changes
36+
37+
## For Checkbox Settings
38+
39+
1. Add the message type to WebviewMessage.ts:
40+
- Add the setting name to the WebviewMessage type's type union
41+
- Example: `| "multisearchDiffEnabled"`
42+
43+
2. Add the setting to ExtensionStateContext.tsx:
44+
- Add the setting to the ExtensionStateContextType interface
45+
- Add the setter function to the interface
46+
- Add the setting to the initial state in useState
47+
- Add the setting to the contextValue object
48+
- Example:
49+
```typescript
50+
interface ExtensionStateContextType {
51+
multisearchDiffEnabled: boolean;
52+
setMultisearchDiffEnabled: (value: boolean) => void;
53+
}
54+
```
55+
56+
3. Add the setting to ClineProvider.ts:
57+
- Add the setting name to the GlobalStateKey type union
58+
- Add the setting to the Promise.all array in getState
59+
- Add the setting to the return value in getState with a default value
60+
- Add the setting to the destructured variables in getStateToPostToWebview
61+
- Add the setting to the return value in getStateToPostToWebview
62+
- Add a case in setWebviewMessageListener to handle the setting's message type
63+
- Example:
64+
```typescript
65+
case "multisearchDiffEnabled":
66+
await this.updateGlobalState("multisearchDiffEnabled", message.bool)
67+
await this.postStateToWebview()
68+
break
69+
```
70+
71+
4. Add the checkbox UI to SettingsView.tsx:
72+
- Import the setting and its setter from ExtensionStateContext
73+
- Add the VSCodeCheckbox component with the setting's state and onChange handler
74+
- Add appropriate labels and description text
75+
- Example:
76+
```typescript
77+
<VSCodeCheckbox
78+
checked={multisearchDiffEnabled}
79+
onChange={(e: any) => setMultisearchDiffEnabled(e.target.checked)}
80+
>
81+
<span style={{ fontWeight: "500" }}>Enable multi-search diff matching</span>
82+
</VSCodeCheckbox>
83+
```
84+
85+
5. Add the setting to handleSubmit in SettingsView.tsx:
86+
- Add a vscode.postMessage call to send the setting's value when clicking Done
87+
- Example:
88+
```typescript
89+
vscode.postMessage({ type: "multisearchDiffEnabled", bool: multisearchDiffEnabled })
90+
```
91+
92+
## For Select/Dropdown Settings
93+
94+
1. Add the message type to WebviewMessage.ts:
95+
- Add the setting name to the WebviewMessage type's type union
96+
- Example: `| "preferredLanguage"`
97+
98+
2. Add the setting to ExtensionStateContext.tsx:
99+
- Add the setting to the ExtensionStateContextType interface
100+
- Add the setter function to the interface
101+
- Add the setting to the initial state in useState with a default value
102+
- Add the setting to the contextValue object
103+
- Example:
104+
```typescript
105+
interface ExtensionStateContextType {
106+
preferredLanguage: string;
107+
setPreferredLanguage: (value: string) => void;
108+
}
109+
```
110+
111+
3. Add the setting to ClineProvider.ts:
112+
- Add the setting name to the GlobalStateKey type union
113+
- Add the setting to the Promise.all array in getState
114+
- Add the setting to the return value in getState with a default value
115+
- Add the setting to the destructured variables in getStateToPostToWebview
116+
- Add the setting to the return value in getStateToPostToWebview
117+
- Add a case in setWebviewMessageListener to handle the setting's message type
118+
- Example:
119+
```typescript
120+
case "preferredLanguage":
121+
await this.updateGlobalState("preferredLanguage", message.text)
122+
await this.postStateToWebview()
123+
break
124+
```
125+
126+
4. Add the select UI to SettingsView.tsx:
127+
- Import the setting and its setter from ExtensionStateContext
128+
- Add the select element with appropriate styling to match VSCode's theme
129+
- Add options for the dropdown
130+
- Add appropriate labels and description text
131+
- Example:
132+
```typescript
133+
<select
134+
value={preferredLanguage}
135+
onChange={(e) => setPreferredLanguage(e.target.value)}
136+
style={{
137+
width: "100%",
138+
padding: "4px 8px",
139+
backgroundColor: "var(--vscode-input-background)",
140+
color: "var(--vscode-input-foreground)",
141+
border: "1px solid var(--vscode-input-border)",
142+
borderRadius: "2px"
143+
}}>
144+
<option value="English">English</option>
145+
<option value="Spanish">Spanish</option>
146+
...
147+
</select>
148+
```
149+
150+
5. Add the setting to handleSubmit in SettingsView.tsx:
151+
- Add a vscode.postMessage call to send the setting's value when clicking Done
152+
- Example:
153+
```typescript
154+
vscode.postMessage({ type: "preferredLanguage", text: preferredLanguage })
155+
```
156+
157+
These steps ensure that:
158+
- The setting's state is properly typed throughout the application
159+
- The setting persists between sessions
160+
- The setting's value is properly synchronized between the webview and extension
161+
- The setting has a proper UI representation in the settings view
162+
- Test coverage is maintained for the new setting

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ A fork of Cline, an autonomous coding agent, tweaked for more speed and flexibil
1313
- Option to use a larger 1280x800 browser
1414
- Quick prompt copying from history
1515
- OpenRouter compression support
16+
- Language selection for Cline's communication (English, Japanese, Spanish, French, German, and more)
1617
- 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
1718
- Runs alongside the original Cline
1819

src/core/Cline.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -769,8 +769,8 @@ export class Cline {
769769
throw new Error("MCP hub not available")
770770
}
771771

772-
const { browserLargeViewport } = await this.providerRef.deref()?.getState() ?? {}
773-
const systemPrompt = await SYSTEM_PROMPT(cwd, this.api.getModel().info.supportsComputerUse ?? false, mcpHub, this.diffStrategy, browserLargeViewport) + await addCustomInstructions(this.customInstructions ?? '', cwd)
772+
const { browserLargeViewport, preferredLanguage } = await this.providerRef.deref()?.getState() ?? {}
773+
const systemPrompt = await SYSTEM_PROMPT(cwd, this.api.getModel().info.supportsComputerUse ?? false, mcpHub, this.diffStrategy, browserLargeViewport) + await addCustomInstructions(this.customInstructions ?? '', cwd, preferredLanguage)
774774

775775
// 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
776776
if (previousApiReqIndex >= 0) {

src/core/prompts/system.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -772,9 +772,17 @@ async function loadRuleFiles(cwd: string): Promise<string> {
772772
return combinedRules
773773
}
774774

775-
export async function addCustomInstructions(customInstructions: string, cwd: string): Promise<string> {
775+
export async function addCustomInstructions(customInstructions: string, cwd: string, preferredLanguage?: string): Promise<string> {
776776
const ruleFileContent = await loadRuleFiles(cwd)
777-
const allInstructions = [customInstructions.trim()]
777+
const allInstructions = []
778+
779+
if (preferredLanguage) {
780+
allInstructions.push(`You should always speak and think in the ${preferredLanguage} language.`)
781+
}
782+
783+
if (customInstructions.trim()) {
784+
allInstructions.push(customInstructions.trim())
785+
}
778786

779787
if (ruleFileContent && ruleFileContent.trim()) {
780788
allInstructions.push(ruleFileContent.trim())

src/core/webview/ClineProvider.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ type GlobalStateKey =
7171
| "alwaysAllowMcp"
7272
| "browserLargeViewport"
7373
| "fuzzyMatchThreshold"
74+
| "preferredLanguage" // Language setting for Cline's communication
7475

7576
export const GlobalFileNames = {
7677
apiConversationHistory: "api_conversation_history.json",
@@ -622,6 +623,10 @@ export class ClineProvider implements vscode.WebviewViewProvider {
622623
await this.updateGlobalState("fuzzyMatchThreshold", message.value)
623624
await this.postStateToWebview()
624625
break
626+
case "preferredLanguage":
627+
await this.updateGlobalState("preferredLanguage", message.text)
628+
await this.postStateToWebview()
629+
break
625630
}
626631
},
627632
null,
@@ -951,6 +956,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
951956
taskHistory,
952957
soundVolume,
953958
browserLargeViewport,
959+
preferredLanguage,
954960
} = await this.getState()
955961

956962
const allowedCommands = vscode.workspace
@@ -977,6 +983,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
977983
allowedCommands,
978984
soundVolume: soundVolume ?? 0.5,
979985
browserLargeViewport: browserLargeViewport ?? false,
986+
preferredLanguage: preferredLanguage ?? 'English',
980987
}
981988
}
982989

@@ -1072,6 +1079,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
10721079
soundVolume,
10731080
browserLargeViewport,
10741081
fuzzyMatchThreshold,
1082+
preferredLanguage,
10751083
] = await Promise.all([
10761084
this.getGlobalState("apiProvider") as Promise<ApiProvider | undefined>,
10771085
this.getGlobalState("apiModelId") as Promise<string | undefined>,
@@ -1112,6 +1120,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
11121120
this.getGlobalState("soundVolume") as Promise<number | undefined>,
11131121
this.getGlobalState("browserLargeViewport") as Promise<boolean | undefined>,
11141122
this.getGlobalState("fuzzyMatchThreshold") as Promise<number | undefined>,
1123+
this.getGlobalState("preferredLanguage") as Promise<string | undefined>,
11151124
])
11161125

11171126
let apiProvider: ApiProvider
@@ -1170,6 +1179,27 @@ export class ClineProvider implements vscode.WebviewViewProvider {
11701179
soundVolume,
11711180
browserLargeViewport: browserLargeViewport ?? false,
11721181
fuzzyMatchThreshold: fuzzyMatchThreshold ?? 1.0,
1182+
preferredLanguage: preferredLanguage ?? (() => {
1183+
// Get VSCode's locale setting
1184+
const vscodeLang = vscode.env.language;
1185+
// Map VSCode locale to our supported languages
1186+
const langMap: { [key: string]: string } = {
1187+
'en': 'English',
1188+
'es': 'Spanish',
1189+
'fr': 'French',
1190+
'de': 'German',
1191+
'it': 'Italian',
1192+
'pt': 'Portuguese',
1193+
'zh': 'Chinese',
1194+
'ja': 'Japanese',
1195+
'ko': 'Korean',
1196+
'ru': 'Russian',
1197+
'ar': 'Arabic',
1198+
'hi': 'Hindi'
1199+
};
1200+
// Return mapped language or default to English
1201+
return langMap[vscodeLang.split('-')[0]] ?? 'English';
1202+
})(),
11731203
}
11741204
}
11751205

src/core/webview/__tests__/ClineProvider.test.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ jest.mock('vscode', () => ({
7373
onDidCloseTextDocument: jest.fn(() => ({ dispose: jest.fn() }))
7474
},
7575
env: {
76-
uriScheme: 'vscode'
76+
uriScheme: 'vscode',
77+
language: 'en'
7778
}
7879
}))
7980

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

236237
const mockState: ExtensionState = {
237238
version: '1.0.0',
239+
preferredLanguage: 'English',
238240
clineMessages: [],
239241
taskHistory: [],
240242
shouldShowAnnouncement: false,
@@ -248,7 +250,7 @@ describe('ClineProvider', () => {
248250
alwaysAllowBrowser: false,
249251
uriScheme: 'vscode',
250252
soundEnabled: false,
251-
diffEnabled: false
253+
diffEnabled: false,
252254
}
253255

254256
const message: ExtensionMessage = {
@@ -300,6 +302,22 @@ describe('ClineProvider', () => {
300302
expect(state).toHaveProperty('diffEnabled')
301303
})
302304

305+
test('preferredLanguage defaults to VSCode language when not set', async () => {
306+
// Mock VSCode language as Spanish
307+
(vscode.env as any).language = 'es-ES';
308+
309+
const state = await provider.getState();
310+
expect(state.preferredLanguage).toBe('Spanish');
311+
});
312+
313+
test('preferredLanguage defaults to English for unsupported VSCode language', async () => {
314+
// Mock VSCode language as an unsupported language
315+
(vscode.env as any).language = 'unsupported-LANG';
316+
317+
const state = await provider.getState();
318+
expect(state.preferredLanguage).toBe('English');
319+
});
320+
303321
test('diffEnabled defaults to true when not set', async () => {
304322
// Mock globalState.get to return undefined for diffEnabled
305323
(mockContext.globalState.get as jest.Mock).mockReturnValue(undefined)

src/shared/ExtensionMessage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export interface ExtensionState {
5555
diffEnabled?: boolean
5656
browserLargeViewport?: boolean
5757
fuzzyMatchThreshold?: number
58+
preferredLanguage: string
5859
}
5960

6061
export interface ClineMessage {

src/shared/WebviewMessage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export interface WebviewMessage {
4040
| "toggleToolAlwaysAllow"
4141
| "toggleMcpServer"
4242
| "fuzzyMatchThreshold"
43+
| "preferredLanguage"
4344
text?: string
4445
disabled?: boolean
4546
askResponse?: ClineAskResponse

0 commit comments

Comments
 (0)