Skip to content

Commit

Permalink
Pass display language as a locale to Electron (#159958)
Browse files Browse the repository at this point in the history
Co-authored-by: Benjamin Pasero <benjamin.pasero@microsoft.com>
  • Loading branch information
rzhao271 and bpasero committed Nov 14, 2022
1 parent fd7e27f commit 5f67407
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 85 deletions.
27 changes: 26 additions & 1 deletion src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,20 @@ if (locale) {
nlsConfigurationPromise = getNLSConfiguration(product.commit, userDataPath, metaDataFile, locale);
}

if (product.quality === 'insider' || product.quality === 'exploration') {

// Pass in the locale to Electron so that the
// Windows Control Overlay is rendered correctly on Windows,
// and so that the traffic lights are rendered properly
// on macOS when using a custom titlebar.
// If the locale is `qps-ploc`, the Microsoft
// Pseudo Language Language Pack is being used.
// In that case, use `en` as the Electron locale.

const electronLocale = (!locale || locale === 'qps-ploc') ? 'en' : locale;
app.commandLine.appendSwitch('lang', electronLocale);
}

// Load our code once ready
app.once('ready', function () {
if (args['trace']) {
Expand Down Expand Up @@ -551,7 +565,18 @@ async function resolveNlsConfiguration() {
// Try to use the app locale. Please note that the app locale is only
// valid after we have received the app ready event. This is why the
// code is here.
let appLocale = app.getLocale();

// The ternary and ts-ignore can both be removed once Electron
// officially adopts the getPreferredSystemLanguages API.
// Ref https://github.com/microsoft/vscode/issues/159813
// and https://github.com/electron/electron/pull/36035
/**
* @type string
*/
// @ts-ignore API not yet available in the official Electron
let appLocale = ((product.quality === 'insider' || product.quality === 'exploration') && app?.getPreferredSystemLanguages()?.length) ?
// @ts-ignore API not yet available in the official Electron
app.getPreferredSystemLanguages()[0] : app.getLocale();
if (!appLocale) {
nlsConfiguration = { locale: 'en', availableLanguages: {} };
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,12 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo

private checkAndInstall(): void {
const language = platform.language;
const locale = platform.locale;
let locale = platform.locale ?? '';
if (locale.startsWith('zh-hans')) {
locale = 'zh-cn';
} else if (locale.startsWith('zh-hant')) {
locale = 'zh-tw';
}
const languagePackSuggestionIgnoreList = <string[]>JSON.parse(this.storageService.get(LANGUAGEPACK_SUGGESTION_IGNORE_STORAGE_KEY, StorageScope.APPLICATION, '[]'));

if (!this.galleryService.isEnabled()) {
Expand All @@ -96,118 +101,121 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo
if (!language || !locale || locale === 'en' || locale.indexOf('en-') === 0) {
return;
}
if (language === locale || languagePackSuggestionIgnoreList.indexOf(locale) > -1) {
if (locale.startsWith(language) || languagePackSuggestionIgnoreList.includes(locale)) {
return;
}

this.isLanguageInstalled(locale)
.then(installed => {
this.isLocaleInstalled(locale)
.then(async (installed) => {
if (installed) {
return;
}

this.galleryService.query({ text: `tag:lp-${locale}` }, CancellationToken.None).then(tagResult => {
let searchLocale = locale;
let tagResult = await this.galleryService.query({ text: `tag:lp-${searchLocale}` }, CancellationToken.None);
if (tagResult.total === 0) {
// Trim the locale and try again.
searchLocale = locale.split('-')[0];
tagResult = await this.galleryService.query({ text: `tag:lp-${searchLocale}` }, CancellationToken.None);
if (tagResult.total === 0) {
return;
}
}

const extensionToInstall = tagResult.total === 1 ? tagResult.firstPage[0] : tagResult.firstPage.filter(e => e.publisher === 'MS-CEINTL' && e.name.indexOf('vscode-language-pack') === 0)[0];
const extensionToFetchTranslationsFrom = extensionToInstall || tagResult.firstPage[0];
const extensionToInstall = tagResult.total === 1 ? tagResult.firstPage[0] : tagResult.firstPage.find(e => e.publisher === 'MS-CEINTL' && e.name.startsWith('vscode-language-pack'));
const extensionToFetchTranslationsFrom = extensionToInstall ?? tagResult.firstPage[0];

if (!extensionToFetchTranslationsFrom.assets.manifest) {
return;
}
if (!extensionToFetchTranslationsFrom.assets.manifest) {
return;
}

Promise.all([this.galleryService.getManifest(extensionToFetchTranslationsFrom, CancellationToken.None), this.galleryService.getCoreTranslation(extensionToFetchTranslationsFrom, locale)])
.then(([manifest, translation]) => {
const loc = manifest && manifest.contributes && manifest.contributes.localizations && manifest.contributes.localizations.filter(x => x.languageId.toLowerCase() === locale)[0];
const languageName = loc ? (loc.languageName || locale) : locale;
const languageDisplayName = loc ? (loc.localizedLanguageName || loc.languageName || locale) : locale;
const translationsFromPack: { [key: string]: string } = translation?.contents?.['vs/workbench/contrib/localization/electron-sandbox/minimalTranslations'] ?? {};
const promptMessageKey = extensionToInstall ? 'installAndRestartMessage' : 'showLanguagePackExtensions';
const useEnglish = !translationsFromPack[promptMessageKey];
Promise.all([this.galleryService.getManifest(extensionToFetchTranslationsFrom, CancellationToken.None), this.galleryService.getCoreTranslation(extensionToFetchTranslationsFrom, searchLocale)])
.then(([manifest, translation]) => {
const loc = manifest && manifest.contributes && manifest.contributes.localizations && manifest.contributes.localizations.find(x => locale.startsWith(x.languageId.toLowerCase()));
const languageName = loc ? (loc.languageName || locale) : locale;
const languageDisplayName = loc ? (loc.localizedLanguageName || loc.languageName || locale) : locale;
const translationsFromPack: { [key: string]: string } = translation?.contents?.['vs/workbench/contrib/localization/electron-sandbox/minimalTranslations'] ?? {};
const promptMessageKey = extensionToInstall ? 'installAndRestartMessage' : 'showLanguagePackExtensions';
const useEnglish = !translationsFromPack[promptMessageKey];

const translations: { [key: string]: string } = {};
Object.keys(minimumTranslatedStrings).forEach(key => {
if (!translationsFromPack[key] || useEnglish) {
translations[key] = minimumTranslatedStrings[key].replace('{0}', languageName);
} else {
translations[key] = `${translationsFromPack[key].replace('{0}', languageDisplayName)} (${minimumTranslatedStrings[key].replace('{0}', languageName)})`;
const translations: { [key: string]: string } = {};
Object.keys(minimumTranslatedStrings).forEach(key => {
if (!translationsFromPack[key] || useEnglish) {
translations[key] = minimumTranslatedStrings[key].replace('{0}', languageName);
} else {
translations[key] = `${translationsFromPack[key].replace('{0}', languageDisplayName)} (${minimumTranslatedStrings[key].replace('{0}', languageName)})`;
}
});

const logUserReaction = (userReaction: string) => {
/* __GDPR__
"languagePackSuggestion:popup" : {
"owner": "TylerLeonhardt",
"userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"language": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
});
*/
this.telemetryService.publicLog('languagePackSuggestion:popup', { userReaction, language: locale });
};

const searchAction = {
label: translations['searchMarketplace'],
run: () => {
logUserReaction('search');
this.paneCompositeService.openPaneComposite(EXTENSIONS_VIEWLET_ID, ViewContainerLocation.Sidebar, true)
.then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer)
.then(viewlet => {
viewlet.search(`tag:lp-${searchLocale}`);
viewlet.focus();
});
}
};

const logUserReaction = (userReaction: string) => {
/* __GDPR__
"languagePackSuggestion:popup" : {
"owner": "TylerLeonhardt",
"userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"language": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('languagePackSuggestion:popup', { userReaction, language: locale });
};
const installAndRestartAction = {
label: translations['installAndRestart'],
run: () => {
logUserReaction('installAndRestart');
this.installExtension(extensionToInstall!).then(() => this.hostService.restart());
}
};

const searchAction = {
label: translations['searchMarketplace'],
run: () => {
logUserReaction('search');
this.paneCompositeService.openPaneComposite(EXTENSIONS_VIEWLET_ID, ViewContainerLocation.Sidebar, true)
.then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer)
.then(viewlet => {
viewlet.search(`tag:lp-${locale}`);
viewlet.focus();
});
}
};
const promptMessage = translations[promptMessageKey];

const installAndRestartAction = {
label: translations['installAndRestart'],
this.notificationService.prompt(
Severity.Info,
promptMessage,
[extensionToInstall ? installAndRestartAction : searchAction,
{
label: localize('neverAgain', "Don't Show Again"),
isSecondary: true,
run: () => {
logUserReaction('installAndRestart');
this.installExtension(extensionToInstall).then(() => this.hostService.restart());
languagePackSuggestionIgnoreList.push(locale);
this.storageService.store(
LANGUAGEPACK_SUGGESTION_IGNORE_STORAGE_KEY,
JSON.stringify(languagePackSuggestionIgnoreList),
StorageScope.APPLICATION,
StorageTarget.USER
);
logUserReaction('neverShowAgain');
}
};

const promptMessage = translations[promptMessageKey];

this.notificationService.prompt(
Severity.Info,
promptMessage,
[extensionToInstall ? installAndRestartAction : searchAction,
{
label: localize('neverAgain', "Don't Show Again"),
isSecondary: true,
run: () => {
languagePackSuggestionIgnoreList.push(locale);
this.storageService.store(
LANGUAGEPACK_SUGGESTION_IGNORE_STORAGE_KEY,
JSON.stringify(languagePackSuggestionIgnoreList),
StorageScope.APPLICATION,
StorageTarget.USER
);
logUserReaction('neverShowAgain');
}
}],
{
onCancel: () => {
logUserReaction('cancelled');
}
}],
{
onCancel: () => {
logUserReaction('cancelled');
}
);

});
});
}
);
});
});

}

private async isLanguageInstalled(language: string | undefined): Promise<boolean> {
private async isLocaleInstalled(locale: string): Promise<boolean> {
const installed = await this.extensionManagementService.getInstalled();
return installed.some(i => !!(i.manifest
&& i.manifest.contributes
&& i.manifest.contributes.localizations
&& i.manifest.contributes.localizations.length
&& i.manifest.contributes.localizations.some(l => l.languageId.toLowerCase() === language)));
&& i.manifest.contributes.localizations.some(l => locale.startsWith(l.languageId.toLowerCase()))));
}

private installExtension(extension: IGalleryExtension): Promise<void> {
Expand Down

1 comment on commit 5f67407

@bilalghufran
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i.manifest.contributes.localizations.so

Please sign in to comment.