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) => (