未验证 提交 951c98ee 编写于 作者: R Ramya Rao 提交者: GitHub

Use translations from manifest to prompt to install lang pack (#50322)

* Use translations from manifest to prompt to install lang pack

* Add missing file

* Use the right key

* Remove unused variables

* Improve messaging
上级 cdf32b2c
...@@ -13,6 +13,7 @@ export interface ILocalization { ...@@ -13,6 +13,7 @@ export interface ILocalization {
languageName?: string; languageName?: string;
languageNameLocalized?: string; languageNameLocalized?: string;
translations: ITranslation[]; translations: ITranslation[];
minimalTranslations?: { [key: string]: string };
} }
export interface ITranslation { export interface ITranslation {
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
// The strings localized in this file will get pulled into the manifest of the language packs.
// So that they are available for VS Code to use without downloading the entire language pack.
export const minimumTranslatedStrings = {
showLanguagePackExtensions: localize('showLanguagePackExtensions', "The Marketplace has extensions that can localize VS Code in the {0} language"),
searchMarketplace: localize('searchMarketplace', "Search Marketplace"),
installAndRestartMessage: localize('installAndRestartMessage', "Install language pack to localize VS Code in {0} language. Restart VS Code after installing for the language to take effect."),
installAndRestart: localize('installAndRestart', "Install and Restart"),
install: localize('install', 'Install')
};
...@@ -34,7 +34,6 @@ import { getHashedRemotesFromUri } from 'vs/workbench/parts/stats/node/workspace ...@@ -34,7 +34,6 @@ import { getHashedRemotesFromUri } from 'vs/workbench/parts/stats/node/workspace
import { IRequestService } from 'vs/platform/request/node/request'; import { IRequestService } from 'vs/platform/request/node/request';
import { asJson } from 'vs/base/node/request'; import { asJson } from 'vs/base/node/request';
import { isNumber } from 'vs/base/common/types'; import { isNumber } from 'vs/base/common/types';
import { language, LANGUAGE_DEFAULT } from 'vs/base/common/platform';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { INotificationService } from 'vs/platform/notification/common/notification'; import { INotificationService } from 'vs/platform/notification/common/notification';
...@@ -46,7 +45,6 @@ const empty: { [key: string]: any; } = Object.create(null); ...@@ -46,7 +45,6 @@ const empty: { [key: string]: any; } = Object.create(null);
const milliSecondsInADay = 1000 * 60 * 60 * 24; const milliSecondsInADay = 1000 * 60 * 60 * 24;
const choiceNever = localize('neverShowAgain', "Don't Show Again"); const choiceNever = localize('neverShowAgain', "Don't Show Again");
const searchMarketplace = localize('searchMarketplace', "Search Marketplace"); const searchMarketplace = localize('searchMarketplace', "Search Marketplace");
const coreLanguages = ['de', 'es', 'fr', 'it', 'ja', 'ko', 'ru', 'tr', 'zh-cn', 'zh-tw'];
interface IDynamicWorkspaceRecommendations { interface IDynamicWorkspaceRecommendations {
remoteSet: string[]; remoteSet: string[];
...@@ -93,7 +91,6 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe ...@@ -93,7 +91,6 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
this._extensionsRecommendationsUrl = product.extensionsGallery.recommendationsUrl; this._extensionsRecommendationsUrl = product.extensionsGallery.recommendationsUrl;
} }
this.getLanguageExtensionRecommendations();
this.getCachedDynamicWorkspaceRecommendations(); this.getCachedDynamicWorkspaceRecommendations();
this._suggestFileBasedRecommendations(); this._suggestFileBasedRecommendations();
this.promptWorkspaceRecommendationsPromise = this._suggestWorkspaceRecommendations(); this.promptWorkspaceRecommendationsPromise = this._suggestWorkspaceRecommendations();
...@@ -132,90 +129,6 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe ...@@ -132,90 +129,6 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
return this._galleryService.isEnabled() && !this.environmentService.extensionDevelopmentPath; return this._galleryService.isEnabled() && !this.environmentService.extensionDevelopmentPath;
} }
private getLanguageExtensionRecommendations() {
const config = this.configurationService.getValue<IExtensionsConfiguration>(ConfigurationKey);
const languagePackSuggestionIgnoreList = <string[]>JSON.parse(this.storageService.get
('extensionsAssistant/languagePackSuggestionIgnore', StorageScope.GLOBAL, '[]'));
if (!language
|| language === LANGUAGE_DEFAULT
|| coreLanguages.some(x => language === x || language.indexOf(x + '-') === 0)
|| config.ignoreRecommendations
|| config.showRecommendationsOnlyOnDemand
|| languagePackSuggestionIgnoreList.indexOf(language) > -1) {
return;
}
this.extensionsService.getInstalled(LocalExtensionType.User).then(locals => {
for (var i = 0; i < locals.length; i++) {
if (locals[i].manifest
&& locals[i].manifest.contributes
&& Array.isArray(locals[i].manifest.contributes.localizations)
&& locals[i].manifest.contributes.localizations.some(x => x.languageId === language)) {
return;
}
}
this._galleryService.query({ text: `tag:lp-${language}` }).then(pager => {
if (!pager || !pager.firstPage || !pager.firstPage.length) {
return;
}
this.notificationService.prompt(
Severity.Info,
localize('showLanguagePackExtensions', "The Marketplace has extensions that can help localizing VS Code to '{0}' locale", language),
[{
label: searchMarketplace,
run: () => {
/* __GDPR__
"languagePackSuggestion:popup" : {
"userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('languagePackSuggestion:popup', { userReaction: 'ok', language });
this.viewletService.openViewlet('workbench.view.extensions', true)
.then(viewlet => viewlet as IExtensionsViewlet)
.then(viewlet => {
viewlet.search(`tag:lp-${language}`);
viewlet.focus();
});
}
},
{
label: choiceNever,
isSecondary: true,
run: () => {
languagePackSuggestionIgnoreList.push(language);
this.storageService.store(
'extensionsAssistant/languagePackSuggestionIgnore',
JSON.stringify(languagePackSuggestionIgnoreList),
StorageScope.GLOBAL
);
/* __GDPR__
"languagePackSuggestion:popup" : {
"userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('languagePackSuggestion:popup', { userReaction: 'neverShowAgain', language });
}
}],
() => {
/* __GDPR__
"languagePackSuggestion:popup" : {
"userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"language": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('languagePackSuggestion:popup', { userReaction: 'cancelled', language });
}
);
});
});
}
getAllRecommendationsWithReason(): { [id: string]: { reasonId: ExtensionRecommendationReason, reasonText: string }; } { getAllRecommendationsWithReason(): { [id: string]: { reasonId: ExtensionRecommendationReason, reasonText: string }; } {
let output: { [id: string]: { reasonId: ExtensionRecommendationReason, reasonText: string }; } = Object.create(null); let output: { [id: string]: { reasonId: ExtensionRecommendationReason, reasonText: string }; } = Object.create(null);
......
...@@ -23,11 +23,12 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' ...@@ -23,11 +23,12 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'
import URI from 'vs/base/common/uri'; import URI from 'vs/base/common/uri';
import { join } from 'vs/base/common/paths'; import { join } from 'vs/base/common/paths';
import { IWindowsService } from 'vs/platform/windows/common/windows'; import { IWindowsService } from 'vs/platform/windows/common/windows';
import { IStorageService, StorageScope, } from 'vs/platform/storage/common/storage'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { TPromise } from 'vs/base/common/winjs.base'; import { TPromise } from 'vs/base/common/winjs.base';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { VIEWLET_ID as EXTENSIONS_VIEWLET_ID, IExtensionsViewlet } from 'vs/workbench/parts/extensions/common/extensions'; import { VIEWLET_ID as EXTENSIONS_VIEWLET_ID, IExtensionsViewlet } from 'vs/workbench/parts/extensions/common/extensions';
import product from 'vs/platform/node/product'; import { minimumTranslatedStrings } from 'vs/platform/node/minimalTranslations';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
// Register action to configure locale and related settings // Register action to configure locale and related settings
const registry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions); const registry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
...@@ -43,7 +44,8 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo ...@@ -43,7 +44,8 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo
@IStorageService private storageService: IStorageService, @IStorageService private storageService: IStorageService,
@IExtensionManagementService private extensionManagementService: IExtensionManagementService, @IExtensionManagementService private extensionManagementService: IExtensionManagementService,
@IExtensionGalleryService private galleryService: IExtensionGalleryService, @IExtensionGalleryService private galleryService: IExtensionGalleryService,
@IViewletService private viewletService: IViewletService @IViewletService private viewletService: IViewletService,
@ITelemetryService private telemetryService: ITelemetryService
) { ) {
super(); super();
this.updateLocaleDefintionSchema(); this.updateLocaleDefintionSchema();
...@@ -96,6 +98,7 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo ...@@ -96,6 +98,7 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo
private checkAndInstall(): void { private checkAndInstall(): void {
const language = platform.language; const language = platform.language;
const locale = platform.locale;
if (language !== 'en' && language !== 'en_us') { if (language !== 'en' && language !== 'en_us') {
this.isLanguageInstalled(language) this.isLanguageInstalled(language)
.then(installed => { .then(installed => {
...@@ -115,53 +118,116 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo ...@@ -115,53 +118,116 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo
return; return;
} }
const bundledTranslations = (product['bundledTranslations'] || {})[platform.locale]; const languagePackSuggestionIgnoreList = <string[]>JSON.parse(this.storageService.get
if (language === platform.locale || !bundledTranslations || !bundledTranslations['languageName']) { ('extensionsAssistant/languagePackSuggestionIgnore', StorageScope.GLOBAL, '[]'));
if (language === locale || languagePackSuggestionIgnoreList.indexOf(language) > -1) {
return; return;
} }
// The initial value for below dont get used. We just have it here so that they get localized. this.isLanguageInstalled(locale)
// The localized strings get pulled into the "product.json" file during endgame to get shipped .then(installed => {
let searchForLanguagePacks = localize('searchForLanguagePacks', "There are extensions in the Marketplace that can localize VS Code using the ${0} language.", bundledTranslations['languageName']); if (installed) {
let searchMarketplace = localize('searchMarketplace', "Search Marketplace"); return;
let dontShowAgain = localize('neverAgain', "Don't Show Again"); }
searchForLanguagePacks = bundledTranslations['searchForLanguagePacks']; const ceintlExtensionSearch = this.galleryService.query({ names: [`MS-CEINTL.vscode-language-pack-${locale}`], pageSize: 1 });
searchMarketplace = bundledTranslations['searchMarketplace']; const tagSearch = this.galleryService.query({ text: `tag:lp-${locale}`, pageSize: 1 });
dontShowAgain = bundledTranslations['neverAgain'];
const dontShowSearchLanguagePacksAgainKey = 'language.install.donotask'; TPromise.join([ceintlExtensionSearch, tagSearch]).then(([ceintlResult, tagResult]) => {
let dontShowSearchForLanguages = JSON.parse(this.storageService.get(dontShowSearchLanguagePacksAgainKey, StorageScope.GLOBAL, '[]')); if (ceintlResult.total === 0 && tagResult.total === 0) {
if (!Array.isArray(dontShowSearchForLanguages)) { return;
dontShowSearchForLanguages = []; }
}
if (dontShowSearchForLanguages.indexOf(platform.locale) > -1 const extensionToInstall = ceintlResult.total === 1 ? ceintlResult.firstPage[0] : tagResult.total === 1 ? tagResult.firstPage[0] : null;
|| !searchForLanguagePacks const extensionToFetchTranslationsFrom = extensionToInstall || tagResult.total > 0 ? tagResult.firstPage[0] : null;
|| !searchMarketplace
|| !dontShowAgain) {
return;
}
this.notificationService.prompt(Severity.Info, searchForLanguagePacks, if (!extensionToFetchTranslationsFrom || !extensionToFetchTranslationsFrom.assets.manifest) {
[ return;
{
label: searchMarketplace, run: () => {
this.viewletService.openViewlet(EXTENSIONS_VIEWLET_ID, true)
.then(viewlet => viewlet as IExtensionsViewlet)
.then(viewlet => {
viewlet.search(`tag:lp-${platform.locale}`);
viewlet.focus();
});
} }
},
{ this.galleryService.getManifest(extensionToFetchTranslationsFrom).then(x => {
label: dontShowAgain, run: () => { if (!x.contributes || !x.contributes.localizations) {
dontShowSearchForLanguages.push(language); return;
this.storageService.store(dontShowSearchLanguagePacksAgainKey, StorageScope.GLOBAL, dontShowSearchForLanguages); }
} const locContribution = x.contributes.localizations.filter(x => x.languageId.toLowerCase() === locale)[0];
} if (!locContribution) {
]); return;
}
const translations = {
...minimumTranslatedStrings,
...(locContribution.minimalTranslations || {})
};
const logUserReaction = (userReaction: string) => {
/* __GDPR__
"languagePackSuggestion:popup" : {
"userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"language": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('languagePackSuggestion:popup', { userReaction, language });
};
const searchAction = {
label: translations['searchMarketplace'],
run: () => {
logUserReaction('search');
this.viewletService.openViewlet(EXTENSIONS_VIEWLET_ID, true)
.then(viewlet => viewlet as IExtensionsViewlet)
.then(viewlet => {
viewlet.search(`tag:lp-${locale}`);
viewlet.focus();
});
}
};
const installAction = {
label: translations['install'],
run: () => {
logUserReaction('install');
this.installExtension(extensionToInstall);
}
};
const installAndRestartAction = {
label: translations['installAndRestart'],
run: () => {
logUserReaction('installAndRestart');
this.installExtension(extensionToInstall).then(() => this.windowsService.relaunch({}));
}
};
const mainActions = extensionToInstall ? [installAndRestartAction, installAction] : [searchAction];
const promptMessage = translations[extensionToInstall ? 'installAndRestartMessage' : 'showLanguagePackExtensions']
.replace('{0}', locContribution.languageNameLocalized || locContribution.languageName || locale);
this.notificationService.prompt(
Severity.Info,
promptMessage,
[...mainActions,
{
label: localize('neverAgain', "Don't Show Again"),
isSecondary: true,
run: () => {
languagePackSuggestionIgnoreList.push(language);
this.storageService.store(
'extensionsAssistant/languagePackSuggestionIgnore',
JSON.stringify(languagePackSuggestionIgnoreList),
StorageScope.GLOBAL
);
logUserReaction('neverShowAgain');
}
}],
() => {
logUserReaction('cancelled');
}
);
});
});
});
} }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册