diff --git a/docs/work-programmes/master-progress.md b/docs/work-programmes/master-progress.md index 41c3d25..37c9ab1 100644 --- a/docs/work-programmes/master-progress.md +++ b/docs/work-programmes/master-progress.md @@ -90,6 +90,7 @@ Updated: 2026-08-29 - 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. - Job creation and Gmail workflow localization verification: 4 focused suites, 11/11 passed; TypeScript passed. The add-job stepper, Gmail continuity management, review queue, usage progress labels, errors and status copy now switch between English and Bokmål without translating user/job/email content. +- Shared active-surface localization/accessibility verification: Profile 13/13 and settings/auth/landing 18/18 passed; TypeScript passed. Tag entry, Pro notices, disabled registration, profile load recovery, dashboard totals, language selector semantics, job-selection checkboxes and locale-aware correspondence dates were aligned with the global EN/NB system. - 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/components/AddJobModal.tsx b/job-tracker-ui/src/components/AddJobModal.tsx index e4cce04..d2a9020 100644 --- a/job-tracker-ui/src/components/AddJobModal.tsx +++ b/job-tracker-ui/src/components/AddJobModal.tsx @@ -515,7 +515,7 @@ export default function AddJobModal({ open, onClose, onCreated, initialUrl }: Pr ))} diff --git a/job-tracker-ui/src/components/Correspondence.tsx b/job-tracker-ui/src/components/Correspondence.tsx index 7c913a1..110c23e 100644 --- a/job-tracker-ui/src/components/Correspondence.tsx +++ b/job-tracker-ui/src/components/Correspondence.tsx @@ -488,7 +488,7 @@ export default function Correspondence({ jobId, jobContext }: { jobId: number; j ) : null} - {isMe ? t("correspondenceMe") : t("correspondenceCompany")}{m.channel ? ` - ${m.channel}` : ""}{m.date ? ` - ${new Date(m.date).toLocaleString()}` : ""} + {isMe ? t("correspondenceMe") : t("correspondenceCompany")}{m.channel ? ` - ${m.channel}` : ""}{m.date ? ` - ${new Date(m.date).toLocaleString(language === "nb" ? "nb-NO" : "en")}` : ""} void deleteMessage(m.id)} sx={{ color: "text.secondary" }}> @@ -654,7 +654,7 @@ export default function Correspondence({ jobId, jobContext }: { jobId: number; j ))} ) : null} - {gmailStatus.lastSyncedAt ? : null} + {gmailStatus.lastSyncedAt ? : null} {gmailStatus.lastSyncAttemptedAt ? : null} {gmailStatus.lastSyncStatus === "error" && gmailStatus.lastSyncError ? : null} {linkedThreadIds.length > 0 ? ( @@ -710,7 +710,7 @@ export default function Correspondence({ jobId, jobContext }: { jobId: number; j {message.subject || t("correspondenceNoSubject")}{message.date ? new Date(message.date).toLocaleString() : ""}} + primary={{message.subject || t("correspondenceNoSubject")}{message.date ? new Date(message.date).toLocaleString(language === "nb" ? "nb-NO" : "en") : ""}} secondary={ {t("correspondenceFromLabel", { value: message.from || t("correspondenceUnknown") })} {message.snippet} diff --git a/job-tracker-ui/src/components/DashboardView.tsx b/job-tracker-ui/src/components/DashboardView.tsx index 47b33b5..94bb31e 100644 --- a/job-tracker-ui/src/components/DashboardView.tsx +++ b/job-tracker-ui/src/components/DashboardView.tsx @@ -654,7 +654,7 @@ export default function DashboardView() { {series.tag} - {series.counts.reduce((sum, value) => sum + value, 0)} total + {t("dashboardTotalCount", { count: series.counts.reduce((sum, value) => sum + value, 0) })} {series.counts.map((count, i) => ( diff --git a/job-tracker-ui/src/components/JobTable.tsx b/job-tracker-ui/src/components/JobTable.tsx index 062f188..75abb84 100644 --- a/job-tracker-ui/src/components/JobTable.tsx +++ b/job-tracker-ui/src/components/JobTable.tsx @@ -771,7 +771,7 @@ export default function JobTable({ refreshToken, pageSize, onPageSizeChange, col - 0 && !selectedAllOnPage} onChange={(e) => toggleSelectAll(e.target.checked)} /> + 0 && !selectedAllOnPage} onChange={(e) => toggleSelectAll(e.target.checked)} /> requestSort("company")}>{t("jobTableCompany")} requestSort("jobTitle")}>{t("jobTableRole")} requestSort("location")}>{t("jobTableLocation")} @@ -809,7 +809,7 @@ export default function JobTable({ refreshToken, pageSize, onPageSizeChange, col "&:focus-visible": { outline: "3px solid", outlineColor: "primary.main", outlineOffset: -3 }, }} > - toggleSelected(job.id, e.target.checked)} /> + toggleSelected(job.id, e.target.checked)} /> {job.company?.name ?? ""} diff --git a/job-tracker-ui/src/components/ProFeatureNotice.tsx b/job-tracker-ui/src/components/ProFeatureNotice.tsx index a5438e8..845cd38 100644 --- a/job-tracker-ui/src/components/ProFeatureNotice.tsx +++ b/job-tracker-ui/src/components/ProFeatureNotice.tsx @@ -1,6 +1,7 @@ import React, { useState } from "react"; import CloseIcon from "@mui/icons-material/Close"; import { Alert, Button, IconButton, Stack, Typography } from "@mui/material"; +import { useOptionalI18n } from "../i18n/I18nProvider"; type Props = { featureKey: string; @@ -13,6 +14,7 @@ function storageKey(featureKey: string) { } export default function ProFeatureNotice({ featureKey, title, children }: Props) { + const i18n = useOptionalI18n(); const [dismissed, setDismissed] = useState(() => { if (typeof window === "undefined") return false; try { @@ -38,8 +40,8 @@ export default function ProFeatureNotice({ featureKey, title, children }: Props) severity="info" action={( - - + + diff --git a/job-tracker-ui/src/components/TagsInput.tsx b/job-tracker-ui/src/components/TagsInput.tsx index 68a2292..114d206 100644 --- a/job-tracker-ui/src/components/TagsInput.tsx +++ b/job-tracker-ui/src/components/TagsInput.tsx @@ -1,9 +1,10 @@ import React from "react"; import { Autocomplete, Chip, TextField } from "@mui/material"; +import { useI18n } from "../i18n/I18nProvider"; export default function TagsInput({ - label = "Tags", + label, value, onChange, }: { @@ -11,6 +12,7 @@ export default function TagsInput({ value: string[]; onChange: (next: string[]) => void; }) { + const { t } = useI18n(); return ( )) } - renderInput={(params) => } + renderInput={(params) => } /> ); } diff --git a/job-tracker-ui/src/i18n/translations.ts b/job-tracker-ui/src/i18n/translations.ts index 482eea2..aeffde9 100644 --- a/job-tracker-ui/src/i18n/translations.ts +++ b/job-tracker-ui/src/i18n/translations.ts @@ -4,6 +4,7 @@ export const translations = { en: { appTitle: "Jobbjakt", appTagline: "Track your hunt", + applicationLanguage: "Application language", dashboard: "Dashboard", jobApplications: "Job Applications", reminders: "Reminders", @@ -898,6 +899,12 @@ export const translations = { addJobModalTailoredCvRequiresPro: "Tailored CV generation requires Pro", addJobModalTailoredCvHelp: "Uses your reviewed Career Profile and keeps the result as an editable suggestion.", addJobModalNoAiHelp: "Create and track the job normally; no AI operation will be started.", + tagsInputPlaceholder: "Add a tag and press Enter", + proFeatureView: "View Pro", + proFeatureDismiss: "Dismiss {title}", + registrationUnavailable: "Registration is currently unavailable.", + dashboardTotalCount: "{count} total", + jobTableSelectJob: "Select {title}", characterCount: "{count} characters", back: "Back", continue: "Continue", @@ -2275,6 +2282,7 @@ export const translations = { nb: { appTitle: "Jobbjakt", appTagline: "Hold oversikt over jobbsøkingen", + applicationLanguage: "Språk i appen", dashboard: "Dashboard", jobApplications: "Jobbsøknader", reminders: "Påminnelser", @@ -3169,6 +3177,12 @@ export const translations = { addJobModalTailoredCvRequiresPro: "Tilpasset CV-generering krever Pro", addJobModalTailoredCvHelp: "Bruker den gjennomgåtte Karriereprofilen og beholder resultatet som et redigerbart forslag.", addJobModalNoAiHelp: "Opprett og spor jobben som vanlig; ingen KI-operasjon startes.", + tagsInputPlaceholder: "Legg til en tagg og trykk Enter", + proFeatureView: "Se Pro", + proFeatureDismiss: "Lukk {title}", + registrationUnavailable: "Registrering er ikke tilgjengelig for øyeblikket.", + dashboardTotalCount: "{count} totalt", + jobTableSelectJob: "Velg {title}", characterCount: "{count} tegn", back: "Tilbake", continue: "Fortsett", diff --git a/job-tracker-ui/src/layout/AppShell.tsx b/job-tracker-ui/src/layout/AppShell.tsx index 25490a6..9c3af57 100644 --- a/job-tracker-ui/src/layout/AppShell.tsx +++ b/job-tracker-ui/src/layout/AppShell.tsx @@ -557,8 +557,9 @@ export default function AppShell({ } export function StackLanguageToggle({ language, setLanguage }: { language: "en" | "nb"; setLanguage: (language: "en" | "nb") => void }) { + const { t } = useI18n(); return ( - + diff --git a/job-tracker-ui/src/views/LoginPage.tsx b/job-tracker-ui/src/views/LoginPage.tsx index 99094ac..d483137 100644 --- a/job-tracker-ui/src/views/LoginPage.tsx +++ b/job-tracker-ui/src/views/LoginPage.tsx @@ -163,7 +163,7 @@ export default function LoginPage({ initialMode = "login" }: { initialMode?: "lo onSubmit={(e) => { e.preventDefault(); void submit(registerMode ? "register" : "login"); }} sx={{ display: "flex", flexDirection: "column", gap: 1.5 }} > - {registerMode && cfg && !allowReg ? Registration is currently unavailable. : null} + {registerMode && cfg && !allowReg ? {t("registrationUnavailable")} : null} {cfg?.requireEmailVerification && emailNotVerified && ( {loadError ? ( - void loadProfile()}>Retry}> - Unable to load profile. + void loadProfile()}>{t("retry")}}> + {t("profileLoadFailed")} {loadError} ) : null}