fix(i18n): localize job email workflows

This commit is contained in:
cesnimda
2026-08-29 22:45:49 +02:00
parent ee8eacf0de
commit 3c9b22c97e
8 changed files with 469 additions and 144 deletions
+24 -17
View File
@@ -357,7 +357,7 @@ export default function AddJobModal({ open, onClose, onCreated, initialUrl }: Pr
try {
await api.post(`/jobapplications/${response.data.id}/generate-tailored-cv-draft`);
} catch (error: any) {
toast(getApiErrorMessage(error, "Job created, but the tailored CV could not be generated."), "warning");
toast(getApiErrorMessage(error, t("addJobModalTailoredCvFailed")), "warning");
}
}
@@ -387,7 +387,14 @@ export default function AddJobModal({ open, onClose, onCreated, initialUrl }: Pr
};
const canSave = normalizedCompanyName.length > 0 && jobTitle.trim().length > 0;
const steps = ["Add job", "Review details", "CV", "Cover letter", "Portfolio", "Additional files"];
const steps = [
t("addJobModalStepAddJob"),
t("addJobModalStepReview"),
t("addJobModalResume"),
t("addJobModalCoverLetter"),
t("addJobModalPortfolio"),
t("addJobModalStepAdditionalFiles"),
];
const optionalStep = activeStep >= 2 && activeStep <= 5;
const setFilesForBucket = (bucket: AttachmentBucketKey, files: FileList | null) => {
@@ -438,14 +445,14 @@ export default function AddJobModal({ open, onClose, onCreated, initialUrl }: Pr
{steps.map((label) => <Step key={label}><StepLabel>{label}</StepLabel></Step>)}
</Stepper>
<Typography variant="subtitle2" sx={{ display: { xs: "block", sm: "none" }, mb: 2 }}>
Step {activeStep + 1} of {steps.length}: {steps[activeStep]}
{t("addJobModalStepProgress", { current: activeStep + 1, total: steps.length, step: steps[activeStep] })}
</Typography>
{activeStep === 0 ? <Box sx={{ display: "grid", gap: 2 }}>
<Box>
<Typography variant="h6" sx={{ fontWeight: 900 }}>Start with the job advert</Typography>
<Typography variant="h6" sx={{ fontWeight: 900 }}>{t("addJobModalStartWithAdvert")}</Typography>
<Typography variant="body2" sx={{ color: "text.secondary", mt: 0.5 }}>
Paste a job URL and Jobbjakt will try to fill the details. You can continue with manual entry whenever extraction is unavailable.
{t("addJobModalStartWithAdvertBody")}
</Typography>
</Box>
<TextField label={t("addJobModalJobUrl")} value={jobUrl} onChange={(e) => setJobUrl(e.target.value)} fullWidth sx={FIELD_SX} />
@@ -453,7 +460,7 @@ export default function AddJobModal({ open, onClose, onCreated, initialUrl }: Pr
<Button variant="contained" onClick={() => void importFromUrl()} disabled={importing || !jobUrl.trim()}>
{importing ? t("addJobModalImporting") : t("addJobModalImportFromUrl")}
</Button>
<Button onClick={() => setActiveStep(1)}>Enter details manually</Button>
<Button onClick={() => setActiveStep(1)}>{t("addJobModalEnterManually")}</Button>
</Box>
</Box> : null}
@@ -570,7 +577,7 @@ export default function AddJobModal({ open, onClose, onCreated, initialUrl }: Pr
rows={6}
value={description}
onChange={(e) => setDescription(e.target.value)}
helperText={`${description.length} characters`}
helperText={t("characterCount", { count: description.length })}
sx={{ gridColumn: "1 / -1" }}
/>
@@ -581,7 +588,7 @@ export default function AddJobModal({ open, onClose, onCreated, initialUrl }: Pr
rows={6}
value={translatedDescription}
onChange={(e) => setTranslatedDescription(e.target.value)}
helperText={`${translatedDescription.length} characters`}
helperText={t("characterCount", { count: translatedDescription.length })}
sx={{ gridColumn: "1 / -1" }}
/>
) : null}
@@ -594,7 +601,7 @@ export default function AddJobModal({ open, onClose, onCreated, initialUrl }: Pr
sx={{ gridColumn: "1 / -1" }}
/>
<TextField label={t("addJobModalNotes")} multiline rows={3} value={notes} onChange={(e) => setNotes(e.target.value)} helperText={`${notes.length} characters`} sx={{ gridColumn: "1 / -1" }} />
<TextField label={t("addJobModalNotes")} multiline rows={3} value={notes} onChange={(e) => setNotes(e.target.value)} helperText={t("characterCount", { count: notes.length })} sx={{ gridColumn: "1 / -1" }} />
</Box>
</> : null}
@@ -603,15 +610,15 @@ export default function AddJobModal({ open, onClose, onCreated, initialUrl }: Pr
<Box>
<Typography variant="h6" sx={{ fontWeight: 900 }}>{steps[activeStep]}</Typography>
<Typography variant="body2" sx={{ color: "text.secondary", mt: 0.5 }}>
{activeStep === 2 ? "Use your master CV later in Career Workspace, or attach the version prepared for this application now." : null}
{activeStep === 3 ? "Attach a drafted cover letter now, or add/generate one from the application workspace later." : null}
{activeStep === 4 ? "Add supporting portfolio material now, or select lightweight profile-linked projects later." : null}
{activeStep === 5 ? "Certificates, references, and other supporting documents are optional." : null}
{activeStep === 2 ? t("addJobModalCvStepHelp") : null}
{activeStep === 3 ? t("addJobModalCoverLetterStepHelp") : null}
{activeStep === 4 ? t("addJobModalPortfolioStepHelp") : null}
{activeStep === 5 ? t("addJobModalOtherFilesStepHelp") : null}
</Typography>
</Box>
{activeStep === 2 ? <>
<FormControlLabel control={<Checkbox disabled={!canUseAi} checked={generateTailoredCv && canUseAi} onChange={(event) => setGenerateTailoredCv(event.target.checked)} />} label={canUseAi ? "Generate a tailored CV draft after creating this job" : "Tailored CV generation requires Pro"} />
<Typography variant="caption" sx={{ color: "text.secondary", mt: -1 }}>{canUseAi ? "Uses your reviewed Career Profile and keeps the result as an editable suggestion." : "Create and track the job normally; no AI operation will be started."}</Typography>
<FormControlLabel control={<Checkbox disabled={!canUseAi} checked={generateTailoredCv && canUseAi} onChange={(event) => setGenerateTailoredCv(event.target.checked)} />} label={canUseAi ? t("addJobModalGenerateTailoredCv") : t("addJobModalTailoredCvRequiresPro")} />
<Typography variant="caption" sx={{ color: "text.secondary", mt: -1 }}>{canUseAi ? t("addJobModalTailoredCvHelp") : t("addJobModalNoAiHelp")}</Typography>
{uploadField("resume", t("addJobModalResume"), t("addJobModalResumeHelp"))}
</> : null}
{activeStep === 3 ? uploadField("coverLetter", t("addJobModalCoverLetter"), t("addJobModalCoverLetterHelp")) : null}
@@ -626,9 +633,9 @@ export default function AddJobModal({ open, onClose, onCreated, initialUrl }: Pr
<DialogActions sx={{ px: 3, py: 2, justifyContent: "space-between", flexWrap: "wrap", gap: 1.5 }}>
<Button variant="outlined" onClick={onClose}>{t("close")}</Button>
<Box sx={{ display: "flex", gap: 1, flexWrap: "wrap" }}>
{activeStep > 0 ? <Button onClick={() => setActiveStep((step) => step - 1)} disabled={saving}>Back</Button> : null}
{activeStep > 0 ? <Button onClick={() => setActiveStep((step) => step - 1)} disabled={saving}>{t("back")}</Button> : null}
{activeStep < steps.length - 1 ? <Button variant="contained" onClick={() => setActiveStep((step) => step + 1)} disabled={saving || (activeStep === 1 && !canSave)}>
{optionalStep ? "Skip and continue" : "Continue"}
{optionalStep ? t("skipAndContinue") : t("continue")}
</Button> : <>
<Button variant="outlined" onClick={() => void createJob(true)} disabled={saving || !canSave}>
{saving ? t("rulesSaving") : t("createAndAddAnother")}