Path: blob/main/src/vs/workbench/services/localization/electron-browser/localeService.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Language, LANGUAGE_DEFAULT } from '../../../../base/common/platform.js';6import { IEnvironmentService } from '../../../../platform/environment/common/environment.js';7import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js';8import { IJSONEditingService } from '../../configuration/common/jsonEditing.js';9import { IActiveLanguagePackService, ILocaleService } from '../common/locale.js';10import { ILanguagePackItem, ILanguagePackService } from '../../../../platform/languagePacks/common/languagePacks.js';11import { IPaneCompositePartService } from '../../panecomposite/browser/panecomposite.js';12import { IViewPaneContainer, ViewContainerLocation } from '../../../common/views.js';13import { IExtensionManagementService } from '../../../../platform/extensionManagement/common/extensionManagement.js';14import { IProgressService, ProgressLocation } from '../../../../platform/progress/common/progress.js';15import { localize } from '../../../../nls.js';16import { toAction } from '../../../../base/common/actions.js';17import { ITextFileService } from '../../textfile/common/textfiles.js';18import { parse } from '../../../../base/common/jsonc.js';19import { IEditorService } from '../../editor/common/editorService.js';20import { IHostService } from '../../host/browser/host.js';21import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js';22import { IProductService } from '../../../../platform/product/common/productService.js';23import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';2425// duplicate of IExtensionsViewPaneContainer in contrib26interface IExtensionsViewPaneContainer extends IViewPaneContainer {27readonly searchValue: string | undefined;28search(text: string): void;29refresh(): Promise<void>;30}3132// duplicate of VIEWLET_ID in contrib/extensions33const EXTENSIONS_VIEWLET_ID = 'workbench.view.extensions';3435class NativeLocaleService implements ILocaleService {36_serviceBrand: undefined;3738constructor(39@IJSONEditingService private readonly jsonEditingService: IJSONEditingService,40@IEnvironmentService private readonly environmentService: IEnvironmentService,41@INotificationService private readonly notificationService: INotificationService,42@ILanguagePackService private readonly languagePackService: ILanguagePackService,43@IPaneCompositePartService private readonly paneCompositePartService: IPaneCompositePartService,44@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,45@IProgressService private readonly progressService: IProgressService,46@ITextFileService private readonly textFileService: ITextFileService,47@IEditorService private readonly editorService: IEditorService,48@IDialogService private readonly dialogService: IDialogService,49@IHostService private readonly hostService: IHostService,50@IProductService private readonly productService: IProductService51) { }5253private async validateLocaleFile(): Promise<boolean> {54try {55const content = await this.textFileService.read(this.environmentService.argvResource, { encoding: 'utf8' });5657// This is the same logic that we do where argv.json is parsed so mirror that:58// https://github.com/microsoft/vscode/blob/32d40cf44e893e87ac33ac4f08de1e5f7fe077fc/src/main.js#L238-L24659parse(content.value);60} catch (error) {61this.notificationService.notify({62severity: Severity.Error,63message: localize('argvInvalid', 'Unable to write display language. Please open the runtime settings, correct errors/warnings in it and try again.'),64actions: {65primary: [66toAction({67id: 'openArgv',68label: localize('openArgv', "Open Runtime Settings"),69run: () => this.editorService.openEditor({ resource: this.environmentService.argvResource })70})71]72}73});74return false;75}76return true;77}7879private async writeLocaleValue(locale: string | undefined): Promise<boolean> {80if (!(await this.validateLocaleFile())) {81return false;82}83await this.jsonEditingService.write(this.environmentService.argvResource, [{ path: ['locale'], value: locale }], true);84return true;85}8687async setLocale(languagePackItem: ILanguagePackItem, skipDialog = false): Promise<void> {88const locale = languagePackItem.id;89if (locale === Language.value() || (!locale && Language.isDefaultVariant())) {90return;91}92const installedLanguages = await this.languagePackService.getInstalledLanguages();93try {9495// Only Desktop has the concept of installing language packs so we only do this for Desktop96// and only if the language pack is not installed97if (!installedLanguages.some(installedLanguage => installedLanguage.id === languagePackItem.id)) {9899// Only actually install a language pack from Microsoft100if (languagePackItem.galleryExtension?.publisher.toLowerCase() !== 'ms-ceintl') {101// Show the view so the user can see the language pack that they should install102// as of now, there are no 3rd party language packs available on the Marketplace.103const viewlet = await this.paneCompositePartService.openPaneComposite(EXTENSIONS_VIEWLET_ID, ViewContainerLocation.Sidebar);104(viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer).search(`@id:${languagePackItem.extensionId}`);105return;106}107108await this.progressService.withProgress(109{110location: ProgressLocation.Notification,111title: localize('installing', "Installing {0} language support...", languagePackItem.label),112},113progress => this.extensionManagementService.installFromGallery(languagePackItem.galleryExtension!, {114// Setting this to false is how you get the extension to be synced with Settings Sync (if enabled).115isMachineScoped: false,116})117);118}119120if (!skipDialog && !await this.showRestartDialog(languagePackItem.label)) {121return;122}123await this.writeLocaleValue(locale);124await this.hostService.restart();125} catch (err) {126this.notificationService.error(err);127}128}129130async clearLocalePreference(): Promise<void> {131try {132await this.writeLocaleValue(undefined);133if (!Language.isDefaultVariant()) {134await this.showRestartDialog('English');135}136} catch (err) {137this.notificationService.error(err);138}139}140141private async showRestartDialog(languageName: string): Promise<boolean> {142const { confirmed } = await this.dialogService.confirm({143message: localize('restartDisplayLanguageMessage1', "Restart {0} to switch to {1}?", this.productService.nameLong, languageName),144detail: localize(145'restartDisplayLanguageDetail1',146"To change the display language to {0}, {1} needs to restart.",147languageName,148this.productService.nameLong149),150primaryButton: localize({ key: 'restart', comment: ['&& denotes a mnemonic character'] }, "&&Restart"),151});152153return confirmed;154}155}156157// This is its own service because the localeService depends on IJSONEditingService which causes a circular dependency158// Once that's ironed out, we can fold this into the localeService.159class NativeActiveLanguagePackService implements IActiveLanguagePackService {160_serviceBrand: undefined;161162constructor(163@ILanguagePackService private readonly languagePackService: ILanguagePackService164) { }165166async getExtensionIdProvidingCurrentLocale(): Promise<string | undefined> {167const language = Language.value();168if (language === LANGUAGE_DEFAULT) {169return undefined;170}171const languages = await this.languagePackService.getInstalledLanguages();172const languagePack = languages.find(l => l.id === language);173return languagePack?.extensionId;174}175}176177registerSingleton(ILocaleService, NativeLocaleService, InstantiationType.Delayed);178registerSingleton(IActiveLanguagePackService, NativeActiveLanguagePackService, InstantiationType.Delayed);179180181