2022-03-22 21:07:14 +01:00
|
|
|
Add display language support
|
|
|
|
|
2022-06-21 23:51:46 +02:00
|
|
|
We can remove this once upstream supports all language packs.
|
|
|
|
|
|
|
|
1. Proxies language packs to the service on the backend.
|
|
|
|
2. NLS configuration is embedded into the HTML for the browser to pick up. This
|
|
|
|
code to generate this configuration is copied from the native portion.
|
2022-08-17 03:26:19 +02:00
|
|
|
3. Remove configuredLocale since we have our own thing.
|
2022-06-21 23:51:46 +02:00
|
|
|
4. Move the argv.json file to the server instead of in-browser storage. This is
|
|
|
|
where the current locale is stored and currently the server needs to be able
|
|
|
|
to read it.
|
|
|
|
5. Add the locale flag.
|
2022-08-17 03:26:19 +02:00
|
|
|
6. Remove the redundant locale verification. It does the same as the existing
|
|
|
|
one but is worse because it does not handle non-existent or empty files.
|
|
|
|
7. Replace some caching and Node requires because code-server does not restart
|
|
|
|
when changing the language unlike native Code.
|
2022-03-22 21:07:14 +01:00
|
|
|
|
|
|
|
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
|
2022-11-09 23:10:03 +01:00
|
|
|
@@ -216,6 +216,9 @@ export async function setupServerService
|
2022-03-22 21:07:14 +01:00
|
|
|
const channel = new ExtensionManagementChannel(extensionManagementService, (ctx: RemoteAgentConnectionContext) => getUriTransformer(ctx.remoteAuthority));
|
|
|
|
socketServer.registerChannel('extensions', channel);
|
|
|
|
|
2022-06-21 23:51:46 +02:00
|
|
|
+ const languagePackChannel = ProxyChannel.fromService<RemoteAgentConnectionContext>(accessor.get(ILanguagePackService));
|
|
|
|
+ socketServer.registerChannel('languagePacks', languagePackChannel);
|
2022-03-22 21:07:14 +01:00
|
|
|
+
|
|
|
|
const encryptionChannel = ProxyChannel.fromService<RemoteAgentConnectionContext>(accessor.get(IEncryptionMainService));
|
|
|
|
socketServer.registerChannel('encryption', encryptionChannel);
|
|
|
|
|
|
|
|
Index: code-server/lib/vscode/src/vs/base/common/platform.ts
|
|
|
|
===================================================================
|
|
|
|
--- code-server.orig/lib/vscode/src/vs/base/common/platform.ts
|
|
|
|
+++ code-server/lib/vscode/src/vs/base/common/platform.ts
|
2022-08-17 03:26:19 +02:00
|
|
|
@@ -2,8 +2,6 @@
|
|
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
-import * as nls from 'vs/nls';
|
|
|
|
-
|
2022-10-18 01:30:39 +02:00
|
|
|
export const LANGUAGE_DEFAULT = 'en';
|
2022-08-17 03:26:19 +02:00
|
|
|
|
|
|
|
let _isWindows = false;
|
2022-10-18 01:30:39 +02:00
|
|
|
@@ -83,17 +81,19 @@ if (typeof navigator === 'object' && !is
|
|
|
|
_isMobile = _userAgent?.indexOf('Mobi') >= 0;
|
2022-03-22 21:07:14 +01:00
|
|
|
_isWeb = true;
|
2022-08-17 03:26:19 +02:00
|
|
|
|
|
|
|
- const configuredLocale = nls.getConfiguredDefaultLocale(
|
|
|
|
- // This call _must_ be done in the file that calls `nls.getConfiguredDefaultLocale`
|
|
|
|
- // to ensure that the NLS AMD Loader plugin has been loaded and configured.
|
|
|
|
- // This is because the loader plugin decides what the default locale is based on
|
|
|
|
- // how it's able to resolve the strings.
|
|
|
|
- nls.localize({ key: 'ensureLoaderPluginIsLoaded', comment: ['{Locked}'] }, '_')
|
|
|
|
- );
|
|
|
|
-
|
|
|
|
- _locale = configuredLocale || LANGUAGE_DEFAULT;
|
2022-06-21 23:51:46 +02:00
|
|
|
+ _locale = LANGUAGE_DEFAULT;
|
2022-08-17 03:26:19 +02:00
|
|
|
|
2022-03-22 21:07:14 +01:00
|
|
|
_language = _locale;
|
|
|
|
+ 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
|
|
|
|
Index: code-server/lib/vscode/src/vs/code/browser/workbench/workbench.html
|
|
|
|
===================================================================
|
|
|
|
--- code-server.orig/lib/vscode/src/vs/code/browser/workbench/workbench.html
|
|
|
|
+++ code-server/lib/vscode/src/vs/code/browser/workbench/workbench.html
|
|
|
|
@@ -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/src/browser/media/favicon-dark-support.svg" />
|
2022-06-21 23:51:46 +02:00
|
|
|
<link rel="alternate icon" href="{{BASE}}/_static/src/browser/media/favicon.ico" type="image/x-icon" />
|
2022-08-17 03:26:19 +02:00
|
|
|
@@ -46,15 +49,26 @@
|
|
|
|
// Set up nls if the user is not using the default language (English)
|
2022-06-21 23:51:46 +02:00
|
|
|
const nlsConfig = {};
|
2022-08-17 03:26:19 +02:00
|
|
|
const locale = window.localStorage.getItem('vscode.nls.locale') || navigator.language;
|
2022-06-21 23:51:46 +02:00
|
|
|
- if (!locale.startsWith('en')) {
|
|
|
|
- nlsConfig['vs/nls'] = {
|
|
|
|
- availableLanguages: {
|
|
|
|
- '*': locale
|
|
|
|
- },
|
2022-08-17 03:26:19 +02:00
|
|
|
- translationServiceUrl: '{{WORKBENCH_NLS_BASE_URL}}'
|
2022-06-21 23:51:46 +02:00
|
|
|
- };
|
|
|
|
- }
|
2022-08-17 03:26:19 +02:00
|
|
|
-
|
2022-03-22 21:07:14 +01:00
|
|
|
+ try {
|
2022-06-21 23:51:46 +02:00
|
|
|
+ nlsConfig['vs/nls'] = JSON.parse(document.getElementById("vscode-remote-nls-configuration").getAttribute("data-settings"))
|
|
|
|
+ if (nlsConfig['vs/nls']._resolvedLanguagePackCoreLocation) {
|
2022-03-22 21:07:14 +01:00
|
|
|
+ const bundles = Object.create(null)
|
2022-06-21 23:51:46 +02:00
|
|
|
+ nlsConfig['vs/nls'].loadBundle = (bundle, _language, cb) => {
|
2022-03-22 21:07:14 +01:00
|
|
|
+ const result = bundles[bundle]
|
|
|
|
+ if (result) {
|
|
|
|
+ return cb(undefined, result)
|
|
|
|
+ }
|
2022-06-21 23:51:46 +02:00
|
|
|
+ const path = nlsConfig['vs/nls']._resolvedLanguagePackCoreLocation + "/" + bundle.replace(/\//g, "!") + ".nls.json"
|
2022-07-15 20:00:05 +02:00
|
|
|
+ fetch(`{{WORKBENCH_WEB_BASE_URL}}/../vscode-remote-resource?path=${encodeURIComponent(path)}`)
|
2022-03-22 21:07:14 +01:00
|
|
|
+ .then((response) => response.json())
|
|
|
|
+ .then((json) => {
|
|
|
|
+ bundles[bundle] = json
|
|
|
|
+ cb(undefined, json)
|
|
|
|
+ })
|
|
|
|
+ .catch(cb)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ } catch (error) { /* Probably fine. */ }
|
2022-06-21 23:51:46 +02:00
|
|
|
require.config({
|
|
|
|
baseUrl: `${baseUrl}/out`,
|
2022-08-17 03:26:19 +02:00
|
|
|
recordStats: true,
|
2022-03-22 21:07:14 +01:00
|
|
|
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
|
2022-11-09 23:10:03 +01:00
|
|
|
@@ -110,7 +110,7 @@ export abstract class AbstractNativeEnvi
|
2022-03-22 21:07:14 +01:00
|
|
|
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
|
|
|
|
@@ -30,6 +30,12 @@ export function getNLSConfiguration(lang
|
|
|
|
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 for example).
|
|
|
|
+ if (value.locale !== 'en' && value.locale !== 'en-us' && Object.keys(value.availableLanguages).length === 0) {
|
|
|
|
+ _cache.delete(key);
|
|
|
|
+ }
|
|
|
|
return value;
|
|
|
|
});
|
|
|
|
_cache.set(key, result);
|
|
|
|
@@ -44,3 +50,43 @@ export namespace InternalNLSConfiguratio
|
|
|
|
return candidate && typeof candidate._languagePackId === 'string';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * The code below is copied from from src/main.js.
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+export const getLocaleFromConfig = async (argvResource: string): Promise<string> => {
|
|
|
|
+ try {
|
|
|
|
+ const content = stripComments(await fs.promises.readFile(argvResource, 'utf8'));
|
|
|
|
+ return JSON.parse(content).locale;
|
|
|
|
+ } catch (error) {
|
|
|
|
+ if (error.code !== "ENOENT") {
|
|
|
|
+ console.warn(error)
|
|
|
|
+ }
|
|
|
|
+ return 'en';
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+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;
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+};
|
|
|
|
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
|
2022-06-21 23:51:46 +02:00
|
|
|
@@ -26,6 +26,7 @@ import { URI } from 'vs/base/common/uri'
|
2022-03-22 21:07:14 +01:00
|
|
|
import { streamToBuffer } from 'vs/base/common/buffer';
|
|
|
|
import { IProductConfiguration } from 'vs/base/common/product';
|
|
|
|
import { isString } from 'vs/base/common/types';
|
|
|
|
+import { getLocaleFromConfig, getNLSConfiguration } from 'vs/server/node/remoteLanguagePacks';
|
2022-06-21 23:51:46 +02:00
|
|
|
import { CharCode } from 'vs/base/common/charCode';
|
|
|
|
import { getRemoteServerRootPath } from 'vs/platform/remote/common/remoteHosts';
|
|
|
|
|
2022-08-17 03:26:19 +02:00
|
|
|
@@ -299,6 +300,8 @@ export class WebClientServer {
|
2022-03-22 21:07:14 +01:00
|
|
|
|
|
|
|
const base = relativeRoot(getOriginalUrl(req))
|
|
|
|
const vscodeBase = relativePath(getOriginalUrl(req))
|
|
|
|
+ const locale = this._environmentService.args.locale || await getLocaleFromConfig(this._environmentService.argvResource.fsPath);
|
|
|
|
+ const nlsConfiguration = await getNLSConfiguration(locale, this._environmentService.userDataPath)
|
|
|
|
|
2022-06-21 23:51:46 +02:00
|
|
|
const workbenchWebConfiguration = {
|
|
|
|
remoteAuthority,
|
2022-10-18 01:30:39 +02:00
|
|
|
@@ -336,6 +339,7 @@ export class WebClientServer {
|
2022-08-17 03:26:19 +02:00
|
|
|
WORKBENCH_NLS_BASE_URL: vscodeBase + (nlsBaseUrl ? `${nlsBaseUrl}${!nlsBaseUrl.endsWith('/') ? '/' : ''}${this._productService.commit}/${this._productService.version}/` : ''),
|
2022-06-21 23:51:46 +02:00
|
|
|
BASE: base,
|
|
|
|
VS_BASE: vscodeBase,
|
|
|
|
+ NLS_CONFIGURATION: asJSON(nlsConfiguration),
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2022-03-22 21:07:14 +01:00
|
|
|
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
|
2022-06-21 23:51:46 +02:00
|
|
|
@@ -15,6 +15,7 @@ export const serverOptions: OptionDescri
|
2022-03-22 21:07:14 +01:00
|
|
|
'disable-update-check': { type: 'boolean' },
|
|
|
|
'auth': { type: 'string' },
|
2022-06-21 23:51:46 +02:00
|
|
|
'disable-file-downloads': { type: 'boolean' },
|
2022-03-22 21:07:14 +01:00
|
|
|
+ 'locale': { type: 'string' },
|
|
|
|
|
|
|
|
/* ----- server setup ----- */
|
|
|
|
|
2022-10-18 01:30:39 +02:00
|
|
|
@@ -96,6 +97,7 @@ export interface ServerParsedArgs {
|
2022-03-22 21:07:14 +01:00
|
|
|
'disable-update-check'?: boolean;
|
|
|
|
'auth'?: string
|
2022-06-21 23:51:46 +02:00
|
|
|
'disable-file-downloads'?: boolean;
|
2022-03-22 21:07:14 +01:00
|
|
|
+ 'locale'?: string
|
|
|
|
|
|
|
|
/* ----- server setup ----- */
|
|
|
|
|
2022-06-21 23:51:46 +02:00
|
|
|
Index: code-server/lib/vscode/src/vs/workbench/workbench.web.main.ts
|
2022-03-22 21:07:14 +01:00
|
|
|
===================================================================
|
2022-06-21 23:51:46 +02:00
|
|
|
--- code-server.orig/lib/vscode/src/vs/workbench/workbench.web.main.ts
|
|
|
|
+++ code-server/lib/vscode/src/vs/workbench/workbench.web.main.ts
|
2022-11-09 23:10:03 +01:00
|
|
|
@@ -119,8 +119,9 @@ import 'vs/workbench/contrib/logs/browse
|
2022-08-17 03:26:19 +02:00
|
|
|
// Explorer
|
|
|
|
import 'vs/workbench/contrib/files/browser/files.web.contribution';
|
2022-06-21 23:51:46 +02:00
|
|
|
|
2022-08-17 03:26:19 +02:00
|
|
|
-// Localization
|
|
|
|
-import 'vs/workbench/contrib/localization/browser/localization.contribution';
|
|
|
|
+// Localization. This does not actually import anything specific to Electron so
|
|
|
|
+// it should be safe.
|
2022-06-21 23:51:46 +02:00
|
|
|
+import 'vs/workbench/contrib/localization/electron-sandbox/localization.contribution';
|
|
|
|
|
2022-08-17 03:26:19 +02:00
|
|
|
// Performance
|
|
|
|
import 'vs/workbench/contrib/performance/browser/performance.web.contribution';
|
2022-06-21 23:51:46 +02:00
|
|
|
Index: code-server/lib/vscode/src/vs/platform/languagePacks/browser/languagePacks.ts
|
|
|
|
===================================================================
|
2022-08-17 03:26:19 +02:00
|
|
|
--- code-server.orig/lib/vscode/src/vs/platform/languagePacks/browser/languagePacks.ts
|
2022-06-21 23:51:46 +02:00
|
|
|
+++ code-server/lib/vscode/src/vs/platform/languagePacks/browser/languagePacks.ts
|
2022-11-09 23:10:03 +01:00
|
|
|
@@ -6,18 +6,24 @@
|
|
|
|
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
|
|
|
import { Language } from 'vs/base/common/platform';
|
|
|
|
import { URI } from 'vs/base/common/uri';
|
2022-03-22 21:07:14 +01:00
|
|
|
+import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
|
2022-11-09 23:10:03 +01:00
|
|
|
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';
|
2022-03-22 21:07:14 +01:00
|
|
|
+import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
2022-08-17 03:26:19 +02:00
|
|
|
|
|
|
|
export class WebLanguagePacksService extends LanguagePackBaseService {
|
|
|
|
+ private readonly languagePackService: ILanguagePackService;
|
2022-03-22 21:07:14 +01:00
|
|
|
+
|
2022-11-09 23:10:03 +01:00
|
|
|
constructor(
|
2022-03-22 21:07:14 +01:00
|
|
|
+ @IRemoteAgentService remoteAgentService: IRemoteAgentService,
|
2022-11-09 23:10:03 +01:00
|
|
|
@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): Promise<URI | undefined> {
|
|
|
|
@@ -73,6 +79,6 @@ export class WebLanguagePacksService ext
|
|
|
|
|
|
|
|
// Web doesn't have a concept of language packs, so we just return an empty array
|
2022-08-17 03:26:19 +02:00
|
|
|
getInstalledLanguages(): Promise<ILanguagePackItem[]> {
|
|
|
|
- return Promise.resolve([]);
|
|
|
|
+ return this.languagePackService.getInstalledLanguages()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Index: code-server/lib/vscode/src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts
|
|
|
|
===================================================================
|
|
|
|
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts
|
|
|
|
+++ code-server/lib/vscode/src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts
|
|
|
|
@@ -41,7 +41,8 @@ export class NativeLocaleService impleme
|
|
|
|
@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' });
|
|
|
|
|
|
|
|
@@ -68,9 +69,6 @@ export class NativeLocaleService impleme
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|