mirror of
https://github.com/Microsoft/vscode
synced 2024-10-05 19:02:54 +00:00
Enable translations for extension code for the web (#155355)
* Initial attempt * alex feedback
This commit is contained in:
parent
12b1f7ceb4
commit
49394cc44d
|
@ -351,12 +351,20 @@ function scanBuiltinExtensions(extensionsRoot, exclude = []) {
|
|||
const children = fs.readdirSync(path.join(extensionsRoot, extensionFolder));
|
||||
const packageNLSPath = children.filter(child => child === 'package.nls.json')[0];
|
||||
const packageNLS = packageNLSPath ? JSON.parse(fs.readFileSync(path.join(extensionsRoot, extensionFolder, packageNLSPath)).toString()) : undefined;
|
||||
let browserNlsMetadataPath;
|
||||
if (packageJSON.browser) {
|
||||
const browserEntrypointFolderPath = path.join(extensionFolder, path.dirname(packageJSON.browser));
|
||||
if (fs.existsSync(path.join(extensionsRoot, browserEntrypointFolderPath, 'nls.metadata.json'))) {
|
||||
browserNlsMetadataPath = path.join(browserEntrypointFolderPath, 'nls.metadata.json');
|
||||
}
|
||||
}
|
||||
const readme = children.filter(child => /^readme(\.txt|\.md|)$/i.test(child))[0];
|
||||
const changelog = children.filter(child => /^changelog(\.txt|\.md|)$/i.test(child))[0];
|
||||
scannedExtensions.push({
|
||||
extensionPath: extensionFolder,
|
||||
packageJSON,
|
||||
packageNLS,
|
||||
browserNlsMetadataPath,
|
||||
readmePath: readme ? path.join(extensionFolder, readme) : undefined,
|
||||
changelogPath: changelog ? path.join(extensionFolder, changelog) : undefined,
|
||||
});
|
||||
|
|
|
@ -412,6 +412,7 @@ export interface IScannedBuiltinExtension {
|
|||
extensionPath: string;
|
||||
packageJSON: any;
|
||||
packageNLS?: any;
|
||||
browserNlsMetadataPath?: string;
|
||||
readmePath?: string;
|
||||
changelogPath?: string;
|
||||
}
|
||||
|
@ -436,6 +437,13 @@ export function scanBuiltinExtensions(extensionsRoot: string, exclude: string[]
|
|||
const children = fs.readdirSync(path.join(extensionsRoot, extensionFolder));
|
||||
const packageNLSPath = children.filter(child => child === 'package.nls.json')[0];
|
||||
const packageNLS = packageNLSPath ? JSON.parse(fs.readFileSync(path.join(extensionsRoot, extensionFolder, packageNLSPath)).toString()) : undefined;
|
||||
let browserNlsMetadataPath: string | undefined;
|
||||
if (packageJSON.browser) {
|
||||
const browserEntrypointFolderPath = path.join(extensionFolder, path.dirname(packageJSON.browser));
|
||||
if (fs.existsSync(path.join(extensionsRoot, browserEntrypointFolderPath, 'nls.metadata.json'))) {
|
||||
browserNlsMetadataPath = path.join(browserEntrypointFolderPath, 'nls.metadata.json');
|
||||
}
|
||||
}
|
||||
const readme = children.filter(child => /^readme(\.txt|\.md|)$/i.test(child))[0];
|
||||
const changelog = children.filter(child => /^changelog(\.txt|\.md|)$/i.test(child))[0];
|
||||
|
||||
|
@ -443,6 +451,7 @@ export function scanBuiltinExtensions(extensionsRoot: string, exclude: string[]
|
|||
extensionPath: extensionFolder,
|
||||
packageJSON,
|
||||
packageNLS,
|
||||
browserNlsMetadataPath,
|
||||
readmePath: readme ? path.join(extensionFolder, readme) : undefined,
|
||||
changelogPath: changelog ? path.join(extensionFolder, changelog) : undefined,
|
||||
});
|
||||
|
|
|
@ -70,6 +70,10 @@ function withNodeDefaults(/**@type WebpackConfig*/extConfig) {
|
|||
return merge(defaultConfig, extConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} context
|
||||
*/
|
||||
function nodePlugins(context) {
|
||||
// Need to find the top-most `package.json` file
|
||||
const folderName = path.relative(__dirname, context).split(/[\\\/]/)[0];
|
||||
|
@ -109,6 +113,13 @@ function withBrowserDefaults(/**@type WebpackConfig*/extConfig, /** @type Additi
|
|||
test: /\.ts$/,
|
||||
exclude: /node_modules/,
|
||||
use: [{
|
||||
// vscode-nls-dev loader:
|
||||
// * rewrite nls-calls
|
||||
loader: 'vscode-nls-dev/lib/webpack-loader',
|
||||
options: {
|
||||
base: path.join(extConfig.context, 'src')
|
||||
}
|
||||
}, {
|
||||
// configure TypeScript loader:
|
||||
// * enable sources maps for end-to-end source maps
|
||||
loader: 'ts-loader',
|
||||
|
@ -123,6 +134,7 @@ function withBrowserDefaults(/**@type WebpackConfig*/extConfig, /** @type Additi
|
|||
},
|
||||
externals: {
|
||||
'vscode': 'commonjs vscode', // ignored because it doesn't exist,
|
||||
'vscode-nls-web-data': 'commonjs vscode-nls-web-data', // ignored because this is injected by the webworker extension host
|
||||
'applicationinsights-native-metrics': 'commonjs applicationinsights-native-metrics', // ignored because we don't ship native module
|
||||
'@opentelemetry/tracing': 'commonjs @opentelemetry/tracing' // ignored because we don't ship this module
|
||||
},
|
||||
|
@ -138,30 +150,39 @@ function withBrowserDefaults(/**@type WebpackConfig*/extConfig, /** @type Additi
|
|||
},
|
||||
// yes, really source maps
|
||||
devtool: 'source-map',
|
||||
plugins: browserPlugins
|
||||
plugins: browserPlugins(extConfig.context)
|
||||
};
|
||||
|
||||
return merge(defaultConfig, extConfig);
|
||||
}
|
||||
|
||||
const browserPlugins = [
|
||||
new optimize.LimitChunkCountPlugin({
|
||||
maxChunks: 1
|
||||
}),
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
{ from: 'src', to: '.', globOptions: { ignore: ['**/test/**', '**/*.ts'] }, noErrorOnMissing: true }
|
||||
]
|
||||
}),
|
||||
new DefinePlugin({
|
||||
'process.platform': JSON.stringify('web'),
|
||||
'process.env': JSON.stringify({}),
|
||||
'process.env.BROWSER_ENV': JSON.stringify('true')
|
||||
})
|
||||
];
|
||||
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} context
|
||||
*/
|
||||
function browserPlugins(context) {
|
||||
// Need to find the top-most `package.json` file
|
||||
const folderName = path.relative(__dirname, context).split(/[\\\/]/)[0];
|
||||
const pkgPath = path.join(__dirname, folderName, 'package.json');
|
||||
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
||||
const id = `${pkg.publisher}.${pkg.name}`;
|
||||
return [
|
||||
new optimize.LimitChunkCountPlugin({
|
||||
maxChunks: 1
|
||||
}),
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
{ from: 'src', to: '.', globOptions: { ignore: ['**/test/**', '**/*.ts'] }, noErrorOnMissing: true }
|
||||
]
|
||||
}),
|
||||
new DefinePlugin({
|
||||
'process.platform': JSON.stringify('web'),
|
||||
'process.env': JSON.stringify({}),
|
||||
'process.env.BROWSER_ENV': JSON.stringify('true')
|
||||
}),
|
||||
new NLSBundlePlugin(id)
|
||||
];
|
||||
}
|
||||
|
||||
module.exports = withNodeDefaults;
|
||||
module.exports.node = withNodeDefaults;
|
||||
|
|
|
@ -37,7 +37,7 @@ module.exports = withBrowserDefaults({
|
|||
extension: './src/extension.browser.ts',
|
||||
},
|
||||
plugins: [
|
||||
...browserPlugins, // add plugins, don't replace inherited
|
||||
...browserPlugins(__dirname), // add plugins, don't replace inherited
|
||||
|
||||
// @ts-ignore
|
||||
new CopyPlugin({
|
||||
|
|
|
@ -323,6 +323,7 @@ export interface IExtension {
|
|||
readonly changelogUrl?: URI;
|
||||
readonly isValid: boolean;
|
||||
readonly validations: readonly [Severity, string][];
|
||||
readonly browserNlsBundleUris?: { [language: string]: URI };
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -389,6 +390,7 @@ export interface IRelaxedExtensionDescription extends IRelaxedExtensionManifest
|
|||
isUserBuiltin: boolean;
|
||||
isUnderDevelopment: boolean;
|
||||
extensionLocation: URI;
|
||||
browserNlsBundleUris?: { [language: string]: URI };
|
||||
}
|
||||
|
||||
export type IExtensionDescription = Readonly<IRelaxedExtensionDescription>;
|
||||
|
|
|
@ -280,10 +280,18 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
|||
|
||||
public async getExtension(extensionId: string): Promise<IExtensionDescription | undefined> {
|
||||
const ext = await this._mainThreadExtensionsProxy.$getExtension(extensionId);
|
||||
let browserNlsBundleUris: { [language: string]: URI } | undefined;
|
||||
if (ext?.browserNlsBundleUris) {
|
||||
browserNlsBundleUris = {};
|
||||
for (const language of Object.keys(ext.browserNlsBundleUris)) {
|
||||
browserNlsBundleUris[language] = URI.revive(ext.browserNlsBundleUris[language]);
|
||||
}
|
||||
}
|
||||
return ext && {
|
||||
...ext,
|
||||
identifier: new ExtensionIdentifier(ext.identifier.value),
|
||||
extensionLocation: URI.revive(ext.extensionLocation),
|
||||
browserNlsBundleUris
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -465,7 +473,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
|||
|
||||
const activationTimesBuilder = new ExtensionActivationTimesBuilder(reason.startup);
|
||||
return Promise.all([
|
||||
this._loadCommonJSModule<IExtensionModule>(extensionDescription.identifier, joinPath(extensionDescription.extensionLocation, entryPoint), activationTimesBuilder),
|
||||
this._loadCommonJSModule<IExtensionModule>(extensionDescription, joinPath(extensionDescription.extensionLocation, entryPoint), activationTimesBuilder),
|
||||
this._loadExtensionContext(extensionDescription)
|
||||
]).then(values => {
|
||||
performance.mark(`code/extHost/willActivateExtension/${extensionDescription.identifier.value}`);
|
||||
|
@ -923,7 +931,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
|||
|
||||
protected abstract _beforeAlmostReadyToRunExtensions(): Promise<void>;
|
||||
protected abstract _getEntryPoint(extensionDescription: IExtensionDescription): string | undefined;
|
||||
protected abstract _loadCommonJSModule<T extends object | undefined>(extensionId: ExtensionIdentifier | null, module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T>;
|
||||
protected abstract _loadCommonJSModule<T extends object | undefined>(extensionId: IExtensionDescription | null, module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T>;
|
||||
public abstract $setRemoteEnvironment(env: { [key: string]: string | null }): Promise<void>;
|
||||
}
|
||||
|
||||
|
|
|
@ -120,7 +120,14 @@ export class ExtensionHostMain {
|
|||
}
|
||||
|
||||
private static _transform(initData: IExtensionHostInitData, rpcProtocol: RPCProtocol): IExtensionHostInitData {
|
||||
initData.allExtensions.forEach((ext) => (<any>ext).extensionLocation = URI.revive(rpcProtocol.transformIncomingURIs(ext.extensionLocation)));
|
||||
initData.allExtensions.forEach((ext) => {
|
||||
(<any>ext).extensionLocation = URI.revive(rpcProtocol.transformIncomingURIs(ext.extensionLocation));
|
||||
const browserNlsBundleUris: { [language: string]: URI } = {};
|
||||
if (ext.browserNlsBundleUris) {
|
||||
Object.keys(ext.browserNlsBundleUris).forEach(lang => browserNlsBundleUris[lang] = URI.revive(rpcProtocol.transformIncomingURIs(ext.browserNlsBundleUris![lang])));
|
||||
(<any>ext).browserNlsBundleUris = browserNlsBundleUris;
|
||||
}
|
||||
});
|
||||
initData.environment.appRoot = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.appRoot));
|
||||
const extDevLocs = initData.environment.extensionDevelopmentLocationURI;
|
||||
if (extDevLocs) {
|
||||
|
|
|
@ -12,7 +12,7 @@ import { AbstractExtHostExtensionService } from 'vs/workbench/api/common/extHost
|
|||
import { ExtHostDownloadService } from 'vs/workbench/api/node/extHostDownloadService';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtensionRuntime } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { CLIServer } from 'vs/workbench/api/node/extHostCLIServer';
|
||||
import { realpathSync } from 'vs/base/node/extpath';
|
||||
|
@ -89,7 +89,7 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
|
|||
return extensionDescription.main;
|
||||
}
|
||||
|
||||
protected _loadCommonJSModule<T>(extensionId: ExtensionIdentifier | null, module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
|
||||
protected _loadCommonJSModule<T>(extension: IExtensionDescription | null, module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
|
||||
if (module.scheme !== Schemas.file) {
|
||||
throw new Error(`Cannot load URI: '${module}', must be of file-scheme`);
|
||||
}
|
||||
|
@ -97,16 +97,17 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
|
|||
activationTimesBuilder.codeLoadingStart();
|
||||
this._logService.trace(`ExtensionService#loadCommonJSModule ${module.toString(true)}`);
|
||||
this._logService.flush();
|
||||
const extensionId = extension?.identifier.value;
|
||||
try {
|
||||
if (extensionId) {
|
||||
performance.mark(`code/extHost/willLoadExtensionCode/${extensionId.value}`);
|
||||
performance.mark(`code/extHost/willLoadExtensionCode/${extensionId}`);
|
||||
}
|
||||
r = require.__$__nodeRequire<T>(module.fsPath);
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
} finally {
|
||||
if (extensionId) {
|
||||
performance.mark(`code/extHost/didLoadExtensionCode/${extensionId.value}`);
|
||||
performance.mark(`code/extHost/didLoadExtensionCode/${extensionId}`);
|
||||
}
|
||||
activationTimesBuilder.codeLoadingStop();
|
||||
}
|
||||
|
|
|
@ -8,10 +8,11 @@ import { ExtensionActivationTimesBuilder } from 'vs/workbench/api/common/extHost
|
|||
import { AbstractExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterceptor';
|
||||
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtensionRuntime } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { ExtHostConsoleForwarder } from 'vs/workbench/api/worker/extHostConsoleForwarder';
|
||||
import { Language } from 'vs/base/common/platform';
|
||||
|
||||
class WorkerRequireInterceptor extends RequireInterceptor {
|
||||
|
||||
|
@ -55,10 +56,11 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
|
|||
return extensionDescription.browser;
|
||||
}
|
||||
|
||||
protected async _loadCommonJSModule<T extends object | undefined>(extensionId: ExtensionIdentifier | null, module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
|
||||
protected async _loadCommonJSModule<T extends object | undefined>(extension: IExtensionDescription | null, module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
|
||||
module = module.with({ path: ensureSuffix(module.path, '.js') });
|
||||
const extensionId = extension?.identifier.value;
|
||||
if (extensionId) {
|
||||
performance.mark(`code/extHost/willFetchExtensionCode/${extensionId.value}`);
|
||||
performance.mark(`code/extHost/willFetchExtensionCode/${extensionId}`);
|
||||
}
|
||||
|
||||
// First resolve the extension entry point URI to something we can load using `fetch`
|
||||
|
@ -67,7 +69,7 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
|
|||
const browserUri = URI.revive(await this._mainThreadExtensionsProxy.$asBrowserUri(module));
|
||||
const response = await fetch(browserUri.toString(true));
|
||||
if (extensionId) {
|
||||
performance.mark(`code/extHost/didFetchExtensionCode/${extensionId.value}`);
|
||||
performance.mark(`code/extHost/didFetchExtensionCode/${extensionId}`);
|
||||
}
|
||||
|
||||
if (response.status !== 200) {
|
||||
|
@ -85,7 +87,7 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
|
|||
initFn = new Function('module', 'exports', 'require', fullSource);
|
||||
} catch (err) {
|
||||
if (extensionId) {
|
||||
console.error(`Loading code for extension ${extensionId.value} failed: ${err.message}`);
|
||||
console.error(`Loading code for extension ${extensionId} failed: ${err.message}`);
|
||||
} else {
|
||||
console.error(`Loading code failed: ${err.message}`);
|
||||
}
|
||||
|
@ -94,10 +96,17 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
|
|||
throw err;
|
||||
}
|
||||
|
||||
const strings: { [key: string]: string[] } = await this.fetchTranslatedStrings(extension);
|
||||
|
||||
// define commonjs globals: `module`, `exports`, and `require`
|
||||
const _exports = {};
|
||||
const _module = { exports: _exports };
|
||||
const _require = (request: string) => {
|
||||
// In order to keep vscode-nls synchronous, we prefetched the translations above
|
||||
// and then return them here when the extension is loaded.
|
||||
if (request === 'vscode-nls-web-data') {
|
||||
return strings;
|
||||
}
|
||||
const result = this._fakeModules!.getModule(request, module);
|
||||
if (result === undefined) {
|
||||
throw new Error(`Cannot load module '${request}'`);
|
||||
|
@ -108,13 +117,13 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
|
|||
try {
|
||||
activationTimesBuilder.codeLoadingStart();
|
||||
if (extensionId) {
|
||||
performance.mark(`code/extHost/willLoadExtensionCode/${extensionId.value}`);
|
||||
performance.mark(`code/extHost/willLoadExtensionCode/${extensionId}`);
|
||||
}
|
||||
initFn(_module, _exports, _require);
|
||||
return <T>(_module.exports !== _exports ? _module.exports : _exports);
|
||||
} finally {
|
||||
if (extensionId) {
|
||||
performance.mark(`code/extHost/didLoadExtensionCode/${extensionId.value}`);
|
||||
performance.mark(`code/extHost/didLoadExtensionCode/${extensionId}`);
|
||||
}
|
||||
activationTimesBuilder.codeLoadingStop();
|
||||
}
|
||||
|
@ -135,6 +144,44 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
|
|||
await timeout(10);
|
||||
}
|
||||
}
|
||||
|
||||
private async fetchTranslatedStrings(extension: IExtensionDescription | null): Promise<{ [key: string]: string[] }> {
|
||||
let strings: { [key: string]: string[] } = {};
|
||||
if (!extension) {
|
||||
return {};
|
||||
}
|
||||
const translationsUri = Language.isDefaultVariant()
|
||||
// If we are in the default variant, load the translations for en only.
|
||||
? extension.browserNlsBundleUris?.en
|
||||
// Otherwise load the translations for the current locale with English as a fallback.
|
||||
: extension.browserNlsBundleUris?.[Language.value()] ?? extension.browserNlsBundleUris?.en;
|
||||
if (extension && translationsUri) {
|
||||
try {
|
||||
const response = await fetch(translationsUri.toString(true));
|
||||
if (!response.ok) {
|
||||
throw new Error(await response.text());
|
||||
}
|
||||
strings = await response.json();
|
||||
} catch (e) {
|
||||
try {
|
||||
console.error(`Failed to load translations for ${extension.identifier.value} from ${translationsUri}: ${e.message}`);
|
||||
const englishStrings = extension.browserNlsBundleUris?.en;
|
||||
if (englishStrings) {
|
||||
const response = await fetch(englishStrings.toString(true));
|
||||
if (!response.ok) {
|
||||
throw new Error(await response.text());
|
||||
}
|
||||
strings = await response.json();
|
||||
}
|
||||
throw new Error('No English strings found');
|
||||
} catch (e) {
|
||||
// TODO what should this do? We really shouldn't ever be here...
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return strings;
|
||||
}
|
||||
}
|
||||
|
||||
function ensureSuffix(path: string, suffix: string): string {
|
||||
|
|
|
@ -20,6 +20,7 @@ interface IBundledExtension {
|
|||
extensionPath: string;
|
||||
packageJSON: IExtensionManifest;
|
||||
packageNLS?: any;
|
||||
browserNlsMetadataPath?: string;
|
||||
readmePath?: string;
|
||||
changelogPath?: string;
|
||||
}
|
||||
|
@ -66,11 +67,19 @@ export class BuiltinExtensionsScannerService implements IBuiltinExtensionsScanne
|
|||
|
||||
this.builtinExtensionsPromises = bundledExtensions.map(async e => {
|
||||
const id = getGalleryExtensionId(e.packageJSON.publisher, e.packageJSON.name);
|
||||
const browserNlsBundleUris: { [language: string]: URI } = {};
|
||||
if (e.browserNlsMetadataPath) {
|
||||
if (this.nlsUrl) {
|
||||
browserNlsBundleUris[Language.value()] = uriIdentityService.extUri.joinPath(this.nlsUrl, id, 'main');
|
||||
}
|
||||
browserNlsBundleUris.en = uriIdentityService.extUri.resolvePath(builtinExtensionsServiceUrl!, e.browserNlsMetadataPath);
|
||||
}
|
||||
return {
|
||||
identifier: { id },
|
||||
location: uriIdentityService.extUri.joinPath(builtinExtensionsServiceUrl!, e.extensionPath),
|
||||
type: ExtensionType.System,
|
||||
isBuiltin: true,
|
||||
browserNlsBundleUris,
|
||||
manifest: e.packageNLS ? await this.localizeManifest(id, e.packageJSON, e.packageNLS) : e.packageJSON,
|
||||
readmeUrl: e.readmePath ? uriIdentityService.extUri.joinPath(builtinExtensionsServiceUrl!, e.readmePath) : undefined,
|
||||
changelogUrl: e.changelogPath ? uriIdentityService.extUri.joinPath(builtinExtensionsServiceUrl!, e.changelogPath) : undefined,
|
||||
|
|
|
@ -76,6 +76,7 @@ interface IWebExtension {
|
|||
// deprecated in favor of packageNLSUris & fallbackPackageNLSUri
|
||||
packageNLSUri?: URI;
|
||||
packageNLSUris?: Map<string, URI>;
|
||||
bundleNLSUris?: Map<string, URI>;
|
||||
fallbackPackageNLSUri?: URI;
|
||||
metadata?: Metadata;
|
||||
}
|
||||
|
@ -444,7 +445,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
|||
}
|
||||
|
||||
async addExtension(location: URI, metadata: Metadata, profileLocation?: URI): Promise<IScannedExtension> {
|
||||
const webExtension = await this.toWebExtension(location, undefined, undefined, undefined, undefined, undefined, metadata);
|
||||
const webExtension = await this.toWebExtension(location, undefined, undefined, undefined, undefined, undefined, undefined, metadata);
|
||||
return this.addWebExtension(webExtension, profileLocation);
|
||||
}
|
||||
|
||||
|
@ -546,7 +547,8 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
|||
}
|
||||
extensionLocation = galleryExtension.properties.targetPlatform === TargetPlatform.WEB ? extensionLocation.with({ query: `${extensionLocation.query ? `${extensionLocation.query}&` : ''}target=${galleryExtension.properties.targetPlatform}` }) : extensionLocation;
|
||||
const extensionResources = await this.listExtensionResources(extensionLocation);
|
||||
const packageNLSResources = this.getNLSResourceMapFromResources(extensionResources);
|
||||
const packageNLSResources = this.getPackageNLSResourceMapFromResources(extensionResources);
|
||||
const bundleNLSResources = this.getBundleNLSResourceMapFromResources(extensionResources);
|
||||
|
||||
// The fallback, in English, will fill in any gaps missing in the localized file.
|
||||
const fallbackPackageNLSResource = extensionResources.find(e => basename(e) === 'package.nls.json');
|
||||
|
@ -554,13 +556,14 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
|||
extensionLocation,
|
||||
galleryExtension.identifier,
|
||||
packageNLSResources,
|
||||
bundleNLSResources,
|
||||
fallbackPackageNLSResource ? URI.parse(fallbackPackageNLSResource) : null,
|
||||
galleryExtension.assets.readme ? URI.parse(galleryExtension.assets.readme.uri) : undefined,
|
||||
galleryExtension.assets.changelog ? URI.parse(galleryExtension.assets.changelog.uri) : undefined,
|
||||
metadata);
|
||||
}
|
||||
|
||||
private getNLSResourceMapFromResources(extensionResources: string[]): Map<string, URI> {
|
||||
private getPackageNLSResourceMapFromResources(extensionResources: string[]): Map<string, URI> {
|
||||
const packageNLSResources = new Map<string, URI>();
|
||||
extensionResources.forEach(e => {
|
||||
// Grab all package.nls.{language}.json files
|
||||
|
@ -572,7 +575,22 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
|||
return packageNLSResources;
|
||||
}
|
||||
|
||||
private async toWebExtension(extensionLocation: URI, identifier?: IExtensionIdentifier, packageNLSUris?: Map<string, URI>, fallbackPackageNLSUri?: URI | null, readmeUri?: URI, changelogUri?: URI, metadata?: Metadata): Promise<IWebExtension> {
|
||||
private getBundleNLSResourceMapFromResources(extensionResources: string[]): Map<string, URI> {
|
||||
const bundleNLSResources = new Map<string, URI>();
|
||||
extensionResources.forEach(e => {
|
||||
// Grab all nls.bundle.{language}.json files
|
||||
const regexResult = /nls\.bundle\.([\w-]+)\.json/.exec(basename(e));
|
||||
if (regexResult?.[1]) {
|
||||
bundleNLSResources.set(regexResult[1], URI.parse(e));
|
||||
}
|
||||
if (basename(e) === 'nls.metadata.json') {
|
||||
bundleNLSResources.set('en', URI.parse(e));
|
||||
}
|
||||
});
|
||||
return bundleNLSResources;
|
||||
}
|
||||
|
||||
private async toWebExtension(extensionLocation: URI, identifier?: IExtensionIdentifier, packageNLSUris?: Map<string, URI>, bundleNLSUris?: Map<string, URI>, fallbackPackageNLSUri?: URI | null, readmeUri?: URI, changelogUri?: URI, metadata?: Metadata): Promise<IWebExtension> {
|
||||
let packageJSONContent;
|
||||
try {
|
||||
packageJSONContent = await this.extensionResourceLoaderService.readExtensionResource(joinPath(extensionLocation, 'package.json'));
|
||||
|
@ -598,6 +616,21 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
|||
}
|
||||
}
|
||||
|
||||
if (bundleNLSUris === undefined) {
|
||||
const englishStringsUri = joinPath(
|
||||
this.uriIdentityService.extUri.dirname(joinPath(extensionLocation, manifest.browser)),
|
||||
'nls.metadata.json'
|
||||
);
|
||||
|
||||
try {
|
||||
await this.extensionResourceLoaderService.readExtensionResource(englishStringsUri);
|
||||
bundleNLSUris = new Map();
|
||||
bundleNLSUris.set('en', englishStringsUri);
|
||||
} catch (error) {
|
||||
// noop if file doesn't exist
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
identifier: { id: getGalleryExtensionId(manifest.publisher, manifest.name), uuid: identifier?.uuid },
|
||||
version: manifest.version,
|
||||
|
@ -605,6 +638,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
|||
readmeUri,
|
||||
changelogUri,
|
||||
packageNLSUris,
|
||||
bundleNLSUris,
|
||||
fallbackPackageNLSUri: fallbackPackageNLSUri ? fallbackPackageNLSUri : undefined,
|
||||
metadata,
|
||||
};
|
||||
|
@ -661,12 +695,20 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
|||
}
|
||||
}
|
||||
|
||||
const browserNlsBundleUris: { [language: string]: URI } = {};
|
||||
if (webExtension.bundleNLSUris) {
|
||||
for (const [language, uri] of webExtension.bundleNLSUris) {
|
||||
browserNlsBundleUris[language] = uri;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
identifier: { id: webExtension.identifier.id, uuid: webExtension.identifier.uuid || uuid },
|
||||
location: webExtension.location,
|
||||
manifest,
|
||||
type,
|
||||
isBuiltin,
|
||||
browserNlsBundleUris,
|
||||
readmeUrl: webExtension.readmeUri,
|
||||
changelogUrl: webExtension.changelogUri,
|
||||
metadata: webExtension.metadata,
|
||||
|
@ -708,7 +750,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
|||
if (!e.packageNLSUris && e.packageNLSUri) {
|
||||
e.fallbackPackageNLSUri = e.packageNLSUri;
|
||||
const extensionResources = await this.listExtensionResources(e.location);
|
||||
e.packageNLSUris = this.getNLSResourceMapFromResources(extensionResources);
|
||||
e.packageNLSUris = this.getPackageNLSResourceMapFromResources(extensionResources);
|
||||
e.packageNLSUri = undefined;
|
||||
}
|
||||
return e;
|
||||
|
|
|
@ -587,7 +587,8 @@ export function toExtensionDescription(extension: IExtension, isUnderDevelopment
|
|||
extensionLocation: extension.location,
|
||||
...extension.manifest,
|
||||
uuid: extension.identifier.uuid,
|
||||
targetPlatform: extension.targetPlatform
|
||||
targetPlatform: extension.targetPlatform,
|
||||
browserNlsBundleUris: extension.browserNlsBundleUris
|
||||
};
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue