Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/localization/electron-browser/localization.contribution.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { localize } from '../../../../nls.js';
7
import { Registry } from '../../../../platform/registry/common/platform.js';
8
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from '../../../common/contributions.js';
9
import { LifecyclePhase } from '../../../services/lifecycle/common/lifecycle.js';
10
import * as platform from '../../../../base/common/platform.js';
11
import { IExtensionManagementService, IExtensionGalleryService, InstallOperation, ILocalExtension, InstallExtensionResult, DidUninstallExtensionEvent } from '../../../../platform/extensionManagement/common/extensionManagement.js';
12
import { INotificationService, NeverShowAgainScope, NotificationPriority } from '../../../../platform/notification/common/notification.js';
13
import Severity from '../../../../base/common/severity.js';
14
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
15
import { IExtensionsWorkbenchService } from '../../extensions/common/extensions.js';
16
import { minimumTranslatedStrings } from './minimalTranslations.js';
17
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
18
import { CancellationToken } from '../../../../base/common/cancellation.js';
19
import { ILocaleService } from '../../../services/localization/common/locale.js';
20
import { IProductService } from '../../../../platform/product/common/productService.js';
21
import { BaseLocalizationWorkbenchContribution } from '../common/localization.contribution.js';
22
23
class NativeLocalizationWorkbenchContribution extends BaseLocalizationWorkbenchContribution {
24
private static LANGUAGEPACK_SUGGESTION_IGNORE_STORAGE_KEY = 'extensionsAssistant/languagePackSuggestionIgnore';
25
26
constructor(
27
@INotificationService private readonly notificationService: INotificationService,
28
@ILocaleService private readonly localeService: ILocaleService,
29
@IProductService private readonly productService: IProductService,
30
@IStorageService private readonly storageService: IStorageService,
31
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
32
@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,
33
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
34
@ITelemetryService private readonly telemetryService: ITelemetryService,
35
) {
36
super();
37
38
this.checkAndInstall();
39
this._register(this.extensionManagementService.onDidInstallExtensions(e => this.onDidInstallExtensions(e)));
40
this._register(this.extensionManagementService.onDidUninstallExtension(e => this.onDidUninstallExtension(e)));
41
}
42
43
private async onDidInstallExtensions(results: readonly InstallExtensionResult[]): Promise<void> {
44
for (const result of results) {
45
if (result.operation === InstallOperation.Install && result.local) {
46
await this.onDidInstallExtension(result.local, !!result.context?.extensionsSync);
47
}
48
}
49
50
}
51
52
private async onDidInstallExtension(localExtension: ILocalExtension, fromSettingsSync: boolean): Promise<void> {
53
const localization = localExtension.manifest.contributes?.localizations?.[0];
54
if (!localization || platform.language === localization.languageId) {
55
return;
56
}
57
const { languageId, languageName } = localization;
58
59
this.notificationService.prompt(
60
Severity.Info,
61
localize('updateLocale', "Would you like to change {0}'s display language to {1} and restart?", this.productService.nameLong, languageName || languageId),
62
[{
63
label: localize('changeAndRestart', "Change Language and Restart"),
64
run: async () => {
65
await this.localeService.setLocale({
66
id: languageId,
67
label: languageName ?? languageId,
68
extensionId: localExtension.identifier.id,
69
// If settings sync installs the language pack, then we would have just shown the notification so no
70
// need to show the dialog.
71
}, true);
72
}
73
}],
74
{
75
sticky: true,
76
priority: NotificationPriority.URGENT,
77
neverShowAgain: { id: 'langugage.update.donotask', isSecondary: true, scope: NeverShowAgainScope.APPLICATION }
78
}
79
);
80
}
81
82
private async onDidUninstallExtension(_event: DidUninstallExtensionEvent): Promise<void> {
83
if (!await this.isLocaleInstalled(platform.language)) {
84
this.localeService.setLocale({
85
id: 'en',
86
label: 'English'
87
});
88
}
89
}
90
91
private async checkAndInstall(): Promise<void> {
92
const language = platform.language;
93
let locale = platform.locale ?? '';
94
const languagePackSuggestionIgnoreList: string[] = JSON.parse(
95
this.storageService.get(
96
NativeLocalizationWorkbenchContribution.LANGUAGEPACK_SUGGESTION_IGNORE_STORAGE_KEY,
97
StorageScope.APPLICATION,
98
'[]'
99
)
100
);
101
102
if (!this.galleryService.isEnabled()) {
103
return;
104
}
105
if (!language || !locale || platform.Language.isDefaultVariant()) {
106
return;
107
}
108
if (locale.startsWith(language) || languagePackSuggestionIgnoreList.includes(locale)) {
109
return;
110
}
111
112
const installed = await this.isLocaleInstalled(locale);
113
if (installed) {
114
return;
115
}
116
117
const fullLocale = locale;
118
let tagResult = await this.galleryService.query({ text: `tag:lp-${locale}` }, CancellationToken.None);
119
if (tagResult.total === 0) {
120
// Trim the locale and try again.
121
locale = locale.split('-')[0];
122
tagResult = await this.galleryService.query({ text: `tag:lp-${locale}` }, CancellationToken.None);
123
if (tagResult.total === 0) {
124
return;
125
}
126
}
127
128
const extensionToInstall = tagResult.total === 1 ? tagResult.firstPage[0] : tagResult.firstPage.find(e => e.publisher === 'MS-CEINTL' && e.name.startsWith('vscode-language-pack'));
129
const extensionToFetchTranslationsFrom = extensionToInstall ?? tagResult.firstPage[0];
130
131
if (!extensionToFetchTranslationsFrom.assets.manifest) {
132
return;
133
}
134
135
const [manifest, translation] = await Promise.all([
136
this.galleryService.getManifest(extensionToFetchTranslationsFrom, CancellationToken.None),
137
this.galleryService.getCoreTranslation(extensionToFetchTranslationsFrom, locale)
138
]);
139
const loc = manifest?.contributes?.localizations?.find(x => locale.startsWith(x.languageId.toLowerCase()));
140
const languageName = loc ? (loc.languageName || locale) : locale;
141
const languageDisplayName = loc ? (loc.localizedLanguageName || loc.languageName || locale) : locale;
142
const translationsFromPack: { [key: string]: string } = translation?.contents?.['vs/workbench/contrib/localization/electron-browser/minimalTranslations'] ?? {};
143
const promptMessageKey = extensionToInstall ? 'installAndRestartMessage' : 'showLanguagePackExtensions';
144
const useEnglish = !translationsFromPack[promptMessageKey];
145
146
const translations: { [key: string]: string } = {};
147
Object.keys(minimumTranslatedStrings).forEach(key => {
148
if (!translationsFromPack[key] || useEnglish) {
149
translations[key] = minimumTranslatedStrings[key].replace('{0}', () => languageName);
150
} else {
151
translations[key] = `${translationsFromPack[key].replace('{0}', () => languageDisplayName)} (${minimumTranslatedStrings[key].replace('{0}', () => languageName)})`;
152
}
153
});
154
155
const logUserReaction = (userReaction: string) => {
156
/* __GDPR__
157
"languagePackSuggestion:popup" : {
158
"owner": "TylerLeonhardt",
159
"userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
160
"language": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
161
}
162
*/
163
this.telemetryService.publicLog('languagePackSuggestion:popup', { userReaction, language: locale });
164
};
165
166
const searchAction = {
167
label: translations['searchMarketplace'],
168
run: async () => {
169
logUserReaction('search');
170
await this.extensionsWorkbenchService.openSearch(`tag:lp-${locale}`);
171
}
172
};
173
174
const installAndRestartAction = {
175
label: translations['installAndRestart'],
176
run: async () => {
177
logUserReaction('installAndRestart');
178
await this.localeService.setLocale({
179
id: locale,
180
label: languageName,
181
extensionId: extensionToInstall?.identifier.id,
182
galleryExtension: extensionToInstall
183
// The user will be prompted if they want to install the language pack before this.
184
}, true);
185
}
186
};
187
188
const promptMessage = translations[promptMessageKey];
189
190
this.notificationService.prompt(
191
Severity.Info,
192
promptMessage,
193
[extensionToInstall ? installAndRestartAction : searchAction,
194
{
195
label: localize('neverAgain', "Don't Show Again"),
196
isSecondary: true,
197
run: () => {
198
languagePackSuggestionIgnoreList.push(fullLocale);
199
this.storageService.store(
200
NativeLocalizationWorkbenchContribution.LANGUAGEPACK_SUGGESTION_IGNORE_STORAGE_KEY,
201
JSON.stringify(languagePackSuggestionIgnoreList),
202
StorageScope.APPLICATION,
203
StorageTarget.USER
204
);
205
logUserReaction('neverShowAgain');
206
}
207
}],
208
{
209
priority: NotificationPriority.OPTIONAL,
210
onCancel: () => {
211
logUserReaction('cancelled');
212
}
213
}
214
);
215
}
216
217
private async isLocaleInstalled(locale: string): Promise<boolean> {
218
const installed = await this.extensionManagementService.getInstalled();
219
return installed.some(i => !!i.manifest.contributes?.localizations?.length
220
&& i.manifest.contributes.localizations.some(l => locale.startsWith(l.languageId.toLowerCase())));
221
}
222
}
223
224
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
225
workbenchRegistry.registerWorkbenchContribution(NativeLocalizationWorkbenchContribution, LifecyclePhase.Eventually);
226
227