11Add display language support
22
3- We can remove this once upstream supports all language packs.
4-
5- 1. Proxies language packs to the service on the backend.
6- 2. NLS configuration is embedded into the HTML for the browser to pick up. This
7- code to generate this configuration is copied from the native portion.
8- 3. Remove configuredLocale since we have our own thing.
9- 4. Move the argv.json file to the server instead of in-browser storage. This is
10- where the current locale is stored and currently the server needs to be able
11- to read it.
12- 5. Add the locale flag.
13- 6. Remove the redundant locale verification. It does the same as the existing
14- one but is worse because it does not handle non-existent or empty files.
15- 7. Replace some caching and Node requires because code-server does not restart
16- when changing the language unlike native Code.
17- 8. Make language extensions installable like normal rather than using the
18- special set/clear language actions.
3+ VS Code web appears to implement language support by setting a cookie and
4+ downloading language packs from a URL configured in the product.json. This patch
5+ supports language pack extensions and uses files on the remote to set the
6+ language instead, so it works like the desktop version.
197
208Index: code-server/lib/vscode/src/vs/server/node/serverServices.ts
219===================================================================
2210--- code-server.orig/lib/vscode/src/vs/server/node/serverServices.ts
2311+++ code-server/lib/vscode/src/vs/server/node/serverServices.ts
24- @@ -11 ,7 +11 ,7 @@ import * as path from 'vs/base/common/pa
12+ @@ -12 ,7 +12 ,7 @@ import * as path from 'vs/base/common/pa
2513 import { IURITransformer } from 'vs/base/common/uriIpc';
2614 import { getMachineId, getSqmMachineId, getdevDeviceId } from 'vs/base/node/id';
2715 import { Promises } from 'vs/base/node/pfs';
@@ -30,7 +18,7 @@ Index: code-server/lib/vscode/src/vs/server/node/serverServices.ts
3018 import { ProtocolConstants } from 'vs/base/parts/ipc/common/ipc.net';
3119 import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
3220 import { ConfigurationService } from 'vs/platform/configuration/common/configurationService';
33- @@ -238 ,6 +238 ,9 @@ export async function setupServerService
21+ @@ -239 ,6 +239 ,9 @@ export async function setupServerService
3422 const channel = new ExtensionManagementChannel(extensionManagementService, (ctx: RemoteAgentConnectionContext) => getUriTransformer(ctx.remoteAuthority));
3523 socketServer.registerChannel('extensions', channel);
3624
@@ -40,100 +28,6 @@ Index: code-server/lib/vscode/src/vs/server/node/serverServices.ts
4028 // clean up extensions folder
4129 remoteExtensionsScanner.whenExtensionsReady().then(() => extensionManagementService.cleanUp());
4230
43- Index: code-server/lib/vscode/src/vs/base/common/platform.ts
44- ===================================================================
45- --- code-server.orig/lib/vscode/src/vs/base/common/platform.ts
46- +++ code-server/lib/vscode/src/vs/base/common/platform.ts
47- @@ -2,8 +2,6 @@
48- * Copyright (c) Microsoft Corporation. All rights reserved.
49- * Licensed under the MIT License. See License.txt in the project root for license information.
50- *--------------------------------------------------------------------------------------------*/
51- - import * as nls from 'vs/nls';
52- -
53- export const LANGUAGE_DEFAULT = 'en';
54-
55- let _isWindows = false;
56- @@ -112,17 +110,21 @@ else if (typeof navigator === 'object' &
57- _isMobile = _userAgent?.indexOf('Mobi') >= 0;
58- _isWeb = true;
59-
60- - const configuredLocale = nls.getConfiguredDefaultLocale(
61- - // This call _must_ be done in the file that calls `nls.getConfiguredDefaultLocale`
62- - // to ensure that the NLS AMD Loader plugin has been loaded and configured.
63- - // This is because the loader plugin decides what the default locale is based on
64- - // how it's able to resolve the strings.
65- - nls.localize({ key: 'ensureLoaderPluginIsLoaded', comment: ['{Locked}'] }, '_')
66- - );
67- -
68- - _locale = configuredLocale || LANGUAGE_DEFAULT;
69- + _locale = LANGUAGE_DEFAULT;
70- _language = _locale;
71- _platformLocale = navigator.language;
72- + const el = typeof document !== 'undefined' && document.getElementById('vscode-remote-nls-configuration');
73- + const rawNlsConfig = el && el.getAttribute('data-settings');
74- + if (rawNlsConfig) {
75- + try {
76- + const nlsConfig: NLSConfig = JSON.parse(rawNlsConfig);
77- + const resolved = nlsConfig.availableLanguages['*'];
78- + _locale = nlsConfig.locale;
79- + _platformLocale = nlsConfig.osLocale;
80- + _language = resolved ? resolved : LANGUAGE_DEFAULT;
81- + _translationsConfigFile = nlsConfig._translationsConfigFile;
82- + } catch (error) { /* Oh well. */ }
83- + }
84- }
85-
86- // Unknown environment
87- Index: code-server/lib/vscode/src/vs/code/browser/workbench/workbench.html
88- ===================================================================
89- --- code-server.orig/lib/vscode/src/vs/code/browser/workbench/workbench.html
90- +++ code-server/lib/vscode/src/vs/code/browser/workbench/workbench.html
91- @@ -23,6 +23,9 @@
92- <!-- Workbench Auth Session -->
93- <meta id="vscode-workbench-auth-session" data-settings="{{WORKBENCH_AUTH_SESSION}}">
94-
95- + <!-- NLS Configuration -->
96- + <meta id="vscode-remote-nls-configuration" data-settings="{{NLS_CONFIGURATION}}">
97- +
98- <!-- Workbench Icon/Manifest/CSS -->
99- <link rel="icon" href="{{BASE}}/_static/src/browser/media/favicon-dark-support.svg" />
100- <link rel="alternate icon" href="{{BASE}}/_static/src/browser/media/favicon.ico" type="image/x-icon" />
101- @@ -48,15 +51,27 @@
102- // Normalize locale to lowercase because translationServiceUrl is case-sensitive.
103- // ref: https://github.com/microsoft/vscode/issues/187795
104- const locale = localStorage.getItem('vscode.nls.locale') || navigator.language.toLowerCase();
105- - if (!locale.startsWith('en')) {
106- - nlsConfig['vs/nls'] = {
107- - availableLanguages: {
108- - '*': locale
109- - },
110- - translationServiceUrl: '{{WORKBENCH_NLS_BASE_URL}}'
111- - };
112- - }
113-
114- + try {
115- + nlsConfig['vs/nls'] = JSON.parse(document.getElementById("vscode-remote-nls-configuration").getAttribute("data-settings"))
116- + if (nlsConfig['vs/nls']._resolvedLanguagePackCoreLocation) {
117- + const bundles = Object.create(null)
118- + nlsConfig['vs/nls'].loadBundle = (bundle, _language, cb) => {
119- + const result = bundles[bundle]
120- + if (result) {
121- + return cb(undefined, result)
122- + }
123- + const path = nlsConfig['vs/nls']._resolvedLanguagePackCoreLocation + "/" + bundle.replace(/\//g, "!") + ".nls.json"
124- + fetch(`{{WORKBENCH_WEB_BASE_URL}}/../vscode-remote-resource?path=${encodeURIComponent(path)}`)
125- + .then((response) => response.json())
126- + .then((json) => {
127- + bundles[bundle] = json
128- + cb(undefined, json)
129- + })
130- + .catch(cb)
131- + }
132- + }
133- + } catch (error) { /* Probably fine. */ }
134- require.config({
135- baseUrl: `${baseUrl}/out`,
136- recordStats: true,
13731Index: code-server/lib/vscode/src/vs/platform/environment/common/environmentService.ts
13832===================================================================
13933--- code-server.orig/lib/vscode/src/vs/platform/environment/common/environmentService.ts
@@ -151,31 +45,37 @@ Index: code-server/lib/vscode/src/vs/server/node/remoteLanguagePacks.ts
15145===================================================================
15246--- code-server.orig/lib/vscode/src/vs/server/node/remoteLanguagePacks.ts
15347+++ code-server/lib/vscode/src/vs/server/node/remoteLanguagePacks.ts
154- @@ -32,6 +32,12 @@ export function getNLSConfiguration(lang
155- if (InternalNLSConfiguration.is(value)) {
156- value._languagePackSupport = true;
157- }
158- + // If the configuration has no results keep trying since code-server
159- + // doesn't restart when a language is installed so this result would
160- + // persist (the plugin might not be installed yet for example).
161- + if (value.locale !== 'en' && value.locale !== 'en-us' && Object.keys(value.availableLanguages).length === 0) {
162- + _cache.delete(key);
163- + }
164- return value;
165- });
166- _cache.set(key, result);
167- @@ -46,3 +52,43 @@ export namespace InternalNLSConfiguratio
168- return candidate && typeof candidate._languagePackId === 'string';
48+ @@ -3,6 +3,8 @@
49+ * Licensed under the MIT License. See License.txt in the project root for license information.
50+ *--------------------------------------------------------------------------------------------*/
51+
52+ + import { promises as fs } from 'fs';
53+ + import * as path from 'path';
54+ import { FileAccess } from 'vs/base/common/network';
55+ import { join } from 'vs/base/common/path';
56+ import type { INLSConfiguration } from 'vs/nls';
57+ @@ -33,7 +35,94 @@ export async function getNLSConfiguratio
58+ if (!result) {
59+ result = resolveNLSConfiguration({ userLocale: language, osLocale: language, commit: product.commit, userDataPath, nlsMetadataPath });
60+ nlsConfigurationCache.set(cacheKey, result);
61+ + // If the language pack does not yet exist, it defaults to English, which is
62+ + // then cached and you have to restart even if you then install the pack.
63+ + result.then((r) => {
64+ + if (!language.startsWith('en') && r.resolvedLanguage.startsWith('en')) {
65+ + nlsConfigurationCache.delete(cacheKey);
66+ + }
67+ + })
16968 }
69+
70+ return result;
17071 }
17172+
17273+ /**
173- + * The code below is copied from from src/main.js.
74+ + * Copied from from src/main.js.
17475+ */
175- +
17676+ export const getLocaleFromConfig = async (argvResource: string): Promise<string> => {
17777+ try {
178- + const content = stripComments(await fs.promises. readFile(argvResource, 'utf8'));
78+ + const content = stripComments(await fs.readFile(argvResource, 'utf8'));
17979+ return JSON.parse(content).locale;
18080+ } catch (error) {
18181+ if (error.code !== "ENOENT") {
@@ -185,6 +85,9 @@ Index: code-server/lib/vscode/src/vs/server/node/remoteLanguagePacks.ts
18585+ }
18686+ };
18787+
88+ + /**
89+ + * Copied from from src/main.js.
90+ + */
18891+ const stripComments = (content: string): string => {
18992+ const regexp = /('(?:[^\\']*(?:\\.)?)*')|('(?:[^\\']*(?:\\.)?)*')|(\/\*(?:\r?\n|.)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))/g;
19093+
@@ -208,51 +111,113 @@ Index: code-server/lib/vscode/src/vs/server/node/remoteLanguagePacks.ts
208111+ }
209112+ });
210113+ };
114+ +
115+ + /**
116+ + * Generate translations then return a path to a JavaScript file that sets the
117+ + * translations into global variables. This file is loaded by the browser to
118+ + * set global variables that the loader uses when looking for translations.
119+ + *
120+ + * Normally, VS Code pulls these files from a CDN but we want them to be local.
121+ + */
122+ + export async function getBrowserNLSConfiguration(locale: string, userDataPath: string): Promise<string> {
123+ + if (locale.startsWith('en')) {
124+ + return ''; // Use fallback translations.
125+ + }
126+ +
127+ + const nlsConfig = await getNLSConfiguration(locale, userDataPath);
128+ + const messagesFile = nlsConfig?.languagePack?.messagesFile;
129+ + const resolvedLanguage = nlsConfig?.resolvedLanguage;
130+ + if (!messagesFile || !resolvedLanguage) {
131+ + return ''; // Use fallback translations.
132+ + }
133+ +
134+ + const nlsFile = path.join(path.dirname(messagesFile), "nls.messages.js");
135+ + try {
136+ + await fs.stat(nlsFile);
137+ + return nlsFile; // We already generated the file.
138+ + } catch (error) {
139+ + // ENOENT is fine, that just means we need to generate the file.
140+ + if (error.code !== 'ENOENT') {
141+ + throw error;
142+ + }
143+ + }
144+ +
145+ + const messages = (await fs.readFile(messagesFile)).toString();
146+ + const content = `globalThis._VSCODE_NLS_MESSAGES=${messages};
147+ + globalThis._VSCODE_NLS_LANGUAGE=${JSON.stringify(resolvedLanguage)};`
148+ + await fs.writeFile(nlsFile, content, "utf-8");
149+ +
150+ + return nlsFile;
151+ + }
211152Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
212153===================================================================
213154--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
214155+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
215- @@ -27 ,6 +27 ,7 @@ import { URI } from 'vs/base/common/uri'
156+ @@ -26 ,6 +26 ,7 @@ import { URI } from 'vs/base/common/uri'
216157 import { streamToBuffer } from 'vs/base/common/buffer';
217158 import { IProductConfiguration } from 'vs/base/common/product';
218159 import { isString } from 'vs/base/common/types';
219- + import { getLocaleFromConfig, getNLSConfiguration } from 'vs/server/node/remoteLanguagePacks';
160+ + import { getLocaleFromConfig, getBrowserNLSConfiguration } from 'vs/server/node/remoteLanguagePacks';
220161 import { CharCode } from 'vs/base/common/charCode';
221162 import { IExtensionManifest } from 'vs/platform/extensions/common/extensions';
222163
223- @@ -348,6 +349,8 @@ export class WebClientServer {
224- callbackRoute: this._callbackRoute
225- };
164+ @@ -97,6 +98,7 @@ export class WebClientServer {
165+ private readonly _webExtensionResourceUrlTemplate: URI | undefined;
226166
227- + const locale = this._environmentService.args.locale || await getLocaleFromConfig(this._environmentService.argvResource.fsPath);
228- + const nlsConfiguration = await getNLSConfiguration(locale, this._environmentService.userDataPath)
229- const nlsBaseUrl = this._productService.extensionsGallery?.nlsBaseUrl;
230- const values: { [key: string]: string } = {
231- WORKBENCH_WEB_CONFIGURATION: asJSON(workbenchWebConfiguration),
232- @@ -356,6 +359,7 @@ export class WebClientServer {
233- WORKBENCH_NLS_BASE_URL: vscodeBase + (nlsBaseUrl ? `${nlsBaseUrl}${!nlsBaseUrl.endsWith('/') ? '/' : ''}${this._productService.commit}/${this._productService.version}/` : ''),
234- BASE: base,
235- VS_BASE: vscodeBase,
236- + NLS_CONFIGURATION: asJSON(nlsConfiguration),
167+ private readonly _staticRoute: string;
168+ + private readonly _serverRoot: string;
169+ private readonly _callbackRoute: string;
170+ private readonly _webExtensionRoute: string;
171+
172+ @@ -111,6 +113,7 @@ export class WebClientServer {
173+ ) {
174+ this._webExtensionResourceUrlTemplate = this._productService.extensionsGallery?.resourceUrlTemplate ? URI.parse(this._productService.extensionsGallery.resourceUrlTemplate) : undefined;
175+
176+ + this._serverRoot = serverRootPath;
177+ this._staticRoute = `${serverRootPath}/static`;
178+ this._callbackRoute = `${serverRootPath}/callback`;
179+ this._webExtensionRoute = `/web-extension-resource`;
180+ @@ -349,14 +352,22 @@ export class WebClientServer {
237181 };
238182
239- if (useTestResolver) {
183+ const cookies = cookie.parse(req.headers.cookie || '');
184+ - const locale = cookies['vscode.nls.locale'] || req.headers['accept-language']?.split(',')[0]?.toLowerCase() || 'en';
185+ + const locale = this._environmentService.args.locale || await getLocaleFromConfig(this._environmentService.argvResource.fsPath) || cookies['vscode.nls.locale'] || req.headers['accept-language']?.split(',')[0]?.toLowerCase() || 'en';
186+ let WORKBENCH_NLS_BASE_URL: string | undefined;
187+ let WORKBENCH_NLS_URL: string;
188+ if (!locale.startsWith('en') && this._productService.nlsCoreBaseUrl) {
189+ WORKBENCH_NLS_BASE_URL = this._productService.nlsCoreBaseUrl;
190+ WORKBENCH_NLS_URL = `${WORKBENCH_NLS_BASE_URL}${this._productService.commit}/${this._productService.version}/${locale}/nls.messages.js`;
191+ } else {
192+ - WORKBENCH_NLS_URL = ''; // fallback will apply
193+ + try {
194+ + const nlsFile = await getBrowserNLSConfiguration(locale, this._environmentService.userDataPath);
195+ + WORKBENCH_NLS_URL = nlsFile
196+ + ? `${vscodeBase}${this._serverRoot}/vscode-remote-resource?path=${encodeURIComponent(nlsFile)}`
197+ + : '';
198+ + } catch (error) {
199+ + console.error("Failed to generate translations", error);
200+ + WORKBENCH_NLS_URL = '';
201+ + }
202+ }
203+
204+ const values: { [key: string]: string } = {
240205Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
241206===================================================================
242207--- code-server.orig/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
243208+++ code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
244- @@ -18,6 +18,7 @@ export const serverOptions: OptionDescri
245- 'auth': { type: 'string' },
209+ @@ -19,6 +19,7 @@ export const serverOptions: OptionDescri
246210 'disable-file-downloads': { type: 'boolean' },
247211 'disable-file-uploads': { type: 'boolean' },
212+ 'disable-getting-started-override': { type: 'boolean' },
248213+ 'locale': { type: 'string' },
249214
250215 /* ----- server setup ----- */
251216
252- @@ -103,6 +104,7 @@ export interface ServerParsedArgs {
253- 'auth'?: string;
217+ @@ -105,6 +106,7 @@ export interface ServerParsedArgs {
254218 'disable-file-downloads'?: boolean;
255219 'disable-file-uploads'?: boolean;
220+ 'disable-getting-started-override'?: boolean,
256221+ 'locale'?: string
257222
258223 /* ----- server setup ----- */
@@ -367,7 +332,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/extensions/browser/extens
367332 }
368333
369334 // Prefers to run on UI
370- @@ -1928 ,17 +1925 ,6 @@ export class SetLanguageAction extends E
335+ @@ -1951 ,17 +1948 ,6 @@ export class SetLanguageAction extends E
371336 update(): void {
372337 this.enabled = false;
373338 this.class = SetLanguageAction.DisabledClass;
@@ -385,15 +350,15 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/extensions/browser/extens
385350 }
386351
387352 override async run(): Promise<any> {
388- @@ -1955 ,7 +1941 ,6 @@ export class ClearLanguageAction extends
389- private static readonly DisabledClass = `${ClearLanguageAction .EnabledClass} disabled`;
353+ @@ -1978 ,7 +1964 ,6 @@ export class ClearLanguageAction extends
354+ private static readonly DisabledClass = `${this .EnabledClass} disabled`;
390355
391356 constructor(
392357- @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
393358 @ILocaleService private readonly localeService: ILocaleService,
394359 ) {
395360 super(ClearLanguageAction.ID, ClearLanguageAction.TITLE.value, ClearLanguageAction.DisabledClass, false);
396- @@ -1965 ,17 +1950 ,6 @@ export class ClearLanguageAction extends
361+ @@ -1988 ,17 +1973 ,6 @@ export class ClearLanguageAction extends
397362 update(): void {
398363 this.enabled = false;
399364 this.class = ClearLanguageAction.DisabledClass;
@@ -411,3 +376,15 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/extensions/browser/extens
411376 }
412377
413378 override async run(): Promise<any> {
379+ Index: code-server/lib/vscode/build/gulpfile.reh.js
380+ ===================================================================
381+ --- code-server.orig/lib/vscode/build/gulpfile.reh.js
382+ +++ code-server/lib/vscode/build/gulpfile.reh.js
383+ @@ -56,6 +56,7 @@ const serverResources = [
384+
385+ // NLS
386+ 'out-build/nls.messages.json',
387+ + 'out-build/nls.keys.json', // Required to generate translations.
388+
389+ // Process monitor
390+ 'out-build/vs/base/node/cpuUsage.sh',
0 commit comments