From 951c98eece0e0f6b52d3a0ca6ee8877344e10c66 Mon Sep 17 00:00:00 2001 From: Ramya Rao Date: Fri, 25 May 2018 16:58:48 -0700 Subject: [PATCH] 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 --- .../localizations/common/localizations.ts | 1 + src/vs/platform/node/minimalTranslations.ts | 18 +++ .../electron-browser/extensionTipsService.ts | 87 ---------- .../localizations.contribution.ts | 150 +++++++++++++----- 4 files changed, 127 insertions(+), 129 deletions(-) create mode 100644 src/vs/platform/node/minimalTranslations.ts diff --git a/src/vs/platform/localizations/common/localizations.ts b/src/vs/platform/localizations/common/localizations.ts index 5f091c4f6f3..b6361b620a4 100644 --- a/src/vs/platform/localizations/common/localizations.ts +++ b/src/vs/platform/localizations/common/localizations.ts @@ -13,6 +13,7 @@ export interface ILocalization { languageName?: string; languageNameLocalized?: string; translations: ITranslation[]; + minimalTranslations?: { [key: string]: string }; } export interface ITranslation { diff --git a/src/vs/platform/node/minimalTranslations.ts b/src/vs/platform/node/minimalTranslations.ts new file mode 100644 index 00000000000..11248d86d88 --- /dev/null +++ b/src/vs/platform/node/minimalTranslations.ts @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * 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') +}; + diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts index 8736c448bc0..315700baf84 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts @@ -34,7 +34,6 @@ import { getHashedRemotesFromUri } from 'vs/workbench/parts/stats/node/workspace import { IRequestService } from 'vs/platform/request/node/request'; import { asJson } from 'vs/base/node/request'; 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 { INotificationService } from 'vs/platform/notification/common/notification'; @@ -46,7 +45,6 @@ const empty: { [key: string]: any; } = Object.create(null); const milliSecondsInADay = 1000 * 60 * 60 * 24; const choiceNever = localize('neverShowAgain', "Don't Show Again"); const searchMarketplace = localize('searchMarketplace', "Search Marketplace"); -const coreLanguages = ['de', 'es', 'fr', 'it', 'ja', 'ko', 'ru', 'tr', 'zh-cn', 'zh-tw']; interface IDynamicWorkspaceRecommendations { remoteSet: string[]; @@ -93,7 +91,6 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe this._extensionsRecommendationsUrl = product.extensionsGallery.recommendationsUrl; } - this.getLanguageExtensionRecommendations(); this.getCachedDynamicWorkspaceRecommendations(); this._suggestFileBasedRecommendations(); this.promptWorkspaceRecommendationsPromise = this._suggestWorkspaceRecommendations(); @@ -132,90 +129,6 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe return this._galleryService.isEnabled() && !this.environmentService.extensionDevelopmentPath; } - private getLanguageExtensionRecommendations() { - const config = this.configurationService.getValue(ConfigurationKey); - const languagePackSuggestionIgnoreList = 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 }; } { let output: { [id: string]: { reasonId: ExtensionRecommendationReason, reasonText: string }; } = Object.create(null); diff --git a/src/vs/workbench/parts/localizations/electron-browser/localizations.contribution.ts b/src/vs/workbench/parts/localizations/electron-browser/localizations.contribution.ts index 7e9a9f3ffe4..6bc4493acf9 100644 --- a/src/vs/workbench/parts/localizations/electron-browser/localizations.contribution.ts +++ b/src/vs/workbench/parts/localizations/electron-browser/localizations.contribution.ts @@ -23,11 +23,12 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import URI from 'vs/base/common/uri'; import { join } from 'vs/base/common/paths'; 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 { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; 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 const registry = Registry.as(Extensions.WorkbenchActions); @@ -43,7 +44,8 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo @IStorageService private storageService: IStorageService, @IExtensionManagementService private extensionManagementService: IExtensionManagementService, @IExtensionGalleryService private galleryService: IExtensionGalleryService, - @IViewletService private viewletService: IViewletService + @IViewletService private viewletService: IViewletService, + @ITelemetryService private telemetryService: ITelemetryService ) { super(); this.updateLocaleDefintionSchema(); @@ -96,6 +98,7 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo private checkAndInstall(): void { const language = platform.language; + const locale = platform.locale; if (language !== 'en' && language !== 'en_us') { this.isLanguageInstalled(language) .then(installed => { @@ -115,53 +118,116 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo return; } - const bundledTranslations = (product['bundledTranslations'] || {})[platform.locale]; - if (language === platform.locale || !bundledTranslations || !bundledTranslations['languageName']) { + const languagePackSuggestionIgnoreList = JSON.parse(this.storageService.get + ('extensionsAssistant/languagePackSuggestionIgnore', StorageScope.GLOBAL, '[]')); + + if (language === locale || languagePackSuggestionIgnoreList.indexOf(language) > -1) { return; } - // The initial value for below dont get used. We just have it here so that they get localized. - // The localized strings get pulled into the "product.json" file during endgame to get shipped - let searchForLanguagePacks = localize('searchForLanguagePacks', "There are extensions in the Marketplace that can localize VS Code using the ${0} language.", bundledTranslations['languageName']); - let searchMarketplace = localize('searchMarketplace', "Search Marketplace"); - let dontShowAgain = localize('neverAgain', "Don't Show Again"); + this.isLanguageInstalled(locale) + .then(installed => { + if (installed) { + return; + } - searchForLanguagePacks = bundledTranslations['searchForLanguagePacks']; - searchMarketplace = bundledTranslations['searchMarketplace']; - dontShowAgain = bundledTranslations['neverAgain']; + const ceintlExtensionSearch = this.galleryService.query({ names: [`MS-CEINTL.vscode-language-pack-${locale}`], pageSize: 1 }); + const tagSearch = this.galleryService.query({ text: `tag:lp-${locale}`, pageSize: 1 }); - const dontShowSearchLanguagePacksAgainKey = 'language.install.donotask'; - let dontShowSearchForLanguages = JSON.parse(this.storageService.get(dontShowSearchLanguagePacksAgainKey, StorageScope.GLOBAL, '[]')); - if (!Array.isArray(dontShowSearchForLanguages)) { - dontShowSearchForLanguages = []; - } + TPromise.join([ceintlExtensionSearch, tagSearch]).then(([ceintlResult, tagResult]) => { + if (ceintlResult.total === 0 && tagResult.total === 0) { + return; + } - if (dontShowSearchForLanguages.indexOf(platform.locale) > -1 - || !searchForLanguagePacks - || !searchMarketplace - || !dontShowAgain) { - return; - } + const extensionToInstall = ceintlResult.total === 1 ? ceintlResult.firstPage[0] : tagResult.total === 1 ? tagResult.firstPage[0] : null; + const extensionToFetchTranslationsFrom = extensionToInstall || tagResult.total > 0 ? tagResult.firstPage[0] : null; - this.notificationService.prompt(Severity.Info, searchForLanguagePacks, - [ - { - 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(); - }); + if (!extensionToFetchTranslationsFrom || !extensionToFetchTranslationsFrom.assets.manifest) { + return; } - }, - { - label: dontShowAgain, run: () => { - dontShowSearchForLanguages.push(language); - this.storageService.store(dontShowSearchLanguagePacksAgainKey, StorageScope.GLOBAL, dontShowSearchForLanguages); - } - } - ]); + + this.galleryService.getManifest(extensionToFetchTranslationsFrom).then(x => { + if (!x.contributes || !x.contributes.localizations) { + return; + } + 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'); + } + ); + + }); + }); + }); } -- GitLab