diff --git a/job-tracker-ui/src/i18n/translations.ts b/job-tracker-ui/src/i18n/translations.ts
index 1b93e92..e7ba77b 100644
--- a/job-tracker-ui/src/i18n/translations.ts
+++ b/job-tracker-ui/src/i18n/translations.ts
@@ -184,6 +184,51 @@ export const translations = {
assetsCoverPlaceholder: "Write it yourself, start from the template, or generate a tailored draft.",
assetsDiscardChanges: "Discard changes",
assetsStartTemplate: "Start from template",
+ cvEditorBack: "Back to CVs",
+ cvEditorName: "CV name",
+ cvEditorNameRequired: "Enter a name before saving.",
+ cvEditorUndo: "Undo",
+ cvEditorRedo: "Redo",
+ cvEditorEdit: "Edit",
+ cvEditorPreview: "Preview",
+ cvEditorDuplicate: "Duplicate",
+ cvEditorUpdating: "Updating…",
+ cvEditorPublic: "Public",
+ cvEditorPrivate: "Private",
+ cvEditorCopyLink: "Copy link",
+ cvEditorExporting: "Exporting…",
+ cvEditorDownloadPdf: "Download PDF",
+ cvEditorContent: "Content",
+ cvEditorTemplate: "Template",
+ cvEditorDesign: "Design",
+ cvEditorLayout: "Layout",
+ cvEditorHistory: "History",
+ cvEditorLivePreview: "Live preview",
+ cvEditorUpdatingPreview: "updating…",
+ cvEditorPreviewUnavailable: "Preview unavailable",
+ cvEditorRetryPreview: "Retry preview",
+ cvEditorPrevious: "Previous",
+ cvEditorNext: "Next",
+ cvEditorPageOf: "Page {page} of {pages}",
+ cvEditorZoomOut: "Zoom out",
+ cvEditorZoom: "Zoom",
+ cvEditorZoomIn: "Zoom in",
+ cvEditorFit: "Fit",
+ cvEditorOverflowWarning: "The preview reported horizontal overflow. Shorten an unbroken value or retry after the latest render.",
+ cvEditorLongDocument: "This CV is {pages} pages. Content remains readable, but consider hiding less relevant entries for a more focused application.",
+ cvEditorPreviewTitle: "CV preview",
+ cvEditorPage: "Page {page}",
+ cvEditorPreviewPage: "CV preview page {page}",
+ cvEditorUnsaved: "Unsaved",
+ cvEditorSaving: "Saving…",
+ cvEditorSaved: "Saved",
+ cvEditorSaveFailed: "Save failed",
+ cvEditorSaveNow: "Save now",
+ cvEditorNoVersions: "No saved versions yet. Edits autosave and appear here.",
+ cvEditorVersionCount: "{count} saved version(s). Restoring keeps history — it adds a new version.",
+ cvEditorVersion: "Version {version}",
+ cvEditorCurrent: "current",
+ cvEditorRestore: "Restore",
richTextBold: "Bold",
richTextBoldPlaceholder: "bold text",
richTextItalic: "Italic",
@@ -1567,6 +1612,51 @@ export const translations = {
assetsCoverPlaceholder: "Skriv selv, start fra malen eller generer et tilpasset utkast.",
assetsDiscardChanges: "Forkast endringer",
assetsStartTemplate: "Start fra mal",
+ cvEditorBack: "Tilbake til CV-er",
+ cvEditorName: "CV-navn",
+ cvEditorNameRequired: "Skriv inn et navn før du lagrer.",
+ cvEditorUndo: "Angre",
+ cvEditorRedo: "Gjør om",
+ cvEditorEdit: "Rediger",
+ cvEditorPreview: "Forhåndsvis",
+ cvEditorDuplicate: "Dupliser",
+ cvEditorUpdating: "Oppdaterer…",
+ cvEditorPublic: "Offentlig",
+ cvEditorPrivate: "Privat",
+ cvEditorCopyLink: "Kopier lenke",
+ cvEditorExporting: "Eksporterer…",
+ cvEditorDownloadPdf: "Last ned PDF",
+ cvEditorContent: "Innhold",
+ cvEditorTemplate: "Mal",
+ cvEditorDesign: "Design",
+ cvEditorLayout: "Oppsett",
+ cvEditorHistory: "Historikk",
+ cvEditorLivePreview: "Direkte forhåndsvisning",
+ cvEditorUpdatingPreview: "oppdaterer…",
+ cvEditorPreviewUnavailable: "Forhåndsvisning utilgjengelig",
+ cvEditorRetryPreview: "Prøv forhåndsvisning på nytt",
+ cvEditorPrevious: "Forrige",
+ cvEditorNext: "Neste",
+ cvEditorPageOf: "Side {page} av {pages}",
+ cvEditorZoomOut: "Zoom ut",
+ cvEditorZoom: "Zoom",
+ cvEditorZoomIn: "Zoom inn",
+ cvEditorFit: "Tilpass",
+ cvEditorOverflowWarning: "Forhåndsvisningen rapporterte vannrett overflyt. Forkort en ubrutt verdi, eller prøv igjen etter siste gjengivelse.",
+ cvEditorLongDocument: "Denne CV-en er på {pages} sider. Innholdet er lesbart, men vurder å skjule mindre relevante oppføringer for en mer fokusert søknad.",
+ cvEditorPreviewTitle: "CV-forhåndsvisning",
+ cvEditorPage: "Side {page}",
+ cvEditorPreviewPage: "CV-forhåndsvisning side {page}",
+ cvEditorUnsaved: "Ikke lagret",
+ cvEditorSaving: "Lagrer…",
+ cvEditorSaved: "Lagret",
+ cvEditorSaveFailed: "Lagring mislyktes",
+ cvEditorSaveNow: "Lagre nå",
+ cvEditorNoVersions: "Ingen lagrede versjoner ennå. Endringer lagres automatisk og vises her.",
+ cvEditorVersionCount: "{count} lagret(e) versjon(er). Gjenoppretting beholder historikken og legger til en ny versjon.",
+ cvEditorVersion: "Versjon {version}",
+ cvEditorCurrent: "gjeldende",
+ cvEditorRestore: "Gjenopprett",
richTextBold: "Fet",
richTextBoldPlaceholder: "fet tekst",
richTextItalic: "Kursiv",
diff --git a/job-tracker-ui/src/views/CvBuilderEditor.tsx b/job-tracker-ui/src/views/CvBuilderEditor.tsx
index dd39aca..354282a 100644
--- a/job-tracker-ui/src/views/CvBuilderEditor.tsx
+++ b/job-tracker-ui/src/views/CvBuilderEditor.tsx
@@ -35,6 +35,7 @@ import {
cvBuilderApi, getCvPageCount, getCvPageMetrics, moveItem,
} from "../cvBuilder";
import { useDialogActions } from "../dialogs";
+import { useI18n } from "../i18n/I18nProvider";
const FONTS = [
"'Segoe UI', Roboto, Arial, sans-serif",
@@ -65,6 +66,7 @@ export default function CvBuilderEditor() {
const variantId = Number(id);
const navigate = useNavigate();
const { toast } = useToast();
+ const { t } = useI18n();
const { confirmAction } = useDialogActions();
const compactEditor = useMediaQuery("(max-width:899.95px)");
@@ -410,7 +412,7 @@ export default function CvBuilderEditor() {
if (loadError) {
return (
- } onClick={() => navigate("/career/builder")}>Back to CVs
+ } onClick={() => navigate("/career/builder")}>{t("cvEditorBack")}
{loadError}
);
@@ -424,26 +426,26 @@ export default function CvBuilderEditor() {
- navigate("/career/builder")}>
+ navigate("/career/builder")}>
renameVariant(e.target.value)}
- error={!name.trim()} helperText={!name.trim() ? "Enter a name before saving." : undefined}
+ error={!name.trim()} helperText={!name.trim() ? t("cvEditorNameRequired") : undefined}
sx={{ minWidth: { xs: 150, sm: 220 }, flex: "1 1 240px", maxWidth: 420 }}
- slotProps={{ input: { style: { fontWeight: 800, fontSize: "1.05rem" } }, htmlInput: { "aria-label": "CV name" } }} />
+ slotProps={{ input: { style: { fontWeight: 800, fontSize: "1.05rem" } }, htmlInput: { "aria-label": t("cvEditorName") } }} />
void retrySave()} />
-
-
+
+
{compactEditor &&
-
-
+
+
}
- } disabled={!name.trim()} onClick={() => void duplicateVariant()}>Duplicate
+ } disabled={!name.trim()} onClick={() => void duplicateVariant()}>{t("cvEditorDuplicate")}
} disabled={publishing || exporting || !name.trim()} onClick={togglePublic}>
- {publishing ? "Updating…" : isPublic ? "Public" : "Private"}
+ {publishing ? t("cvEditorUpdating") : isPublic ? t("cvEditorPublic") : t("cvEditorPrivate")}
- {isPublic && } onClick={copyPublicLink}>Copy link}
- } disabled={exporting || publishing || !name.trim()} onClick={exportPdf}>{exporting ? "Exporting…" : "Download PDF"}
+ {isPublic && } onClick={copyPublicLink}>{t("cvEditorCopyLink")}}
+ } disabled={exporting || publishing || !name.trim()} onClick={exportPdf}>{exporting ? t("cvEditorExporting") : t("cvEditorDownloadPdf")}
@@ -451,11 +453,11 @@ export default function CvBuilderEditor() {
{ setTab(v); if (v === 4) loadVersions(); }} variant="scrollable" scrollButtons="auto" allowScrollButtonsMobile sx={{ mb: 1.5, minHeight: 38 }}>
-
-
-
-
-
+
+
+
+
+
{tab === 0 && }
@@ -467,35 +469,35 @@ export default function CvBuilderEditor() {
- Live preview
- {previewing && }
- {previewError && }
- {previewError && }
+ {t("cvEditorLivePreview")}
+ {previewing && }
+ {previewError && }
+ {previewError && }
{pages >= 3 && }
-
- Page {page} of {pages}
-
+
+ {t("cvEditorPageOf", { page, pages })}
+
- setZoom((z) => Math.max(MIN_PREVIEW_ZOOM, +(z - 0.1).toFixed(2)))}>
- setZoom(v as number)} sx={{ width: 90 }} aria-label="Zoom" />
- setZoom((z) => Math.min(1, +(z + 0.1).toFixed(2)))}>
+ setZoom((z) => Math.max(MIN_PREVIEW_ZOOM, +(z - 0.1).toFixed(2)))}>
+ setZoom(v as number)} sx={{ width: 90 }} aria-label={t("cvEditorZoom")} />
+ setZoom((z) => Math.min(1, +(z + 0.1).toFixed(2)))}>
{Math.round(zoom * 100)}%
-
+
- {previewOverflow && The preview reported horizontal overflow. Shorten an unbroken value or retry after the latest render.}
- {pages >= 3 && This CV is {pages} pages. Content remains readable, but consider hiding less relevant entries for a more focused application.}
-
+ {previewOverflow && {t("cvEditorOverflowWarning")}}
+ {pages >= 3 && {t("cvEditorLongDocument", { pages })}}
+
{Array.from({ length: pages }).map((_, index) => (
- Page {index + 1}
+ {t("cvEditorPage", { page: index + 1 })}