Groundwork for language support
- Implement the localization service. - Use the proper build process which generates the require JSON files. - Implement getting the locale and language configuration.
This commit is contained in:
@ -19,6 +19,7 @@ import { ITelemetryService } from "vs/platform/telemetry/common/telemetry";
|
||||
import { ExtensionScanner, ExtensionScannerInput } from "vs/workbench/services/extensions/node/extensionPoints";
|
||||
import { DiskFileSystemProvider } from "vs/workbench/services/files/node/diskFileSystemProvider";
|
||||
|
||||
import { getTranslations } from "vs/server/src/nls";
|
||||
import { getUriTransformer } from "vs/server/src/util";
|
||||
|
||||
/**
|
||||
@ -214,7 +215,7 @@ export class ExtensionEnvironmentChannel implements IServerChannel {
|
||||
}
|
||||
|
||||
private async scanExtensions(locale: string): Promise<IExtensionDescription[]> {
|
||||
const translations = {}; // TODO: translations
|
||||
const translations = await getTranslations(locale, this.environment.userDataPath);
|
||||
|
||||
const scanMultiple = (isBuiltin: boolean, isUnderDevelopment: boolean, paths: string[]): Promise<IExtensionDescription[][]> => {
|
||||
return Promise.all(paths.map((path) => {
|
||||
|
@ -10,6 +10,7 @@ import product from "vs/platform/product/node/product";
|
||||
import { MainServer } from "vs/server/src/server";
|
||||
import { enableExtensionTars } from "vs/server/src/tar";
|
||||
import { AuthType, buildAllowedMessage, generateCertificate, generatePassword, localRequire, open, unpackExecutables } from "vs/server/src/util";
|
||||
import { main as vsCli } from "vs/code/node/cliProcessMain";
|
||||
|
||||
const { logger } = localRequire<typeof import("@coder/logger/out/index")>("@coder/logger/out/index");
|
||||
|
||||
@ -65,10 +66,6 @@ options.push({ id: "socket", type: "string", cat: "o", description: "Listen on a
|
||||
|
||||
options.push(last);
|
||||
|
||||
interface IMainCli {
|
||||
main: (argv: ParsedArgs) => Promise<void>;
|
||||
}
|
||||
|
||||
const main = async (): Promise<void | void[]> => {
|
||||
const args = validatePaths(parseMainProcessArgv(process.argv)) as Args;
|
||||
["extra-extensions-dir", "extra-builtin-extensions-dir"].forEach((key) => {
|
||||
@ -108,8 +105,7 @@ const main = async (): Promise<void | void[]> => {
|
||||
};
|
||||
|
||||
if (shouldSpawnCliProcess()) {
|
||||
const cli = await new Promise<IMainCli>((c, e) => require(["vs/code/node/cliProcessMain"], c, e));
|
||||
await cli.main(args);
|
||||
await vsCli(args);
|
||||
return process.exit(0); // There is a WriteStream instance keeping it open.
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,14 @@
|
||||
import { coderApi, vscodeApi } from "vs/server/src/api";
|
||||
import "vs/css!./media/firefox";
|
||||
import { ServiceCollection } from "vs/platform/instantiation/common/serviceCollection";
|
||||
|
||||
import { coderApi, vscodeApi } from "vs/server/src/api";
|
||||
|
||||
import "vs/css!./media/firefox";
|
||||
|
||||
/**
|
||||
* This is called by vs/workbench/browser/web.main.ts after the workbench has
|
||||
* been initialized so we can initialize our own client-side code.
|
||||
*/
|
||||
export const initialize = (services: ServiceCollection): void => {
|
||||
export const initialize = async (services: ServiceCollection): Promise<void> => {
|
||||
const target = window as any;
|
||||
target.ide = coderApi(services);
|
||||
target.vscode = vscodeApi(services);
|
||||
|
@ -5,9 +5,11 @@ import { VSBuffer } from "vs/base/common/buffer";
|
||||
import { Emitter } from "vs/base/common/event";
|
||||
import { ISocket } from "vs/base/parts/ipc/common/ipc.net";
|
||||
import { NodeSocket } from "vs/base/parts/ipc/node/ipc.net";
|
||||
import { IEnvironmentService } from "vs/platform/environment/common/environment";
|
||||
import { ILogService } from "vs/platform/log/common/log";
|
||||
import { IExtHostReadyMessage } from "vs/workbench/services/extensions/common/extensionHostProtocol";
|
||||
|
||||
import { getNlsConfiguration } from "vs/server/src/nls";
|
||||
import { Protocol } from "vs/server/src/protocol";
|
||||
import { uriTransformerPath } from "vs/server/src/util";
|
||||
|
||||
@ -57,19 +59,25 @@ export class ManagementConnection extends Connection {
|
||||
}
|
||||
|
||||
export class ExtensionHostConnection extends Connection {
|
||||
private process: cp.ChildProcess;
|
||||
private process?: cp.ChildProcess;
|
||||
|
||||
public constructor(protocol: Protocol, buffer: VSBuffer, private readonly log: ILogService) {
|
||||
public constructor(
|
||||
locale:string, protocol: Protocol, buffer: VSBuffer,
|
||||
private readonly log: ILogService,
|
||||
private readonly environment: IEnvironmentService,
|
||||
) {
|
||||
super(protocol);
|
||||
this.protocol.dispose();
|
||||
this.process = this.spawn(buffer);
|
||||
this.spawn(locale, buffer).then((p) => this.process = p);
|
||||
this.protocol.getUnderlyingSocket().pause();
|
||||
}
|
||||
|
||||
protected dispose(): void {
|
||||
if (!this.disposed) {
|
||||
this.disposed = true;
|
||||
this.process.kill();
|
||||
if (this.process) {
|
||||
this.process.kill();
|
||||
}
|
||||
this.protocol.getSocket().end();
|
||||
this._onClose.fire();
|
||||
}
|
||||
@ -85,14 +93,15 @@ export class ExtensionHostConnection extends Connection {
|
||||
private sendInitMessage(buffer: VSBuffer): void {
|
||||
const socket = this.protocol.getUnderlyingSocket();
|
||||
socket.pause();
|
||||
this.process.send({
|
||||
this.process!.send({ // Process must be set at this point.
|
||||
type: "VSCODE_EXTHOST_IPC_SOCKET",
|
||||
initialDataChunk: (buffer.buffer as Buffer).toString("base64"),
|
||||
skipWebSocketFrames: this.protocol.getSocket() instanceof NodeSocket,
|
||||
}, socket);
|
||||
}
|
||||
|
||||
private spawn(buffer: VSBuffer): cp.ChildProcess {
|
||||
private async spawn(locale: string, buffer: VSBuffer): Promise<cp.ChildProcess> {
|
||||
const config = await getNlsConfiguration(locale, this.environment.userDataPath);
|
||||
const proc = cp.fork(
|
||||
getPathFromAmdModule(require, "bootstrap-fork"),
|
||||
[ "--type=extensionHost", `--uriTransformerPath=${uriTransformerPath}` ],
|
||||
@ -105,6 +114,7 @@ export class ExtensionHostConnection extends Connection {
|
||||
VSCODE_EXTHOST_WILL_SEND_SOCKET: "true",
|
||||
VSCODE_HANDLES_UNCAUGHT_ERRORS: "true",
|
||||
VSCODE_LOG_STACK: "false",
|
||||
VSCODE_NLS_CONFIG: JSON.stringify(config),
|
||||
},
|
||||
silent: true,
|
||||
},
|
||||
|
81
src/nls.ts
Normal file
81
src/nls.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import * as path from "path";
|
||||
import * as fs from "fs";
|
||||
import * as util from "util";
|
||||
|
||||
import { getPathFromAmdModule } from "vs/base/common/amd";
|
||||
import * as lp from "vs/base/node/languagePacks";
|
||||
import product from "vs/platform/product/node/product";
|
||||
import { Translations } from "vs/workbench/services/extensions/common/extensionPoints";
|
||||
|
||||
const configurations = new Map<string, Promise<lp.NLSConfiguration>>();
|
||||
const metadataPath = path.join(getPathFromAmdModule(require, ""), "nls.metadata.json");
|
||||
|
||||
export const isInternalConfiguration = (config: lp.NLSConfiguration): config is lp.InternalNLSConfiguration => {
|
||||
return config && !!(<lp.InternalNLSConfiguration>config)._languagePackId;
|
||||
};
|
||||
|
||||
const DefaultConfiguration = {
|
||||
locale: "en",
|
||||
availableLanguages: {},
|
||||
};
|
||||
|
||||
export const getNlsConfiguration = async (locale: string, userDataPath: string): Promise<lp.NLSConfiguration> => {
|
||||
const id = `${locale}: ${userDataPath}`;
|
||||
if (!configurations.has(id)) {
|
||||
configurations.set(id, new Promise(async (resolve) => {
|
||||
const config = product.commit && await util.promisify(fs.exists)(metadataPath)
|
||||
? await lp.getNLSConfiguration(product.commit, userDataPath, metadataPath, locale)
|
||||
: DefaultConfiguration;
|
||||
if (isInternalConfiguration(config)) {
|
||||
config._languagePackSupport = true;
|
||||
}
|
||||
resolve(config);
|
||||
}));
|
||||
}
|
||||
return configurations.get(id)!;
|
||||
};
|
||||
|
||||
export const getTranslations = async (locale: string, userDataPath: string): Promise<Translations> => {
|
||||
const config = await getNlsConfiguration(locale, userDataPath);
|
||||
if (isInternalConfiguration(config)) {
|
||||
try {
|
||||
return JSON.parse(await util.promisify(fs.readFile)(config._translationsConfigFile, "utf8"));
|
||||
} catch (error) { /* Nothing yet. */}
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
export const getLocaleFromConfig = async (userDataPath: string): Promise<string> => {
|
||||
let locale = "en";
|
||||
try {
|
||||
const localeConfigUri = path.join(userDataPath, "User/locale.json");
|
||||
const content = stripComments(await util.promisify(fs.readFile)(localeConfigUri, "utf8"));
|
||||
locale = JSON.parse(content).locale;
|
||||
} catch (error) { /* Ignore. */ }
|
||||
return locale;
|
||||
};
|
||||
|
||||
// Taken from src/main.js in the main VS Code source.
|
||||
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;
|
||||
}
|
||||
});
|
||||
};
|
@ -14,8 +14,9 @@ import { sanitizeFilePath } from "vs/base/common/extpath";
|
||||
import { UriComponents, URI } from "vs/base/common/uri";
|
||||
import { generateUuid } from "vs/base/common/uuid";
|
||||
import { getMachineId } from 'vs/base/node/id';
|
||||
import { IPCServer, ClientConnectionEvent, StaticRouter } from "vs/base/parts/ipc/common/ipc";
|
||||
import { NLSConfiguration } from "vs/base/node/languagePacks";
|
||||
import { mkdirp, rimraf } from "vs/base/node/pfs";
|
||||
import { IPCServer, ClientConnectionEvent, StaticRouter } from "vs/base/parts/ipc/common/ipc";
|
||||
import { LogsDataCleaner } from "vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner";
|
||||
import { IConfigurationService } from "vs/platform/configuration/common/configuration";
|
||||
import { ConfigurationService } from "vs/platform/configuration/node/configurationService";
|
||||
@ -33,6 +34,7 @@ import { InstantiationService } from "vs/platform/instantiation/common/instantia
|
||||
import { ServiceCollection } from "vs/platform/instantiation/common/serviceCollection";
|
||||
import { ILocalizationsService } from "vs/platform/localizations/common/localizations";
|
||||
import { LocalizationsService } from "vs/platform/localizations/node/localizations";
|
||||
import { LocalizationsChannel } from "vs/platform/localizations/node/localizationsIpc";
|
||||
import { getLogLevel, ILogService } from "vs/platform/log/common/log";
|
||||
import { LogLevelSetterChannel } from "vs/platform/log/common/logIpc";
|
||||
import { SpdLogService } from "vs/platform/log/node/spdlogService";
|
||||
@ -56,6 +58,7 @@ import { IWorkbenchConstructionOptions } from "vs/workbench/workbench.web.api";
|
||||
import { Connection, ManagementConnection, ExtensionHostConnection } from "vs/server/src/connection";
|
||||
import { ExtensionEnvironmentChannel, FileProviderChannel , } from "vs/server/src/channel";
|
||||
import { TelemetryClient } from "vs/server/src/insights";
|
||||
import { getNlsConfiguration, getLocaleFromConfig } from "vs/server/src/nls";
|
||||
import { Protocol } from "vs/server/src/protocol";
|
||||
import { AuthType, getMediaMime, getUriTransformer, localRequire, tmpdir } from "vs/server/src/util";
|
||||
|
||||
@ -74,6 +77,7 @@ export interface Options {
|
||||
REMOTE_USER_DATA_URI: UriComponents | URI;
|
||||
PRODUCT_CONFIGURATION: IProductConfiguration | null;
|
||||
CONNECTION_AUTH_TOKEN: string;
|
||||
NLS_CONFIGURATION: NLSConfiguration;
|
||||
}
|
||||
|
||||
export interface Response {
|
||||
@ -456,7 +460,8 @@ export class MainServer extends Server {
|
||||
util.promisify(fs.readFile)(filePath, "utf8"),
|
||||
this.servicesPromise,
|
||||
]);
|
||||
|
||||
const environment = this.services.get(IEnvironmentService) as IEnvironmentService;
|
||||
const locale = environment.args.locale || await getLocaleFromConfig(environment.userDataPath);
|
||||
const webviewEndpoint = this.address(request) + "/webview/";
|
||||
const cwd = process.env.VSCODE_CWD || process.cwd();
|
||||
const workspacePath = parsedUrl.query.workspace as string | undefined;
|
||||
@ -479,6 +484,7 @@ export class MainServer extends Server {
|
||||
),
|
||||
PRODUCT_CONFIGURATION: product,
|
||||
CONNECTION_AUTH_TOKEN: "",
|
||||
NLS_CONFIGURATION: await getNlsConfiguration(locale, environment.userDataPath),
|
||||
};
|
||||
|
||||
Object.keys(options).forEach((key) => {
|
||||
@ -528,7 +534,10 @@ export class MainServer extends Server {
|
||||
} else {
|
||||
const buffer = protocol.readEntireBuffer();
|
||||
connection = new ExtensionHostConnection(
|
||||
protocol, buffer, this.services.get(ILogService) as ILogService,
|
||||
message.args ? message.args.language : "en",
|
||||
protocol, buffer,
|
||||
this.services.get(ILogService) as ILogService,
|
||||
this.services.get(IEnvironmentService) as IEnvironmentService,
|
||||
);
|
||||
}
|
||||
connections.set(token, connection);
|
||||
@ -576,7 +585,9 @@ export class MainServer extends Server {
|
||||
|
||||
await new Promise((resolve) => {
|
||||
const instantiationService = new InstantiationService(this.services);
|
||||
this.services.set(ILocalizationsService, instantiationService.createInstance(LocalizationsService));
|
||||
const localizationService = instantiationService.createInstance(LocalizationsService);
|
||||
this.services.set(ILocalizationsService, localizationService);
|
||||
this.ipc.registerChannel("localizations", new LocalizationsChannel(localizationService));
|
||||
instantiationService.invokeFunction(() => {
|
||||
instantiationService.createInstance(LogsDataCleaner);
|
||||
this.ipc.registerChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME, new FileProviderChannel(environmentService, logService));
|
||||
|
Reference in New Issue
Block a user