From 171e00f7fc680c917881771d3ecddf15ce3d83bc Mon Sep 17 00:00:00 2001 From: qiyongxiao1 Date: Tue, 15 Sep 2026 13:13:38 +0800 Subject: [PATCH] fix: resolve extension install 404 and language pack detection - Fix extension install 404 error: Use gallery manifest asset URL directly instead of constructing from resourceUrlTemplate, which may point to Open VSX versions that don't exist (e.g., Microsoft Marketplace returns newer versions) - Fix language pack detection: Scan installed extensions for language packs in addition to checking localStorage, and properly map extension names to locale codes (zh-hans -> zh-cn, zh-hant -> zh-tw) --- .../languagePacks/browser/languagePacks.ts | 55 ++++++++++++++++--- .../browser/webExtensionsScannerService.ts | 49 ++++++++++++----- 2 files changed, 83 insertions(+), 21 deletions(-) diff --git a/src/vs/platform/languagePacks/browser/languagePacks.ts b/src/vs/platform/languagePacks/browser/languagePacks.ts index 584abc461..1bd86c554 100644 --- a/src/vs/platform/languagePacks/browser/languagePacks.ts +++ b/src/vs/platform/languagePacks/browser/languagePacks.ts @@ -7,11 +7,15 @@ import { URI } from '../../../base/common/uri.js'; import { ILanguagePackItem, ILanguagePackService } from '../common/languagePacks.js'; import { IExtensionGalleryService } from '../../extensionManagement/common/extensionManagement.js'; import { CancellationToken } from '../../../base/common/cancellation.js'; +import { IExtensionService } from '../../../workbench/services/extensions/common/extensions.js'; export class WebLanguagePacksService implements ILanguagePackService { declare readonly _serviceBrand: undefined; - constructor(@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService) {} + constructor( + @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, + @IExtensionService private readonly extensionService: IExtensionService + ) {} async getBuiltInExtensionTranslationsUri(_id: string, _language: string): Promise { return undefined; @@ -45,20 +49,43 @@ export class WebLanguagePacksService implements ILanguagePackService { } async getInstalledLanguages(): Promise { + const items: ILanguagePackItem[] = []; + const seen = new Set(); + + // Check localStorage for explicitly set locale const extensionId = localStorage.getItem('vscode.nls.languagePackExtensionId'); const locale = localStorage.getItem('vscode.nls.locale'); - if (!extensionId || !locale) { - return []; - } - - return [ - { + if (extensionId && locale) { + items.push({ id: locale, label: this.getLanguageLabel(locale), extensionId + }); + seen.add(locale.toLowerCase()); + } + + // Also scan installed extensions for language packs + try { + const extensions = this.extensionService.extensions; + for (const ext of extensions) { + if (ext.identifier && ext.name.startsWith('vscode-language-pack-')) { + const extLocale = this.getLocaleFromExtensionName(ext.name); + if (extLocale && !seen.has(extLocale.toLowerCase())) { + items.push({ + id: extLocale, + label: this.getLanguageLabel(extLocale), + extensionId: ext.identifier.value + }); + seen.add(extLocale.toLowerCase()); + } + } } - ]; + } catch { + // ignore errors scanning extensions + } + + return items; } private getLanguageLabel(locale: string): string { @@ -80,4 +107,16 @@ export class WebLanguagePacksService implements ILanguagePackService { }; return labels[locale.toLowerCase()] ?? locale; } + + // Map extension name suffixes to locale codes + // e.g. vscode-language-pack-zh-hans -> zh-cn + private getLocaleFromExtensionName(name: string): string | undefined { + const nameToLocale: Record = { + 'zh-hans': 'zh-cn', + 'zh-hant': 'zh-tw', + 'pt-br': 'pt-br' + }; + const suffix = name.replace('vscode-language-pack-', ''); + return nameToLocale[suffix] ?? suffix; + } } diff --git a/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts b/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts index 137bfcc2a..a7739d992 100644 --- a/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts +++ b/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts @@ -877,16 +877,37 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten galleryExtension: IGalleryExtension, metadata?: Metadata ): Promise { - const extensionLocation = await this.extensionResourceLoaderService.getExtensionGalleryResourceURL( - { - publisher: galleryExtension.publisher, - name: galleryExtension.name, - version: galleryExtension.version, - targetPlatform: - galleryExtension.properties.targetPlatform === TargetPlatform.WEB ? TargetPlatform.WEB : undefined - }, - 'extension' - ); + // Use the gallery extension's manifest asset URL directly (already proxied + // correctly by the marketplace proxy for both Microsoft and Open VSX sources) + // instead of constructing a URL from resourceUrlTemplate which may point to + // a version that doesn't exist on Open VSX. + let manifest: IExtensionManifest | undefined; + let extensionLocation: URI | undefined; + + if (galleryExtension.assets.manifest) { + try { + const manifestUri = URI.parse(galleryExtension.assets.manifest.uri); + const content = await this.extensionResourceLoaderService.readExtensionResource(manifestUri); + manifest = JSON.parse(content); + // Use the manifest URI's directory as the extension location + extensionLocation = joinPath(manifestUri, '..'); + } catch (error) { + this.logService.warn(`Failed to fetch manifest from gallery asset, falling back to resourceUrlTemplate`, getErrorMessage(error)); + } + } + + if (!extensionLocation) { + extensionLocation = await this.extensionResourceLoaderService.getExtensionGalleryResourceURL( + { + publisher: galleryExtension.publisher, + name: galleryExtension.name, + version: galleryExtension.version, + targetPlatform: + galleryExtension.properties.targetPlatform === TargetPlatform.WEB ? TargetPlatform.WEB : undefined + }, + 'extension' + ); + } if (!extensionLocation) { throw new Error('No extension gallery service configured.'); @@ -897,7 +918,8 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten galleryExtension.identifier, galleryExtension.assets.readme ? URI.parse(galleryExtension.assets.readme.uri) : undefined, galleryExtension.assets.changelog ? URI.parse(galleryExtension.assets.changelog.uri) : undefined, - metadata + metadata, + manifest ); } @@ -906,7 +928,8 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten identifier?: IExtensionIdentifier, readmeUri?: URI, changelogUri?: URI, - metadata?: Metadata + metadata?: Metadata, + manifest?: IExtensionManifest ): Promise { const extensionResources = await this.listExtensionResources(extensionLocation); const packageNLSResources = this.getPackageNLSResourceMapFromResources(extensionResources); @@ -916,7 +939,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten return this.toWebExtension( extensionLocation, identifier, - undefined, + manifest, packageNLSResources, fallbackPackageNLSResource ? URI.parse(fallbackPackageNLSResource) : null, readmeUri,