UNPKG

@sussudio/platform

Version:

Internal APIs for VS Code's service injection the base services.

272 lines (271 loc) 10 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? (desc = Object.getOwnPropertyDescriptor(target, key)) : desc, d; if (typeof Reflect === 'object' && typeof Reflect.decorate === 'function') r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if ((d = decorators[i])) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); }; }; import { createHash } from 'crypto'; import { equals } from '@sussudio/base/common/arrays.mjs'; import { Queue } from '@sussudio/base/common/async.mjs'; import { Disposable } from '@sussudio/base/common/lifecycle.mjs'; import { Schemas } from '@sussudio/base/common/network.mjs'; import { join } from '@sussudio/base/common/path.mjs'; import { Promises } from '@sussudio/base/node/pfs.mjs'; import { INativeEnvironmentService } from '../../environment/common/environment.mjs'; import { IExtensionGalleryService, IExtensionManagementService, } from '../../extensionManagement/common/extensionManagement.mjs'; import { areSameExtensions } from '../../extensionManagement/common/extensionManagementUtil.mjs'; import { ILogService } from '../../log/common/log.mjs'; import { LanguagePackBaseService } from '../common/languagePacks.mjs'; import { Language } from '@sussudio/base/common/platform.mjs'; import { URI } from '@sussudio/base/common/uri.mjs'; let NativeLanguagePackService = class NativeLanguagePackService extends LanguagePackBaseService { extensionManagementService; logService; cache; constructor(extensionManagementService, environmentService, extensionGalleryService, logService) { super(extensionGalleryService); this.extensionManagementService = extensionManagementService; this.logService = logService; this.cache = this._register(new LanguagePacksCache(environmentService, logService)); this.extensionManagementService.registerParticipant({ postInstall: async (extension) => { return this.postInstallExtension(extension); }, postUninstall: async (extension) => { return this.postUninstallExtension(extension); }, }); } async getBuiltInExtensionTranslationsUri(id) { const packs = await this.cache.getLanguagePacks(); const pack = packs[Language.value()]; if (!pack) { this.logService.warn(`No language pack found for ${Language.value()}`); return undefined; } const translation = pack.translations[id]; return translation ? URI.file(translation) : undefined; } async getInstalledLanguages() { const languagePacks = await this.cache.getLanguagePacks(); const languages = Object.keys(languagePacks).map((locale) => { const languagePack = languagePacks[locale]; const baseQuickPick = this.createQuickPickItem(locale, languagePack.label); return { ...baseQuickPick, extensionId: languagePack.extensions[0].extensionIdentifier.id, }; }); languages.push({ ...this.createQuickPickItem('en', 'English'), extensionId: 'default', }); languages.sort((a, b) => a.label.localeCompare(b.label)); return languages; } async postInstallExtension(extension) { if ( extension && extension.manifest && extension.manifest.contributes && extension.manifest.contributes.localizations && extension.manifest.contributes.localizations.length ) { this.logService.info('Adding language packs from the extension', extension.identifier.id); await this.update(); } } async postUninstallExtension(extension) { const languagePacks = await this.cache.getLanguagePacks(); if ( Object.keys(languagePacks).some( (language) => languagePacks[language] && languagePacks[language].extensions.some((e) => areSameExtensions(e.extensionIdentifier, extension.identifier), ), ) ) { this.logService.info('Removing language packs from the extension', extension.identifier.id); await this.update(); } } async update() { const [current, installed] = await Promise.all([ this.cache.getLanguagePacks(), this.extensionManagementService.getInstalled(), ]); const updated = await this.cache.update(installed); return !equals(Object.keys(current), Object.keys(updated)); } }; NativeLanguagePackService = __decorate( [ __param(0, IExtensionManagementService), __param(1, INativeEnvironmentService), __param(2, IExtensionGalleryService), __param(3, ILogService), ], NativeLanguagePackService, ); export { NativeLanguagePackService }; let LanguagePacksCache = class LanguagePacksCache extends Disposable { logService; languagePacks = {}; languagePacksFilePath; languagePacksFileLimiter; initializedCache; constructor(environmentService, logService) { super(); this.logService = logService; this.languagePacksFilePath = join(environmentService.userDataPath, 'languagepacks.json'); this.languagePacksFileLimiter = new Queue(); } getLanguagePacks() { // if queue is not empty, fetch from disk if (this.languagePacksFileLimiter.size || !this.initializedCache) { return this.withLanguagePacks().then(() => this.languagePacks); } return Promise.resolve(this.languagePacks); } update(extensions) { return this.withLanguagePacks((languagePacks) => { Object.keys(languagePacks).forEach((language) => delete languagePacks[language]); this.createLanguagePacksFromExtensions(languagePacks, ...extensions); }).then(() => this.languagePacks); } createLanguagePacksFromExtensions(languagePacks, ...extensions) { for (const extension of extensions) { if ( extension && extension.manifest && extension.manifest.contributes && extension.manifest.contributes.localizations && extension.manifest.contributes.localizations.length ) { this.createLanguagePacksFromExtension(languagePacks, extension); } } Object.keys(languagePacks).forEach((languageId) => this.updateHash(languagePacks[languageId])); } createLanguagePacksFromExtension(languagePacks, extension) { const extensionIdentifier = extension.identifier; const localizations = extension.manifest.contributes && extension.manifest.contributes.localizations ? extension.manifest.contributes.localizations : []; for (const localizationContribution of localizations) { if (extension.location.scheme === Schemas.file && isValidLocalization(localizationContribution)) { let languagePack = languagePacks[localizationContribution.languageId]; if (!languagePack) { languagePack = { hash: '', extensions: [], translations: {}, label: localizationContribution.localizedLanguageName ?? localizationContribution.languageName, }; languagePacks[localizationContribution.languageId] = languagePack; } const extensionInLanguagePack = languagePack.extensions.filter((e) => areSameExtensions(e.extensionIdentifier, extensionIdentifier), )[0]; if (extensionInLanguagePack) { extensionInLanguagePack.version = extension.manifest.version; } else { languagePack.extensions.push({ extensionIdentifier, version: extension.manifest.version }); } for (const translation of localizationContribution.translations) { languagePack.translations[translation.id] = join(extension.location.fsPath, translation.path); } } } } updateHash(languagePack) { if (languagePack) { const md5 = createHash('md5'); for (const extension of languagePack.extensions) { md5.update(extension.extensionIdentifier.uuid || extension.extensionIdentifier.id).update(extension.version); } languagePack.hash = md5.digest('hex'); } } withLanguagePacks(fn = () => null) { return this.languagePacksFileLimiter.queue(() => { let result = null; return Promises.readFile(this.languagePacksFilePath, 'utf8') .then(undefined, (err) => (err.code === 'ENOENT' ? Promise.resolve('{}') : Promise.reject(err))) .then((raw) => { try { return JSON.parse(raw); } catch (e) { return {}; } }) .then((languagePacks) => { result = fn(languagePacks); return languagePacks; }) .then((languagePacks) => { for (const language of Object.keys(languagePacks)) { if (!languagePacks[language]) { delete languagePacks[language]; } } this.languagePacks = languagePacks; this.initializedCache = true; const raw = JSON.stringify(this.languagePacks); this.logService.debug('Writing language packs', raw); return Promises.writeFile(this.languagePacksFilePath, raw); }) .then( () => result, (error) => this.logService.error(error), ); }); } }; LanguagePacksCache = __decorate([__param(0, INativeEnvironmentService), __param(1, ILogService)], LanguagePacksCache); function isValidLocalization(localization) { if (typeof localization.languageId !== 'string') { return false; } if (!Array.isArray(localization.translations) || localization.translations.length === 0) { return false; } for (const translation of localization.translations) { if (typeof translation.id !== 'string') { return false; } if (typeof translation.path !== 'string') { return false; } } if (localization.languageName && typeof localization.languageName !== 'string') { return false; } if (localization.localizedLanguageName && typeof localization.localizedLanguageName !== 'string') { return false; } return true; }