fix: localize activity event labels
This commit is contained in:
parent
b449e9c616
commit
713b95b5a8
|
|
@ -37,19 +37,19 @@
|
||||||
'activity.session.handled'
|
'activity.session.handled'
|
||||||
];
|
];
|
||||||
var EVENT_LABELS = {
|
var EVENT_LABELS = {
|
||||||
'workspace.selected': 'Workspace selected',
|
'workspace.selected': { key: 'ui.event.workspaceSelected', fallback: 'Deal selected' },
|
||||||
'case.selected': 'Workspace selected',
|
'case.selected': { key: 'ui.event.workspaceSelected', fallback: 'Deal selected' },
|
||||||
'file.opened': 'File opened',
|
'file.opened': { key: 'ui.event.fileOpened', fallback: 'File opened' },
|
||||||
'file.changed': 'File changed',
|
'file.changed': { key: 'ui.event.fileChanged', fallback: 'File changed' },
|
||||||
'note.saved': 'Note edited',
|
'note.saved': { key: 'ui.event.noteEdited', fallback: 'Note edited' },
|
||||||
'action.started': 'Work session detected',
|
'action.started': { key: 'ui.event.workSessionDetected', fallback: 'Work session detected' },
|
||||||
'browser.capture.received': 'Browser capture received',
|
'browser.capture.received': { key: 'ui.event.browserCaptureReceived', fallback: 'Browser capture received' },
|
||||||
'browser.capture.page': 'Page captured',
|
'browser.capture.page': { key: 'ui.event.pageCaptured', fallback: 'Page captured' },
|
||||||
'browser.capture.selection': 'Selection captured',
|
'browser.capture.selection': { key: 'ui.event.selectionCaptured', fallback: 'Selection captured' },
|
||||||
'browser.capture.link': 'Link captured',
|
'browser.capture.link': { key: 'ui.event.linkCaptured', fallback: 'Link captured' },
|
||||||
'browser.capture.file': 'File captured',
|
'browser.capture.file': { key: 'ui.event.fileCaptured', fallback: 'File captured' },
|
||||||
'browser.capture.converted': 'Capture converted',
|
'browser.capture.converted': { key: 'ui.event.captureConverted', fallback: 'Capture converted' },
|
||||||
'browser.activity.domain': 'Browser domain activity'
|
'browser.activity.domain': { key: 'ui.event.browserDomainActivity', fallback: 'Browser domain activity' }
|
||||||
};
|
};
|
||||||
var LOW_VALUE_EVENT_TYPES = {
|
var LOW_VALUE_EVENT_TYPES = {
|
||||||
'workspace.selected': true,
|
'workspace.selected': true,
|
||||||
|
|
@ -412,25 +412,27 @@
|
||||||
}).slice(0, MAX_CANDIDATES);
|
}).slice(0, MAX_CANDIDATES);
|
||||||
}
|
}
|
||||||
|
|
||||||
function humanEventType(type) {
|
function humanEventType(type, translate) {
|
||||||
return EVENT_LABELS[text(type).toLowerCase()] || '';
|
var label = EVENT_LABELS[text(type).toLowerCase()];
|
||||||
|
return label ? translate(label.key, null, label.fallback) : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function humanEventTitle(activity) {
|
function humanEventTitle(activity, translate) {
|
||||||
var explicit = text(activity && activity.title).trim();
|
var explicit = text(activity && activity.title).trim();
|
||||||
var type = text(activity && activity.type).trim();
|
var type = text(activity && activity.type).trim();
|
||||||
if (explicit && explicit.toLowerCase() !== type.toLowerCase()) return explicit;
|
var label = humanEventType(type, translate);
|
||||||
return humanEventType(type) || text(activity && (activity.summary || activity.activityId)).trim() || 'Activity event';
|
if (explicit && explicit.toLowerCase() !== type.toLowerCase()) return label ? label + ' — ' + explicit : explicit;
|
||||||
|
return label || text(activity && (activity.summary || activity.activityId)).trim() || translate('ui.event.activity', null, 'Activity event');
|
||||||
}
|
}
|
||||||
|
|
||||||
function eventKind(activity) {
|
function eventKind(activity, translate) {
|
||||||
var type = text(activity && activity.type).toLowerCase();
|
var type = text(activity && activity.type).toLowerCase();
|
||||||
if (type.indexOf('browser.capture') === 0) return 'Capture';
|
if (type.indexOf('browser.capture') === 0) return translate('ui.kind.capture', null, 'Capture');
|
||||||
if (type.indexOf('file.') === 0) return 'File';
|
if (type.indexOf('file.') === 0) return translate('ui.kind.file', null, 'File');
|
||||||
if (type.indexOf('note.') === 0) return 'Note';
|
if (type.indexOf('note.') === 0) return translate('ui.kind.note', null, 'Note');
|
||||||
if (type.indexOf('workspace') !== -1 || type.indexOf('case.') === 0) return 'Workspace';
|
if (type.indexOf('workspace') !== -1 || type.indexOf('case.') === 0) return translate('ui.kind.deal', null, 'Deal');
|
||||||
if (type.indexOf('action.') === 0) return 'Work';
|
if (type.indexOf('action.') === 0) return translate('ui.kind.work', null, 'Work');
|
||||||
return 'Activity';
|
return translate('ui.kind.activity', null, 'Activity');
|
||||||
}
|
}
|
||||||
|
|
||||||
function globalEventKeys(settings) {
|
function globalEventKeys(settings) {
|
||||||
|
|
@ -760,14 +762,10 @@
|
||||||
el('div', { className: 'activity-time', textContent: formatDate(activity.occurredAt) || '-' }),
|
el('div', { className: 'activity-time', textContent: formatDate(activity.occurredAt) || '-' }),
|
||||||
el('div', { className: 'activity-main' }, [
|
el('div', { className: 'activity-main' }, [
|
||||||
el('div', { className: 'activity-row-head' }, [
|
el('div', { className: 'activity-row-head' }, [
|
||||||
el('span', { className: 'activity-type', textContent: eventKind(activity) }),
|
el('span', { className: 'activity-type', textContent: eventKind(activity, tr) }),
|
||||||
el('span', { className: 'activity-title-text', textContent: humanEventTitle(activity) })
|
el('span', { className: 'activity-title-text', textContent: humanEventTitle(activity, tr) })
|
||||||
]),
|
]),
|
||||||
activity.summary ? el('div', { className: 'activity-summary', textContent: activity.summary }) : null,
|
activity.summary ? el('div', { className: 'activity-summary', textContent: activity.summary }) : null
|
||||||
el('details', { className: 'activity-details' }, [
|
|
||||||
el('summary', {}, [tr('ui.details', null, 'Details')]),
|
|
||||||
el('div', { className: 'activity-source', textContent: tr('ui.eventSource', { event: activity.type, source: activity.sourcePluginId ? ' · ' + activity.sourcePluginId : '' }, 'Event: ' + activity.type + (activity.sourcePluginId ? ' · Source: ' + activity.sourcePluginId : '')) })
|
|
||||||
])
|
|
||||||
])
|
])
|
||||||
]));
|
]));
|
||||||
});
|
});
|
||||||
|
|
@ -824,19 +822,19 @@
|
||||||
el('div', {}, [
|
el('div', {}, [
|
||||||
el('div', { className: 'activity-candidate-title', textContent: tr('ui.candidate', null, 'Possible journal entry') }),
|
el('div', { className: 'activity-candidate-title', textContent: tr('ui.candidate', null, 'Possible journal entry') }),
|
||||||
el('div', { className: 'activity-candidate-facts' }, [
|
el('div', { className: 'activity-candidate-facts' }, [
|
||||||
el('div', { textContent: 'Workspace: ' + candidate.workspaceRootPath }),
|
el('div', { textContent: tr('ui.candidateDeal', { deal: candidate.workspaceRootPath }, 'Deal: ' + candidate.workspaceRootPath) }),
|
||||||
el('div', { textContent: 'Time: ' + candidateTimeRange(candidate) }),
|
el('div', { textContent: tr('ui.candidateTime', { time: candidateTimeRange(candidate) }, 'Time: ' + candidateTimeRange(candidate)) }),
|
||||||
el('div', { textContent: 'Estimated duration: ' + candidate.estimatedMinutes + ' min' }),
|
el('div', { textContent: tr('ui.candidateDuration', { minutes: candidate.estimatedMinutes }, 'Estimated duration: ' + candidate.estimatedMinutes + ' min') }),
|
||||||
el('div', { textContent: 'Activities: ' + candidate.activityCount })
|
el('div', { textContent: tr('ui.candidateActivities', { count: candidate.activityCount }, 'Activities: ' + candidate.activityCount) })
|
||||||
]),
|
]),
|
||||||
el('details', { className: 'activity-candidate-activities' }, [
|
el('details', { className: 'activity-candidate-activities' }, [
|
||||||
el('summary', { textContent: 'Included activities (' + candidate.activityCount + ')' })
|
el('summary', { textContent: tr('ui.includedActivities', { count: candidate.activityCount }, 'Included activities (' + candidate.activityCount + ')') })
|
||||||
].concat(candidate.activities.map(function (activity) {
|
].concat(candidate.activities.map(function (activity) {
|
||||||
return el('div', { className: 'activity-candidate-activity', textContent: activity.occurredAt + ' · ' + activity.type + ' · ' + activity.activityId });
|
return el('div', { className: 'activity-candidate-activity', textContent: activity.occurredAt + ' · ' + humanEventTitle(activity, tr) });
|
||||||
})))
|
})))
|
||||||
]),
|
]),
|
||||||
el('div', { className: 'activity-candidate-actions' }, [
|
el('div', { className: 'activity-candidate-actions' }, [
|
||||||
el('div', { className: 'activity-candidate-duration', textContent: candidate.estimatedMinutes + ' min' }),
|
el('div', { className: 'activity-candidate-duration', textContent: tr('ui.minutes', { count: candidate.estimatedMinutes }, candidate.estimatedMinutes + ' min') }),
|
||||||
el('button', { className: 'activity-btn', type: 'button', 'data-work-session-action': 'review', textContent: tr('ui.review', null, 'Review'), onClick: function () { reviewCandidate(candidate); } }),
|
el('button', { className: 'activity-btn', type: 'button', 'data-work-session-action': 'review', textContent: tr('ui.review', null, 'Review'), onClick: function () { reviewCandidate(candidate); } }),
|
||||||
el('button', { className: 'activity-btn', type: 'button', 'data-work-session-action': 'dismiss', textContent: tr('ui.dismiss', null, 'Dismiss'), onClick: function () { dismissCandidate(candidate); } })
|
el('button', { className: 'activity-btn', type: 'button', 'data-work-session-action': 'dismiss', textContent: tr('ui.dismiss', null, 'Dismiss'), onClick: function () { dismissCandidate(candidate); } })
|
||||||
])
|
])
|
||||||
|
|
@ -845,7 +843,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
countEl.textContent = events.length + ' event' + (events.length === 1 ? '' : 's');
|
countEl.textContent = tr(events.length === 1 ? 'ui.eventCount.one' : 'ui.eventCount.many', { count: events.length }, events.length + ' event' + (events.length === 1 ? '' : 's'));
|
||||||
clearBtn.disabled = events.length === 0;
|
clearBtn.disabled = events.length === 0;
|
||||||
statusEl.textContent = statusText;
|
statusEl.textContent = statusText;
|
||||||
statusEl.className = 'activity-status' + (statusClass ? ' ' + statusClass : '');
|
statusEl.className = 'activity-status' + (statusClass ? ' ' + statusClass : '');
|
||||||
|
|
@ -917,7 +915,9 @@
|
||||||
if (typeof unsubscribe === 'function') unsubscribers.push(unsubscribe);
|
if (typeof unsubscribe === 'function') unsubscribers.push(unsubscribe);
|
||||||
});
|
});
|
||||||
})).then(function () {
|
})).then(function () {
|
||||||
statusText = scope.mode === 'global' ? 'Listening for all activity' : 'Listening for workspace activity';
|
statusText = scope.mode === 'global'
|
||||||
|
? tr('ui.listeningGlobal', null, 'Listening for all activity')
|
||||||
|
: tr('ui.listeningDeal', null, 'Listening for Deal activity');
|
||||||
statusClass = '';
|
statusClass = '';
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
reportError('ui.subscriptionsUnavailable', 'Activity updates are unavailable. Please try again.', err);
|
reportError('ui.subscriptionsUnavailable', 'Activity updates are unavailable. Please try again.', err);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"manifest.name": "Activity",
|
"manifest.name": "Activity",
|
||||||
"manifest.description": "Workspace-scoped activity log for public plugin events.",
|
"manifest.description": "Deal-scoped activity log for public plugin events.",
|
||||||
"contributions.views.verstak.activity.view.title": "Activity",
|
"contributions.views.verstak.activity.view.title": "Activity",
|
||||||
"contributions.sidebarItems.verstak.activity.sidebar.title": "Activity",
|
"contributions.sidebarItems.verstak.activity.sidebar.title": "Activity",
|
||||||
"contributions.workspaceItems.verstak.activity.workspace.title": "Activity",
|
"contributions.workspaceItems.verstak.activity.workspace.title": "Activity",
|
||||||
|
|
@ -10,8 +10,8 @@
|
||||||
"ui.workspaceTitle": "Activity · {workspace}",
|
"ui.workspaceTitle": "Activity · {workspace}",
|
||||||
"ui.clear": "Clear",
|
"ui.clear": "Clear",
|
||||||
"ui.clearConfirmTitle": "Clear activity?",
|
"ui.clearConfirmTitle": "Clear activity?",
|
||||||
"ui.clearGlobalWarning": "This permanently deletes all recorded activity and journal suggestions in every case.",
|
"ui.clearGlobalWarning": "This permanently deletes all recorded activity and journal suggestions in every Deal.",
|
||||||
"ui.clearWorkspaceWarning": "This permanently deletes recorded activity for {workspace}. Activity in other cases remains.",
|
"ui.clearWorkspaceWarning": "This permanently deletes recorded activity for {workspace}. Activity in other Deals remains.",
|
||||||
"ui.confirmClear": "Clear activity",
|
"ui.confirmClear": "Clear activity",
|
||||||
"ui.cancel": "Cancel",
|
"ui.cancel": "Cancel",
|
||||||
"ui.saveError": "Could not save activity. Please try again.",
|
"ui.saveError": "Could not save activity. Please try again.",
|
||||||
|
|
@ -29,5 +29,34 @@
|
||||||
"ui.candidates": "Possible journal entries",
|
"ui.candidates": "Possible journal entries",
|
||||||
"ui.candidate": "Possible journal entry",
|
"ui.candidate": "Possible journal entry",
|
||||||
"ui.review": "Review",
|
"ui.review": "Review",
|
||||||
"ui.dismiss": "Dismiss"
|
"ui.dismiss": "Dismiss",
|
||||||
|
"ui.event.workspaceSelected": "Deal selected",
|
||||||
|
"ui.event.fileOpened": "File opened",
|
||||||
|
"ui.event.fileChanged": "File changed",
|
||||||
|
"ui.event.noteEdited": "Note edited",
|
||||||
|
"ui.event.workSessionDetected": "Work session detected",
|
||||||
|
"ui.event.browserCaptureReceived": "Browser capture received",
|
||||||
|
"ui.event.pageCaptured": "Page captured",
|
||||||
|
"ui.event.selectionCaptured": "Selection captured",
|
||||||
|
"ui.event.linkCaptured": "Link captured",
|
||||||
|
"ui.event.fileCaptured": "File captured",
|
||||||
|
"ui.event.captureConverted": "Capture converted",
|
||||||
|
"ui.event.browserDomainActivity": "Browser domain activity",
|
||||||
|
"ui.event.activity": "Activity event",
|
||||||
|
"ui.kind.capture": "Capture",
|
||||||
|
"ui.kind.file": "File",
|
||||||
|
"ui.kind.note": "Note",
|
||||||
|
"ui.kind.deal": "Deal",
|
||||||
|
"ui.kind.work": "Work",
|
||||||
|
"ui.kind.activity": "Activity",
|
||||||
|
"ui.candidateDeal": "Deal: {deal}",
|
||||||
|
"ui.candidateTime": "Time: {time}",
|
||||||
|
"ui.candidateDuration": "Estimated duration: {minutes} min",
|
||||||
|
"ui.candidateActivities": "Activities: {count}",
|
||||||
|
"ui.includedActivities": "Included activities ({count})",
|
||||||
|
"ui.minutes": "{count} min",
|
||||||
|
"ui.eventCount.one": "{count} event",
|
||||||
|
"ui.eventCount.many": "{count} events",
|
||||||
|
"ui.listeningGlobal": "Listening for all activity",
|
||||||
|
"ui.listeningDeal": "Listening for Deal activity"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"manifest.name": "Активность",
|
"manifest.name": "Активность",
|
||||||
"manifest.description": "Журнал публичных событий плагинов в рабочем пространстве.",
|
"manifest.description": "Журнал публичных событий плагинов в Деле.",
|
||||||
"contributions.views.verstak.activity.view.title": "Активность",
|
"contributions.views.verstak.activity.view.title": "Активность",
|
||||||
"contributions.sidebarItems.verstak.activity.sidebar.title": "Активность",
|
"contributions.sidebarItems.verstak.activity.sidebar.title": "Активность",
|
||||||
"contributions.workspaceItems.verstak.activity.workspace.title": "Активность",
|
"contributions.workspaceItems.verstak.activity.workspace.title": "Активность",
|
||||||
|
|
@ -29,5 +29,34 @@
|
||||||
"ui.candidates": "Возможные записи журнала",
|
"ui.candidates": "Возможные записи журнала",
|
||||||
"ui.candidate": "Возможная запись журнала",
|
"ui.candidate": "Возможная запись журнала",
|
||||||
"ui.review": "Проверить",
|
"ui.review": "Проверить",
|
||||||
"ui.dismiss": "Скрыть"
|
"ui.dismiss": "Скрыть",
|
||||||
|
"ui.event.workspaceSelected": "Выбрано Дело",
|
||||||
|
"ui.event.fileOpened": "Открыт файл",
|
||||||
|
"ui.event.fileChanged": "Изменён файл",
|
||||||
|
"ui.event.noteEdited": "Изменена заметка",
|
||||||
|
"ui.event.workSessionDetected": "Обнаружена рабочая сессия",
|
||||||
|
"ui.event.browserCaptureReceived": "Получено сохранённое из браузера",
|
||||||
|
"ui.event.pageCaptured": "Сохранена страница",
|
||||||
|
"ui.event.selectionCaptured": "Сохранено выделение",
|
||||||
|
"ui.event.linkCaptured": "Сохранена ссылка",
|
||||||
|
"ui.event.fileCaptured": "Сохранён файл",
|
||||||
|
"ui.event.captureConverted": "Сохранённое преобразовано",
|
||||||
|
"ui.event.browserDomainActivity": "Активность на домене в браузере",
|
||||||
|
"ui.event.activity": "Событие активности",
|
||||||
|
"ui.kind.capture": "Сохранённое",
|
||||||
|
"ui.kind.file": "Файл",
|
||||||
|
"ui.kind.note": "Заметка",
|
||||||
|
"ui.kind.deal": "Дело",
|
||||||
|
"ui.kind.work": "Работа",
|
||||||
|
"ui.kind.activity": "Активность",
|
||||||
|
"ui.candidateDeal": "Дело: {deal}",
|
||||||
|
"ui.candidateTime": "Время: {time}",
|
||||||
|
"ui.candidateDuration": "Оценка длительности: {minutes} мин.",
|
||||||
|
"ui.candidateActivities": "Активностей: {count}",
|
||||||
|
"ui.includedActivities": "Учтённые активности ({count})",
|
||||||
|
"ui.minutes": "{count} мин.",
|
||||||
|
"ui.eventCount.one": "Событий: {count}",
|
||||||
|
"ui.eventCount.many": "Событий: {count}",
|
||||||
|
"ui.listeningGlobal": "Учёт всей активности включён",
|
||||||
|
"ui.listeningDeal": "Учёт активности Дела включён"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
"name": "Activity",
|
"name": "Activity",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"apiVersion": "0.1.0",
|
"apiVersion": "0.1.0",
|
||||||
"description": "Workspace-scoped activity log for public plugin events.",
|
"description": "Deal-scoped activity log for public plugin events.",
|
||||||
"localization": { "defaultLocale": "en", "locales": { "en": "locales/en.json", "ru": "locales/ru.json" } },
|
"localization": { "defaultLocale": "en", "locales": { "en": "locales/en.json", "ru": "locales/ru.json" } },
|
||||||
"source": "official",
|
"source": "official",
|
||||||
"icon": "activity",
|
"icon": "activity",
|
||||||
|
|
|
||||||
|
|
@ -280,13 +280,16 @@ async function mountWithApi(api, props = { workspaceNode: { name: 'Project' }, w
|
||||||
}
|
}
|
||||||
if (api.storedEvents(globalKey).length !== 0) throw new Error('workspace activity leaked into global storage');
|
if (api.storedEvents(globalKey).length !== 0) throw new Error('workspace activity leaked into global storage');
|
||||||
if (!container.textContent.includes('Example Article')) throw new Error('browser capture title was not rendered');
|
if (!container.textContent.includes('Example Article')) throw new Error('browser capture title was not rendered');
|
||||||
if (!container.textContent.includes('browser.capture.selection')) throw new Error('event type was not rendered');
|
if (!container.textContent.includes('Selection captured')) throw new Error('browser capture label was not rendered');
|
||||||
if (!container.textContent.includes('browser.capture.converted')) throw new Error('conversion event type was not rendered');
|
if (!container.textContent.includes('Capture converted')) throw new Error('conversion label was not rendered');
|
||||||
|
if (container.textContent.includes('browser.capture.selection') || container.textContent.includes('browser.capture.converted') || container.textContent.includes('verstak.browser-inbox')) {
|
||||||
|
throw new Error('Activity must not expose technical event or plugin identifiers in its UI');
|
||||||
|
}
|
||||||
if (!container.textContent.includes('Possible journal entries')) throw new Error('work session candidate section was not rendered');
|
if (!container.textContent.includes('Possible journal entries')) throw new Error('work session candidate section was not rendered');
|
||||||
if (container.textContent.includes('Project work on 2026-06-27')) throw new Error('candidate must not invent a worklog title');
|
if (container.textContent.includes('Project work on 2026-06-27')) throw new Error('candidate must not invent a worklog title');
|
||||||
const candidateNode = walk(container, (node) => node.getAttribute && node.getAttribute('data-work-session-candidate'));
|
const candidateNode = walk(container, (node) => node.getAttribute && node.getAttribute('data-work-session-candidate'));
|
||||||
if (!candidateNode) throw new Error('work session candidate data attribute was not rendered');
|
if (!candidateNode) throw new Error('work session candidate data attribute was not rendered');
|
||||||
if (!candidateNode.textContent.includes('Workspace: Project')) throw new Error('candidate workspace was not rendered');
|
if (!candidateNode.textContent.includes('Deal: Project')) throw new Error('candidate Deal was not rendered');
|
||||||
if (!candidateNode.textContent.includes('Estimated duration: 20 min')) throw new Error('candidate duration was not rendered');
|
if (!candidateNode.textContent.includes('Estimated duration: 20 min')) throw new Error('candidate duration was not rendered');
|
||||||
if (!candidateNode.textContent.includes('Activities: 3')) throw new Error('candidate activity count was not rendered');
|
if (!candidateNode.textContent.includes('Activities: 3')) throw new Error('candidate activity count was not rendered');
|
||||||
if (!walk(candidateNode, (node) => node.getAttribute && node.getAttribute('data-work-session-action') === 'review')) throw new Error('candidate review action was not rendered');
|
if (!walk(candidateNode, (node) => node.getAttribute && node.getAttribute('data-work-session-action') === 'review')) throw new Error('candidate review action was not rendered');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue