From c3a83b3181b3d97cad0facf486053f1c2bbcbc1a Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Sat, 5 Sep 2026 15:08:15 +0300 Subject: [PATCH] fix(vscode): default to the VS Code display language before a saved locale The splash and the UI bundle both started in English on a fresh install even when VS Code ran in a supported language, because only OpenChamber's own saved locale was consulted. The HTML now exposes VS Code's display language; the splash and detectInitialLocale use it until the user picks a locale, which still wins. Claude-Session: https://claude.ai/code/session_01VqV56Hez25hTxXH4ipJfzH --- packages/ui/src/lib/i18n/runtime.ts | 12 +++++ packages/vscode/src/DOCUMENTATION.md | 10 +++-- packages/vscode/src/webviewHtml.ts | 65 ++++++++++++++-------------- 3 files changed, 52 insertions(+), 35 deletions(-) diff --git a/packages/ui/src/lib/i18n/runtime.ts b/packages/ui/src/lib/i18n/runtime.ts index cbcc8e6d..509dc382 100644 --- a/packages/ui/src/lib/i18n/runtime.ts +++ b/packages/ui/src/lib/i18n/runtime.ts @@ -102,11 +102,23 @@ export function writeStoredLocale(locale: Locale): void { } } +declare global { + interface Window { + /** The host application's display language (VS Code sets it), used before the user picks a locale. */ + __OPENCHAMBER_HOST_LANGUAGE__?: string; + } +} + export function detectInitialLocale(): Locale { const stored = readStoredLocale(); if (stored) { return stored; } + const hostLanguage = globalThis.window?.__OPENCHAMBER_HOST_LANGUAGE__; + if (hostLanguage) { + return normalizeLocale(hostLanguage); + } + return DEFAULT_LOCALE; } diff --git a/packages/vscode/src/DOCUMENTATION.md b/packages/vscode/src/DOCUMENTATION.md index 8cabb2b6..fa988275 100644 --- a/packages/vscode/src/DOCUMENTATION.md +++ b/packages/vscode/src/DOCUMENTATION.md @@ -201,6 +201,10 @@ half-translated locale looks like a shipped feature. `localizationBundles.test.t enforces that, and it is the check to run whenever a feature adds a new string. The pre-bundle loading splash in `webviewHtml.ts` is separate: its strings are -inlined in the generated HTML and chosen from OpenChamber's own saved locale -(`openchamber.i18n.v1` in webview localStorage), not from VS Code's display -language, because the splash renders before the webview bundle loads. +inlined in the generated HTML because the splash renders before the webview +bundle loads. It picks them from OpenChamber's own saved locale +(`openchamber.i18n.v1` in webview localStorage) and, before the user has +chosen one, from VS Code's display language, which the HTML exposes as +`window.__OPENCHAMBER_HOST_LANGUAGE__`. The UI bundle reads the same value as +its default locale (`detectInitialLocale`), so a fresh install in a supported +language starts in that language on both the splash and the app. diff --git a/packages/vscode/src/webviewHtml.ts b/packages/vscode/src/webviewHtml.ts index 8ea45e22..2e69eef4 100644 --- a/packages/vscode/src/webviewHtml.ts +++ b/packages/vscode/src/webviewHtml.ts @@ -195,23 +195,32 @@ export function getWebviewHtml(options: WebviewHtmlOptions): string { initialSessionId: ${initialSessionId ? `"${initialSessionId.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"` : 'null'}, }; window.__OPENCHAMBER_HOME__ = "${workspaceFolder.replace(/\\/g, '\\\\')}"; - - function getBootstrapMessages() { - var locale = 'en'; + // VS Code's display language. The UI bundle uses it as the default locale + // until the user picks one; the splash below picks its strings from it too. + window.__OPENCHAMBER_HOST_LANGUAGE__ = ${JSON.stringify(vscode.env.language)}; + + // OpenChamber's own saved locale wins; before one exists, VS Code's display + // language decides, so a fresh install in a supported language never boots + // in English. + function resolveBootstrapLanguage() { try { var rawLocale = window.localStorage.getItem('openchamber.i18n.v1'); if (rawLocale) { var parsedLocale = JSON.parse(rawLocale); - if (parsedLocale && typeof parsedLocale.locale === 'string') { - var detected = parsedLocale.locale.toLowerCase(); - if (detected.indexOf('fr') === 0) { - locale = 'fr'; - } else if (detected.indexOf('tr') === 0) { - locale = 'tr'; - } - } + if (parsedLocale && typeof parsedLocale.locale === 'string') return parsedLocale.locale.toLowerCase(); } } catch {} + return String(window.__OPENCHAMBER_HOST_LANGUAGE__ || '').toLowerCase(); + } + + function getBootstrapMessages() { + var locale = 'en'; + var detected = resolveBootstrapLanguage(); + if (detected.indexOf('fr') === 0) { + locale = 'fr'; + } else if (detected.indexOf('tr') === 0) { + locale = 'tr'; + } if (locale === 'fr') { return { @@ -296,27 +305,19 @@ export function getWebviewHtml(options: WebviewHtmlOptions): string { const statusEl = document.getElementById('loading-status'); const getDevMessages = () => { - try { - const rawLocale = window.localStorage.getItem('openchamber.i18n.v1'); - if (rawLocale) { - const parsedLocale = JSON.parse(rawLocale); - if (parsedLocale && typeof parsedLocale.locale === 'string') { - const detected = parsedLocale.locale.toLowerCase(); - if (detected.indexOf('fr') === 0) { - return { - startingDevServer: (host) => 'Démarrage du serveur de développement de la webview (' + host + ')...', - waitingDevServer: (host, attempt) => 'En attente du serveur de développement de la webview (' + host + ')... tentative ' + attempt, - }; - } - if (detected.indexOf('tr') === 0) { - return { - startingDevServer: (host) => 'Webview dev sunucusu başlatılıyor (' + host + ')...', - waitingDevServer: (host, attempt) => 'Webview dev sunucusu bekleniyor (' + host + ')... deneme ' + attempt, - }; - } - } - } - } catch {} + const detected = resolveBootstrapLanguage(); + if (detected.indexOf('fr') === 0) { + return { + startingDevServer: (host) => 'Démarrage du serveur de développement de la webview (' + host + ')...', + waitingDevServer: (host, attempt) => 'En attente du serveur de développement de la webview (' + host + ')... tentative ' + attempt, + }; + } + if (detected.indexOf('tr') === 0) { + return { + startingDevServer: (host) => 'Webview dev sunucusu başlatılıyor (' + host + ')...', + waitingDevServer: (host, attempt) => 'Webview dev sunucusu bekleniyor (' + host + ')... deneme ' + attempt, + }; + } return { startingDevServer: (host) => 'Starting webview dev server (' + host + ')...', waitingDevServer: (host, attempt) => 'Waiting for webview dev server (' + host + ')... attempt ' + attempt,