@theia/plugin-ext 1.37.0-next.8 → 1.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/lib/common/language-pack-service.d.ts +16 -0
  2. package/lib/common/language-pack-service.d.ts.map +1 -0
  3. package/lib/common/language-pack-service.js +21 -0
  4. package/lib/common/language-pack-service.js.map +1 -0
  5. package/lib/common/plugin-api-rpc-model.d.ts +6 -0
  6. package/lib/common/plugin-api-rpc-model.d.ts.map +1 -1
  7. package/lib/common/plugin-api-rpc-model.js +6 -1
  8. package/lib/common/plugin-api-rpc-model.js.map +1 -1
  9. package/lib/common/plugin-api-rpc.d.ts +22 -1
  10. package/lib/common/plugin-api-rpc.d.ts.map +1 -1
  11. package/lib/common/plugin-api-rpc.js +2 -1
  12. package/lib/common/plugin-api-rpc.js.map +1 -1
  13. package/lib/common/plugin-protocol.d.ts +2 -0
  14. package/lib/common/plugin-protocol.d.ts.map +1 -1
  15. package/lib/common/plugin-protocol.js.map +1 -1
  16. package/lib/hosted/browser/hosted-plugin.d.ts +6 -2
  17. package/lib/hosted/browser/hosted-plugin.d.ts.map +1 -1
  18. package/lib/hosted/browser/hosted-plugin.js +26 -5
  19. package/lib/hosted/browser/hosted-plugin.js.map +1 -1
  20. package/lib/hosted/browser/worker/worker-main.js +4 -2
  21. package/lib/hosted/browser/worker/worker-main.js.map +1 -1
  22. package/lib/hosted/node/hosted-plugin-deployer-handler.js +2 -2
  23. package/lib/hosted/node/hosted-plugin-deployer-handler.js.map +1 -1
  24. package/lib/hosted/node/hosted-plugin-localization-service.d.ts +13 -1
  25. package/lib/hosted/node/hosted-plugin-localization-service.d.ts.map +1 -1
  26. package/lib/hosted/node/hosted-plugin-localization-service.js +122 -10
  27. package/lib/hosted/node/hosted-plugin-localization-service.js.map +1 -1
  28. package/lib/hosted/node/plugin-ext-hosted-backend-module.d.ts.map +1 -1
  29. package/lib/hosted/node/plugin-ext-hosted-backend-module.js +7 -0
  30. package/lib/hosted/node/plugin-ext-hosted-backend-module.js.map +1 -1
  31. package/lib/hosted/node/plugin-host-rpc.d.ts +2 -2
  32. package/lib/hosted/node/plugin-host-rpc.d.ts.map +1 -1
  33. package/lib/hosted/node/plugin-host-rpc.js +6 -4
  34. package/lib/hosted/node/plugin-host-rpc.js.map +1 -1
  35. package/lib/hosted/node/plugin-language-pack-service.d.ts +8 -0
  36. package/lib/hosted/node/plugin-language-pack-service.d.ts.map +1 -0
  37. package/lib/hosted/node/plugin-language-pack-service.js +54 -0
  38. package/lib/hosted/node/plugin-language-pack-service.js.map +1 -0
  39. package/lib/hosted/node/scanners/scanner-theia.d.ts.map +1 -1
  40. package/lib/hosted/node/scanners/scanner-theia.js +1 -0
  41. package/lib/hosted/node/scanners/scanner-theia.js.map +1 -1
  42. package/lib/main/browser/comments/comment-thread-widget.d.ts.map +1 -1
  43. package/lib/main/browser/comments/comment-thread-widget.js +5 -1
  44. package/lib/main/browser/comments/comment-thread-widget.js.map +1 -1
  45. package/lib/main/browser/comments/comments-main.d.ts +6 -1
  46. package/lib/main/browser/comments/comments-main.d.ts.map +1 -1
  47. package/lib/main/browser/comments/comments-main.js +15 -0
  48. package/lib/main/browser/comments/comments-main.js.map +1 -1
  49. package/lib/main/browser/localization-main.d.ts +9 -0
  50. package/lib/main/browser/localization-main.d.ts.map +1 -0
  51. package/lib/main/browser/localization-main.js +32 -0
  52. package/lib/main/browser/localization-main.js.map +1 -0
  53. package/lib/main/browser/main-context.d.ts.map +1 -1
  54. package/lib/main/browser/main-context.js +3 -0
  55. package/lib/main/browser/main-context.js.map +1 -1
  56. package/lib/main/browser/plugin-ext-frontend-module.d.ts.map +1 -1
  57. package/lib/main/browser/plugin-ext-frontend-module.js +5 -0
  58. package/lib/main/browser/plugin-ext-frontend-module.js.map +1 -1
  59. package/lib/main/browser/view/tree-view-widget.d.ts.map +1 -1
  60. package/lib/main/browser/view/tree-view-widget.js +10 -1
  61. package/lib/main/browser/view/tree-view-widget.js.map +1 -1
  62. package/lib/main/electron-browser/webview/electron-webview-widget-factory.d.ts +1 -0
  63. package/lib/main/electron-browser/webview/electron-webview-widget-factory.d.ts.map +1 -1
  64. package/lib/main/electron-browser/webview/electron-webview-widget-factory.js +4 -15
  65. package/lib/main/electron-browser/webview/electron-webview-widget-factory.js.map +1 -1
  66. package/lib/main/node/plugin-github-resolver.d.ts +3 -1
  67. package/lib/main/node/plugin-github-resolver.d.ts.map +1 -1
  68. package/lib/main/node/plugin-github-resolver.js +64 -71
  69. package/lib/main/node/plugin-github-resolver.js.map +1 -1
  70. package/lib/main/node/plugin-http-resolver.d.ts +2 -0
  71. package/lib/main/node/plugin-http-resolver.d.ts.map +1 -1
  72. package/lib/main/node/plugin-http-resolver.js +31 -31
  73. package/lib/main/node/plugin-http-resolver.js.map +1 -1
  74. package/lib/main/node/plugins-key-value-storage.js +1 -1
  75. package/lib/main/node/plugins-key-value-storage.js.map +1 -1
  76. package/lib/plugin/comments.d.ts +3 -0
  77. package/lib/plugin/comments.d.ts.map +1 -1
  78. package/lib/plugin/comments.js +24 -0
  79. package/lib/plugin/comments.js.map +1 -1
  80. package/lib/plugin/localization-ext.d.ts +17 -0
  81. package/lib/plugin/localization-ext.d.ts.map +1 -0
  82. package/lib/plugin/localization-ext.js +74 -0
  83. package/lib/plugin/localization-ext.js.map +1 -0
  84. package/lib/plugin/output-channel/log-output-channel.d.ts +24 -0
  85. package/lib/plugin/output-channel/log-output-channel.d.ts.map +1 -0
  86. package/lib/plugin/output-channel/log-output-channel.js +92 -0
  87. package/lib/plugin/output-channel/log-output-channel.js.map +1 -0
  88. package/lib/plugin/output-channel/output-channel-item.d.ts +3 -3
  89. package/lib/plugin/output-channel/output-channel-item.d.ts.map +1 -1
  90. package/lib/plugin/output-channel/output-channel-item.js.map +1 -1
  91. package/lib/plugin/output-channel-registry.d.ts +7 -2
  92. package/lib/plugin/output-channel-registry.d.ts.map +1 -1
  93. package/lib/plugin/output-channel-registry.js +15 -6
  94. package/lib/plugin/output-channel-registry.js.map +1 -1
  95. package/lib/plugin/plugin-context.d.ts +2 -1
  96. package/lib/plugin/plugin-context.d.ts.map +1 -1
  97. package/lib/plugin/plugin-context.js +26 -3
  98. package/lib/plugin/plugin-context.js.map +1 -1
  99. package/lib/plugin/plugin-manager.d.ts +3 -2
  100. package/lib/plugin/plugin-manager.d.ts.map +1 -1
  101. package/lib/plugin/plugin-manager.js +4 -1
  102. package/lib/plugin/plugin-manager.js.map +1 -1
  103. package/lib/plugin/preference-registry.d.ts +1 -1
  104. package/lib/plugin/preference-registry.js +1 -1
  105. package/lib/plugin/stubs/tests-api.d.ts +2 -1
  106. package/lib/plugin/stubs/tests-api.d.ts.map +1 -1
  107. package/lib/plugin/stubs/tests-api.js +2 -1
  108. package/lib/plugin/stubs/tests-api.js.map +1 -1
  109. package/lib/plugin/tree/tree-views.d.ts +2 -0
  110. package/lib/plugin/tree/tree-views.d.ts.map +1 -1
  111. package/lib/plugin/tree/tree-views.js +10 -3
  112. package/lib/plugin/tree/tree-views.js.map +1 -1
  113. package/lib/plugin/type-converters.d.ts.map +1 -1
  114. package/lib/plugin/type-converters.js +6 -3
  115. package/lib/plugin/type-converters.js.map +1 -1
  116. package/lib/plugin/types-impl.d.ts +8 -4
  117. package/lib/plugin/types-impl.d.ts.map +1 -1
  118. package/lib/plugin/types-impl.js +12 -7
  119. package/lib/plugin/types-impl.js.map +1 -1
  120. package/package.json +28 -30
  121. package/src/common/language-pack-service.ts +34 -0
  122. package/src/common/plugin-api-rpc-model.ts +7 -0
  123. package/src/common/plugin-api-rpc.ts +25 -2
  124. package/src/common/plugin-protocol.ts +2 -0
  125. package/src/hosted/browser/hosted-plugin.ts +27 -6
  126. package/src/hosted/browser/worker/worker-main.ts +5 -2
  127. package/src/hosted/node/hosted-plugin-deployer-handler.ts +2 -2
  128. package/src/hosted/node/hosted-plugin-localization-service.ts +132 -11
  129. package/src/hosted/node/plugin-ext-hosted-backend-module.ts +12 -0
  130. package/src/hosted/node/plugin-host-rpc.ts +8 -5
  131. package/src/hosted/node/plugin-language-pack-service.ts +43 -0
  132. package/src/hosted/node/scanners/scanner-theia.ts +1 -0
  133. package/src/main/browser/comments/comment-thread-widget.tsx +6 -1
  134. package/src/main/browser/comments/comments-main.ts +18 -1
  135. package/src/main/browser/localization-main.ts +34 -0
  136. package/src/main/browser/main-context.ts +4 -0
  137. package/src/main/browser/plugin-ext-frontend-module.ts +6 -0
  138. package/src/main/browser/view/tree-view-widget.tsx +11 -1
  139. package/src/main/electron-browser/webview/electron-webview-widget-factory.ts +4 -15
  140. package/src/main/node/plugin-github-resolver.ts +65 -81
  141. package/src/main/node/plugin-http-resolver.ts +29 -35
  142. package/src/main/node/plugins-key-value-storage.ts +1 -1
  143. package/src/plugin/comments.ts +32 -1
  144. package/src/plugin/localization-ext.ts +84 -0
  145. package/src/plugin/output-channel/log-output-channel.ts +108 -0
  146. package/src/plugin/output-channel/output-channel-item.ts +2 -2
  147. package/src/plugin/output-channel-registry.ts +20 -7
  148. package/src/plugin/plugin-context.ts +31 -3
  149. package/src/plugin/plugin-manager.ts +5 -1
  150. package/src/plugin/preference-registry.ts +1 -1
  151. package/src/plugin/stubs/tests-api.ts +3 -1
  152. package/src/plugin/tree/tree-views.ts +12 -3
  153. package/src/plugin/type-converters.ts +5 -3
  154. package/src/plugin/types-impl.ts +8 -3
  155. package/LICENSE +0 -642
@@ -49,7 +49,8 @@ import { WaitUntilEvent } from '@theia/core/lib/common/event';
49
49
  import { FileSearchService } from '@theia/file-search/lib/common/file-search-service';
50
50
  import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
51
51
  import { PluginViewRegistry } from '../../main/browser/view/plugin-view-registry';
52
- import { TaskProviderRegistry, TaskResolverRegistry } from '@theia/task/lib/browser/task-contribution';
52
+ import { WillResolveTaskProvider, TaskProviderRegistry, TaskResolverRegistry } from '@theia/task/lib/browser/task-contribution';
53
+ import { TaskDefinitionRegistry } from '@theia/task/lib/browser/task-definition-registry';
53
54
  import { WebviewEnvironment } from '../../main/browser/webview/webview-environment';
54
55
  import { WebviewWidget } from '../../main/browser/webview/webview';
55
56
  import { WidgetManager } from '@theia/core/lib/browser/widget-manager';
@@ -73,6 +74,7 @@ export type PluginHost = 'frontend' | string;
73
74
  export type DebugActivationEvent = 'onDebugResolve' | 'onDebugInitialConfigurations' | 'onDebugAdapterProtocolTracker' | 'onDebugDynamicConfigurations';
74
75
 
75
76
  export const PluginProgressLocation = 'plugin';
77
+ export const ALL_ACTIVATION_EVENT = '*';
76
78
 
77
79
  @injectable()
78
80
  export class HostedPluginSupport {
@@ -139,6 +141,9 @@ export class HostedPluginSupport {
139
141
  @inject(TaskResolverRegistry)
140
142
  protected readonly taskResolverRegistry: TaskResolverRegistry;
141
143
 
144
+ @inject(TaskDefinitionRegistry)
145
+ protected readonly taskDefinitionRegistry: TaskDefinitionRegistry;
146
+
142
147
  @inject(ProgressService)
143
148
  protected readonly progressService: ProgressService;
144
149
 
@@ -205,7 +210,7 @@ export class HostedPluginSupport {
205
210
  this.debugSessionManager.onWillResolveDebugConfiguration(event => this.ensureDebugActivation(event, 'onDebugResolve', event.debugType));
206
211
  this.debugConfigurationManager.onWillProvideDebugConfiguration(event => this.ensureDebugActivation(event, 'onDebugInitialConfigurations'));
207
212
  // Activate all providers of dynamic configurations, i.e. Let the user pick a configuration from all the available ones.
208
- this.debugConfigurationManager.onWillProvideDynamicDebugConfiguration(event => this.ensureDebugActivation(event, 'onDebugDynamicConfigurations', '*'));
213
+ this.debugConfigurationManager.onWillProvideDynamicDebugConfiguration(event => this.ensureDebugActivation(event, 'onDebugDynamicConfigurations', ALL_ACTIVATION_EVENT));
209
214
  this.viewRegistry.onDidExpandView(id => this.activateByView(id));
210
215
  this.taskProviderRegistry.onWillProvideTaskProvider(event => this.ensureTaskActivation(event));
211
216
  this.taskResolverRegistry.onWillProvideTaskResolver(event => this.ensureTaskActivation(event));
@@ -510,7 +515,7 @@ export class HostedPluginSupport {
510
515
  workspaceState,
511
516
  env: {
512
517
  queryParams: getQueryParameters(),
513
- language: nls.locale || 'en',
518
+ language: nls.locale || nls.defaultLocale,
514
519
  shell: defaultShell,
515
520
  uiKind: isElectron ? UIKind.Desktop : UIKind.Web,
516
521
  appName: FrontendApplicationConfigProvider.get().applicationName,
@@ -610,6 +615,10 @@ export class HostedPluginSupport {
610
615
  await this.activateByEvent(`onCommand:${commandId}`);
611
616
  }
612
617
 
618
+ async activateByTaskType(taskType: string): Promise<void> {
619
+ await this.activateByEvent(`onTaskType:${taskType}`);
620
+ }
621
+
613
622
  async activateByCustomEditor(viewType: string): Promise<void> {
614
623
  await this.activateByEvent(`onCustomEditor:${viewType}`);
615
624
  }
@@ -648,8 +657,20 @@ export class HostedPluginSupport {
648
657
  event.waitUntil(p);
649
658
  }
650
659
 
651
- protected ensureTaskActivation(event: WaitUntilEvent): void {
652
- event.waitUntil(this.activateByCommand('workbench.action.tasks.runTask'));
660
+ protected ensureTaskActivation(event: WillResolveTaskProvider): void {
661
+ const promises = [this.activateByCommand('workbench.action.tasks.runTask')];
662
+ const taskType = event.taskType;
663
+ if (taskType) {
664
+ if (taskType === ALL_ACTIVATION_EVENT) {
665
+ for (const taskDefinition of this.taskDefinitionRegistry.getAll()) {
666
+ promises.push(this.activateByTaskType(taskDefinition.taskType));
667
+ }
668
+ } else {
669
+ promises.push(this.activateByTaskType(taskType));
670
+ }
671
+ }
672
+
673
+ event.waitUntil(Promise.all(promises));
653
674
  }
654
675
 
655
676
  protected ensureDebugActivation(event: WaitUntilEvent, activationEvent?: DebugActivationEvent, debugType?: string): void {
@@ -678,7 +699,7 @@ export class HostedPluginSupport {
678
699
  for (const activationEvent of activationEvents) {
679
700
  if (/^workspaceContains:/.test(activationEvent)) {
680
701
  const fileNameOrGlob = activationEvent.substr('workspaceContains:'.length);
681
- if (fileNameOrGlob.indexOf('*') >= 0 || fileNameOrGlob.indexOf('?') >= 0) {
702
+ if (fileNameOrGlob.indexOf(ALL_ACTIVATION_EVENT) >= 0 || fileNameOrGlob.indexOf('?') >= 0) {
682
703
  includePatterns.push(fileNameOrGlob);
683
704
  } else {
684
705
  paths.push(fileNameOrGlob);
@@ -36,6 +36,7 @@ import { WorkspaceExtImpl } from '../../../plugin/workspace';
36
36
  import { createDebugExtStub } from './debug-stub';
37
37
  import { loadManifest } from './plugin-manifest-loader';
38
38
  import { WorkerEnvExtImpl } from './worker-env-ext';
39
+ import { LocalizationExtImpl } from '../../../plugin/localization-ext';
39
40
 
40
41
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
41
42
  const ctx = self as any;
@@ -77,6 +78,7 @@ const debugExt = createDebugExtStub(rpc);
77
78
  const clipboardExt = new ClipboardExt(rpc);
78
79
  const webviewExt = new WebviewsExtImpl(rpc, workspaceExt);
79
80
  const secretsExt = new SecretsExtImpl(rpc);
81
+ const localizationExt = new LocalizationExtImpl(rpc);
80
82
  const terminalService: TerminalServiceExt = new TerminalServiceExtImpl(rpc);
81
83
 
82
84
  const pluginManager = new PluginManagerExtImpl({
@@ -171,7 +173,7 @@ const pluginManager = new PluginManagerExtImpl({
171
173
  }
172
174
  }
173
175
  }
174
- }, envExt, terminalService, storageProxy, secretsExt, preferenceRegistryExt, webviewExt, rpc);
176
+ }, envExt, terminalService, storageProxy, secretsExt, preferenceRegistryExt, webviewExt, localizationExt, rpc);
175
177
 
176
178
  const apiFactory = createAPIFactory(
177
179
  rpc,
@@ -183,7 +185,8 @@ const apiFactory = createAPIFactory(
183
185
  workspaceExt,
184
186
  messageRegistryExt,
185
187
  clipboardExt,
186
- webviewExt
188
+ webviewExt,
189
+ localizationExt
187
190
  );
188
191
  let defaultApi: typeof theia;
189
192
 
@@ -133,7 +133,7 @@ export class HostedPluginDeployerHandler implements PluginDeployerHandler {
133
133
  if (await this.deployPlugin(plugin, 'backend')) { successes++; }
134
134
  }
135
135
  // rebuild translation config after deployment
136
- this.localizationService.buildTranslationConfig([...this.deployedBackendPlugins.values()]);
136
+ await this.localizationService.buildTranslationConfig([...this.deployedBackendPlugins.values()]);
137
137
  // resolve on first deploy
138
138
  this.backendPluginsMetadataDeferred.resolve(undefined);
139
139
  return successes;
@@ -174,7 +174,7 @@ export class HostedPluginDeployerHandler implements PluginDeployerHandler {
174
174
  const { type } = entry;
175
175
  const deployed: DeployedPlugin = { metadata, type };
176
176
  deployed.contributes = this.reader.readContribution(manifest);
177
- this.localizationService.deployLocalizations(deployed);
177
+ await this.localizationService.deployLocalizations(deployed);
178
178
  deployedPlugins.set(id, deployed);
179
179
  deployPlugin.debug(`Deployed ${entryPoint} plugin "${id}" from "${metadata.model.entryPoint[entryPoint] || pluginPath}"`);
180
180
  } catch (e) {
@@ -19,12 +19,12 @@ import * as fs from '@theia/core/shared/fs-extra';
19
19
  import { LocalizationProvider } from '@theia/core/lib/node/i18n/localization-provider';
20
20
  import { Localization } from '@theia/core/lib/common/i18n/localization';
21
21
  import { inject, injectable } from '@theia/core/shared/inversify';
22
- import { DeployedPlugin, Localization as PluginLocalization, PluginIdentifiers } from '../../common';
23
- import { URI } from '@theia/core/shared/vscode-uri';
22
+ import { DeployedPlugin, Localization as PluginLocalization, PluginIdentifiers, Translation } from '../../common';
24
23
  import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
25
24
  import { BackendApplicationContribution } from '@theia/core/lib/node';
26
- import { Disposable, isObject, MaybePromise } from '@theia/core';
25
+ import { Disposable, DisposableCollection, isObject, MaybePromise, nls, URI } from '@theia/core';
27
26
  import { Deferred } from '@theia/core/lib/common/promise-util';
27
+ import { LanguagePackBundle, LanguagePackService } from '../../common/language-pack-service';
28
28
 
29
29
  export interface VSCodeNlsConfig {
30
30
  locale: string
@@ -42,6 +42,9 @@ export class HostedPluginLocalizationService implements BackendApplicationContri
42
42
  @inject(LocalizationProvider)
43
43
  protected readonly localizationProvider: LocalizationProvider;
44
44
 
45
+ @inject(LanguagePackService)
46
+ protected readonly languagePackService: LanguagePackService;
47
+
45
48
  @inject(EnvVariablesServer)
46
49
  protected readonly envVariables: EnvVariablesServer;
47
50
 
@@ -62,26 +65,93 @@ export class HostedPluginLocalizationService implements BackendApplicationContri
62
65
  .then(() => this._ready.resolve());
63
66
  }
64
67
 
65
- deployLocalizations(plugin: DeployedPlugin): void {
68
+ async deployLocalizations(plugin: DeployedPlugin): Promise<void> {
69
+ const disposable = new DisposableCollection();
66
70
  if (plugin.contributes?.localizations) {
71
+ // Indicator that this plugin is a vscode language pack
72
+ // Language packs translate Theia and some builtin vscode extensions
67
73
  const localizations = buildLocalizations(plugin.contributes.localizations);
68
- const versionedId = PluginIdentifiers.componentsToVersionedId(plugin.metadata.model);
69
- this.localizationDisposeMap.set(versionedId, Disposable.create(() => {
74
+ disposable.push(Disposable.create(() => {
70
75
  this.localizationProvider.removeLocalizations(...localizations);
71
- this.localizationDisposeMap.delete(versionedId);
72
76
  }));
73
77
  this.localizationProvider.addLocalizations(...localizations);
74
78
  }
79
+ if (plugin.metadata.model.l10n || plugin.contributes?.localizations) {
80
+ // Indicator that this plugin is a vscode language pack or has its own localization bundles
81
+ // These bundles are purely used for translating plugins
82
+ // The branch above builds localizations for Theia's own strings
83
+ disposable.push(await this.updateLanguagePackBundles(plugin));
84
+ }
85
+ if (!disposable.disposed) {
86
+ const versionedId = PluginIdentifiers.componentsToVersionedId(plugin.metadata.model);
87
+ disposable.push(Disposable.create(() => {
88
+ this.localizationDisposeMap.delete(versionedId);
89
+ }));
90
+ this.localizationDisposeMap.set(versionedId, disposable);
91
+ }
75
92
  }
76
93
 
77
94
  undeployLocalizations(plugin: PluginIdentifiers.VersionedId): void {
78
95
  this.localizationDisposeMap.get(plugin)?.dispose();
79
96
  }
80
97
 
98
+ protected async updateLanguagePackBundles(plugin: DeployedPlugin): Promise<Disposable> {
99
+ const disposable = new DisposableCollection();
100
+ const pluginId = plugin.metadata.model.id;
101
+ const packageUri = new URI(plugin.metadata.model.packageUri);
102
+ if (plugin.contributes?.localizations) {
103
+ for (const localization of plugin.contributes.localizations) {
104
+ for (const translation of localization.translations) {
105
+ const l10n = getL10nTranslation(translation);
106
+ if (l10n) {
107
+ const translatedPluginId = translation.id;
108
+ const translationUri = packageUri.resolve(translation.path);
109
+ const locale = localization.languageId;
110
+ // We store a bundle for another extension in here
111
+ // Hence we use `translatedPluginId` instead of `pluginId`
112
+ this.languagePackService.storeBundle(translatedPluginId, locale, {
113
+ contents: processL10nBundle(l10n),
114
+ uri: translationUri.toString()
115
+ });
116
+ disposable.push(Disposable.create(() => {
117
+ // Only dispose the deleted locale for the specific plugin
118
+ this.languagePackService.deleteBundle(translatedPluginId, locale);
119
+ }));
120
+ }
121
+ }
122
+ }
123
+ }
124
+ // The `l10n` field of the plugin model points to a relative directory path within the plugin
125
+ // It is supposed to contain localization bundles that contain translations of the plugin strings into different languages
126
+ if (plugin.metadata.model.l10n) {
127
+ const bundleDirectory = packageUri.resolve(plugin.metadata.model.l10n);
128
+ const bundles = await loadPluginBundles(bundleDirectory);
129
+ if (bundles) {
130
+ for (const [locale, bundle] of Object.entries(bundles)) {
131
+ this.languagePackService.storeBundle(pluginId, locale, bundle);
132
+ }
133
+ disposable.push(Disposable.create(() => {
134
+ // Dispose all bundles contributed by the deleted plugin
135
+ this.languagePackService.deleteBundle(pluginId);
136
+ }));
137
+ }
138
+ }
139
+ return disposable;
140
+ }
141
+
142
+ /**
143
+ * Performs localization of the plugin model. Translates entries such as command names, view names and other items.
144
+ *
145
+ * Translatable items are indicated with a `%id%` value.
146
+ * The `id` is the translation key that gets replaced with the localized value for the currently selected language.
147
+ *
148
+ * Returns a copy of the plugin argument and does not modify the argument.
149
+ * This is done to preserve the original `%id%` values for subsequent invocations of this method.
150
+ */
81
151
  async localizePlugin(plugin: DeployedPlugin): Promise<DeployedPlugin> {
82
152
  const currentLanguage = this.localizationProvider.getCurrentLanguage();
83
153
  const localization = this.localizationProvider.loadLocalization(currentLanguage);
84
- const pluginPath = URI.parse(plugin.metadata.model.packageUri).fsPath;
154
+ const pluginPath = new URI(plugin.metadata.model.packageUri).path.fsPath();
85
155
  const pluginId = plugin.metadata.model.id;
86
156
  try {
87
157
  const translations = await loadPackageTranslations(pluginPath, currentLanguage);
@@ -98,7 +168,7 @@ export class HostedPluginLocalizationService implements BackendApplicationContri
98
168
  getNlsConfig(): VSCodeNlsConfig {
99
169
  const locale = this.localizationProvider.getCurrentLanguage();
100
170
  const configFile = this.translationConfigFiles.get(locale);
101
- if (locale === 'en' || !configFile) {
171
+ if (locale === nls.defaultLocale || !configFile) {
102
172
  return { locale, availableLanguages: {} };
103
173
  }
104
174
  const cache = path.dirname(configFile);
@@ -118,7 +188,7 @@ export class HostedPluginLocalizationService implements BackendApplicationContri
118
188
  const configs = new Map<string, Record<string, string>>();
119
189
  for (const plugin of plugins) {
120
190
  if (plugin.contributes?.localizations) {
121
- const pluginPath = URI.parse(plugin.metadata.model.packageUri).fsPath;
191
+ const pluginPath = new URI(plugin.metadata.model.packageUri).path.fsPath();
122
192
  for (const localization of plugin.contributes.localizations) {
123
193
  const config = configs.get(localization.languageId) || {};
124
194
  for (const translation of localization.translations) {
@@ -140,12 +210,59 @@ export class HostedPluginLocalizationService implements BackendApplicationContri
140
210
  }
141
211
 
142
212
  protected async getLocalizationCacheDir(): Promise<string> {
143
- const configDir = URI.parse(await this.envVariables.getConfigDirUri()).fsPath;
213
+ const configDir = new URI(await this.envVariables.getConfigDirUri()).path.fsPath();
144
214
  const cacheDir = path.join(configDir, 'localization-cache');
145
215
  return cacheDir;
146
216
  }
147
217
  }
148
218
 
219
+ // New plugin localization logic using vscode.l10n
220
+
221
+ function getL10nTranslation(translation: Translation): UnprocessedL10nBundle | undefined {
222
+ // 'bundle' is a special key that contains all translations for the l10n vscode API
223
+ // If that doesn't exist, we can assume that the language pack is using the old vscode-nls API
224
+ return translation.contents.bundle;
225
+ }
226
+
227
+ async function loadPluginBundles(l10nUri: URI): Promise<Record<string, LanguagePackBundle> | undefined> {
228
+ try {
229
+ const directory = l10nUri.path.fsPath();
230
+ const files = await fs.readdir(directory);
231
+ const result: Record<string, LanguagePackBundle> = {};
232
+ await Promise.all(files.map(async fileName => {
233
+ const match = fileName.match(/^bundle\.l10n\.([\w\-]+)\.json$/);
234
+ if (match) {
235
+ const locale = match[1];
236
+ const contents = await fs.readJSON(path.join(directory, fileName));
237
+ result[locale] = {
238
+ contents,
239
+ uri: l10nUri.resolve(fileName).toString()
240
+ };
241
+ }
242
+ }));
243
+ return result;
244
+ } catch (err) {
245
+ // The directory either doesn't exist or its contents cannot be parsed
246
+ console.error(`Failed to load plugin localization bundles from ${l10nUri}.`, err);
247
+ // In any way we should just safely return undefined
248
+ return undefined;
249
+ }
250
+ }
251
+
252
+ type UnprocessedL10nBundle = Record<string, string | { message: string }>;
253
+
254
+ function processL10nBundle(bundle: UnprocessedL10nBundle): Record<string, string> {
255
+ const processedBundle: Record<string, string> = {};
256
+ for (const [name, value] of Object.entries(bundle)) {
257
+ const stringValue = typeof value === 'string' ? value : value.message;
258
+ processedBundle[name] = stringValue;
259
+ }
260
+ return processedBundle;
261
+ }
262
+
263
+ // Old plugin localization logic for vscode-nls
264
+ // vscode-nls was used until version 1.73 of VSCode to translate extensions
265
+
149
266
  function buildLocalizations(localizations: PluginLocalization[]): Localization[] {
150
267
  const theiaLocalizations: Localization[] = [];
151
268
  for (const localization of localizations) {
@@ -173,6 +290,10 @@ function buildTranslationKey(pluginId: string, scope: string, key: string): stri
173
290
  return `${pluginId}/${Localization.transformKey(scope)}/${key}`;
174
291
  }
175
292
 
293
+ // Localization logic for `package.json` entries
294
+ // Extensions can use `package.nls.json` files to store translations for values in their package.json
295
+ // This logic has not changed with the introduction of the vscode.l10n API
296
+
176
297
  interface PackageTranslation {
177
298
  translation?: Record<string, string>
178
299
  default?: Record<string, string>
@@ -34,6 +34,10 @@ import { HostedPluginDeployerHandler } from './hosted-plugin-deployer-handler';
34
34
  import { PluginUriFactory } from './scanners/plugin-uri-factory';
35
35
  import { FilePluginUriFactory } from './scanners/file-plugin-uri-factory';
36
36
  import { HostedPluginLocalizationService } from './hosted-plugin-localization-service';
37
+ import { LanguagePackService, languagePackServicePath } from '../../common/language-pack-service';
38
+ import { PluginLanguagePackService } from './plugin-language-pack-service';
39
+ import { JsonRpcConnectionHandler } from '@theia/core/lib/common/messaging/proxy-factory';
40
+ import { ConnectionHandler } from '@theia/core/lib/common/messaging/handler';
37
41
 
38
42
  const commonHostedConnectionModule = ConnectionContainerModule.create(({ bind, bindBackendService }) => {
39
43
  bind(HostedPluginProcess).toSelf().inSingletonScope();
@@ -64,6 +68,14 @@ export function bindCommonHostedBackend(bind: interfaces.Bind): void {
64
68
  bind(HostedPluginDeployerHandler).toSelf().inSingletonScope();
65
69
  bind(PluginDeployerHandler).toService(HostedPluginDeployerHandler);
66
70
 
71
+ bind(PluginLanguagePackService).toSelf().inSingletonScope();
72
+ bind(LanguagePackService).toService(PluginLanguagePackService);
73
+ bind(ConnectionHandler).toDynamicValue(ctx =>
74
+ new JsonRpcConnectionHandler(languagePackServicePath, () =>
75
+ ctx.container.get(LanguagePackService)
76
+ )
77
+ ).inSingletonScope();
78
+
67
79
  bind(GrammarsReader).toSelf().inSingletonScope();
68
80
  bind(HostedPluginProcessConfiguration).toConstantValue({
69
81
  path: path.join(__dirname, 'plugin-host'),
@@ -16,7 +16,7 @@
16
16
 
17
17
  import { dynamicRequire } from '@theia/core/lib/node/dynamic-require';
18
18
  import { PluginManagerExtImpl } from '../../plugin/plugin-manager';
19
- import { MAIN_RPC_CONTEXT, Plugin, PluginAPIFactory } from '../../common/plugin-api-rpc';
19
+ import { LocalizationExt, MAIN_RPC_CONTEXT, Plugin, PluginAPIFactory } from '../../common/plugin-api-rpc';
20
20
  import { PluginMetadata } from '../../common/plugin-protocol';
21
21
  import { createAPIFactory } from '../../plugin/plugin-context';
22
22
  import { EnvExtImpl } from '../../plugin/env';
@@ -35,6 +35,7 @@ import { TerminalServiceExtImpl } from '../../plugin/terminal-ext';
35
35
  import { SecretsExtImpl } from '../../plugin/secrets-ext';
36
36
  import { BackendInitializationFn } from '../../common';
37
37
  import { connectProxyResolver } from './plugin-host-proxy';
38
+ import { LocalizationExtImpl } from '../../plugin/localization-ext';
38
39
 
39
40
  /**
40
41
  * Handle the RPC calls.
@@ -61,7 +62,8 @@ export class PluginHostRPC {
61
62
  const webviewExt = new WebviewsExtImpl(this.rpc, workspaceExt);
62
63
  const terminalService = new TerminalServiceExtImpl(this.rpc);
63
64
  const secretsExt = new SecretsExtImpl(this.rpc);
64
- this.pluginManager = this.createPluginManager(envExt, terminalService, storageProxy, preferenceRegistryExt, webviewExt, secretsExt, this.rpc);
65
+ const localizationExt = new LocalizationExtImpl(this.rpc);
66
+ this.pluginManager = this.createPluginManager(envExt, terminalService, storageProxy, preferenceRegistryExt, webviewExt, secretsExt, localizationExt, this.rpc);
65
67
  this.rpc.set(MAIN_RPC_CONTEXT.HOSTED_PLUGIN_MANAGER_EXT, this.pluginManager);
66
68
  this.rpc.set(MAIN_RPC_CONTEXT.EDITORS_AND_DOCUMENTS_EXT, editorsAndDocumentsExt);
67
69
  this.rpc.set(MAIN_RPC_CONTEXT.WORKSPACE_EXT, workspaceExt);
@@ -80,7 +82,8 @@ export class PluginHostRPC {
80
82
  workspaceExt,
81
83
  messageRegistryExt,
82
84
  clipboardExt,
83
- webviewExt
85
+ webviewExt,
86
+ localizationExt
84
87
  );
85
88
  connectProxyResolver(workspaceExt, preferenceRegistryExt);
86
89
  }
@@ -102,7 +105,7 @@ export class PluginHostRPC {
102
105
 
103
106
  createPluginManager(
104
107
  envExt: EnvExtImpl, terminalService: TerminalServiceExtImpl, storageProxy: KeyValueStorageProxy,
105
- preferencesManager: PreferenceRegistryExtImpl, webview: WebviewsExtImpl, secretsExt: SecretsExtImpl,
108
+ preferencesManager: PreferenceRegistryExtImpl, webview: WebviewsExtImpl, secretsExt: SecretsExtImpl, localization: LocalizationExt,
106
109
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
107
110
  rpc: any
108
111
  ): PluginManagerExtImpl {
@@ -239,7 +242,7 @@ export class PluginHostRPC {
239
242
  `Path ${extensionTestsPath} does not point to a valid extension test runner.`
240
243
  );
241
244
  } : undefined
242
- }, envExt, terminalService, storageProxy, secretsExt, preferencesManager, webview, rpc);
245
+ }, envExt, terminalService, storageProxy, secretsExt, preferencesManager, webview, localization, rpc);
243
246
  return pluginManager;
244
247
  }
245
248
  }
@@ -0,0 +1,43 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2023 TypeFox and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { injectable } from '@theia/core/shared/inversify';
18
+ import { LanguagePackBundle, LanguagePackService } from '../../common/language-pack-service';
19
+
20
+ @injectable()
21
+ export class PluginLanguagePackService implements LanguagePackService {
22
+
23
+ protected readonly storage = new Map<string, Map<string, LanguagePackBundle>>();
24
+
25
+ storeBundle(pluginId: string, locale: string, bundle: LanguagePackBundle): void {
26
+ if (!this.storage.has(pluginId)) {
27
+ this.storage.set(pluginId, new Map());
28
+ }
29
+ this.storage.get(pluginId)!.set(locale, bundle);
30
+ }
31
+
32
+ deleteBundle(pluginId: string, locale?: string): void {
33
+ if (locale) {
34
+ this.storage.get(pluginId)?.delete(locale);
35
+ } else {
36
+ this.storage.delete(pluginId);
37
+ }
38
+ }
39
+
40
+ async getBundle(pluginId: string, locale: string): Promise<LanguagePackBundle | undefined> {
41
+ return this.storage.get(pluginId)?.get(locale);
42
+ }
43
+ }
@@ -116,6 +116,7 @@ export class TheiaPluginScanner implements PluginScanner {
116
116
  version: plugin.version,
117
117
  displayName: plugin.displayName,
118
118
  description: plugin.description,
119
+ l10n: plugin.l10n,
119
120
  engine: {
120
121
  type: this._apiType,
121
122
  version: plugin.engines[this._apiType]
@@ -18,6 +18,7 @@ import {
18
18
  Comment,
19
19
  CommentMode,
20
20
  CommentThread,
21
+ CommentThreadState,
21
22
  CommentThreadCollapsibleState
22
23
  } from '../../../common/plugin-api-rpc-model';
23
24
  import { CommentGlyphWidget } from './comment-glyph-widget';
@@ -97,6 +98,9 @@ export class CommentThreadWidget extends BaseWidget {
97
98
  commentForm.update();
98
99
  }
99
100
  }));
101
+ this.toDispose.push(this._commentThread.onDidChangeState(_state => {
102
+ this.update();
103
+ }));
100
104
  this.contextMenu = this.menus.getMenu(COMMENT_THREAD_CONTEXT);
101
105
  this.contextMenu.children.map(node => node instanceof ActionMenuNode && node.when).forEach(exp => {
102
106
  if (typeof exp === 'string') {
@@ -231,7 +235,8 @@ export class CommentThreadWidget extends BaseWidget {
231
235
  if (this._commentThread.comments && this._commentThread.comments.length) {
232
236
  const onlyUnique = (value: Comment, index: number, self: Comment[]) => self.indexOf(value) === index;
233
237
  const participantsList = this._commentThread.comments.filter(onlyUnique).map(comment => `@${comment.userName}`).join(', ');
234
- label = `Participants: ${participantsList}`;
238
+ const resolutionState = this._commentThread.state === CommentThreadState.Resolved ? '(Resolved)' : '(Unresolved)';
239
+ label = `Participants: ${participantsList} ${resolutionState}`;
235
240
  } else {
236
241
  label = 'Start discussion';
237
242
  }
@@ -23,7 +23,7 @@ import {
23
23
  CommentThreadChangedEvent
24
24
  } from '../../../common/plugin-api-rpc-model';
25
25
  import { Event, Emitter } from '@theia/core/lib/common/event';
26
- import { CommentThreadCollapsibleState } from '../../../plugin/types-impl';
26
+ import { CommentThreadCollapsibleState, CommentThreadState } from '../../../plugin/types-impl';
27
27
  import {
28
28
  CommentProviderFeatures,
29
29
  CommentsExt,
@@ -124,6 +124,21 @@ export class CommentThreadImpl implements CommentThread, Disposable {
124
124
  private readonly onDidChangeCollapsibleStateEmitter = new Emitter<CommentThreadCollapsibleState | undefined>();
125
125
  readonly onDidChangeCollapsibleState = this.onDidChangeCollapsibleStateEmitter.event;
126
126
 
127
+ private _state: CommentThreadState | undefined;
128
+ get state(): CommentThreadState | undefined {
129
+ return this._state;
130
+ }
131
+
132
+ set state(newState: CommentThreadState | undefined) {
133
+ if (this._state !== newState) {
134
+ this._state = newState;
135
+ this.onDidChangeStateEmitter.fire(this._state);
136
+ }
137
+ }
138
+
139
+ private readonly onDidChangeStateEmitter = new Emitter<CommentThreadState | undefined>();
140
+ readonly onDidChangeState = this.onDidChangeStateEmitter.event;
141
+
127
142
  private readonly onDidChangeCanReplyEmitter = new Emitter<boolean>();
128
143
  readonly onDidChangeCanReply = this.onDidChangeCanReplyEmitter.event;
129
144
 
@@ -163,12 +178,14 @@ export class CommentThreadImpl implements CommentThread, Disposable {
163
178
  if (modified('contextValue')) { this._contextValue = changes.contextValue; }
164
179
  if (modified('comments')) { this._comments = changes.comments; }
165
180
  if (modified('collapseState')) { this._collapsibleState = changes.collapseState; }
181
+ if (modified('state')) { this._state = changes.state; }
166
182
  if (modified('canReply')) { this._canReply = changes.canReply!; }
167
183
  }
168
184
 
169
185
  dispose(): void {
170
186
  this._isDisposed = true;
171
187
  this.onDidChangeCollapsibleStateEmitter.dispose();
188
+ this.onDidChangeStateEmitter.dispose();
172
189
  this.onDidChangeCommentsEmitter.dispose();
173
190
  this.onDidChangeInputEmitter.dispose();
174
191
  this.onDidChangeLabelEmitter.dispose();
@@ -0,0 +1,34 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2023 TypeFox and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { nls } from '@theia/core';
18
+ import { interfaces } from '@theia/core/shared/inversify';
19
+ import { LocalizationMain } from '../../common/plugin-api-rpc';
20
+ import { LanguagePackBundle, LanguagePackService } from '../../common/language-pack-service';
21
+
22
+ export class LocalizationMainImpl implements LocalizationMain {
23
+
24
+ private readonly languagePackService: LanguagePackService;
25
+
26
+ constructor(container: interfaces.Container) {
27
+ this.languagePackService = container.get(LanguagePackService);
28
+ }
29
+
30
+ async $fetchBundle(id: string): Promise<LanguagePackBundle | undefined> {
31
+ const bundle = await this.languagePackService.getBundle(id, nls.locale ?? nls.defaultLocale);
32
+ return bundle;
33
+ }
34
+ }
@@ -59,6 +59,7 @@ import { MonacoLanguages } from '@theia/monaco/lib/browser/monaco-languages';
59
59
  import { UntitledResourceResolver } from '@theia/core/lib/common/resource';
60
60
  import { ThemeService } from '@theia/core/lib/browser/theming';
61
61
  import { TabsMainImpl } from './tabs/tabs-main';
62
+ import { LocalizationMainImpl } from './localization-main';
62
63
 
63
64
  export function setUpPluginApi(rpc: RPCProtocol, container: interfaces.Container): void {
64
65
  const authenticationMain = new AuthenticationMainImpl(rpc, container);
@@ -184,4 +185,7 @@ export function setUpPluginApi(rpc: RPCProtocol, container: interfaces.Container
184
185
 
185
186
  const tabsMain = new TabsMainImpl(rpc, container);
186
187
  rpc.set(PLUGIN_RPC_CONTEXT.TABS_MAIN, tabsMain);
188
+
189
+ const localizationMain = new LocalizationMainImpl(container);
190
+ rpc.set(PLUGIN_RPC_CONTEXT.LOCALIZATION_MAIN, localizationMain);
187
191
  }
@@ -83,6 +83,7 @@ import './theme-icon-override';
83
83
  import { PluginTerminalRegistry } from './plugin-terminal-registry';
84
84
  import { DnDFileContentStore } from './view/dnd-file-content-store';
85
85
  import { WebviewContextKeys } from './webview/webview-context-keys';
86
+ import { LanguagePackService, languagePackServicePath } from '../../common/language-pack-service';
86
87
 
87
88
  export default new ContainerModule((bind, unbind, isBound, rebind) => {
88
89
 
@@ -249,4 +250,9 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
249
250
  rebind(AuthenticationService).toService(PluginAuthenticationServiceImpl);
250
251
 
251
252
  bind(PluginTerminalRegistry).toSelf().inSingletonScope();
253
+
254
+ bind(LanguagePackService).toDynamicValue(ctx => {
255
+ const provider = ctx.container.get(WebSocketConnectionProvider);
256
+ return provider.createProxy<LanguagePackService>(languagePackServicePath);
257
+ }).inSingletonScope();
252
258
  });