Skip to content

Commit 76663e7

Browse files
authored
Restore display language support (#34)
For coder/code-server#4598.
1 parent 53bc751 commit 76663e7

File tree

12 files changed

+164
-8
lines changed

12 files changed

+164
-8
lines changed

src/vs/base/common/platform.ts

+16
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,22 @@ if (typeof navigator === 'object' && !isElectronRenderer) {
8181
_isWeb = true;
8282
_locale = navigator.language;
8383
_language = _locale;
84+
85+
/**
86+
* Make languages work.
87+
*
88+
* @author coder
89+
*/
90+
const el = typeof document !== 'undefined' && document.getElementById('vscode-remote-nls-configuration');
91+
const rawNlsConfig = el && el.getAttribute('data-settings');
92+
if (rawNlsConfig) {
93+
try {
94+
const nlsConfig: NLSConfig = JSON.parse(rawNlsConfig);
95+
_locale = nlsConfig.locale;
96+
_translationsConfigFile = nlsConfig._translationsConfigFile;
97+
_language = nlsConfig.availableLanguages['*'] || LANGUAGE_DEFAULT;
98+
} catch (error) { /* Oh well. */ }
99+
}
84100
}
85101

86102
// Native environment

src/vs/base/parts/ipc/common/ipc.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1056,7 +1056,7 @@ export namespace ProxyChannel {
10561056

10571057
export interface ICreateServiceChannelOptions extends IProxyOptions { }
10581058

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

src/vs/code/browser/workbench/workbench-dev.html

+3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
<!-- Workbench Auth Session -->
2424
<meta id="vscode-workbench-auth-session" data-settings="{{WORKBENCH_AUTH_SESSION}}">
2525

26+
<!-- NLS Configuration -->
27+
<meta id="vscode-remote-nls-configuration" data-settings="{{NLS_CONFIGURATION}}">
28+
2629
<!-- Builtin Extensions (running out of sources) -->
2730
<meta id="vscode-workbench-builtin-extensions" data-settings="{{WORKBENCH_BUILTIN_EXTENSIONS}}">
2831

src/vs/code/browser/workbench/workbench.html

+28-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
<!-- Workbench Auth Session -->
2424
<meta id="vscode-workbench-auth-session" data-settings="{{WORKBENCH_AUTH_SESSION}}">
2525

26+
<!-- NLS Configuration -->
27+
<meta id="vscode-remote-nls-configuration" data-settings="{{NLS_CONFIGURATION}}">
28+
2629
<!-- Workbench Icon/Manifest/CSS -->
2730
<link rel="icon" href="{{BASE}}/static/resources/server/favicon-dark-support.svg" type="image/svg+xml" />
2831
<link rel="alternate icon" href="{{BASE}}/static/resources/server/favicon.svg" type="image/svg+xml" />
@@ -42,9 +45,31 @@
4245
<script src="{{BASE}}/static/out/vs/webPackagePaths.js"></script>
4346
<script>
4447
/**
45-
* Updated to use relative path.
48+
* Use relative paths and NLS configuration (based on
49+
* ../../../../bootstrap.js).
4650
* @author coder
4751
*/
52+
let nlsConfig
53+
try {
54+
nlsConfig = JSON.parse(document.getElementById("vscode-remote-nls-configuration").getAttribute("data-settings"))
55+
if (nlsConfig._resolvedLanguagePackCoreLocation) {
56+
const bundles = Object.create(null)
57+
nlsConfig.loadBundle = (bundle, _language, cb) => {
58+
const result = bundles[bundle]
59+
if (result) {
60+
return cb(undefined, result)
61+
}
62+
const path = nlsConfig._resolvedLanguagePackCoreLocation + "/" + bundle.replace(/\//g, "!") + ".nls.json"
63+
fetch(`{{BASE}}/vscode-remote-resource/?path=${encodeURIComponent(path)}`)
64+
.then((response) => response.json())
65+
.then((json) => {
66+
bundles[bundle] = json
67+
cb(undefined, json)
68+
})
69+
.catch(cb)
70+
}
71+
}
72+
} catch (error) { /* Probably fine. */ }
4873
Object.keys(self.webPackagePaths).map(function (key, index) {
4974
self.webPackagePaths[key] = new URL(`{{BASE}}/static/node_modules/${key}/${self.webPackagePaths[key]}`, window.location.href).toString();
5075
});
@@ -56,7 +81,8 @@
5681
return value;
5782
}
5883
}),
59-
paths: self.webPackagePaths
84+
paths: self.webPackagePaths,
85+
'vs/nls': nlsConfig,
6086
});
6187
</script>
6288
<script>

src/vs/platform/environment/common/environmentService.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron
106106
return URI.file(join(vscodePortable, 'argv.json'));
107107
}
108108

109-
return joinPath(this.userHome, this.productService.dataFolderName, 'argv.json');
109+
return joinPath(this.appSettingsHome, 'argv.json');
110110
}
111111

112112
@memoize

src/vs/server/@types/code-server-lib/index.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ declare global {
1818
export interface ServerParsedArgs {
1919
//#region
2020
auth?: AuthType;
21+
locale?: string
2122
//#endregion
2223

2324
port?: string;

src/vs/server/remoteExtensionHostAgentServer.ts

+15-3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { generateUuid } from 'vs/base/common/uuid';
1818
import { Promises } from 'vs/base/node/pfs';
1919
import { findFreePort } from 'vs/base/node/ports';
2020
import * as platform from 'vs/base/common/platform';
21+
import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
2122
import { PersistentProtocol, ProtocolConstants } from 'vs/base/parts/ipc/common/ipc.net';
2223
import { NodeSocket, WebSocketNodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
2324
import { ConnectionType, ConnectionTypeRequest, ErrorMessage, HandshakeMessage, IRemoteExtensionHostStartParams, ITunnelConnectionStartParams, SignRequest } from 'vs/platform/remote/common/remoteAgentConnection';
@@ -346,6 +347,13 @@ export class RemoteExtensionHostAgentServer extends Disposable {
346347
const channel = new ExtensionManagementChannel(extensionManagementService, (ctx: RemoteAgentConnectionContext) => this._getUriTransformer(ctx.remoteAuthority));
347348
this._socketServer.registerChannel('extensions', channel);
348349

350+
/**
351+
* Register localizations channel.
352+
* @author coder
353+
*/
354+
const localizationsChannel = ProxyChannel.fromService<RemoteAgentConnectionContext>(accessor.get(ILocalizationsService));
355+
this._socketServer.registerChannel('localizations', localizationsChannel);
356+
349357
// clean up deprecated extensions
350358
(extensionManagementService as ExtensionManagementService).removeDeprecatedExtensions();
351359

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

426438
const desiredPath = parsedUrl.query['path'];
427439
if (typeof desiredPath !== 'string') {

src/vs/server/remoteLanguagePacks.ts

+55
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import * as fs from 'fs';
77
import { FileAccess } from 'vs/base/common/network';
88
import * as path from 'vs/base/common/path';
9+
import { URI } from 'vs/base/common/uri';
910

1011
import * as lp from 'vs/base/node/languagePacks';
1112
import product from 'vs/platform/product/common/product';
@@ -30,6 +31,16 @@ export function getNLSConfiguration(language: string, userDataPath: string): Pro
3031
if (InternalNLSConfiguration.is(value)) {
3132
value._languagePackSupport = true;
3233
}
34+
/**
35+
* If the configuration has no results keep trying since code-server
36+
* doesn't restart when a language is installed so this result would
37+
* persist (the plugin might not be installed yet or something).
38+
*
39+
* @author coder
40+
*/
41+
if (value.locale !== 'en' && value.locale !== 'en-us' && Object.keys(value.availableLanguages).length === 0) {
42+
_cache.delete(key);
43+
}
3344
return value;
3445
});
3546
_cache.set(key, result);
@@ -44,3 +55,47 @@ export namespace InternalNLSConfiguration {
4455
return candidate && typeof candidate._languagePackId === 'string';
4556
}
4657
}
58+
59+
/**
60+
* @author coder
61+
*/
62+
export const getLocaleFromConfig = async (argvResource: URI): Promise<string> => {
63+
try {
64+
const content = stripComments(await fs.promises.readFile(argvResource.fsPath, 'utf8'));
65+
return JSON.parse(content).locale;
66+
} catch (error) {
67+
if (error.code !== "ENOENT") {
68+
console.warn(error)
69+
}
70+
return 'en';
71+
}
72+
};
73+
74+
/**
75+
* Taken from src/main.js.
76+
*
77+
* @author coder
78+
*/
79+
const stripComments = (content: string): string => {
80+
const regexp = /('(?:[^\\']*(?:\\.)?)*')|('(?:[^\\']*(?:\\.)?)*')|(\/\*(?:\r?\n|.)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))/g;
81+
82+
return content.replace(regexp, (match, _m1, _m2, m3, m4) => {
83+
// Only one of m1, m2, m3, m4 matches
84+
if (m3) {
85+
// A block comment. Replace with nothing
86+
return '';
87+
} else if (m4) {
88+
// A line comment. If it ends in \r?\n then keep it.
89+
const length_1 = m4.length;
90+
if (length_1 > 2 && m4[length_1 - 1] === '\n') {
91+
return m4[length_1 - 2] === '\r' ? '\r\n' : '\n';
92+
}
93+
else {
94+
return '';
95+
}
96+
} else {
97+
// We match a string
98+
return match;
99+
}
100+
});
101+
};

src/vs/server/serverEnvironmentService.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ import { AuthType } from 'vs/base/common/auth';
1515
export const serverOptions: OptionDescriptions<ServerParsedArgs> = {
1616
//#region @coder
1717
'auth': { type: 'string' },
18-
'port': { type: 'string' },
18+
'locale': { type: 'string' },
1919
//#endregion
2020

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

7779
port?: string;

src/vs/server/webClientServer.ts

+5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { isLinux } from 'vs/base/common/platform';
1515
import { URI, UriComponents } from 'vs/base/common/uri';
1616
import { createRemoteURITransformer } from 'vs/server/remoteUriTransformer';
1717
import { ILogService } from 'vs/platform/log/common/log';
18+
import { getLocaleFromConfig, getNLSConfiguration } from 'vs/server/remoteLanguagePacks';
1819
import { IServerEnvironmentService } from 'vs/server/serverEnvironmentService';
1920
import { extname, dirname, join, normalize } from 'vs/base/common/path';
2021
import { FileAccess } from 'vs/base/common/network';
@@ -297,6 +298,9 @@ export class WebClientServer {
297298
scopes: [['user:email'], ['repo']]
298299
} : undefined;
299300

301+
const locale = this._environmentService.args.locale || await getLocaleFromConfig(this._environmentService.argvResource);
302+
const nlsConfiguration = await getNLSConfiguration(locale, this._environmentService.userDataPath)
303+
300304
const base = relativeRoot(getOriginalUrl(req))
301305
const vscodeBase = relativePath(getOriginalUrl(req))
302306
const data = (await util.promisify(fs.readFile)(filePath)).toString()
@@ -332,6 +336,7 @@ export class WebClientServer {
332336
userDataPath: this._environmentService.userDataPath,
333337
settingsSyncOptions: !this._environmentService.isBuilt && this._environmentService.args['enable-sync'] ? { enabled: true } : undefined,
334338
})))
339+
.replace(/{{NLS_CONFIGURATION}}/g, () => escapeAttribute(JSON.stringify(nlsConfiguration)))
335340
.replace(/{{CLIENT_BACKGROUND_COLOR}}/g, () => backgroundColor)
336341
.replace(/{{CLIENT_FOREGROUND_COLOR}}/g, () => foregroundColor)
337342
.replace('{{WORKBENCH_AUTH_SESSION}}', () => authSessionInfo ? escapeAttribute(JSON.stringify(authSessionInfo)) : '')
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Coder Technologies. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
7+
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
8+
import { ILocalizationsService } from 'vs/platform/localizations/common/localizations';
9+
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
10+
11+
/**
12+
* Add localizations service for the browser.
13+
* @author coder
14+
*/
15+
16+
// @ts-ignore: interface is implemented via proxy
17+
export class LocalizationsService implements ILocalizationsService {
18+
19+
declare readonly _serviceBrand: undefined;
20+
21+
constructor(
22+
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
23+
) {
24+
return ProxyChannel.toService<ILocalizationsService>(remoteAgentService.getConnection()!.getChannel('localizations'));
25+
}
26+
}
27+
28+
registerSingleton(ILocalizationsService, LocalizationsService, true);

src/vs/workbench/workbench.web.main.ts

+8
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,14 @@ registerSingleton(ICustomEndpointTelemetryService, NullEndpointTelemetryService,
110110

111111
//#region --- workbench contributions
112112

113+
/**
114+
* Support localizations in web.
115+
* @author code
116+
*/
117+
// Localizations
118+
import 'vs/workbench/contrib/localizations/browser/localizations.contribution';
119+
import 'vs/workbench/services/localizations/browser/localizationsService';
120+
113121
// Output
114122
import 'vs/workbench/contrib/output/common/outputChannelModelService';
115123

0 commit comments

Comments
 (0)