Skip to content

Commit 2ef8f83

Browse files
xiaojingmingxjm
andauthored
feat: Adds os-locale package for improved language detection (#460)
Introduces the `os-locale` package to enhance the application's ability to detect the user's default language. This change allows the application to prioritize VS Code's configured language, falling back to the operating system's locale if necessary. Additionally, it updates the pnpm-lock file to include the new dependency and modifies the package.json accordingly. This improvement aims to provide a more localized experience for users across different languages. Co-authored-by: xjm <[email protected]>
1 parent 45f5e3a commit 2ef8f83

File tree

10 files changed

+100
-9
lines changed

10 files changed

+100
-9
lines changed

pnpm-lock.yaml

Lines changed: 26 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/core/environment/getEnvironmentDetails.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { Task } from "../task/Task"
2222
import { formatReminderSection } from "./reminder"
2323
import { getShell } from "../../utils/shell"
2424
import { getOperatingSystem } from "../../utils/zgsmUtils"
25+
import { defaultLang } from "../../utils/language"
2526

2627
export async function getEnvironmentDetails(cline: Task, includeFileDetails: boolean = false) {
2728
let details = ""
@@ -235,7 +236,7 @@ export async function getEnvironmentDetails(cline: Task, includeFileDetails: boo
235236
const modeDetails = await getFullModeDetails(currentMode, customModes, customModePrompts, {
236237
cwd: cline.cwd,
237238
globalCustomInstructions: promptSuggestion + simpleAskSuggestion + shellSuggestion + globalCustomInstructions,
238-
language: language ?? formatLanguage(vscode.env.language),
239+
language: language ?? formatLanguage(await defaultLang()),
239240
})
240241
details += `\n\n# Operating System\n${getOperatingSystem()}`
241242
details += `\n\n# Default Shell\n${getShell()}`

src/core/prompts/__tests__/system-prompt.spec.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import { defaultModeSlug, modes, Mode } from "../../../shared/modes"
5353
import "../../../utils/path"
5454
import { addCustomInstructions } from "../sections/custom-instructions"
5555
import { MultiSearchReplaceDiffStrategy } from "../../diff/strategies/multi-search-replace"
56+
import { resetLanguageCache } from "../../../utils/language"
5657

5758
// Mock the sections
5859
vi.mock("../sections/modes", () => ({
@@ -394,6 +395,8 @@ describe("SYSTEM_PROMPT", () => {
394395

395396
it("should include vscode language in custom instructions", async () => {
396397
// Mock vscode.env.language
398+
resetLanguageCache()
399+
397400
const vscode = vi.mocked(await import("vscode")) as any
398401
vscode.env = { language: "es" }
399402
// Ensure workspace mock is maintained
@@ -444,6 +447,8 @@ describe("SYSTEM_PROMPT", () => {
444447

445448
// Reset mock
446449
vscode.env = { language: "en" }
450+
resetLanguageCache()
451+
447452
vscode.workspace = {
448453
workspaceFolders: [
449454
{

src/core/prompts/system.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
addCustomInstructions,
2929
markdownFormattingSection,
3030
} from "./sections"
31+
import { defaultLang } from "../../utils/language"
3132

3233
// Helper function to get prompt component, filtering out empty objects
3334
export function getPromptComponent(
@@ -125,7 +126,7 @@ ${getSystemInfoSection(cwd)}
125126
${getObjectiveSection(codeIndexManager, experiments)}
126127
127128
${await addCustomInstructions(baseInstructions, globalCustomInstructions || "", cwd, mode, {
128-
language: language ?? formatLanguage(vscode.env.language),
129+
language: language ?? formatLanguage(await defaultLang()),
129130
rooIgnoreInstructions,
130131
settings,
131132
})}`
@@ -162,7 +163,7 @@ export const SYSTEM_PROMPT = async (
162163
const variablesForPrompt: PromptVariables = {
163164
workspace: cwd,
164165
mode: mode,
165-
language: language ?? formatLanguage(vscode.env.language),
166+
language: language ?? formatLanguage(await defaultLang()),
166167
shell: vscode.env.shell,
167168
operatingSystem: os.type(),
168169
}
@@ -188,7 +189,7 @@ export const SYSTEM_PROMPT = async (
188189
cwd,
189190
mode,
190191
{
191-
language: language ?? formatLanguage(vscode.env.language),
192+
language: language ?? formatLanguage(await defaultLang()),
192193
rooIgnoreInstructions,
193194
settings,
194195
},

src/core/webview/ClineProvider.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ import { ZgsmAuthCommands } from "../costrict/auth"
9797
import { getClientId } from "../../utils/getClientId"
9898
import { defaultCodebaseIndexEnabled } from "../../services/code-index/constants"
9999
import { CodeReviewService, ReviewTargetType } from "../costrict/code-review"
100+
import { defaultLang } from "../../utils/language"
100101

101102
/**
102103
* https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
@@ -1913,7 +1914,7 @@ export class ClineProvider
19131914
telemetryKey,
19141915
machineId,
19151916
showRooIgnoredFiles: showRooIgnoredFiles ?? false,
1916-
language: language ?? formatLanguage(vscode.env.language),
1917+
language: language ?? formatLanguage(await defaultLang()),
19171918
renderContext: this.renderContext,
19181919
maxReadFileLine: maxReadFileLine ?? 500,
19191920
maxImageFileSize: maxImageFileSize ?? 5,
@@ -2112,7 +2113,7 @@ export class ClineProvider
21122113
terminalZdotdir: stateValues.terminalZdotdir ?? false,
21132114
terminalCompressProgressBar: stateValues.terminalCompressProgressBar ?? true,
21142115
mode: stateValues.mode ?? defaultModeSlug,
2115-
language: stateValues.language ?? formatLanguage(vscode.env.language),
2116+
language: stateValues.language ?? formatLanguage(await defaultLang()),
21162117
mcpEnabled: stateValues.mcpEnabled ?? true,
21172118
enableMcpServerCreation: stateValues.enableMcpServerCreation ?? true,
21182119
alwaysApproveResubmit: stateValues.alwaysApproveResubmit ?? false,

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { Task, TaskOptions } from "../../task/Task"
1616
import { safeWriteJson } from "../../../utils/safeWriteJson"
1717

1818
import { ClineProvider } from "../ClineProvider"
19+
import { resetLanguageCache } from "../../../utils/language"
1920

2021
// Mock setup must come before imports.
2122
vi.mock("../../prompts/sections/custom-instructions")
@@ -769,11 +770,14 @@ describe("ClineProvider", () => {
769770
})
770771

771772
test("language is set to VSCode language", async () => {
773+
resetLanguageCache()
772774
// Mock VSCode language as Spanish
773775
;(vscode.env as any).language = "pt-BR"
774776

775777
const state = await provider.getState()
776778
expect(state.language).toBe("pt-BR")
779+
;(vscode.env as any).language = "en"
780+
resetLanguageCache()
777781
})
778782

779783
test("diffEnabled defaults to true when not set", async () => {

src/extension.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import {
4343
} from "./activate"
4444
import { initializeI18n } from "./i18n"
4545
import { getCommand } from "./utils/commands"
46+
import { defaultLang } from "./utils/language"
4647

4748
/**
4849
* Built using https://github.com/microsoft/vscode-webview-ui-toolkit
@@ -86,7 +87,7 @@ export async function activate(context: vscode.ExtensionContext) {
8687
const mdmService = await MdmService.createInstance(cloudLogger)
8788

8889
// Initialize i18n for internationalization support.
89-
initializeI18n(context.globalState.get("language") ?? formatLanguage(vscode.env.language))
90+
initializeI18n(context.globalState.get("language") ?? formatLanguage(await defaultLang()))
9091

9192
// Initialize terminal shell execution handlers.
9293
TerminalRegistry.initialize()

src/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,7 @@
733733
"node-ipc": "^12.0.0",
734734
"ollama": "^0.5.17",
735735
"openai": "^5.12.2",
736+
"os-locale": "^6.0.2",
736737
"os-name": "^6.0.0",
737738
"p-limit": "^6.2.0",
738739
"p-wait-for": "^5.0.2",

src/utils/language.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import * as osLocale from "os-locale"
2+
import * as vscode from "vscode"
3+
4+
/**
5+
* Language cache variable to store the obtained language result.
6+
* Type is Promise<string> | null, initially null indicates not fetched yet.
7+
*/
8+
let languageCache: Promise<string> | null = null
9+
10+
/**
11+
* Reset the language cache. Used primarily for testing.
12+
*/
13+
export const resetLanguageCache = (): void => {
14+
languageCache = null
15+
}
16+
17+
/**
18+
* Get the default language for the application.
19+
* First checks VS Code's configured language, uses it if not English.
20+
* Otherwise falls back to the operating system's locale.
21+
* @returns Promise with the determined language string
22+
*/
23+
async function getDefaultLanguage(): Promise<string> {
24+
const vscodeLanguage = vscode.env.language
25+
if (vscodeLanguage && !vscodeLanguage.toLowerCase().startsWith("en")) {
26+
console.log(`[language] Using VS Code language: ${vscodeLanguage}`)
27+
return vscodeLanguage
28+
}
29+
30+
const osLang = await osLocale.osLocale()
31+
console.log(`[language] VS Code language is English or not set, determined OS locale: ${osLang}`)
32+
return osLang
33+
}
34+
35+
/**
36+
* Cached function to get the default language.
37+
* If the language is not yet fetched, calls getDefaultLanguage() and caches the result.
38+
* Subsequent calls directly return the cached result.
39+
* @returns Promise with the determined language string
40+
*/
41+
export const defaultLang = (): Promise<string> => {
42+
if (!languageCache) {
43+
console.log("[language] Fetching default language...")
44+
languageCache = getDefaultLanguage()
45+
}
46+
47+
return languageCache
48+
}

webview-ui/src/context/ExtensionStateContext.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
205205
useZgsmCustomConfig: false,
206206
zgsmCodebaseIndexEnabled: true,
207207
fuzzyMatchThreshold: 1.0,
208-
language: "en", // Default language code
208+
language: "en", // Default fallback language (will be updated from extension)
209209
writeDelayMs: 1000,
210210
browserViewportSize: "900x600",
211211
screenshotQuality: 75,
@@ -344,6 +344,10 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
344344
if ((newState as any).includeTaskHistoryInEnhance !== undefined) {
345345
setIncludeTaskHistoryInEnhance((newState as any).includeTaskHistoryInEnhance)
346346
}
347+
// Update language if present in state message
348+
if (newState.language !== undefined) {
349+
setState((prevState) => ({ ...prevState, language: newState.language }))
350+
}
347351
// Handle marketplace data if present in state message
348352
if (newState.marketplaceItems !== undefined) {
349353
setMarketplaceItems(newState.marketplaceItems)

0 commit comments

Comments
 (0)