diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index 909ab84d057..a94b0a43327 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -66,6 +66,10 @@ "name": "vs/workbench/parts/markers", "project": "vscode-workbench" }, + { + "name": "vs/workbench/parts/localizations", + "project": "vscode-workbench" + }, { "name": "vs/workbench/parts/logs", "project": "vscode-workbench" diff --git a/extensions/configuration-editing/src/extension.ts b/extensions/configuration-editing/src/extension.ts index 76d473c365e..2266b8005af 100644 --- a/extensions/configuration-editing/src/extension.ts +++ b/extensions/configuration-editing/src/extension.ts @@ -28,9 +28,6 @@ export function activate(context: vscode.ExtensionContext): void { //extensions suggestions context.subscriptions.push(...registerExtensionsCompletions()); - //locale suggestions - context.subscriptions.push(registerLocaleCompletionsInLanguageDocument()); - // launch.json decorations context.subscriptions.push(vscode.window.onDidChangeActiveTextEditor(editor => updateLaunchJsonDecorations(editor), null, context.subscriptions)); context.subscriptions.push(vscode.workspace.onDidChangeTextDocument(event => { @@ -109,20 +106,6 @@ function registerSettingsCompletions(): vscode.Disposable { }); } -function registerLocaleCompletionsInLanguageDocument(): vscode.Disposable { - return vscode.languages.registerCompletionItemProvider({ pattern: '**/locale.json' }, { - provideCompletionItems(document, position, token) { - const location = getLocation(document.getText(), document.offsetAt(position)); - const range = document.getWordRangeAtPosition(position) || new vscode.Range(position, position); - if (location.path[0] === 'locale') { - const extensionsContent = parse(document.getText()); - return provideContributedLocalesProposals(range); - } - return []; - } - }); -} - function provideContributedLocalesProposals(range: vscode.Range): vscode.ProviderResult { const contributedLocales: string[] = []; for (const extension of vscode.extensions.all) { diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/contributions.ts b/src/vs/code/electron-browser/sharedProcess/contrib/contributions.ts index 50ec33a43c9..27252840ff7 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/contributions.ts +++ b/src/vs/code/electron-browser/sharedProcess/contrib/contributions.ts @@ -8,11 +8,9 @@ import { NodeCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; -import { LanguagePacksCache } from 'vs/platform/localizations/node/localizations'; export function createSharedProcessContributions(service: IInstantiationService): IDisposable { return combinedDisposable([ service.createInstance(NodeCachedDataCleaner), - service.createInstance(LanguagePacksCache) ]); } diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index f13a6d2c95f..baf9a0bbe85 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -40,6 +40,9 @@ import { createSharedProcessContributions } from 'vs/code/electron-browser/share import { createSpdLogService } from 'vs/platform/log/node/spdlogService'; import { ILogService, LogLevel } from 'vs/platform/log/common/log'; import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc'; +import { LocalizationsService } from 'vs/platform/localizations/node/localizations'; +import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; +import { LocalizationsChannel } from 'vs/platform/localizations/common/localizationsIpc'; export interface ISharedProcessConfiguration { readonly machineId: string; @@ -141,6 +144,7 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService)); services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService)); + services.set(ILocalizationsService, new SyncDescriptor(LocalizationsService)); const instantiationService2 = instantiationService.createChild(services); @@ -152,6 +156,10 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I // clean up deprecated extensions (extensionManagementService as ExtensionManagementService).removeDeprecatedExtensions(); + const localizationsService = accessor.get(ILocalizationsService); + const localizationsChannel = new LocalizationsChannel(localizationsService); + server.registerChannel('localizations', localizationsChannel); + createSharedProcessContributions(instantiationService2); }); }); diff --git a/src/vs/platform/localizations/common/localizations.ts b/src/vs/platform/localizations/common/localizations.ts index 701eedd0bea..4fc29861e0d 100644 --- a/src/vs/platform/localizations/common/localizations.ts +++ b/src/vs/platform/localizations/common/localizations.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { localize } from 'vs/nls'; -import { ExtensionsRegistry } from 'vs/platform/extensions/common/extensionsRegistry'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { TPromise } from 'vs/base/common/winjs.base'; +import Event from 'vs/base/common/event'; export interface ILocalization { languageId: string; @@ -23,6 +23,9 @@ export interface ITranslation { export const ILocalizationsService = createDecorator('localizationsService'); export interface ILocalizationsService { _serviceBrand: any; + + readonly onDidLanguagesChange: Event; + getLanguageIds(): TPromise; } export function isValidLocalization(localization: ILocalization): boolean { @@ -47,51 +50,4 @@ export function isValidLocalization(localization: ILocalization): boolean { return false; } return true; -} - -ExtensionsRegistry.registerExtensionPoint('localizations', [], { - description: localize('vscode.extension.contributes.localizations', "Contributes localizations to the editor"), - type: 'array', - default: [], - items: { - type: 'object', - required: ['languageId', 'translations'], - defaultSnippets: [{ body: { languageId: '', languageName: '', languageNameLocalized: '', translations: [{ id: 'vscode', path: '' }] } }], - properties: { - languageId: { - description: localize('vscode.extension.contributes.localizations.languageId', 'Id of the language into which the display strings are translated.'), - type: 'string' - }, - languageName: { - description: localize('vscode.extension.contributes.localizations.languageName', 'Name of the language in English.'), - type: 'string' - }, - languageNameLocalized: { - description: localize('vscode.extension.contributes.localizations.languageNameLocalized', 'Name of the language in contributed language.'), - type: 'string' - }, - translations: { - description: localize('vscode.extension.contributes.localizations.translations', 'List of translations associated to the language.'), - type: 'array', - default: [{ id: 'vscode', path: '' }], - items: { - type: 'object', - required: ['id', 'path'], - properties: { - id: { - type: 'string', - description: localize('vscode.extension.contributes.localizations.translations.id', "Id of VS Code or Extension for which this translation is contributed to. Id of VS Code is always `vscode` and of extension should be in format `publisherId.extensionName`."), - pattern: '^((vscode)|([a-z0-9A-Z][a-z0-9\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\-A-Z]*))$', - patternErrorMessage: localize('vscode.extension.contributes.localizations.translations.id.pattern', "Id should be `vscode` or in format `publisherId.extensionName` for translating VS code or an extension respectively.") - }, - path: { - type: 'string', - description: localize('vscode.extension.contributes.localizations.translations.path', "A relative path to a file containing translations for the language.") - } - }, - defaultSnippets: [{ body: { id: '', path: '' } }], - }, - } - } - } -}); \ No newline at end of file +} \ No newline at end of file diff --git a/src/vs/platform/localizations/common/localizationsIpc.ts b/src/vs/platform/localizations/common/localizationsIpc.ts new file mode 100644 index 00000000000..0ad01d0baa5 --- /dev/null +++ b/src/vs/platform/localizations/common/localizationsIpc.ts @@ -0,0 +1,48 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { TPromise } from 'vs/base/common/winjs.base'; +import { IChannel, eventToCall, eventFromCall } from 'vs/base/parts/ipc/common/ipc'; +import Event, { buffer } from 'vs/base/common/event'; +import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; + +export interface ILocalizationsChannel extends IChannel { + call(command: 'event:onDidLanguagesChange'): TPromise; + call(command: 'getLanguageIds'): TPromise; + call(command: string, arg?: any): TPromise; +} + +export class LocalizationsChannel implements ILocalizationsChannel { + + onDidLanguagesChange: Event; + + constructor(private service: ILocalizationsService) { + this.onDidLanguagesChange = buffer(service.onDidLanguagesChange, true); + } + + call(command: string, arg?: any): TPromise { + switch (command) { + case 'event:onDidLanguagesChange': return eventToCall(this.onDidLanguagesChange); + case 'getLanguageIds': return this.service.getLanguageIds(); + } + return undefined; + } +} + +export class LocalizationsChannelClient implements ILocalizationsService { + + _serviceBrand: any; + + constructor(private channel: ILocalizationsChannel) { } + + private _onDidLanguagesChange = eventFromCall(this.channel, 'event:onDidLanguagesChange'); + get onDidLanguagesChange(): Event { return this._onDidLanguagesChange; } + + getLanguageIds(): TPromise { + return this.channel.call('getLanguageIds'); + } +} \ No newline at end of file diff --git a/src/vs/platform/localizations/node/localizations.ts b/src/vs/platform/localizations/node/localizations.ts index 0e8fb3a98e4..8b41df4be45 100644 --- a/src/vs/platform/localizations/node/localizations.ts +++ b/src/vs/platform/localizations/node/localizations.ts @@ -11,9 +11,12 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { join } from 'vs/base/common/paths'; import { TPromise } from 'vs/base/common/winjs.base'; import { Limiter } from 'vs/base/common/async'; -import { areSameExtensions, getGalleryExtensionIdFromLocal } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { areSameExtensions, getGalleryExtensionIdFromLocal, getIdFromLocalExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ILogService } from 'vs/platform/log/common/log'; -import { isValidLocalization } from 'vs/platform/localizations/common/localizations'; +import { isValidLocalization, ILocalizationsService } from 'vs/platform/localizations/common/localizations'; +import product from 'vs/platform/node/product'; +import { distinct, equals } from 'vs/base/common/arrays'; +import Event, { Emitter } from 'vs/base/common/event'; interface ILanguagePack { hash: string; @@ -24,10 +27,19 @@ interface ILanguagePack { translations: { [id: string]: string }; } -export class LanguagePacksCache extends Disposable { +const systemLanguages: string[] = ['de', 'en', 'en-US', 'es', 'fr', 'it', 'ja', 'ko', 'ru', 'zh-CN', 'zh-TW']; +if (product.quality !== 'stable') { + systemLanguages.push('hu'); +} - private languagePacksFilePath: string; - private languagePacksFileLimiter: Limiter; +export class LocalizationsService extends Disposable implements ILocalizationsService { + + _serviceBrand: any; + + private readonly cache: LanguagePacksCache; + + private readonly _onDidLanguagesChange: Emitter = this._register(new Emitter()); + readonly onDidLanguagesChange: Event = this._onDidLanguagesChange.event; constructor( @IExtensionManagementService private extensionManagementService: IExtensionManagementService, @@ -35,37 +47,80 @@ export class LanguagePacksCache extends Disposable { @ILogService private logService: ILogService ) { super(); - this.languagePacksFilePath = join(environmentService.userDataPath, 'languagepacks.json'); - this.languagePacksFileLimiter = new Limiter(1); + this.cache = this._register(new LanguagePacksCache(environmentService, logService)); this._register(extensionManagementService.onDidInstallExtension(({ local }) => this.onDidInstallExtension(local))); this._register(extensionManagementService.onDidUninstallExtension(({ identifier }) => this.onDidUninstallExtension(identifier))); - this.reset(); + this.extensionManagementService.getInstalled().then(installed => this.cache.update(installed)); + } + + getLanguageIds(): TPromise { + return this.cache.getLanguagePacks() + .then(languagePacks => { + const languages = [...systemLanguages, ...Object.keys(languagePacks)]; + return TPromise.as(distinct(languages)); + }); } private onDidInstallExtension(extension: ILocalExtension): void { if (extension && extension.manifest && extension.manifest.contributes && extension.manifest.contributes.localizations && extension.manifest.contributes.localizations.length) { this.logService.debug('Adding language packs from the extension', extension.identifier.id); - this.reset(); + this.update(); } } private onDidUninstallExtension(identifier: IExtensionIdentifier): void { - if (this.withLanguagePacks(languagePacks => Object.keys(languagePacks).some(language => languagePacks[language] && languagePacks[language].extensions.some(e => areSameExtensions(e.extensionIdentifier, identifier))))) { - this.logService.debug('Removing language packs from the extension', identifier.id); - this.reset(); - } + this.cache.getLanguagePacks() + .then(languagePacks => { + identifier = { id: getIdFromLocalExtensionId(identifier.id), uuid: identifier.uuid }; + if (Object.keys(languagePacks).some(language => languagePacks[language] && languagePacks[language].extensions.some(e => areSameExtensions(e.extensionIdentifier, identifier)))) { + this.logService.debug('Removing language packs from the extension', identifier.id); + this.update(); + } + }); } - private reset(): void { - this.extensionManagementService.getInstalled() - .then(installed => { - this.withLanguagePacks(languagePacks => { - Object.keys(languagePacks).forEach(language => languagePacks[language] = undefined); - this.createLanguagePacksFromExtensions(languagePacks, ...installed); - }); - }); + private update(): void { + TPromise.join([this.cache.getLanguagePacks(), this.extensionManagementService.getInstalled()]) + .then(([current, installed]) => this.cache.update(installed) + .then(updated => { + if (!equals(Object.keys(current), Object.keys(updated))) { + this._onDidLanguagesChange.fire(); + } + })); + } +} + +class LanguagePacksCache extends Disposable { + + private languagePacks: { [language: string]: ILanguagePack } = {}; + private languagePacksFilePath: string; + private languagePacksFileLimiter: Limiter; + + constructor( + @IEnvironmentService environmentService: IEnvironmentService, + @ILogService private logService: ILogService + ) { + super(); + this.languagePacksFilePath = join(environmentService.userDataPath, 'languagepacks.json'); + this.languagePacksFileLimiter = new Limiter(1); + } + + getLanguagePacks(): TPromise<{ [language: string]: ILanguagePack }> { + // if queue is not empty, fetch from disk + if (this.languagePacksFileLimiter.size) { + return this.withLanguagePacks() + .then(() => this.languagePacks); + } + return TPromise.as(this.languagePacks); + } + + update(extensions: ILocalExtension[]): TPromise<{ [language: string]: ILanguagePack }> { + return this.withLanguagePacks(languagePacks => { + Object.keys(languagePacks).forEach(language => languagePacks[language] = undefined); + this.createLanguagePacksFromExtensions(languagePacks, ...extensions); + }).then(() => this.languagePacks); } private createLanguagePacksFromExtensions(languagePacks: { [language: string]: ILanguagePack }, ...extensions: ILocalExtension[]): void { @@ -109,7 +164,7 @@ export class LanguagePacksCache extends Disposable { } } - private withLanguagePacks(fn: (languagePacks: { [language: string]: ILanguagePack }) => T): TPromise { + private withLanguagePacks(fn: (languagePacks: { [language: string]: ILanguagePack }) => T = () => null): TPromise { return this.languagePacksFileLimiter.queue(() => { let result: T = null; return pfs.readFile(this.languagePacksFilePath, 'utf8') @@ -122,7 +177,8 @@ export class LanguagePacksCache extends Disposable { delete languagePacks[language]; } } - const raw = JSON.stringify(languagePacks); + this.languagePacks = languagePacks; + const raw = JSON.stringify(this.languagePacks); this.logService.debug('Writing language packs', raw); return pfs.writeFile(this.languagePacksFilePath, raw); }) diff --git a/src/vs/workbench/electron-browser/actions.ts b/src/vs/workbench/electron-browser/actions.ts index 00db7589aea..9b388c81270 100644 --- a/src/vs/workbench/electron-browser/actions.ts +++ b/src/vs/workbench/electron-browser/actions.ts @@ -23,7 +23,7 @@ import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configur import { IExtensionManagementService, LocalExtensionType, IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import paths = require('vs/base/common/paths'); -import { isMacintosh, isLinux, language } from 'vs/base/common/platform'; +import { isMacintosh, isLinux } from 'vs/base/common/platform'; import { IQuickOpenService, IFilePickOpenEntry, ISeparator, IPickOpenAction, IPickOpenItem } from 'vs/platform/quickOpen/common/quickOpen'; import * as browser from 'vs/base/browser/browser'; import { IIntegrityService } from 'vs/platform/integrity/common/integrity'; @@ -40,11 +40,10 @@ import { getPathLabel, getBaseLabel } from 'vs/base/common/labels'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { IPanel } from 'vs/workbench/common/panel'; import { IWorkspaceIdentifier, getWorkspaceLabel, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; -import { FileKind, IFileService } from 'vs/platform/files/common/files'; +import { FileKind } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IExtensionService, ActivationTimes } from 'vs/platform/extensions/common/extensions'; import { getEntries } from 'vs/base/common/performance'; -import { IEditor } from 'vs/platform/editor/common/editor'; import { IIssueService, IssueReporterData, IssueType, IssueReporterStyles } from 'vs/platform/issue/common/issue'; import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; import { textLinkForeground, inputBackground, inputBorder, inputForeground, buttonBackground, buttonHoverBackground, buttonForeground, inputValidationErrorBorder, foreground, inputActiveOptionBorder } from 'vs/platform/theme/common/colorRegistry'; @@ -1605,46 +1604,4 @@ export class ToggleWindowTabsBar extends Action { public run(): TPromise { return this.windowsService.toggleWindowTabsBar().then(() => true); } -} - -export class ConfigureLocaleAction extends Action { - public static readonly ID = 'workbench.action.configureLocale'; - public static readonly LABEL = nls.localize('configureLocale', "Configure Language"); - - private static DEFAULT_CONTENT: string = [ - '{', - `\t// ${nls.localize('displayLanguage', 'Defines VSCode\'s display language.')}`, - `\t// ${nls.localize('doc', 'See {0} for a list of supported languages.', 'https://go.microsoft.com/fwlink/?LinkId=761051')}`, - `\t// ${nls.localize('restart', 'Changing the value requires restarting VSCode.')}`, - `\t"locale":"${language}"`, - '}' - ].join('\n'); - - constructor(id: string, label: string, - @IFileService private fileService: IFileService, - @IWorkspaceContextService private contextService: IWorkspaceContextService, - @IEnvironmentService private environmentService: IEnvironmentService, - @IWorkbenchEditorService private editorService: IWorkbenchEditorService - ) { - super(id, label); - } - - public run(event?: any): TPromise { - const file = URI.file(paths.join(this.environmentService.appSettingsHome, 'locale.json')); - return this.fileService.resolveFile(file).then(null, (error) => { - return this.fileService.createFile(file, ConfigureLocaleAction.DEFAULT_CONTENT); - }).then((stat) => { - if (!stat) { - return undefined; - } - return this.editorService.openEditor({ - resource: stat.resource, - options: { - forceOpen: true - } - }); - }, (error) => { - throw new Error(nls.localize('fail.createSettings', "Unable to create '{0}' ({1}).", getPathLabel(file, this.contextService), error)); - }); - } -} +} \ No newline at end of file diff --git a/src/vs/workbench/electron-browser/main.contribution.ts b/src/vs/workbench/electron-browser/main.contribution.ts index 700ea3ef4e6..8097564e8b8 100644 --- a/src/vs/workbench/electron-browser/main.contribution.ts +++ b/src/vs/workbench/electron-browser/main.contribution.ts @@ -14,15 +14,13 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'v import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform'; -import { KeybindingsReferenceAction, OpenDocumentationUrlAction, OpenIntroductoryVideosUrlAction, OpenTipsAndTricksUrlAction, OpenIssueReporterAction, ReportPerformanceIssueUsingReporterAction, ZoomResetAction, ZoomOutAction, ZoomInAction, ToggleFullScreenAction, ToggleMenuBarAction, CloseWorkspaceAction, CloseCurrentWindowAction, SwitchWindow, NewWindowAction, CloseMessagesAction, NavigateUpAction, NavigateDownAction, NavigateLeftAction, NavigateRightAction, IncreaseViewSizeAction, DecreaseViewSizeAction, ShowStartupPerformance, ToggleSharedProcessAction, QuickSwitchWindow, QuickOpenRecentAction, inRecentFilesPickerContextKey, ConfigureLocaleAction } from 'vs/workbench/electron-browser/actions'; +import { KeybindingsReferenceAction, OpenDocumentationUrlAction, OpenIntroductoryVideosUrlAction, OpenTipsAndTricksUrlAction, OpenIssueReporterAction, ReportPerformanceIssueUsingReporterAction, ZoomResetAction, ZoomOutAction, ZoomInAction, ToggleFullScreenAction, ToggleMenuBarAction, CloseWorkspaceAction, CloseCurrentWindowAction, SwitchWindow, NewWindowAction, CloseMessagesAction, NavigateUpAction, NavigateDownAction, NavigateLeftAction, NavigateRightAction, IncreaseViewSizeAction, DecreaseViewSizeAction, ShowStartupPerformance, ToggleSharedProcessAction, QuickSwitchWindow, QuickOpenRecentAction, inRecentFilesPickerContextKey } from 'vs/workbench/electron-browser/actions'; import { MessagesVisibleContext } from 'vs/workbench/electron-browser/workbench'; -import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { registerCommands } from 'vs/workbench/electron-browser/commands'; import { AddRootFolderAction, GlobalRemoveRootFolderAction, OpenWorkspaceAction, SaveWorkspaceAsAction, OpenWorkspaceConfigFileAction, OpenFolderAsWorkspaceInNewWindowAction, OpenFileFolderAction, OpenFileAction, OpenFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { inQuickOpenContext, getQuickNavigateHandler } from 'vs/workbench/browser/parts/quickopen/quickopen'; import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; // Contribute Commands registerCommands(); @@ -425,38 +423,4 @@ configurationRegistry.registerConfiguration({ 'description': nls.localize('zenMode.restore', "Controls if a window should restore to zen mode if it was exited in zen mode.") } } -}); - -// Register action to configure locale and related settings - -const registry = Registry.as(Extensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(ConfigureLocaleAction, ConfigureLocaleAction.ID, ConfigureLocaleAction.LABEL), 'Configure Language'); - -let enumValues: string[] = ['de', 'en', 'en-US', 'es', 'fr', 'it', 'ja', 'ko', 'ru', 'zh-CN', 'zh-TW']; -if (product.quality !== 'stable') { - enumValues.push('hu'); -} - -const schemaId = 'vscode://schemas/locale'; -// Keep en-US since we generated files with that content. -const schema: IJSONSchema = - { - id: schemaId, - allowComments: true, - description: 'Locale Definition file', - type: 'object', - default: { - 'locale': 'en' - }, - required: ['locale'], - properties: { - locale: { - type: 'string', - enum: enumValues, - description: nls.localize('JsonSchema.locale', 'The UI Language to use.') - } - } - }; - -const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); -jsonRegistry.registerSchema(schemaId, schema); +}); \ No newline at end of file diff --git a/src/vs/workbench/electron-browser/shell.ts b/src/vs/workbench/electron-browser/shell.ts index b1bb403feb2..dfce549450b 100644 --- a/src/vs/workbench/electron-browser/shell.ts +++ b/src/vs/workbench/electron-browser/shell.ts @@ -90,6 +90,8 @@ import { ILogService } from 'vs/platform/log/common/log'; import { WORKBENCH_BACKGROUND } from 'vs/workbench/common/theme'; import { stat } from 'fs'; import { join } from 'path'; +import { ILocalizationsChannel, LocalizationsChannelClient } from 'vs/platform/localizations/common/localizationsIpc'; +import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; /** * Services that we require for the Shell @@ -447,6 +449,9 @@ export class WorkbenchShell { serviceCollection.set(IIntegrityService, new SyncDescriptor(IntegrityServiceImpl)); + const localizationsChannel = getDelayedChannel(sharedProcess.then(c => c.getChannel('localizations'))); + serviceCollection.set(ILocalizationsService, new SyncDescriptor(LocalizationsChannelClient, localizationsChannel)); + return [instantiationService, serviceCollection]; } diff --git a/src/vs/workbench/parts/localizations/browser/localizations.contribution.ts b/src/vs/workbench/parts/localizations/browser/localizations.contribution.ts new file mode 100644 index 00000000000..9af94e32e96 --- /dev/null +++ b/src/vs/workbench/parts/localizations/browser/localizations.contribution.ts @@ -0,0 +1,110 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkbenchContribution, Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; +import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; +import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { ConfigureLocaleAction } from 'vs/workbench/parts/localizations/browser/localizationsActions'; +import { ExtensionsRegistry } from 'vs/platform/extensions/common/extensionsRegistry'; +import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { language } from 'vs/base/common/platform'; + +// Register action to configure locale and related settings +const registry = Registry.as(Extensions.WorkbenchActions); +registry.registerWorkbenchAction(new SyncActionDescriptor(ConfigureLocaleAction, ConfigureLocaleAction.ID, ConfigureLocaleAction.LABEL), 'Configure Language'); + +export class LocalesSchemaUpdater extends Disposable implements IWorkbenchContribution { + constructor( + @ILocalizationsService private localizationService: ILocalizationsService + ) { + super(); + this.update(); + this._register(this.localizationService.onDidLanguagesChange(() => this.update())); + } + + private update(): void { + this.localizationService.getLanguageIds() + .then(languageIds => registerLocaleDefinitionSchema(languageIds)); + } +} + +function registerLocaleDefinitionSchema(languages: string[]): void { + const localeDefinitionFileSchemaId = 'vscode://schemas/locale'; + const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); + // Keep en-US since we generated files with that content. + jsonRegistry.registerSchema(localeDefinitionFileSchemaId, { + id: localeDefinitionFileSchemaId, + allowComments: true, + description: 'Locale Definition file', + type: 'object', + default: { + 'locale': 'en' + }, + required: ['locale'], + properties: { + locale: { + type: 'string', + enum: languages, + description: localize('JsonSchema.locale', 'The UI Language to use.') + } + } + }); +} + +registerLocaleDefinitionSchema([language]); +const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); +workbenchRegistry.registerWorkbenchContribution(LocalesSchemaUpdater, LifecyclePhase.Eventually); + +ExtensionsRegistry.registerExtensionPoint('localizations', [], { + description: localize('vscode.extension.contributes.localizations', "Contributes localizations to the editor"), + type: 'array', + default: [], + items: { + type: 'object', + required: ['languageId', 'translations'], + defaultSnippets: [{ body: { languageId: '', languageName: '', languageNameLocalized: '', translations: [{ id: 'vscode', path: '' }] } }], + properties: { + languageId: { + description: localize('vscode.extension.contributes.localizations.languageId', 'Id of the language into which the display strings are translated.'), + type: 'string' + }, + languageName: { + description: localize('vscode.extension.contributes.localizations.languageName', 'Name of the language in English.'), + type: 'string' + }, + languageNameLocalized: { + description: localize('vscode.extension.contributes.localizations.languageNameLocalized', 'Name of the language in contributed language.'), + type: 'string' + }, + translations: { + description: localize('vscode.extension.contributes.localizations.translations', 'List of translations associated to the language.'), + type: 'array', + default: [{ id: 'vscode', path: '' }], + items: { + type: 'object', + required: ['id', 'path'], + properties: { + id: { + type: 'string', + description: localize('vscode.extension.contributes.localizations.translations.id', "Id of VS Code or Extension for which this translation is contributed to. Id of VS Code is always `vscode` and of extension should be in format `publisherId.extensionName`."), + pattern: '^((vscode)|([a-z0-9A-Z][a-z0-9\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\-A-Z]*))$', + patternErrorMessage: localize('vscode.extension.contributes.localizations.translations.id.pattern', "Id should be `vscode` or in format `publisherId.extensionName` for translating VS code or an extension respectively.") + }, + path: { + type: 'string', + description: localize('vscode.extension.contributes.localizations.translations.path', "A relative path to a file containing translations for the language.") + } + }, + defaultSnippets: [{ body: { id: '', path: '' } }], + }, + } + } + } +}); \ No newline at end of file diff --git a/src/vs/workbench/parts/localizations/browser/localizationsActions.ts b/src/vs/workbench/parts/localizations/browser/localizationsActions.ts new file mode 100644 index 00000000000..06d969dd98e --- /dev/null +++ b/src/vs/workbench/parts/localizations/browser/localizationsActions.ts @@ -0,0 +1,59 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { Action } from 'vs/base/common/actions'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { IEditor } from 'vs/platform/editor/common/editor'; +import { join } from 'vs/base/common/paths'; +import URI from 'vs/base/common/uri'; +import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { getPathLabel } from 'vs/base/common/labels'; +import { language } from 'vs/base/common/platform'; + +export class ConfigureLocaleAction extends Action { + public static readonly ID = 'workbench.action.configureLocale'; + public static readonly LABEL = localize('configureLocale', "Configure Language"); + + private static DEFAULT_CONTENT: string = [ + '{', + `\t// ${localize('displayLanguage', 'Defines VSCode\'s display language.')}`, + `\t// ${localize('doc', 'See {0} for a list of supported languages.', 'https://go.microsoft.com/fwlink/?LinkId=761051')}`, + `\t// ${localize('restart', 'Changing the value requires restarting VSCode.')}`, + `\t"locale":"${language}"`, + '}' + ].join('\n'); + + constructor(id: string, label: string, + @IFileService private fileService: IFileService, + @IWorkspaceContextService private contextService: IWorkspaceContextService, + @IEnvironmentService private environmentService: IEnvironmentService, + @IWorkbenchEditorService private editorService: IWorkbenchEditorService + ) { + super(id, label); + } + + public run(event?: any): TPromise { + const file = URI.file(join(this.environmentService.appSettingsHome, 'locale.json')); + return this.fileService.resolveFile(file).then(null, (error) => { + return this.fileService.createFile(file, ConfigureLocaleAction.DEFAULT_CONTENT); + }).then((stat) => { + if (!stat) { + return undefined; + } + return this.editorService.openEditor({ + resource: stat.resource, + options: { + forceOpen: true + } + }); + }, (error) => { + throw new Error(localize('fail.createSettings', "Unable to create '{0}' ({1}).", getPathLabel(file, this.contextService), error)); + }); + } +} \ No newline at end of file diff --git a/src/vs/workbench/workbench.main.ts b/src/vs/workbench/workbench.main.ts index 8149e729d2a..e0e2837dfc8 100644 --- a/src/vs/workbench/workbench.main.ts +++ b/src/vs/workbench/workbench.main.ts @@ -22,7 +22,7 @@ import 'vs/platform/actions/electron-browser/menusExtensionPoint'; import 'vs/workbench/api/browser/viewsExtensionPoint'; // Localizations -import 'vs/platform/localizations/common/localizations'; +import 'vs/workbench/parts/localizations/browser/localizations.contribution'; // Workbench import 'vs/workbench/browser/actions/toggleActivityBarVisibility';