From ee8eacf0de27c77b32d7191c3677e8286ecd92f7 Mon Sep 17 00:00:00 2001 From: cesnimda Date: Sat, 29 Aug 2026 22:35:28 +0200 Subject: [PATCH] fix(i18n): localize admin diagnostics --- docs/work-programmes/master-progress.md | 2 + job-tracker-ui/src/admin-system-page.test.tsx | 11 +++ job-tracker-ui/src/i18n/translations.ts | 80 ++++++++++++++++++ job-tracker-ui/src/views/AdminSystemPage.tsx | 82 +++++++++---------- 4 files changed, 134 insertions(+), 41 deletions(-) diff --git a/docs/work-programmes/master-progress.md b/docs/work-programmes/master-progress.md index 956cf2e..ffdbc2b 100644 --- a/docs/work-programmes/master-progress.md +++ b/docs/work-programmes/master-progress.md @@ -35,6 +35,7 @@ Updated: 2026-08-29 - Localized the complete public landing experience—hero, previews, feature cards, workflow, plan capabilities, CTAs, accessibility text, and footer—while retaining the server-aligned Free/Pro plan structure and commercial-terms safeguards. - Reused the shared EN/NO language control on the unauthenticated landing page, so language can be changed before sign-in and persists through the existing locale mechanism. - Finished the remaining active CV Builder format labels: long-document page counts, US Letter naming, and localized long/short date examples now follow the selected UI language. +- Localized the Admin System runtime and CV benchmark diagnostics, including probe/email failures, model/Ollama state, parser findings, benchmark summaries, fallback guidance, and locale-aware timestamps while leaving runtime/provider/file values untouched. ### In progress @@ -87,6 +88,7 @@ Updated: 2026-08-29 - Next optimized production build and integrated TypeScript check passed after the Job Email localization batch. - Public landing focused verification: 2 suites, 3/3 passed, including live switching to Bokmål and preservation of protected-route sign-in intent; TypeScript passed. - CV Builder focused verification: 12/12 passed; TypeScript passed after the final format-label pass. +- Admin System focused verification: 3/3 passed, including Bokmål benchmark diagnostics that preserve runtime values; TypeScript passed. - Backend matcher/intelligence focused verification: 35/35 passed, including detection of a manually created Norwegian advert with no saved translation. - Full backend: 712/712 tests passed on .NET 9. - Next optimized production build and TypeScript: passed after the Job Workspace/checklist batch. diff --git a/job-tracker-ui/src/admin-system-page.test.tsx b/job-tracker-ui/src/admin-system-page.test.tsx index 9c25784..c606218 100644 --- a/job-tracker-ui/src/admin-system-page.test.tsx +++ b/job-tracker-ui/src/admin-system-page.test.tsx @@ -21,6 +21,7 @@ const mockedApi = api as jest.Mocked; describe('AdminSystemPage', () => { beforeEach(() => { + window.localStorage.clear(); mockedApi.get.mockImplementation((url: string) => { if (url === '/admin/system') { return Promise.resolve({ @@ -199,4 +200,14 @@ describe('AdminSystemPage', () => { })); }); }); + + it('localizes benchmark diagnostics without changing runtime values', async () => { + window.localStorage.setItem('uiLanguage', 'nb'); + render(); + + expect(await screen.findByText('Gjennomgang av CV-referansetest')).toBeTruthy(); + expect(screen.getByText(/Mistenkelig kontaktsted: san francisco, hobbies/i)).toBeTruthy(); + expect(screen.getByText('Production')).toBeTruthy(); + expect(screen.getByText(/Modell · qwen2.5:7b/i)).toBeTruthy(); + }); }); diff --git a/job-tracker-ui/src/i18n/translations.ts b/job-tracker-ui/src/i18n/translations.ts index 98c9646..bd6ee73 100644 --- a/job-tracker-ui/src/i18n/translations.ts +++ b/job-tracker-ui/src/i18n/translations.ts @@ -1367,6 +1367,46 @@ export const translations = { kanbanFollowUpNow: "Follow up now", kanbanReplyDueIn: "Reply due in {days}d", adminSystemTitle: "System status", + adminSystemLoadFailed: "Failed to load system status.", + adminSystemTestEmailFailed: "Failed to send test email.", + adminSystemEmailSaveFailed: "Failed to save email settings.", + adminSystemProbeFailed: "Failed to run AI service probe.", + adminSystemVersionCommit: "Version {version} · Commit {commit}", + adminSystemNoTarget: "No target", + adminSystemUnknownDevice: "unknown device", + adminSystemProbeDevice: "{latency} ms probe · {device}", + adminSystemHealthDevice: "{latency} ms health · {device}", + adminSystemOllamaVersion: "Ollama version", + adminSystemLoadedModels: "Loaded models", + adminSystemOcrEnabled: "OCR {languages}", + adminSystemModelName: "Model · {model}", + adminSystemLoadedModel: "Loaded · {model}", + adminBenchmarkTitle: "CV benchmark review", + adminBenchmarkRoot: "Benchmark root", + adminBenchmarkLastUpdate: "Last benchmark update", + adminBenchmarkCorpusRoot: "Corpus root", + adminBenchmarkFiles: "Files", + adminBenchmarkCorpusInputs: "Corpus inputs", + adminBenchmarkCoverage: "Coverage", + adminBenchmarkCoverageHelp: "Structured field coverage", + adminBenchmarkConfidence: "Confidence", + adminBenchmarkConfidenceHelp: "Field metadata confidence", + adminBenchmarkConsistency: "Consistency", + adminBenchmarkConsistencyHelp: "Normalization consistency", + adminBenchmarkMissingApproved: "Missing approved", + adminBenchmarkNeedsReview: "Needs fixture review", + adminBenchmarkTopFindings: "Top parser findings", + adminBenchmarkNoAnomalies: "No standout benchmark anomalies in the current run.", + adminBenchmarkWeakest: "Weakest files in current run", + adminBenchmarkCoverageValue: "Coverage {value}", + adminBenchmarkConfidenceValue: "Confidence {value}", + adminBenchmarkConsistencyValue: "Consistency {value}", + adminBenchmarkLatestSummary: "Latest markdown summary", + adminBenchmarkRunScript: "Run scripts/run-cv-benchmark.sh to generate the latest corpus report and fixture candidates.", + adminBenchmarkSuspiciousLocation: "Suspicious contact location: {value}", + adminBenchmarkEducationOvercaptured: "Education qualification looks over-captured.", + adminBenchmarkWorkTitleOvercaptured: "Work title looks over-captured.", + adminBenchmarkQualificationOther: "Qualification level fell back to Other.", adminSystemSubtitle: "Production diagnostics for runtime, database, auth, email, AI service health, and OCR readiness.", adminSystemRunProbe: "Run probe now", adminSystemRunningProbe: "Running probe...", @@ -3456,6 +3496,46 @@ export const translations = { kanbanFollowUpNow: "Følg opp nå", kanbanReplyDueIn: "Svar forfaller om {days}d", adminSystemTitle: "Systemstatus", + adminSystemLoadFailed: "Kunne ikke laste systemstatus.", + adminSystemTestEmailFailed: "Kunne ikke sende test-e-post.", + adminSystemEmailSaveFailed: "Kunne ikke lagre e-postinnstillingene.", + adminSystemProbeFailed: "Kunne ikke kjøre kontrollen av AI-tjenesten.", + adminSystemVersionCommit: "Versjon {version} · Commit {commit}", + adminSystemNoTarget: "Ingen målressurs", + adminSystemUnknownDevice: "ukjent enhet", + adminSystemProbeDevice: "{latency} ms kontroll · {device}", + adminSystemHealthDevice: "{latency} ms helse · {device}", + adminSystemOllamaVersion: "Ollama-versjon", + adminSystemLoadedModels: "Innlastede modeller", + adminSystemOcrEnabled: "OCR {languages}", + adminSystemModelName: "Modell · {model}", + adminSystemLoadedModel: "Innlastet · {model}", + adminBenchmarkTitle: "Gjennomgang av CV-referansetest", + adminBenchmarkRoot: "Rotmappe for referansetest", + adminBenchmarkLastUpdate: "Siste oppdatering av referansetest", + adminBenchmarkCorpusRoot: "Rotmappe for korpus", + adminBenchmarkFiles: "Filer", + adminBenchmarkCorpusInputs: "Inndata i korpus", + adminBenchmarkCoverage: "Dekning", + adminBenchmarkCoverageHelp: "Dekning av strukturerte felt", + adminBenchmarkConfidence: "Sikkerhet", + adminBenchmarkConfidenceHelp: "Sikkerhet i feltmetadata", + adminBenchmarkConsistency: "Konsistens", + adminBenchmarkConsistencyHelp: "Konsistens i normalisering", + adminBenchmarkMissingApproved: "Mangler godkjenning", + adminBenchmarkNeedsReview: "Trenger gjennomgang av testdata", + adminBenchmarkTopFindings: "Viktigste parserfunn", + adminBenchmarkNoAnomalies: "Ingen fremtredende avvik i den gjeldende kjøringen.", + adminBenchmarkWeakest: "Svakeste filer i gjeldende kjøring", + adminBenchmarkCoverageValue: "Dekning {value}", + adminBenchmarkConfidenceValue: "Sikkerhet {value}", + adminBenchmarkConsistencyValue: "Konsistens {value}", + adminBenchmarkLatestSummary: "Siste markdown-sammendrag", + adminBenchmarkRunScript: "Kjør scripts/run-cv-benchmark.sh for å generere den nyeste korpusrapporten og kandidater til testdata.", + adminBenchmarkSuspiciousLocation: "Mistenkelig kontaktsted: {value}", + adminBenchmarkEducationOvercaptured: "Utdanningskvalifikasjonen ser ut til å inneholde for mye tekst.", + adminBenchmarkWorkTitleOvercaptured: "Jobbtittelen ser ut til å inneholde for mye tekst.", + adminBenchmarkQualificationOther: "Kvalifikasjonsnivået falt tilbake til Annet.", adminSystemSubtitle: "Produksjonsdiagnostikk for kjøretid, database, autentisering, e-post, AI-tjenestehelse og OCR-beredskap.", adminSystemRunProbe: "Kjør probe nå", adminSystemRunningProbe: "Kjører probe...", diff --git a/job-tracker-ui/src/views/AdminSystemPage.tsx b/job-tracker-ui/src/views/AdminSystemPage.tsx index d544b17..8397e77 100644 --- a/job-tracker-ui/src/views/AdminSystemPage.tsx +++ b/job-tracker-ui/src/views/AdminSystemPage.tsx @@ -166,8 +166,8 @@ function displayMetadata(value?: string | null) { return value && value.trim().length > 0 ? value : "-"; } -function formatDate(value?: string | null) { - return value ? new Date(value).toLocaleString() : "-"; +function formatDate(value?: string | null, locale?: string) { + return value ? new Date(value).toLocaleString(locale) : "-"; } function formatPercent(value?: number | null) { @@ -206,7 +206,7 @@ function DetailRow({ label, value }: { label: string; value: React.ReactNode }) } export default function AdminSystemPage() { - const { t } = useI18n(); + const { language, t } = useI18n(); const [tab, setTab] = useState(0); const [status, setStatus] = useState(null); const [emailSettings, setEmailSettings] = useState(null); @@ -237,7 +237,7 @@ export default function AdminSystemPage() { setSmtpPassword(""); setClearPassword(false); } catch (e: any) { - setError(getApiErrorMessage(e, "Failed to load system status.")); + setError(getApiErrorMessage(e, t("adminSystemLoadFailed"))); setStatus(null); setEmailSettings(null); setBenchmarkStatus(null); @@ -277,20 +277,20 @@ export default function AdminSystemPage() { return benchmarkIndex.Entries.flatMap((entry) => { const findings: Array<{ file: string; issue: string }> = []; if (entry.ContactLocation && /(culture|education|arial|hobbies|cooperate|ag, ni|bold)/i.test(entry.ContactLocation)) { - findings.push({ file: entry.FileName, issue: `Suspicious contact location: ${entry.ContactLocation}` }); + findings.push({ file: entry.FileName, issue: t("adminBenchmarkSuspiciousLocation", { value: entry.ContactLocation }) }); } if (entry.FirstEducation && entry.FirstEducation.length > 120) { - findings.push({ file: entry.FileName, issue: "Education qualification looks over-captured." }); + findings.push({ file: entry.FileName, issue: t("adminBenchmarkEducationOvercaptured") }); } if ((entry.FirstJob ?? "").length > 120) { - findings.push({ file: entry.FileName, issue: "Work title looks over-captured." }); + findings.push({ file: entry.FileName, issue: t("adminBenchmarkWorkTitleOvercaptured") }); } if ((entry.QualificationLevels ?? []).includes("Other")) { - findings.push({ file: entry.FileName, issue: "Qualification level fell back to Other." }); + findings.push({ file: entry.FileName, issue: t("adminBenchmarkQualificationOther") }); } return findings; }).slice(0, 10); - }, [benchmarkIndex]); + }, [benchmarkIndex, language]); const sendTestEmail = async () => { setSendingTestEmail(true); @@ -302,7 +302,7 @@ export default function AdminSystemPage() { }); setError(null); } catch (e: any) { - setError(getApiErrorMessage(e, "Failed to send test email.")); + setError(getApiErrorMessage(e, t("adminSystemTestEmailFailed"))); } finally { setSendingTestEmail(false); } @@ -329,7 +329,7 @@ export default function AdminSystemPage() { setClearPassword(false); await load(); } catch (e: any) { - setError(getApiErrorMessage(e, "Failed to save email settings.")); + setError(getApiErrorMessage(e, t("adminSystemEmailSaveFailed"))); } finally { setSavingSettings(false); } @@ -352,7 +352,7 @@ export default function AdminSystemPage() { await api.post("/admin/system/ai/probe"); await load(); } catch (e: any) { - setError(getApiErrorMessage(e, "Failed to run AI service probe.")); + setError(getApiErrorMessage(e, t("adminSystemProbeFailed"))); } finally { setRunningProbe(false); } @@ -382,12 +382,12 @@ export default function AdminSystemPage() { @@ -468,13 +468,13 @@ export default function AdminSystemPage() { - - + + - - - + + + @@ -497,57 +497,57 @@ export default function AdminSystemPage() { - + {(status?.ai.ollamaInstalledModels ?? []).slice(0, 4).map((model) => ( - + ))} {(status?.ai.ollamaLoadedModels ?? []).slice(0, 3).map((model) => ( - + ))} - CV benchmark review + {t("adminBenchmarkTitle")} - - - + + + {benchmarkIndex ? ( <> - - - - - 0 ? "warning" : "success"} /> + + + + + 0 ? "warning" : "success"} /> - Top parser findings + {t("adminBenchmarkTopFindings")} {benchmarkFindings.length > 0 ? benchmarkFindings.map((finding) => ( {finding.file} {finding.issue} - )) : No standout benchmark anomalies in the current run.} + )) : {t("adminBenchmarkNoAnomalies")}} - Weakest files in current run + {t("adminBenchmarkWeakest")} {weakestEntries.map((entry) => ( {entry.FileName} - - - + + + {entry.DiffSummary || "-"} @@ -557,7 +557,7 @@ export default function AdminSystemPage() { - Latest markdown summary + {t("adminBenchmarkLatestSummary")} {benchmarkStatus?.reportMarkdown || "-"} @@ -566,7 +566,7 @@ export default function AdminSystemPage() { ) : ( - {benchmarkStatus?.reportMarkdown || "Run scripts/run-cv-benchmark.sh to generate the latest corpus report and fixture candidates."} + {benchmarkStatus?.reportMarkdown || t("adminBenchmarkRunScript")} )}