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}