diff --git a/job-tracker-ui/src/career-workspace-page.test.tsx b/job-tracker-ui/src/career-workspace-page.test.tsx
index a3dac27..1d6f61c 100644
--- a/job-tracker-ui/src/career-workspace-page.test.tsx
+++ b/job-tracker-ui/src/career-workspace-page.test.tsx
@@ -4,6 +4,7 @@ import { render, screen, waitFor } from "@testing-library/react";
import { cvBuilderApi } from "./cvBuilder";
import CareerWorkspaceOverview, { CareerWorkspaceImportRun } from "./views/career/CareerWorkspaceOverview";
+import { I18nProvider } from "./i18n/I18nProvider";
jest.mock("./cvBuilder", () => ({
cvBuilderApi: { list: jest.fn() },
@@ -25,20 +26,24 @@ function renderOverview({
loadError?: string | null;
} = {}) {
return render(
- ,
+
+
+ ,
);
}
beforeEach(() => {
+ window.localStorage.removeItem("uiLanguage");
mockedList.mockResolvedValue([]);
});
afterEach(() => {
+ window.localStorage.removeItem("uiLanguage");
jest.clearAllMocks();
});
@@ -55,6 +60,17 @@ test("first-run workspace presents concise profile, import, general CV and job-s
expect(await screen.findByText("No CV documents yet.")).toBeInTheDocument();
});
+test("career workspace overview and import state render in Bokmål", async () => {
+ window.localStorage.setItem("uiLanguage", "nb");
+ renderOverview({ runs: [{ id: 7, status: "pending_review" }] });
+
+ expect(screen.getByRole("heading", { name: "Karriereområde", level: 1 })).toBeInTheDocument();
+ expect(screen.getAllByText("Se gjennom importert CV", { selector: "a" })).toHaveLength(2);
+ expect(screen.getByText("Én import venter på godkjenning.")).toBeInTheDocument();
+ expect(screen.getByRole("navigation", { name: "Handlinger i karriereområdet" })).toBeInTheDocument();
+ expect(await screen.findByText("Ingen CV-dokumenter ennå.")).toBeInTheDocument();
+});
+
test("returning workspace shows missing profile information and recent general and job-specific CVs", async () => {
mockedList.mockResolvedValue([
{ id: 8, name: "Backend CV", themeId: "modern", publicSlug: "backend", isPublic: false, version: 2, jobApplicationId: 42, updatedAtUtc: "2026-08-08T10:00:00Z" },
diff --git a/job-tracker-ui/src/i18n/translations.ts b/job-tracker-ui/src/i18n/translations.ts
index 5ceda24..00fb911 100644
--- a/job-tracker-ui/src/i18n/translations.ts
+++ b/job-tracker-ui/src/i18n/translations.ts
@@ -427,6 +427,46 @@ export const translations = {
cvAiGrammar: "Fix grammar",
cvAiImpact: "Add measurable impact",
cvAiBullets: "Suggest bullet points",
+ careerOverviewTitle: "Career Workspace",
+ careerOverviewSubtitle: "Choose what you want to work on next.",
+ careerOverviewOpenBuilder: "Open CV Builder",
+ careerOverviewFirstRun: "Start by adding career information manually or importing a CV.",
+ careerOverviewUnavailable: "Workspace status is unavailable. The profile editor below can retry the request.",
+ careerOverviewProfileStatus: "Profile status",
+ careerOverviewCompleteness: "Profile completeness",
+ careerOverviewCompletenessLabel: "Career profile completeness",
+ careerOverviewAddNext: "Add next: {items}",
+ careerOverviewImproveProfile: "Improve profile",
+ careerOverviewCvImport: "CV import",
+ careerOverviewReviewImport: "Review imported CV",
+ careerOverviewReviewOne: "1 import is waiting for your approval.",
+ careerOverviewReviewMany: "{count} imports are waiting for your approval.",
+ careerOverviewViewProcessing: "View CV processing",
+ careerOverviewProcessingOne: "1 CV import is processing.",
+ careerOverviewProcessingMany: "{count} CV imports are processing.",
+ careerOverviewResolveImport: "Resolve CV import",
+ careerOverviewFailedOne: "1 CV import needs attention.",
+ careerOverviewFailedMany: "{count} CV imports need attention.",
+ careerOverviewImportCv: "Import a CV",
+ careerOverviewImportComplete: "Your latest CV import is complete.",
+ careerOverviewNoImport: "No CV has been imported yet.",
+ careerOverviewActions: "Career workspace actions",
+ careerOverviewCareerProfile: "Career profile",
+ careerOverviewCareerProfileBody: "Add or improve the facts used in every CV.",
+ careerOverviewEditProfile: "Edit career profile",
+ careerOverviewImportBody: "Extract information, then review every proposed change before applying it.",
+ careerOverviewGeneralCv: "General CV",
+ careerOverviewGeneralCvBody: "Create and manage reusable CV documents from your profile.",
+ careerOverviewCreateGeneralCv: "Create a general CV",
+ careerOverviewJobCv: "Job-specific CV",
+ careerOverviewJobCvBody: "Open a saved job to tailor and attach a CV for that application.",
+ careerOverviewChooseJob: "Choose a job",
+ careerOverviewRecentCvs: "Recent CVs",
+ careerOverviewViewAll: "View all CVs",
+ careerOverviewRecentFailed: "Recent CVs could not be loaded. Open the CV Builder to try again.",
+ careerOverviewNoCvs: "No CV documents yet.",
+ careerOverviewUpdated: "Updated {date}",
+ careerOverviewJobSpecific: "Job-specific",
richTextBold: "Bold",
richTextBoldPlaceholder: "bold text",
richTextItalic: "Italic",
@@ -2053,6 +2093,46 @@ export const translations = {
cvAiGrammar: "Rett grammatikk",
cvAiImpact: "Legg til målbar effekt",
cvAiBullets: "Foreslå punkter",
+ careerOverviewTitle: "Karriereområde",
+ careerOverviewSubtitle: "Velg hva du vil arbeide med videre.",
+ careerOverviewOpenBuilder: "Åpne CV-bygger",
+ careerOverviewFirstRun: "Start med å legge til karriereinformasjon manuelt eller importere en CV.",
+ careerOverviewUnavailable: "Status for karriereområdet er utilgjengelig. Profilredigeringen nedenfor kan prøve forespørselen på nytt.",
+ careerOverviewProfileStatus: "Profilstatus",
+ careerOverviewCompleteness: "Hvor komplett profilen er",
+ careerOverviewCompletenessLabel: "Hvor komplett karriereprofilen er",
+ careerOverviewAddNext: "Legg til neste: {items}",
+ careerOverviewImproveProfile: "Forbedre profil",
+ careerOverviewCvImport: "CV-import",
+ careerOverviewReviewImport: "Se gjennom importert CV",
+ careerOverviewReviewOne: "Én import venter på godkjenning.",
+ careerOverviewReviewMany: "{count} importer venter på godkjenning.",
+ careerOverviewViewProcessing: "Vis CV-behandling",
+ careerOverviewProcessingOne: "Én CV-import behandles.",
+ careerOverviewProcessingMany: "{count} CV-importer behandles.",
+ careerOverviewResolveImport: "Løs problem med CV-import",
+ careerOverviewFailedOne: "Én CV-import trenger oppmerksomhet.",
+ careerOverviewFailedMany: "{count} CV-importer trenger oppmerksomhet.",
+ careerOverviewImportCv: "Importer en CV",
+ careerOverviewImportComplete: "Den siste CV-importen er fullført.",
+ careerOverviewNoImport: "Ingen CV er importert ennå.",
+ careerOverviewActions: "Handlinger i karriereområdet",
+ careerOverviewCareerProfile: "Karriereprofil",
+ careerOverviewCareerProfileBody: "Legg til eller forbedre fakta som brukes i alle CV-er.",
+ careerOverviewEditProfile: "Rediger karriereprofil",
+ careerOverviewImportBody: "Trekk ut informasjon, og se deretter gjennom alle foreslåtte endringer før de brukes.",
+ careerOverviewGeneralCv: "Generell CV",
+ careerOverviewGeneralCvBody: "Opprett og administrer gjenbrukbare CV-dokumenter fra profilen din.",
+ careerOverviewCreateGeneralCv: "Opprett en generell CV",
+ careerOverviewJobCv: "Jobbspesifikk CV",
+ careerOverviewJobCvBody: "Åpne en lagret jobb for å tilpasse og koble en CV til søknaden.",
+ careerOverviewChooseJob: "Velg en jobb",
+ careerOverviewRecentCvs: "Nylige CV-er",
+ careerOverviewViewAll: "Vis alle CV-er",
+ careerOverviewRecentFailed: "Nylige CV-er kunne ikke lastes. Åpne CV-byggeren for å prøve igjen.",
+ careerOverviewNoCvs: "Ingen CV-dokumenter ennå.",
+ careerOverviewUpdated: "Oppdatert {date}",
+ careerOverviewJobSpecific: "Jobbspesifikk",
richTextBold: "Fet",
richTextBoldPlaceholder: "fet tekst",
richTextItalic: "Kursiv",
diff --git a/job-tracker-ui/src/views/career/CareerWorkspaceOverview.tsx b/job-tracker-ui/src/views/career/CareerWorkspaceOverview.tsx
index 367cde8..05f6767 100644
--- a/job-tracker-ui/src/views/career/CareerWorkspaceOverview.tsx
+++ b/job-tracker-ui/src/views/career/CareerWorkspaceOverview.tsx
@@ -8,6 +8,7 @@ import WorkOutlineIcon from "@mui/icons-material/WorkOutline";
import { CvVariantSummary, cvBuilderApi } from "../../cvBuilder";
import type { UserOperation } from "../../types";
+import { useI18n } from "../../i18n/I18nProvider";
export type CareerWorkspaceCompleteness = {
percent: number;
@@ -54,6 +55,7 @@ export default function CareerWorkspaceOverview({ completeness, runs, loading, l
loading: boolean;
loadError: string | null;
}) {
+ const { language, t } = useI18n();
const [recentCvs, setRecentCvs] = useState([]);
const [recentLoading, setRecentLoading] = useState(true);
const [recentError, setRecentError] = useState(false);
@@ -82,11 +84,11 @@ export default function CareerWorkspaceOverview({ completeness, runs, loading, l
const review = runs.filter((run) => run.status === "pending_review");
const active = runs.filter(isActive);
const failed = runs.filter((run) => run.status === "failed" || run.operation?.status === "failed");
- if (review.length > 0) return { severity: "warning" as const, label: "Review imported CV", message: `${review.length} import${review.length === 1 ? " is" : "s are"} waiting for your approval.` };
- if (active.length > 0) return { severity: "info" as const, label: "View CV processing", message: `${active.length} CV import${active.length === 1 ? " is" : "s are"} processing.` };
- if (failed.length > 0) return { severity: "error" as const, label: "Resolve CV import", message: `${failed.length} CV import${failed.length === 1 ? " needs" : "s need"} attention.` };
- return { severity: "success" as const, label: "Import a CV", message: runs.length > 0 ? "Your latest CV import is complete." : "No CV has been imported yet." };
- }, [runs]);
+ if (review.length > 0) return { severity: "warning" as const, label: t("careerOverviewReviewImport"), message: t(review.length === 1 ? "careerOverviewReviewOne" : "careerOverviewReviewMany", { count: review.length }) };
+ if (active.length > 0) return { severity: "info" as const, label: t("careerOverviewViewProcessing"), message: t(active.length === 1 ? "careerOverviewProcessingOne" : "careerOverviewProcessingMany", { count: active.length }) };
+ if (failed.length > 0) return { severity: "error" as const, label: t("careerOverviewResolveImport"), message: t(failed.length === 1 ? "careerOverviewFailedOne" : "careerOverviewFailedMany", { count: failed.length }) };
+ return { severity: "success" as const, label: t("careerOverviewImportCv"), message: t(runs.length > 0 ? "careerOverviewImportComplete" : "careerOverviewNoImport") };
+ }, [language, runs, t]);
const isFirstRun = !loading && !recentLoading && !loadError && (completeness?.percent ?? 0) === 0 && runs.length === 0 && recentCvs.length === 0;
@@ -95,65 +97,65 @@ export default function CareerWorkspaceOverview({ completeness, runs, loading, l
- Career Workspace
- Choose what you want to work on next.
+ {t("careerOverviewTitle")}
+ {t("careerOverviewSubtitle")}
- } href="/career/builder">Open CV Builder
+ } href="/career/builder">{t("careerOverviewOpenBuilder")}
- {isFirstRun ? Start by adding career information manually or importing a CV. : null}
- {loadError ? Workspace status is unavailable. The profile editor below can retry the request. : null}
+ {isFirstRun ? {t("careerOverviewFirstRun")} : null}
+ {loadError ? {t("careerOverviewUnavailable")} : null}
-
+
- Profile completeness
+ {t("careerOverviewCompleteness")}
{loading ? : {completeness?.percent ?? 0}%}
-
+
{!loading && completeness?.missing.length ? (
- Add next: {completeness.missing.slice(0, 3).join(", ")}
+ {t("careerOverviewAddNext", { items: completeness.missing.slice(0, 3).join(", ") })}
) : null}
-
+
- CV import
+ {t("careerOverviewCvImport")}
{importState.message}
-
- } />
- } />
- } />
- } />
+
+ } />
+ } />
+ } />
+ } />
- Recent CVs
-
+ {t("careerOverviewRecentCvs")}
+
{recentLoading ? (
) : recentError ? (
- Recent CVs could not be loaded. Open the CV Builder to try again.
+ {t("careerOverviewRecentFailed")}
) : recentCvs.length === 0 ? (
- No CV documents yet.
+ {t("careerOverviewNoCvs")}
) : (
{recentCvs.map((cv) => (
{cv.name}
- Updated {new Date(cv.updatedAtUtc).toLocaleDateString()}
+ {t("careerOverviewUpdated", { date: new Date(cv.updatedAtUtc).toLocaleDateString(language === "nb" ? "nb-NO" : "en-GB") })}
- {cv.jobApplicationId ? : null}
-
+ {cv.jobApplicationId ? : null}
+
))}