Archived
1
0

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:
Asher
2019-08-02 19:26:41 -05:00
parent 60ed0653bc
commit 712274d912
14 changed files with 472 additions and 244 deletions

View File

@ -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) => {

View File

@ -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.
}

View File

@ -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);

View File

@ -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
View 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;
}
});
};

View File

@ -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));