Handle language pack uninstallation (#171891)

The most important part of this change is that we now listen for when extensions are uninstalled and if they uninstalled language currently set then a modal will show up prompting for restart (required to change the language back to English).

This change also makes the LocaleService the thing that writes to the argv.json instead of a few places doing it

and cleans up some of the wording.

Fixes #82791
This commit is contained in:
Tyler James Leonhardt 2023-01-20 18:29:11 -08:00 committed by GitHub
parent 71a7c90c1e
commit c321a36929
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 62 additions and 41 deletions

View file

@ -1510,7 +1510,8 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
if (locale === language) {
return;
}
return this.localeService.setLocale({ id: locale, galleryExtension: extension.gallery, extensionId: extension.identifier.id, label: extension.displayName });
const localizedLanguageName = extension.gallery?.properties?.localizedLanguages?.[0];
return this.localeService.setLocale({ id: locale, galleryExtension: extension.gallery, extensionId: extension.identifier.id, label: localizedLanguageName ?? extension.displayName });
}
setEnablement(extensions: IExtension | IExtension[], enablementState: EnablementState): Promise<void> {

View file

@ -20,7 +20,7 @@ export class WebLocaleService implements ILocaleService {
@IProductService private readonly productService: IProductService
) { }
async setLocale(languagePackItem: ILanguagePackItem): Promise<void> {
async setLocale(languagePackItem: ILanguagePackItem, _skipDialog = false): Promise<void> {
const locale = languagePackItem.id;
if (locale === Language.value() || (!locale && Language.value() === navigator.language)) {
return;

View file

@ -10,6 +10,6 @@ export const ILocaleService = createDecorator<ILocaleService>('localizationServi
export interface ILocaleService {
readonly _serviceBrand: undefined;
setLocale(languagePackItem: ILanguagePackItem): Promise<void>;
setLocale(languagePackItem: ILanguagePackItem, skipDialog?: boolean): Promise<void>;
clearLocalePreference(): Promise<void>;
}

View file

@ -75,7 +75,7 @@ export class NativeLocaleService implements ILocaleService {
return true;
}
async setLocale(languagePackItem: ILanguagePackItem): Promise<void> {
async setLocale(languagePackItem: ILanguagePackItem, skipDialog = false): Promise<void> {
const locale = languagePackItem.id;
if (locale === Language.value() || (!locale && Language.isDefaultVariant())) {
return;
@ -108,7 +108,7 @@ export class NativeLocaleService implements ILocaleService {
);
}
if (await this.writeLocaleValue(locale)) {
if (await this.writeLocaleValue(locale) && !skipDialog) {
await this.showRestartDialog(languagePackItem.label);
}
} catch (err) {
@ -127,15 +127,15 @@ export class NativeLocaleService implements ILocaleService {
}
}
private async showRestartDialog(languageName: string) {
private async showRestartDialog(languageName: string): Promise<void> {
const restartDialog = await this.dialogService.confirm({
type: 'info',
message: localize('restartDisplayLanguageMessage', "To change the display language, {0} needs to restart", this.productService.nameLong),
message: localize('restartDisplayLanguageMessage1', "Restart {0} to switch to {1}?", this.productService.nameLong, languageName),
detail: localize(
'restartDisplayLanguageDetail',
"Press the restart button to restart {0} and set the display language to {1}.",
this.productService.nameLong,
languageName
'restartDisplayLanguageDetail1',
"To change the display language to {0}, {1} needs to restart.",
languageName,
this.productService.nameLong
),
primaryButton: localize({ key: 'restart', comment: ['&& denotes a mnemonic character'] }, "&&Restart"),
});

View file

@ -10,11 +10,9 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import * as platform from 'vs/base/common/platform';
import { IExtensionManagementService, IExtensionGalleryService, IGalleryExtension, InstallOperation, InstallExtensionResult } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionManagementService, IExtensionGalleryService, InstallOperation, ILocalExtension, InstallExtensionResult, DidUninstallExtensionEvent } from 'vs/platform/extensionManagement/common/extensionManagement';
import { INotificationService, NeverShowAgainScope } from 'vs/platform/notification/common/notification';
import Severity from 'vs/base/common/severity';
import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { VIEWLET_ID as EXTENSIONS_VIEWLET_ID, IExtensionsViewPaneContainer } from 'vs/workbench/contrib/extensions/common/extensions';
@ -28,6 +26,7 @@ import { ClearDisplayLanguageAction, ConfigureDisplayLanguageAction } from 'vs/w
import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { ILocaleService } from 'vs/workbench/contrib/localization/common/locale';
import { NativeLocaleService } from 'vs/workbench/contrib/localization/electron-sandbox/localeService';
import { IProductService } from 'vs/platform/product/common/productService';
registerSingleton(ILocaleService, NativeLocaleService, InstantiationType.Delayed);
@ -40,8 +39,8 @@ const LANGUAGEPACK_SUGGESTION_IGNORE_STORAGE_KEY = 'extensionsAssistant/language
export class LocalizationWorkbenchContribution extends Disposable implements IWorkbenchContribution {
constructor(
@INotificationService private readonly notificationService: INotificationService,
@IJSONEditingService private readonly jsonEditingService: IJSONEditingService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@ILocaleService private readonly localeService: ILocaleService,
@IProductService private readonly productService: IProductService,
@IHostService private readonly hostService: IHostService,
@IStorageService private readonly storageService: IStorageService,
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@ -53,30 +52,39 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo
this.checkAndInstall();
this._register(this.extensionManagementService.onDidInstallExtensions(e => this.onDidInstallExtensions(e)));
this._register(this.extensionManagementService.onDidUninstallExtension(e => this.onDidUninstallExtension(e)));
}
private onDidInstallExtensions(results: readonly InstallExtensionResult[]): void {
for (const e of results) {
if (e.operation !== InstallOperation.Install || !e.local?.manifest?.contributes?.localizations?.length) {
continue;
}
const languageId = e.local.manifest.contributes.localizations[0].languageId;
if (platform.language === languageId) {
continue;
private async onDidInstallExtensions(results: readonly InstallExtensionResult[]): Promise<void> {
for (const result of results) {
if (result.operation === InstallOperation.Install && result.local) {
await this.onDidInstallExtension(result.local, !!result.context?.extensionsSync);
}
}
}
private async onDidInstallExtension(localExtension: ILocalExtension, fromSettingsSync: boolean): Promise<void> {
const localization = localExtension.manifest.contributes?.localizations?.[0];
if (!localization || platform.language === localization.languageId) {
return;
}
const { languageId, languageName } = localization;
if (fromSettingsSync) {
this.notificationService.prompt(
Severity.Info,
localize('updateLocale', "Would you like to change VS Code's UI language to {0} and restart?", e.local.manifest.contributes.localizations[0].languageName || e.local.manifest.contributes.localizations[0].languageId),
localize('updateLocale', "Would you like to change {0}'s display language to {1} and restart?", this.productService.nameLong, languageName || languageId),
[{
label: localize('changeAndRestart', "Change Language and Restart"),
run: async () => {
try {
await this.jsonEditingService.write(this.environmentService.argvResource, [{ path: ['locale'], value: languageId }], true);
await this.hostService.restart();
} catch (e) {
this.notificationService.error(e);
}
await this.localeService.setLocale({
id: languageId,
label: languageName ?? languageId,
extensionId: localExtension.identifier.id,
// If settings sync installs the language pack, then we would have just shown the notification so no
// need to show the dialog.
}, true);
}
}],
{
@ -84,6 +92,22 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo
neverShowAgain: { id: 'langugage.update.donotask', isSecondary: true, scope: NeverShowAgainScope.APPLICATION }
}
);
return;
}
await this.localeService.setLocale({
id: languageId,
label: languageName ?? languageId,
extensionId: localExtension.identifier.id,
});
}
private async onDidUninstallExtension(_event: DidUninstallExtensionEvent): Promise<void> {
if (!await this.isLocaleInstalled(platform.language)) {
this.localeService.setLocale({
id: 'en',
label: 'English'
});
}
}
@ -172,8 +196,12 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo
label: translations['installAndRestart'],
run: async () => {
logUserReaction('installAndRestart');
await this.installExtension(extensionToInstall!);
await this.jsonEditingService.write(this.environmentService.argvResource, [{ path: ['locale'], value: locale }], true);
await this.localeService.setLocale({
id: locale,
label: languageName,
extensionId: extensionToInstall?.identifier.id,
// The user will be prompted if they want to install the language pack before this.
}, true);
await this.hostService.restart();
}
};
@ -213,14 +241,6 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo
return installed.some(i => !!i.manifest.contributes?.localizations?.length
&& i.manifest.contributes.localizations.some(l => locale.startsWith(l.languageId.toLowerCase())));
}
private installExtension(extension: IGalleryExtension): Promise<void> {
return this.paneCompositeService.openPaneComposite(EXTENSIONS_VIEWLET_ID, ViewContainerLocation.Sidebar)
.then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer)
.then(viewlet => viewlet.search(`@id:${extension.identifier.id}`))
.then(() => this.extensionManagementService.installFromGallery(extension))
.then(() => undefined, err => this.notificationService.error(err));
}
}
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);