diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 2cd44bc0f05..ad4d47485e7 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -430,25 +430,25 @@ export class CommandCenter { case Status.INDEX_MODIFIED: case Status.INDEX_RENAMED: case Status.INDEX_ADDED: - return `${basename} (Index)`; + return localize('git.title.index', '{0} (Index)', basename); case Status.MODIFIED: case Status.BOTH_ADDED: case Status.BOTH_MODIFIED: - return `${basename} (Working Tree)`; + return localize('git.title.workingTree', '{0} (Working Tree)', basename); case Status.DELETED_BY_US: - return `${basename} (Theirs)`; + return localize('git.title.theirs', '{0} (Theirs)', basename); case Status.DELETED_BY_THEM: - return `${basename} (Ours)`; + return localize('git.title.ours', '{0} (Ours)', basename); case Status.UNTRACKED: + return localize('git.title.untracked', '{0} (Untracked)', basename); - return `${basename} (Untracked)`; + default: + return ''; } - - return ''; } @command('git.clone') @@ -2348,12 +2348,12 @@ export class CommandCenter { let title; if ((item.previousRef === 'HEAD' || item.previousRef === '~') && item.ref === '') { - title = `${basename} (Working Tree)`; + title = localize('git.title.workingTree', '{0} (Working Tree)', basename); } else if (item.previousRef === 'HEAD' && item.ref === '~') { - title = `${basename} (Index)`; + title = localize('git.title.index', '{0} (Index)', basename); } else { - title = `${basename} (${item.shortPreviousRef}) \u27f7 ${basename} (${item.shortRef})`; + title = localize('git.title.diffRefs', '{0} ({1}) \u27f7 {0} ({2})', basename, item.shortPreviousRef, item.shortRef); } return commands.executeCommand('vscode.diff', toGitUri(uri, item.previousRef), item.ref === '' ? uri : toGitUri(uri, item.ref), title); diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 4f22b0784bb..4f1d21796ab 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -40,6 +40,29 @@ export const enum ResourceGroupType { export class Resource implements SourceControlResourceState { + static getStatusText(type: Status) { + switch (type) { + case Status.INDEX_MODIFIED: return localize('index modified', "Index Modified"); + case Status.MODIFIED: return localize('modified', "Modified"); + case Status.INDEX_ADDED: return localize('index added', "Index Added"); + case Status.INDEX_DELETED: return localize('index deleted', "Index Deleted"); + case Status.DELETED: return localize('deleted', "Deleted"); + case Status.INDEX_RENAMED: return localize('index renamed', "Index Renamed"); + case Status.INDEX_COPIED: return localize('index copied', "Index Copied"); + case Status.UNTRACKED: return localize('untracked', "Untracked"); + case Status.IGNORED: return localize('ignored', "Ignored"); + case Status.INTENT_TO_ADD: return localize('intent to add', "Intent to Add"); + case Status.BOTH_DELETED: return localize('both deleted', "Both Deleted"); + case Status.ADDED_BY_US: return localize('added by us', "Added By Us"); + case Status.DELETED_BY_THEM: return localize('deleted by them', "Deleted By Them"); + case Status.ADDED_BY_THEM: return localize('added by them', "Added By Them"); + case Status.DELETED_BY_US: return localize('deleted by us', "Deleted By Us"); + case Status.BOTH_ADDED: return localize('both added', "Both Added"); + case Status.BOTH_MODIFIED: return localize('both modified', "Both Modified"); + default: return ''; + } + } + @memoize get resourceUri(): Uri { if (this.renameResourceUri && (this._type === Status.MODIFIED || this._type === Status.DELETED || this._type === Status.INDEX_RENAMED || this._type === Status.INDEX_COPIED)) { @@ -110,26 +133,7 @@ export class Resource implements SourceControlResourceState { } private get tooltip(): string { - switch (this.type) { - case Status.INDEX_MODIFIED: return localize('index modified', "Index Modified"); - case Status.MODIFIED: return localize('modified', "Modified"); - case Status.INDEX_ADDED: return localize('index added', "Index Added"); - case Status.INDEX_DELETED: return localize('index deleted', "Index Deleted"); - case Status.DELETED: return localize('deleted', "Deleted"); - case Status.INDEX_RENAMED: return localize('index renamed', "Index Renamed"); - case Status.INDEX_COPIED: return localize('index copied', "Index Copied"); - case Status.UNTRACKED: return localize('untracked', "Untracked"); - case Status.IGNORED: return localize('ignored', "Ignored"); - case Status.INTENT_TO_ADD: return localize('intent to add', "Intent to Add"); - case Status.BOTH_DELETED: return localize('both deleted', "Both Deleted"); - case Status.ADDED_BY_US: return localize('added by us', "Added By Us"); - case Status.DELETED_BY_THEM: return localize('deleted by them', "Deleted By Them"); - case Status.ADDED_BY_THEM: return localize('added by them', "Added By Them"); - case Status.DELETED_BY_US: return localize('deleted by us', "Deleted By Us"); - case Status.BOTH_ADDED: return localize('both added', "Both Added"); - case Status.BOTH_MODIFIED: return localize('both modified', "Both Modified"); - default: return ''; - } + return Resource.getStatusText(this.type); } private get strikeThrough(): boolean { diff --git a/extensions/git/src/timelineProvider.ts b/extensions/git/src/timelineProvider.ts index f0a95d61a34..3fe8e306c95 100644 --- a/extensions/git/src/timelineProvider.ts +++ b/extensions/git/src/timelineProvider.ts @@ -3,17 +3,18 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as nls from 'vscode-nls'; import * as dayjs from 'dayjs'; import * as advancedFormat from 'dayjs/plugin/advancedFormat'; import { CancellationToken, Disposable, Event, EventEmitter, ThemeIcon, Timeline, TimelineChangeEvent, TimelineItem, TimelineOptions, TimelineProvider, Uri, workspace } from 'vscode'; import { Model } from './model'; -import { Repository } from './repository'; +import { Repository, Resource } from './repository'; import { debounce } from './decorators'; -import { Status } from './api/git'; dayjs.extend(advancedFormat); -// TODO[ECA]: Localize all the strings +const localize = nls.loadMessageBundle(); + // TODO[ECA]: Localize or use a setting for date format export class GitTimelineItem extends TimelineItem { @@ -68,7 +69,7 @@ export class GitTimelineProvider implements TimelineProvider { } readonly id = 'git-history'; - readonly label = 'Git History'; + readonly label = localize('git.timeline.source', 'Git History'); private _disposable: Disposable; @@ -171,38 +172,18 @@ export class GitTimelineProvider implements TimelineProvider { }); if (options.cursor === undefined || options.before) { + const you = localize('git.timeline.you', 'You'); + const index = repo.indexGroup.resourceStates.find(r => r.resourceUri.fsPath === uri.fsPath); if (index) { const date = this._repoStatusDate ?? new Date(); dateFormatter = dayjs(date); - let status; - switch (index.type) { - case Status.INDEX_MODIFIED: - status = 'Modified'; - break; - case Status.INDEX_ADDED: - status = 'Added'; - break; - case Status.INDEX_DELETED: - status = 'Deleted'; - break; - case Status.INDEX_RENAMED: - status = 'Renamed'; - break; - case Status.INDEX_COPIED: - status = 'Copied'; - break; - default: - status = ''; - break; - } - - const item = new GitTimelineItem('~', 'HEAD', 'Staged Changes', date.getTime(), 'index', 'git:file:index'); + const item = new GitTimelineItem('~', 'HEAD', localize('git.timeline.stagedChanges', 'Staged Changes'), date.getTime(), 'index', 'git:file:index'); // TODO[ECA]: Replace with a better icon -- reflecting its status maybe? item.iconPath = new (ThemeIcon as any)('git-commit'); - item.description = 'You'; - item.detail = `You \u2014 Index\n${dateFormatter.format('MMMM Do, YYYY h:mma')}\n${status}`; + item.description = you; + item.detail = localize('git.timeline.detail', '{0} \u2014 {1}\n{2}\n\n{3}', you, localize('git.index', 'Index'), dateFormatter.format('MMMM Do, YYYY h:mma'), Resource.getStatusText(index.type)); item.command = { title: 'Open Comparison', command: 'git.timeline.openDiff', @@ -217,33 +198,11 @@ export class GitTimelineProvider implements TimelineProvider { const date = new Date(); dateFormatter = dayjs(date); - let status; - switch (working.type) { - case Status.INDEX_MODIFIED: - status = 'Modified'; - break; - case Status.INDEX_ADDED: - status = 'Added'; - break; - case Status.INDEX_DELETED: - status = 'Deleted'; - break; - case Status.INDEX_RENAMED: - status = 'Renamed'; - break; - case Status.INDEX_COPIED: - status = 'Copied'; - break; - default: - status = ''; - break; - } - - const item = new GitTimelineItem('', index ? '~' : 'HEAD', 'Uncommited Changes', date.getTime(), 'working', 'git:file:working'); + const item = new GitTimelineItem('', index ? '~' : 'HEAD', localize('git.timeline.uncommitedChanges', 'Uncommited Changes'), date.getTime(), 'working', 'git:file:working'); // TODO[ECA]: Replace with a better icon -- reflecting its status maybe? item.iconPath = new (ThemeIcon as any)('git-commit'); - item.description = 'You'; - item.detail = `You \u2014 Working Tree\n${dateFormatter.format('MMMM Do, YYYY h:mma')}\n${status}`; + item.description = you; + item.detail = localize('git.timeline.detail', '{0} \u2014 {1}\n{2}\n\n{3}', you, localize('git.workingTree', 'Working Tree'), dateFormatter.format('MMMM Do, YYYY h:mma'), Resource.getStatusText(working.type)); item.command = { title: 'Open Comparison', command: 'git.timeline.openDiff', diff --git a/src/vs/base/common/date.ts b/src/vs/base/common/date.ts index ec970c13103..4655bc663ee 100644 --- a/src/vs/base/common/date.ts +++ b/src/vs/base/common/date.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { pad } from './strings'; +import { localize } from 'vs/nls'; const minute = 60; const hour = minute * 60; @@ -12,7 +13,6 @@ const week = day * 7; const month = day * 30; const year = day * 365; -// TODO[ECA]: Localize strings export function fromNow(date: number | Date, appendAgoLabel?: boolean): string { if (typeof date !== 'number') { date = date.getTime(); @@ -20,36 +20,99 @@ export function fromNow(date: number | Date, appendAgoLabel?: boolean): string { const seconds = Math.round((new Date().getTime() - date) / 1000); if (seconds < 30) { - return 'now'; + return localize('date.fromNow.now', 'now'); } let value: number; - let unit: string; if (seconds < minute) { value = seconds; - unit = 'sec'; - } else if (seconds < hour) { - value = Math.floor(seconds / minute); - unit = 'min'; - } else if (seconds < day) { - value = Math.floor(seconds / hour); - unit = 'hr'; - } else if (seconds < week) { - value = Math.floor(seconds / day); - unit = 'day'; - } else if (seconds < month) { - value = Math.floor(seconds / week); - unit = 'wk'; - } else if (seconds < year) { - value = Math.floor(seconds / month); - unit = 'mo'; - } else { - value = Math.floor(seconds / year); - unit = 'yr'; + + if (appendAgoLabel) { + return value === 1 + ? localize('date.fromNow.seconds.singular.ago', '{0} sec ago', value) + : localize('date.fromNow.seconds.plural.ago', '{0} secs ago', value); + } else { + return value === 1 + ? localize('date.fromNow.seconds.singular', '{0} sec', value) + : localize('date.fromNow.seconds.plural', '{0} secs', value); + } } - return `${value} ${unit}${value === 1 ? '' : 's'}${appendAgoLabel ? ' ago' : ''}`; + if (seconds < hour) { + value = Math.floor(seconds / minute); + if (appendAgoLabel) { + return value === 1 + ? localize('date.fromNow.minutes.singular.ago', '{0} min ago', value) + : localize('date.fromNow.minutes.plural.ago', '{0} mins ago', value); + } else { + return value === 1 + ? localize('date.fromNow.minutes.singular', '{0} min', value) + : localize('date.fromNow.minutes.plural', '{0} mins', value); + } + } + if (seconds < day) { + value = Math.floor(seconds / hour); + if (appendAgoLabel) { + return value === 1 + ? localize('date.fromNow.hours.singular.ago', '{0} hr ago', value) + : localize('date.fromNow.hours.plural.ago', '{0} hrs ago', value); + } else { + return value === 1 + ? localize('date.fromNow.hours.singular', '{0} hr', value) + : localize('date.fromNow.hours.plural', '{0} hrs', value); + } + } + + if (seconds < week) { + value = Math.floor(seconds / day); + if (appendAgoLabel) { + return value === 1 + ? localize('date.fromNow.days.singular.ago', '{0} day ago', value) + : localize('date.fromNow.days.plural.ago', '{0} days ago', value); + } else { + return value === 1 + ? localize('date.fromNow.days.singular', '{0} day', value) + : localize('date.fromNow.days.plural', '{0} days', value); + } + } + + if (seconds < month) { + value = Math.floor(seconds / week); + if (appendAgoLabel) { + return value === 1 + ? localize('date.fromNow.weeks.singular.ago', '{0} wk ago', value) + : localize('date.fromNow.weeks.plural.ago', '{0} wks ago', value); + } else { + return value === 1 + ? localize('date.fromNow.weeks.singular', '{0} wk', value) + : localize('date.fromNow.weeks.plural', '{0} wks', value); + } + } + + if (seconds < year) { + value = Math.floor(seconds / month); + if (appendAgoLabel) { + return value === 1 + ? localize('date.fromNow.months.singular.ago', '{0} mo ago', value) + : localize('date.fromNow.months.plural.ago', '{0} mos ago', value); + } else { + return value === 1 + ? localize('date.fromNow.months.singular', '{0} mo', value) + : localize('date.fromNow.months.plural', '{0} mos', value); + } + } + + value = Math.floor(seconds / year); + if (appendAgoLabel) { + return value === 1 + ? localize('date.fromNow.years.singular.ago', '{0} yr ago', value) + : localize('date.fromNow.years.plural.ago', '{0} yrs ago', value); + } else { + return value === 1 + ? localize('date.fromNow.years.singular', '{0} yr', value) + : localize('date.fromNow.years.plural', '{0} yrs', value); + } } export function toLocalISOString(date: Date): string { diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index 62e956ab2fb..d438a1b739c 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -38,8 +38,6 @@ import { MenuItemAction, IMenuService, MenuId } from 'vs/platform/actions/common import { fromNow } from 'vs/base/common/date'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -// TODO[ECA]: Localize all the strings - const InitialPageSize = 20; const SubsequentPageSize = 40; @@ -240,7 +238,7 @@ export class TimelinePane extends ViewPane { // TODO[ECA]: Are these the right the list of schemes to exclude? Is there a better way? if (this._uri && (this._uri.scheme === 'vscode-settings' || this._uri.scheme === 'webview-panel' || this._uri.scheme === 'walkThrough')) { - this.message = 'The active editor cannot provide timeline information.'; + this.message = localize('timeline.editorCannotProvideTimeline', 'The active editor cannot provide timeline information.'); this._tree.setChildren(null, undefined); return; @@ -253,7 +251,7 @@ export class TimelinePane extends ViewPane { } this._tree.setChildren(null, undefined); - this.message = `Loading timeline for ${basename(uri.fsPath)}...`; + this.message = localize('timeline.loading', 'Loading timeline for ${0}...', basename(uri.fsPath)); }, 500, this._uri); } } @@ -410,7 +408,7 @@ export class TimelinePane extends ViewPane { this._items.push({ element: { handle: 'vscode-command:loadMore', - label: 'Load more', + label: localize('timeline.loadMore', 'Load more'), timestamp: 0 } as CommandItem }); @@ -512,7 +510,7 @@ export class TimelinePane extends ViewPane { } if (this._items.length === 0) { - this.message = 'No timeline information was provided.'; + this.message = localize('timeline.noTimelineInfo', 'No timeline information was provided.'); } else { this.message = undefined; } @@ -556,7 +554,7 @@ export class TimelinePane extends ViewPane { this._messageElement = DOM.append(this._container, DOM.$('.message')); DOM.addClass(this._messageElement, 'timeline-subtle'); - this.message = 'The active editor cannot provide timeline information.'; + this.message = localize('timeline.editorCannotProvideTimeline', 'The active editor cannot provide timeline information.'); this._treeElement = document.createElement('div'); DOM.addClasses(this._treeElement, 'customview-tree', 'file-icon-themable-tree', 'hide-arrows');