diff --git a/job-tracker-ui/src/gmail-review-page.test.tsx b/job-tracker-ui/src/gmail-review-page.test.tsx index 5b23b276..2da3af3d 100644 --- a/job-tracker-ui/src/gmail-review-page.test.tsx +++ b/job-tracker-ui/src/gmail-review-page.test.tsx @@ -226,6 +226,37 @@ describe('GmailReviewPage', () => { expect(mockedApi.put).not.toHaveBeenCalled(); }); + test.each([ + { count: 0, candidatesEn: '0 candidate threads', candidatesNb: '0 kandidattråder', jobsEn: '', jobsNb: '', messagesEn: '', messagesNb: '' }, + { count: 1, candidatesEn: '1 candidate thread', candidatesNb: '1 kandidattråd', jobsEn: '1 suggested job', jobsNb: '1 foreslått jobb', messagesEn: '1 message', messagesNb: '1 melding' }, + { count: 2, candidatesEn: '2 candidate threads', candidatesNb: '2 kandidattråder', jobsEn: '2 suggested jobs', jobsNb: '2 foreslåtte jobber', messagesEn: '2 messages', messagesNb: '2 meldinger' }, + ])('uses the correct review counts for $count on a live language switch', async ({ count, candidatesEn, candidatesNb, jobsEn, jobsNb, messagesEn, messagesNb }) => { + const threads = Array.from({ length: count }, (_, index) => reviewThread({ threadId: `thread-${index}`, subject: `Synthetic review ${index}`, messageCount: count })); + const items = threads.map(thread => ({ threadId: thread.threadId, companyName: 'Synthetic laboratory', suggestedJobTitle: 'Research technician' })); + mockedApi.get.mockImplementation((url: string) => Promise.resolve({ data: url === '/gmail/review-candidates' ? reviewQueue(threads) : { count, items } } as any)); + renderPage(); + await screen.findByText(candidatesEn); + if (count) { + expect(screen.getByText(jobsEn)).toBeVisible(); + expect(threadRow('Synthetic review 0').getByText(`${messagesEn} · ${copy('gmailReviewRouteReview')}`)).toBeVisible(); + } else { + expect(screen.getByText(copy('gmailReviewNoCandidates'))).toBeVisible(); + expect(screen.queryByText('0 suggested jobs')).not.toBeInTheDocument(); + } + + await changeLanguage('nb'); + expect(screen.getByText(candidatesNb)).toBeVisible(); + if (count) { + expect(screen.getByText(jobsNb)).toBeVisible(); + expect(threadRow('Synthetic review 0').getByText(`${messagesNb} · ${copy('gmailReviewRouteReview')}`)).toBeVisible(); + } else { + expect(screen.getByText(copy('gmailReviewNoCandidates'))).toBeVisible(); + expect(screen.queryByText('0 foreslåtte jobber')).not.toBeInTheDocument(); + } + expect(mockedApi.get).toHaveBeenCalledTimes(2); + expect(mockedApi.post).not.toHaveBeenCalled(); + }); + test('decision, manual sync and suggested creation each own their captured request against duplicate and competing actions', async () => { const second = reviewThread({ threadId: 'thread-2', subject: 'Other owned thread' }); const suggestion = { threadId: 'thread-1', companyName: 'Acme', suggestedJobTitle: 'Backend Developer', subject: 'Suggestion subject', recruiterName: 'Maria', recruiterEmail: 'maria@acme.test', preview: 'Recorded preview' }; diff --git a/job-tracker-ui/src/i18n/translations.ts b/job-tracker-ui/src/i18n/translations.ts index bde6d07b..0acbe055 100644 --- a/job-tracker-ui/src/i18n/translations.ts +++ b/job-tracker-ui/src/i18n/translations.ts @@ -2529,12 +2529,15 @@ export const translations = { gmailReviewRouteSuggested: "Suggested", gmailReviewRouteLinked: "Linked", gmailReviewRouteRejected: "Rejected", + gmailReviewCandidateThread: "{count} candidate thread", gmailReviewCandidateThreads: "{count} candidate threads", gmailReviewAutoLinkCount: "{count} auto-link", gmailReviewReviewCount: "{count} review", gmailReviewUnmatchedCount: "{count} unmatched", + gmailReviewSuggestedJobCount: "{count} suggested job", gmailReviewSuggestedJobsCount: "{count} suggested jobs", gmailReviewNoCandidates: "No Gmail review candidates match the current filter.", + gmailReviewSingleMessageSummary: "{count} message · {route}", gmailReviewThreadSummary: "{count} messages · {route}", gmailReviewHasImported: "Has imported messages", gmailReviewNotes: "Review notes", @@ -5749,12 +5752,15 @@ export const translations = { gmailReviewRouteSuggested: "Foreslått", gmailReviewRouteLinked: "Tilknyttet", gmailReviewRouteRejected: "Avvist", + gmailReviewCandidateThread: "{count} kandidattråd", gmailReviewCandidateThreads: "{count} kandidattråder", gmailReviewAutoLinkCount: "{count} automatisk tilknytning", gmailReviewReviewCount: "{count} til kontroll", gmailReviewUnmatchedCount: "{count} uten treff", + gmailReviewSuggestedJobCount: "{count} foreslått jobb", gmailReviewSuggestedJobsCount: "{count} foreslåtte jobber", gmailReviewNoCandidates: "Ingen kandidater til Gmail-kontroll samsvarer med gjeldende filter.", + gmailReviewSingleMessageSummary: "{count} melding · {route}", gmailReviewThreadSummary: "{count} meldinger · {route}", gmailReviewHasImported: "Har importerte meldinger", gmailReviewNotes: "Kontrollnotater", diff --git a/job-tracker-ui/src/views/GmailReviewPage.tsx b/job-tracker-ui/src/views/GmailReviewPage.tsx index 29f1ea92..71463dc4 100644 --- a/job-tracker-ui/src/views/GmailReviewPage.tsx +++ b/job-tracker-ui/src/views/GmailReviewPage.tsx @@ -241,11 +241,15 @@ export default function GmailReviewPage({ embedded = false, active = true }: { e {!unavailable && data ? ( - + - {suggestions?.count ? : null} + {suggestions?.count ? : null} ) : null} @@ -261,7 +265,9 @@ export default function GmailReviewPage({ embedded = false, active = true }: { e {thread.subject} - {t("gmailReviewThreadSummary", { count: thread.messageCount, route: routeLabel(thread.routing) })} + {thread.messageCount === 1 + ? t("gmailReviewSingleMessageSummary", { count: thread.messageCount, route: routeLabel(thread.routing) }) + : t("gmailReviewThreadSummary", { count: thread.messageCount, route: routeLabel(thread.routing) })} {thread.matchedQueries.slice(0, 3).map((query) => (