diff --git a/apps/dashboard/src/RoomCardV2.test.ts b/apps/dashboard/src/RoomCardV2.test.ts index 16cbb4c..89643ec 100644 --- a/apps/dashboard/src/RoomCardV2.test.ts +++ b/apps/dashboard/src/RoomCardV2.test.ts @@ -24,8 +24,13 @@ const formatters = { formatDate: (value: string | null | undefined) => value ?? '-', formatDuration: (value: number | null) => (value === null ? '-' : `${value}`), formatLiveElapsed: (value: number) => `${value}`, - senderRoleClass: (value: string | null | undefined) => - (value ?? '').toLowerCase().includes('owner') ? 'role-owner' : 'role-human', + senderRoleClass: (value: string | null | undefined) => { + const role = (value ?? '').toLowerCase(); + if (role.includes('owner')) return 'role-owner'; + if (role.includes('reviewer')) return 'role-reviewer'; + if (role.includes('arbiter')) return 'role-arbiter'; + return 'role-human'; + }, statusLabel: (status: string) => status, }; @@ -110,6 +115,51 @@ describe('RoomCardV2', () => { expect(html).toContain('prod 배포 완료'); }); + it('localizes protocol role and verdict labels in Korean rooms', () => { + const html = renderToStaticMarkup( + createElement(RoomCardV2, { + activity: activity({ + pairedTask: { + id: 'task-1', + title: 'Review pending', + status: 'in_review', + roundTripCount: 2, + updatedAt: '2026-04-28T02:03:00.000Z', + currentTurn: null, + outputs: [ + { + id: 1, + turnNumber: 2, + role: 'reviewer', + verdict: 'continue', + createdAt: '2026-04-28T02:02:00.000Z', + outputText: '검증 완료. 다음 단계로 진행 가능합니다.', + }, + ], + }, + }), + activityLoading: false, + busy: false, + draft: '', + entry, + expanded: true, + inboxItems: [], + locale: 'ko', + onDraftChange: () => {}, + onSendMessage: () => {}, + onToggle: () => {}, + pinned: true, + t, + ...formatters, + }), + ); + + expect(html).toContain('>리뷰어'); + expect(html).toContain('>계속'); + expect(html).not.toContain('>reviewer'); + expect(html).not.toContain('>continue'); + }); + it('renders live progress as markdown', () => { const html = renderToStaticMarkup( createElement(RoomCardV2, { @@ -244,6 +294,7 @@ describe('RoomCardV2', () => { ); expect(html).toContain('class="room-watcher-fold"'); + expect(html).toContain('>리뷰어'); expect(html).toContain(watcherTail); }); }); diff --git a/apps/dashboard/src/RoomCardV2.tsx b/apps/dashboard/src/RoomCardV2.tsx index 8caad08..6fe7da4 100644 --- a/apps/dashboard/src/RoomCardV2.tsx +++ b/apps/dashboard/src/RoomCardV2.tsx @@ -5,6 +5,7 @@ import type { DashboardOverview, DashboardRoomActivity } from './api'; import type { Locale, Messages } from './i18n'; import { ParsedBody } from './ParsedBody'; import { RoomAttachmentGallery } from './RoomAttachmentGallery'; +import { displayRole, displayVerdict } from './roomDisplayLabels'; import { buildRoomThreadEntries, isWatcherRoomMessage, @@ -421,7 +422,9 @@ function RoomCurrentTurnSummary({ return (
- {turn.role} + + {displayRole(turn.role, locale)} + {turn.intentKind} {turn.attemptNo > 1 ? ( @@ -504,7 +507,9 @@ function CollapsedLiveTurn({
LIVE - {turn.role} + + {displayRole(turn.role, locale)} + {turn.intentKind} {turn.state} {turn.executorServiceId ? ( @@ -574,7 +579,7 @@ function CollapsedWatcherStrip({ 워쳐 {watcherCount} - {lastWatcher.senderName} + {displayRole(lastWatcher.senderName, locale)} {lastWatcher.content.slice(0, 90)} @@ -764,7 +769,7 @@ function RoomTimelineEntry({
  • - {entry.senderName} + {displayRole(entry.senderName, locale)} {entry.turnNumber !== undefined ? ( @@ -774,7 +779,7 @@ function RoomTimelineEntry({ ) : null} {verdict ? ( - {verdict} + {displayVerdict(verdict, locale)} ) : null}
    @@ -818,7 +823,7 @@ function RoomLiveTimelineEntry({ )} - {turn.role} + {displayRole(turn.role, locale)}
  • - {message.senderName} + {displayRole(message.senderName, locale)}
    diff --git a/apps/dashboard/src/roomDisplayLabels.ts b/apps/dashboard/src/roomDisplayLabels.ts new file mode 100644 index 0000000..a4d7e2c --- /dev/null +++ b/apps/dashboard/src/roomDisplayLabels.ts @@ -0,0 +1,34 @@ +import type { Locale } from './i18n'; + +const KO_ROLE_LABELS: Record = { + arbiter: '중재자', + owner: '오너', + reviewer: '리뷰어', +}; + +const KO_VERDICT_LABELS: Record = { + blocked: '차단', + continue: '계속', + done: '완료', + done_with_concerns: '우려 있음', + needs_context: '컨텍스트 필요', + proceed: '진행', + reset: '초기화', + revise: '수정 필요', + step_done: '단계 완료', + task_done: '작업 완료', +}; + +export function displayRole( + value: string | null | undefined, + locale: Locale, +): string { + const raw = value ?? ''; + if (locale !== 'ko') return raw; + return KO_ROLE_LABELS[raw.trim().toLowerCase()] ?? raw; +} + +export function displayVerdict(value: string, locale: Locale): string { + if (locale !== 'ko') return value; + return KO_VERDICT_LABELS[value.trim().toLowerCase()] ?? value; +}