Skip to content

Restore display language support #34

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/vs/base/common/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,22 @@ if (typeof navigator === 'object' && !isElectronRenderer) {
_isWeb = true;
_locale = navigator.language;
_language = _locale;

/**
* Make languages work.
*
* @author coder
*/
const el = typeof document !== 'undefined' && document.getElementById('vscode-remote-nls-configuration');
const rawNlsConfig = el && el.getAttribute('data-settings');
if (rawNlsConfig) {
try {
const nlsConfig: NLSConfig = JSON.parse(rawNlsConfig);
_locale = nlsConfig.locale;
_translationsConfigFile = nlsConfig._translationsConfigFile;
_language = nlsConfig.availableLanguages['*'] || LANGUAGE_DEFAULT;
} catch (error) { /* Oh well. */ }
}
}

// Native environment
Expand Down
2 changes: 1 addition & 1 deletion src/vs/base/parts/ipc/common/ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1056,7 +1056,7 @@ export namespace ProxyChannel {

export interface ICreateServiceChannelOptions extends IProxyOptions { }

export function fromService(service: unknown, options?: ICreateServiceChannelOptions): IServerChannel {
export function fromService<TContext>(service: unknown, options?: ICreateServiceChannelOptions): IServerChannel<TContext> {
const handler = service as { [key: string]: unknown };
const disableMarshalling = options && options.disableMarshalling;

Expand Down
3 changes: 3 additions & 0 deletions src/vs/code/browser/workbench/workbench-dev.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
<!-- Workbench Auth Session -->
<meta id="vscode-workbench-auth-session" data-settings="{{WORKBENCH_AUTH_SESSION}}">

<!-- NLS Configuration -->
<meta id="vscode-remote-nls-configuration" data-settings="{{NLS_CONFIGURATION}}">

<!-- Builtin Extensions (running out of sources) -->
<meta id="vscode-workbench-builtin-extensions" data-settings="{{WORKBENCH_BUILTIN_EXTENSIONS}}">

Expand Down
30 changes: 28 additions & 2 deletions src/vs/code/browser/workbench/workbench.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
<!-- Workbench Auth Session -->
<meta id="vscode-workbench-auth-session" data-settings="{{WORKBENCH_AUTH_SESSION}}">

<!-- NLS Configuration -->
<meta id="vscode-remote-nls-configuration" data-settings="{{NLS_CONFIGURATION}}">

<!-- Workbench Icon/Manifest/CSS -->
<link rel="icon" href="{{BASE}}/static/resources/server/favicon-dark-support.svg" type="image/svg+xml" />
<link rel="alternate icon" href="{{BASE}}/static/resources/server/favicon.svg" type="image/svg+xml" />
Expand All @@ -42,9 +45,31 @@
<script src="{{BASE}}/static/out/vs/webPackagePaths.js"></script>
<script>
/**
* Updated to use relative path.
* Use relative paths and NLS configuration (based on
* ../../../../bootstrap.js).
* @author coder
*/
let nlsConfig
try {
nlsConfig = JSON.parse(document.getElementById("vscode-remote-nls-configuration").getAttribute("data-settings"))
if (nlsConfig._resolvedLanguagePackCoreLocation) {
const bundles = Object.create(null)
nlsConfig.loadBundle = (bundle, _language, cb) => {
const result = bundles[bundle]
if (result) {
return cb(undefined, result)
}
const path = nlsConfig._resolvedLanguagePackCoreLocation + "/" + bundle.replace(/\//g, "!") + ".nls.json"
fetch(`{{BASE}}/vscode-remote-resource/?path=${encodeURIComponent(path)}`)
.then((response) => response.json())
.then((json) => {
bundles[bundle] = json
cb(undefined, json)
})
.catch(cb)
}
}
} catch (error) { /* Probably fine. */ }
Object.keys(self.webPackagePaths).map(function (key, index) {
self.webPackagePaths[key] = new URL(`{{BASE}}/static/node_modules/${key}/${self.webPackagePaths[key]}`, window.location.href).toString();
});
Expand All @@ -56,7 +81,8 @@
return value;
}
}),
paths: self.webPackagePaths
paths: self.webPackagePaths,
'vs/nls': nlsConfig,
});
</script>
<script>
Expand Down
2 changes: 1 addition & 1 deletion src/vs/platform/environment/common/environmentService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron
return URI.file(join(vscodePortable, 'argv.json'));
}

return joinPath(this.userHome, this.productService.dataFolderName, 'argv.json');
return joinPath(this.appSettingsHome, 'argv.json');
}

@memoize
Expand Down
1 change: 1 addition & 0 deletions src/vs/server/@types/code-server-lib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ declare global {
export interface ServerParsedArgs {
//#region
auth?: AuthType;
locale?: string
//#endregion

port?: string;
Expand Down
18 changes: 15 additions & 3 deletions src/vs/server/remoteExtensionHostAgentServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { generateUuid } from 'vs/base/common/uuid';
import { Promises } from 'vs/base/node/pfs';
import { findFreePort } from 'vs/base/node/ports';
import * as platform from 'vs/base/common/platform';
import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
import { PersistentProtocol, ProtocolConstants } from 'vs/base/parts/ipc/common/ipc.net';
import { NodeSocket, WebSocketNodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
import { ConnectionType, ConnectionTypeRequest, ErrorMessage, HandshakeMessage, IRemoteExtensionHostStartParams, ITunnelConnectionStartParams, SignRequest } from 'vs/platform/remote/common/remoteAgentConnection';
Expand Down Expand Up @@ -346,6 +347,13 @@ export class RemoteExtensionHostAgentServer extends Disposable {
const channel = new ExtensionManagementChannel(extensionManagementService, (ctx: RemoteAgentConnectionContext) => this._getUriTransformer(ctx.remoteAuthority));
this._socketServer.registerChannel('extensions', channel);

/**
* Register localizations channel.
* @author coder
*/
const localizationsChannel = ProxyChannel.fromService<RemoteAgentConnectionContext>(accessor.get(ILocalizationsService));
this._socketServer.registerChannel('localizations', localizationsChannel);

// clean up deprecated extensions
(extensionManagementService as ExtensionManagementService).removeDeprecatedExtensions();

Expand Down Expand Up @@ -419,9 +427,13 @@ export class RemoteExtensionHostAgentServer extends Disposable {
if (parsedPath.base === 'vscode-remote-resource') {
// Handle HTTP requests for resources rendered in the rich client (images, fonts, etc.)
// These resources could be files shipped with extensions or even workspace files.
if (parsedUrl.query['tkn'] !== this._connectionToken) {
return this._webClientServer.serveError(req, res, 403, `Forbidden.`);
}
/**
* Disable for now since we have our own auth.
* @author coder
*/
// if (parsedUrl.query['tkn'] !== this._connectionToken) {
// return this._webClientServer.serveError(req, res, 403, `Forbidden.`);
// }

const desiredPath = parsedUrl.query['path'];
if (typeof desiredPath !== 'string') {
Expand Down
55 changes: 55 additions & 0 deletions src/vs/server/remoteLanguagePacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import * as fs from 'fs';
import { FileAccess } from 'vs/base/common/network';
import * as path from 'vs/base/common/path';
import { URI } from 'vs/base/common/uri';

import * as lp from 'vs/base/node/languagePacks';
import product from 'vs/platform/product/common/product';
Expand All @@ -30,6 +31,16 @@ export function getNLSConfiguration(language: string, userDataPath: string): Pro
if (InternalNLSConfiguration.is(value)) {
value._languagePackSupport = true;
}
/**
* If the configuration has no results keep trying since code-server
* doesn't restart when a language is installed so this result would
* persist (the plugin might not be installed yet or something).
*
* @author coder
*/
if (value.locale !== 'en' && value.locale !== 'en-us' && Object.keys(value.availableLanguages).length === 0) {
_cache.delete(key);
}
return value;
});
_cache.set(key, result);
Expand All @@ -44,3 +55,47 @@ export namespace InternalNLSConfiguration {
return candidate && typeof candidate._languagePackId === 'string';
}
}

/**
* @author coder
*/
export const getLocaleFromConfig = async (argvResource: URI): Promise<string> => {
try {
const content = stripComments(await fs.promises.readFile(argvResource.fsPath, 'utf8'));
return JSON.parse(content).locale;
} catch (error) {
if (error.code !== "ENOENT") {
console.warn(error)
}
return 'en';
}
};

/**
* Taken from src/main.js.
*
* @author coder
*/
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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What exactly is this? I know you said taken from src/main so I guess I'll have to look there when reviewing this from a computer.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the file can contain comments so they remove them since JSON.parse will choke otherwise.

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;
}
});
};
4 changes: 3 additions & 1 deletion src/vs/server/serverEnvironmentService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ import { AuthType } from 'vs/base/common/auth';
export const serverOptions: OptionDescriptions<ServerParsedArgs> = {
//#region @coder
'auth': { type: 'string' },
'port': { type: 'string' },
'locale': { type: 'string' },
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean you can pass locale as a command line arg?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, gonna add it back on the code-server side.

//#endregion

'port': { type: 'string' },
'pick-port': { type: 'string' },
'connectionToken': { type: 'string' }, // deprecated in favor of `--connection-token`
'connection-token': { type: 'string', description: nls.localize('connection-token', "A secret that must be included by the web client with all requests.") },
Expand Down Expand Up @@ -72,6 +73,7 @@ export const serverOptions: OptionDescriptions<ServerParsedArgs> = {
export interface ServerParsedArgs {
//#region
auth?: AuthType;
'locale'?: string
//#endregion

port?: string;
Expand Down
5 changes: 5 additions & 0 deletions src/vs/server/webClientServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { isLinux } from 'vs/base/common/platform';
import { URI, UriComponents } from 'vs/base/common/uri';
import { createRemoteURITransformer } from 'vs/server/remoteUriTransformer';
import { ILogService } from 'vs/platform/log/common/log';
import { getLocaleFromConfig, getNLSConfiguration } from 'vs/server/remoteLanguagePacks';
import { IServerEnvironmentService } from 'vs/server/serverEnvironmentService';
import { extname, dirname, join, normalize } from 'vs/base/common/path';
import { FileAccess } from 'vs/base/common/network';
Expand Down Expand Up @@ -297,6 +298,9 @@ export class WebClientServer {
scopes: [['user:email'], ['repo']]
} : undefined;

const locale = this._environmentService.args.locale || await getLocaleFromConfig(this._environmentService.argvResource);
const nlsConfiguration = await getNLSConfiguration(locale, this._environmentService.userDataPath)

const base = relativeRoot(getOriginalUrl(req))
const vscodeBase = relativePath(getOriginalUrl(req))
const data = (await util.promisify(fs.readFile)(filePath)).toString()
Expand Down Expand Up @@ -332,6 +336,7 @@ export class WebClientServer {
userDataPath: this._environmentService.userDataPath,
settingsSyncOptions: !this._environmentService.isBuilt && this._environmentService.args['enable-sync'] ? { enabled: true } : undefined,
})))
.replace(/{{NLS_CONFIGURATION}}/g, () => escapeAttribute(JSON.stringify(nlsConfiguration)))
.replace(/{{CLIENT_BACKGROUND_COLOR}}/g, () => backgroundColor)
.replace(/{{CLIENT_FOREGROUND_COLOR}}/g, () => foregroundColor)
.replace('{{WORKBENCH_AUTH_SESSION}}', () => authSessionInfo ? escapeAttribute(JSON.stringify(authSessionInfo)) : '')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Coder Technologies. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { ILocalizationsService } from 'vs/platform/localizations/common/localizations';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';

/**
* Add localizations service for the browser.
* @author coder
*/

// @ts-ignore: interface is implemented via proxy
export class LocalizationsService implements ILocalizationsService {

declare readonly _serviceBrand: undefined;

constructor(
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
) {
return ProxyChannel.toService<ILocalizationsService>(remoteAgentService.getConnection()!.getChannel('localizations'));
}
}

registerSingleton(ILocalizationsService, LocalizationsService, true);
8 changes: 8 additions & 0 deletions src/vs/workbench/workbench.web.main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,14 @@ registerSingleton(ICustomEndpointTelemetryService, NullEndpointTelemetryService,

//#region --- workbench contributions

/**
* Support localizations in web.
* @author code
*/
// Localizations
import 'vs/workbench/contrib/localizations/browser/localizations.contribution';
import 'vs/workbench/services/localizations/browser/localizationsService';

// Output
import 'vs/workbench/contrib/output/common/outputChannelModelService';

Expand Down