mirror of https://github.com/coder/code-server.git
391 lines
17 KiB
Diff
391 lines
17 KiB
Diff
Add display language support
|
|
|
|
VS Code web appears to implement language support by setting a cookie and
|
|
downloading language packs from a URL configured in the product.json. This patch
|
|
supports language pack extensions and uses files on the remote to set the
|
|
language instead, so it works like the desktop version.
|
|
|
|
Index: code-server/lib/vscode/src/vs/server/node/serverServices.ts
|
|
===================================================================
|
|
--- code-server.orig/lib/vscode/src/vs/server/node/serverServices.ts
|
|
+++ code-server/lib/vscode/src/vs/server/node/serverServices.ts
|
|
@@ -12,7 +12,7 @@ import * as path from 'vs/base/common/pa
|
|
import { IURITransformer } from 'vs/base/common/uriIpc';
|
|
import { getMachineId, getSqmMachineId, getdevDeviceId } from 'vs/base/node/id';
|
|
import { Promises } from 'vs/base/node/pfs';
|
|
-import { ClientConnectionEvent, IMessagePassingProtocol, IPCServer, StaticRouter } from 'vs/base/parts/ipc/common/ipc';
|
|
+import { ClientConnectionEvent, IMessagePassingProtocol, IPCServer, ProxyChannel, StaticRouter } from 'vs/base/parts/ipc/common/ipc';
|
|
import { ProtocolConstants } from 'vs/base/parts/ipc/common/ipc.net';
|
|
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
|
import { ConfigurationService } from 'vs/platform/configuration/common/configurationService';
|
|
@@ -239,6 +239,9 @@ export async function setupServerService
|
|
const channel = new ExtensionManagementChannel(extensionManagementService, (ctx: RemoteAgentConnectionContext) => getUriTransformer(ctx.remoteAuthority));
|
|
socketServer.registerChannel('extensions', channel);
|
|
|
|
+ const languagePackChannel = ProxyChannel.fromService<RemoteAgentConnectionContext>(accessor.get(ILanguagePackService), disposables);
|
|
+ socketServer.registerChannel('languagePacks', languagePackChannel);
|
|
+
|
|
// clean up extensions folder
|
|
remoteExtensionsScanner.whenExtensionsReady().then(() => extensionManagementService.cleanUp());
|
|
|
|
Index: code-server/lib/vscode/src/vs/platform/environment/common/environmentService.ts
|
|
===================================================================
|
|
--- code-server.orig/lib/vscode/src/vs/platform/environment/common/environmentService.ts
|
|
+++ code-server/lib/vscode/src/vs/platform/environment/common/environmentService.ts
|
|
@@ -101,7 +101,7 @@ export abstract class AbstractNativeEnvi
|
|
return URI.file(join(vscodePortable, 'argv.json'));
|
|
}
|
|
|
|
- return joinPath(this.userHome, this.productService.dataFolderName, 'argv.json');
|
|
+ return joinPath(this.appSettingsHome, 'argv.json');
|
|
}
|
|
|
|
@memoize
|
|
Index: code-server/lib/vscode/src/vs/server/node/remoteLanguagePacks.ts
|
|
===================================================================
|
|
--- code-server.orig/lib/vscode/src/vs/server/node/remoteLanguagePacks.ts
|
|
+++ code-server/lib/vscode/src/vs/server/node/remoteLanguagePacks.ts
|
|
@@ -3,6 +3,8 @@
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
+import { promises as fs } from 'fs';
|
|
+import * as path from 'path';
|
|
import { FileAccess } from 'vs/base/common/network';
|
|
import { join } from 'vs/base/common/path';
|
|
import type { INLSConfiguration } from 'vs/nls';
|
|
@@ -33,7 +35,94 @@ export async function getNLSConfiguratio
|
|
if (!result) {
|
|
result = resolveNLSConfiguration({ userLocale: language, osLocale: language, commit: product.commit, userDataPath, nlsMetadataPath });
|
|
nlsConfigurationCache.set(cacheKey, result);
|
|
+ // If the language pack does not yet exist, it defaults to English, which is
|
|
+ // then cached and you have to restart even if you then install the pack.
|
|
+ result.then((r) => {
|
|
+ if (!language.startsWith('en') && r.resolvedLanguage.startsWith('en')) {
|
|
+ nlsConfigurationCache.delete(cacheKey);
|
|
+ }
|
|
+ })
|
|
}
|
|
|
|
return result;
|
|
}
|
|
+
|
|
+/**
|
|
+ * Copied from from src/main.js.
|
|
+ */
|
|
+export const getLocaleFromConfig = async (argvResource: string): Promise<string> => {
|
|
+ try {
|
|
+ const content = stripComments(await fs.readFile(argvResource, 'utf8'));
|
|
+ return JSON.parse(content).locale;
|
|
+ } catch (error) {
|
|
+ if (error.code !== "ENOENT") {
|
|
+ console.warn(error)
|
|
+ }
|
|
+ return 'en';
|
|
+ }
|
|
+};
|
|
+
|
|
+/**
|
|
+ * Copied from from src/main.js.
|
|
+ */
|
|
+const stripComments = (content: string): string => {
|
|
+ const regexp = /('(?:[^\\']*(?:\\.)?)*')|('(?:[^\\']*(?:\\.)?)*')|(\/\*(?:\r?\n|.)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))/g;
|
|
+
|
|
+ return content.replace(regexp, (match, _m1, _m2, m3, m4) => {
|
|
+ // Only one of m1, m2, m3, m4 matches
|
|
+ if (m3) {
|
|
+ // A block comment. Replace with nothing
|
|
+ return '';
|
|
+ } else if (m4) {
|
|
+ // A line comment. If it ends in \r?\n then keep it.
|
|
+ const length_1 = m4.length;
|
|
+ if (length_1 > 2 && m4[length_1 - 1] === '\n') {
|
|
+ return m4[length_1 - 2] === '\r' ? '\r\n' : '\n';
|
|
+ }
|
|
+ else {
|
|
+ return '';
|
|
+ }
|
|
+ } else {
|
|
+ // We match a string
|
|
+ return match;
|
|
+ }
|
|
+ });
|
|
+};
|
|
+
|
|
+/**
|
|
+ * Generate translations then return a path to a JavaScript file that sets the
|
|
+ * translations into global variables. This file is loaded by the browser to
|
|
+ * set global variables that the loader uses when looking for translations.
|
|
+ *
|
|
+ * Normally, VS Code pulls these files from a CDN but we want them to be local.
|
|
+ */
|
|
+export async function getBrowserNLSConfiguration(locale: string, userDataPath: string): Promise<string> {
|
|
+ if (locale.startsWith('en')) {
|
|
+ return ''; // Use fallback translations.
|
|
+ }
|
|
+
|
|
+ const nlsConfig = await getNLSConfiguration(locale, userDataPath);
|
|
+ const messagesFile = nlsConfig?.languagePack?.messagesFile;
|
|
+ const resolvedLanguage = nlsConfig?.resolvedLanguage;
|
|
+ if (!messagesFile || !resolvedLanguage) {
|
|
+ return ''; // Use fallback translations.
|
|
+ }
|
|
+
|
|
+ const nlsFile = path.join(path.dirname(messagesFile), "nls.messages.js");
|
|
+ try {
|
|
+ await fs.stat(nlsFile);
|
|
+ return nlsFile; // We already generated the file.
|
|
+ } catch (error) {
|
|
+ // ENOENT is fine, that just means we need to generate the file.
|
|
+ if (error.code !== 'ENOENT') {
|
|
+ throw error;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ const messages = (await fs.readFile(messagesFile)).toString();
|
|
+ const content = `globalThis._VSCODE_NLS_MESSAGES=${messages};
|
|
+globalThis._VSCODE_NLS_LANGUAGE=${JSON.stringify(resolvedLanguage)};`
|
|
+ await fs.writeFile(nlsFile, content, "utf-8");
|
|
+
|
|
+ return nlsFile;
|
|
+}
|
|
Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
|
===================================================================
|
|
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
|
|
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
|
|
@@ -26,6 +26,7 @@ import { URI } from 'vs/base/common/uri'
|
|
import { streamToBuffer } from 'vs/base/common/buffer';
|
|
import { IProductConfiguration } from 'vs/base/common/product';
|
|
import { isString } from 'vs/base/common/types';
|
|
+import { getLocaleFromConfig, getBrowserNLSConfiguration } from 'vs/server/node/remoteLanguagePacks';
|
|
import { CharCode } from 'vs/base/common/charCode';
|
|
import { IExtensionManifest } from 'vs/platform/extensions/common/extensions';
|
|
|
|
@@ -97,6 +98,7 @@ export class WebClientServer {
|
|
private readonly _webExtensionResourceUrlTemplate: URI | undefined;
|
|
|
|
private readonly _staticRoute: string;
|
|
+ private readonly _serverRoot: string;
|
|
private readonly _callbackRoute: string;
|
|
private readonly _webExtensionRoute: string;
|
|
|
|
@@ -111,6 +113,7 @@ export class WebClientServer {
|
|
) {
|
|
this._webExtensionResourceUrlTemplate = this._productService.extensionsGallery?.resourceUrlTemplate ? URI.parse(this._productService.extensionsGallery.resourceUrlTemplate) : undefined;
|
|
|
|
+ this._serverRoot = serverRootPath;
|
|
this._staticRoute = `${serverRootPath}/static`;
|
|
this._callbackRoute = `${serverRootPath}/callback`;
|
|
this._webExtensionRoute = `/web-extension-resource`;
|
|
@@ -349,14 +352,22 @@ export class WebClientServer {
|
|
};
|
|
|
|
const cookies = cookie.parse(req.headers.cookie || '');
|
|
- const locale = cookies['vscode.nls.locale'] || req.headers['accept-language']?.split(',')[0]?.toLowerCase() || 'en';
|
|
+ const locale = this._environmentService.args.locale || await getLocaleFromConfig(this._environmentService.argvResource.fsPath) || cookies['vscode.nls.locale'] || req.headers['accept-language']?.split(',')[0]?.toLowerCase() || 'en';
|
|
let WORKBENCH_NLS_BASE_URL: string | undefined;
|
|
let WORKBENCH_NLS_URL: string;
|
|
if (!locale.startsWith('en') && this._productService.nlsCoreBaseUrl) {
|
|
WORKBENCH_NLS_BASE_URL = this._productService.nlsCoreBaseUrl;
|
|
WORKBENCH_NLS_URL = `${WORKBENCH_NLS_BASE_URL}${this._productService.commit}/${this._productService.version}/${locale}/nls.messages.js`;
|
|
} else {
|
|
- WORKBENCH_NLS_URL = ''; // fallback will apply
|
|
+ try {
|
|
+ const nlsFile = await getBrowserNLSConfiguration(locale, this._environmentService.userDataPath);
|
|
+ WORKBENCH_NLS_URL = nlsFile
|
|
+ ? `${vscodeBase}${this._serverRoot}/vscode-remote-resource?path=${encodeURIComponent(nlsFile)}`
|
|
+ : '';
|
|
+ } catch (error) {
|
|
+ console.error("Failed to generate translations", error);
|
|
+ WORKBENCH_NLS_URL = '';
|
|
+ }
|
|
}
|
|
|
|
const values: { [key: string]: string } = {
|
|
Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
|
|
===================================================================
|
|
--- code-server.orig/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
|
|
+++ code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
|
|
@@ -19,6 +19,7 @@ export const serverOptions: OptionDescri
|
|
'disable-file-downloads': { type: 'boolean' },
|
|
'disable-file-uploads': { type: 'boolean' },
|
|
'disable-getting-started-override': { type: 'boolean' },
|
|
+ 'locale': { type: 'string' },
|
|
|
|
/* ----- server setup ----- */
|
|
|
|
@@ -105,6 +106,7 @@ export interface ServerParsedArgs {
|
|
'disable-file-downloads'?: boolean;
|
|
'disable-file-uploads'?: boolean;
|
|
'disable-getting-started-override'?: boolean,
|
|
+ 'locale'?: string
|
|
|
|
/* ----- server setup ----- */
|
|
|
|
Index: code-server/lib/vscode/src/vs/workbench/workbench.web.main.ts
|
|
===================================================================
|
|
--- code-server.orig/lib/vscode/src/vs/workbench/workbench.web.main.ts
|
|
+++ code-server/lib/vscode/src/vs/workbench/workbench.web.main.ts
|
|
@@ -52,7 +52,7 @@ import 'vs/workbench/services/dialogs/br
|
|
import 'vs/workbench/services/host/browser/browserHostService';
|
|
import 'vs/workbench/services/lifecycle/browser/lifecycleService';
|
|
import 'vs/workbench/services/clipboard/browser/clipboardService';
|
|
-import 'vs/workbench/services/localization/browser/localeService';
|
|
+import 'vs/workbench/services/localization/electron-sandbox/localeService';
|
|
import 'vs/workbench/services/path/browser/pathService';
|
|
import 'vs/workbench/services/themes/browser/browserHostColorSchemeService';
|
|
import 'vs/workbench/services/encryption/browser/encryptionService';
|
|
@@ -118,8 +118,9 @@ registerSingleton(ILanguagePackService,
|
|
// Logs
|
|
import 'vs/workbench/contrib/logs/browser/logs.contribution';
|
|
|
|
-// Localization
|
|
-import 'vs/workbench/contrib/localization/browser/localization.contribution';
|
|
+// Localization. This does not actually import anything specific to Electron so
|
|
+// it should be safe.
|
|
+import 'vs/workbench/contrib/localization/electron-sandbox/localization.contribution';
|
|
|
|
// Performance
|
|
import 'vs/workbench/contrib/performance/browser/performance.web.contribution';
|
|
Index: code-server/lib/vscode/src/vs/platform/languagePacks/browser/languagePacks.ts
|
|
===================================================================
|
|
--- code-server.orig/lib/vscode/src/vs/platform/languagePacks/browser/languagePacks.ts
|
|
+++ code-server/lib/vscode/src/vs/platform/languagePacks/browser/languagePacks.ts
|
|
@@ -5,18 +5,24 @@
|
|
|
|
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
|
import { URI } from 'vs/base/common/uri';
|
|
+import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
|
|
import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
|
import { IExtensionResourceLoaderService } from 'vs/platform/extensionResourceLoader/common/extensionResourceLoader';
|
|
-import { ILanguagePackItem, LanguagePackBaseService } from 'vs/platform/languagePacks/common/languagePacks';
|
|
+import { ILanguagePackItem, ILanguagePackService, LanguagePackBaseService } from 'vs/platform/languagePacks/common/languagePacks';
|
|
import { ILogService } from 'vs/platform/log/common/log';
|
|
+import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
|
|
|
export class WebLanguagePacksService extends LanguagePackBaseService {
|
|
+ private readonly languagePackService: ILanguagePackService;
|
|
+
|
|
constructor(
|
|
+ @IRemoteAgentService remoteAgentService: IRemoteAgentService,
|
|
@IExtensionResourceLoaderService private readonly extensionResourceLoaderService: IExtensionResourceLoaderService,
|
|
@IExtensionGalleryService extensionGalleryService: IExtensionGalleryService,
|
|
@ILogService private readonly logService: ILogService
|
|
) {
|
|
super(extensionGalleryService);
|
|
+ this.languagePackService = ProxyChannel.toService<ILanguagePackService>(remoteAgentService.getConnection()!.getChannel('languagePacks'))
|
|
}
|
|
|
|
async getBuiltInExtensionTranslationsUri(id: string, language: string): Promise<URI | undefined> {
|
|
@@ -72,6 +78,6 @@ export class WebLanguagePacksService ext
|
|
|
|
// Web doesn't have a concept of language packs, so we just return an empty array
|
|
getInstalledLanguages(): Promise<ILanguagePackItem[]> {
|
|
- return Promise.resolve([]);
|
|
+ return this.languagePackService.getInstalledLanguages()
|
|
}
|
|
}
|
|
Index: code-server/lib/vscode/src/vs/workbench/services/localization/electron-sandbox/localeService.ts
|
|
===================================================================
|
|
--- code-server.orig/lib/vscode/src/vs/workbench/services/localization/electron-sandbox/localeService.ts
|
|
+++ code-server/lib/vscode/src/vs/workbench/services/localization/electron-sandbox/localeService.ts
|
|
@@ -51,7 +51,8 @@ class NativeLocaleService implements ILo
|
|
@IProductService private readonly productService: IProductService
|
|
) { }
|
|
|
|
- private async validateLocaleFile(): Promise<boolean> {
|
|
+ // Make public just so we do not have to patch all the unused code out.
|
|
+ public async validateLocaleFile(): Promise<boolean> {
|
|
try {
|
|
const content = await this.textFileService.read(this.environmentService.argvResource, { encoding: 'utf8' });
|
|
|
|
@@ -78,9 +79,6 @@ class NativeLocaleService implements ILo
|
|
}
|
|
|
|
private async writeLocaleValue(locale: string | undefined): Promise<boolean> {
|
|
- if (!(await this.validateLocaleFile())) {
|
|
- return false;
|
|
- }
|
|
await this.jsonEditingService.write(this.environmentService.argvResource, [{ path: ['locale'], value: locale }], true);
|
|
return true;
|
|
}
|
|
Index: code-server/lib/vscode/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts
|
|
===================================================================
|
|
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts
|
|
+++ code-server/lib/vscode/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts
|
|
@@ -411,9 +411,6 @@ export class InstallAction extends Exten
|
|
if (this.extension.isBuiltin) {
|
|
return;
|
|
}
|
|
- if (this.extensionsWorkbenchService.canSetLanguage(this.extension)) {
|
|
- return;
|
|
- }
|
|
if (this.extension.state !== ExtensionState.Uninstalled) {
|
|
return;
|
|
}
|
|
@@ -695,7 +692,7 @@ export abstract class InstallInOtherServ
|
|
}
|
|
|
|
if (isLanguagePackExtension(this.extension.local.manifest)) {
|
|
- return true;
|
|
+ return false;
|
|
}
|
|
|
|
// Prefers to run on UI
|
|
@@ -1951,17 +1948,6 @@ export class SetLanguageAction extends E
|
|
update(): void {
|
|
this.enabled = false;
|
|
this.class = SetLanguageAction.DisabledClass;
|
|
- if (!this.extension) {
|
|
- return;
|
|
- }
|
|
- if (!this.extensionsWorkbenchService.canSetLanguage(this.extension)) {
|
|
- return;
|
|
- }
|
|
- if (this.extension.gallery && language === getLocale(this.extension.gallery)) {
|
|
- return;
|
|
- }
|
|
- this.enabled = true;
|
|
- this.class = SetLanguageAction.EnabledClass;
|
|
}
|
|
|
|
override async run(): Promise<any> {
|
|
@@ -1978,7 +1964,6 @@ export class ClearLanguageAction extends
|
|
private static readonly DisabledClass = `${this.EnabledClass} disabled`;
|
|
|
|
constructor(
|
|
- @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
|
|
@ILocaleService private readonly localeService: ILocaleService,
|
|
) {
|
|
super(ClearLanguageAction.ID, ClearLanguageAction.TITLE.value, ClearLanguageAction.DisabledClass, false);
|
|
@@ -1988,17 +1973,6 @@ export class ClearLanguageAction extends
|
|
update(): void {
|
|
this.enabled = false;
|
|
this.class = ClearLanguageAction.DisabledClass;
|
|
- if (!this.extension) {
|
|
- return;
|
|
- }
|
|
- if (!this.extensionsWorkbenchService.canSetLanguage(this.extension)) {
|
|
- return;
|
|
- }
|
|
- if (this.extension.gallery && language !== getLocale(this.extension.gallery)) {
|
|
- return;
|
|
- }
|
|
- this.enabled = true;
|
|
- this.class = ClearLanguageAction.EnabledClass;
|
|
}
|
|
|
|
override async run(): Promise<any> {
|
|
Index: code-server/lib/vscode/build/gulpfile.reh.js
|
|
===================================================================
|
|
--- code-server.orig/lib/vscode/build/gulpfile.reh.js
|
|
+++ code-server/lib/vscode/build/gulpfile.reh.js
|
|
@@ -56,6 +56,7 @@ const serverResources = [
|
|
|
|
// NLS
|
|
'out-build/nls.messages.json',
|
|
+ 'out-build/nls.keys.json', // Required to generate translations.
|
|
|
|
// Process monitor
|
|
'out-build/vs/base/node/cpuUsage.sh',
|