fix(i18n): localize application workflows

Use stable checklist and interview keys for system copy while preserving user-authored content. Localize creation-tab errors, language selectors, match bands, dates, and selected-CV context.
This commit is contained in:
cesnimda
2026-08-29 16:51:59 +02:00
parent 9edcbfc5de
commit a704c75440
11 changed files with 173 additions and 37 deletions
@@ -26,6 +26,7 @@ import { useI18n } from "../i18n/I18nProvider";
// is application-specific by nature. docs/architecture/application-workspace.md.
function useAsset<T>(load: () => Promise<T>, deps: React.DependencyList) {
const { t } = useI18n();
const [data, setData] = useState<T | null>(null);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
@@ -44,7 +45,7 @@ function useAsset<T>(load: () => Promise<T>, deps: React.DependencyList) {
}
})
.catch((err) => {
if (!cancelled) setError(getApiErrorMessage(err, "Could not load this section."));
if (!cancelled) setError(getApiErrorMessage(err, t("assetsLoadFailed")));
})
.finally(() => {
if (!cancelled) setLoading(false);
@@ -52,7 +53,7 @@ function useAsset<T>(load: () => Promise<T>, deps: React.DependencyList) {
return () => {
cancelled = true;
};
}, [run]);
}, [run, t]);
useEffect(() => reload(), [reload]);
@@ -98,7 +99,7 @@ export function ApplicationCvSection({ jobId }: { jobId: number }) {
setData(await applicationAssetsApi.attachVariant(jobId, variantId));
setError(null);
} catch (err) {
setError(getApiErrorMessage(err, "Could not change the attached CV."));
setError(getApiErrorMessage(err, t("assetsAttachCvFailed")));
} finally {
setBusy(false);
}
@@ -110,11 +111,11 @@ export function ApplicationCvSection({ jobId }: { jobId: number }) {
if (!data?.attachedVariantId) return;
setBusy(true);
try {
const copy = await cvBuilderApi.duplicate(data.attachedVariantId, `${data.attachedVariantName || "CV"} — tailored copy`);
const copy = await cvBuilderApi.duplicate(data.attachedVariantId, t("assetsTailoredCopyName", { name: data.attachedVariantName || "CV" }));
setData(await applicationAssetsApi.attachVariant(jobId, copy.id));
window.location.assign(`/career/builder/${copy.id}`);
} catch (err) {
setError(getApiErrorMessage(err, "Could not create a tailored CV copy."));
setError(getApiErrorMessage(err, t("assetsDuplicateCvFailed")));
} finally {
setBusy(false);
}
@@ -308,7 +309,7 @@ export function ApplicationCoverLetterSection({ jobId, onDirtyChange }: { jobId:
setDraftAiAction(null);
setError(null);
} catch (err) {
setError(getApiErrorMessage(err, "Could not save the cover letter."));
setError(getApiErrorMessage(err, t("assetsCoverSaveFailed")));
} finally {
setBusy(false);
}
@@ -322,7 +323,7 @@ export function ApplicationCoverLetterSection({ jobId, onDirtyChange }: { jobId:
setDraftAiAction(null);
setError(null);
} catch (err) {
setError(getApiErrorMessage(err, "Could not restore that version."));
setError(getApiErrorMessage(err, t("assetsCoverRestoreFailed")));
} finally {
setBusy(false);
}
@@ -394,7 +395,7 @@ export function ApplicationCoverLetterSection({ jobId, onDirtyChange }: { jobId:
{v.isCurrent && <Chip size="small" color="primary" variant="outlined" label={t("assetsCurrent")} />}
</Stack>
<Typography variant="caption" color="text.secondary">
{new Date(v.createdAtUtc).toLocaleString()} · {t("assetsCharacterCount", { count: v.length })}
{new Date(v.createdAtUtc).toLocaleString(language === "nb" ? "nb-NO" : "en")} · {t("assetsCharacterCount", { count: v.length })}
</Typography>
</Box>
{!v.isCurrent && (
@@ -458,7 +459,7 @@ function CoverLetterAiAssistant({ jobId, currentText, onApply }: { jobId: number
});
setSuggestion(result.result.text?.trim() ?? "");
} catch (err) {
setError(getApiErrorMessage(err, "Could not generate a cover-letter suggestion."));
setError(getApiErrorMessage(err, t("coverAiGenerateFailed")));
} finally {
setBusy(false);
}
@@ -499,8 +500,8 @@ function CoverLetterAiAssistant({ jobId, currentText, onApply }: { jobId: number
<FormControl size="small" sx={{ minWidth: 190 }}>
<InputLabel>{t("coverAiDocumentLanguage")}</InputLabel>
<Select label={t("coverAiDocumentLanguage")} value={language} onChange={(event) => setLanguage(event.target.value as "en" | "nb-NO")}>
<MenuItem value="en">English</MenuItem>
<MenuItem value="nb-NO">Norsk bokmål</MenuItem>
<MenuItem value="en">{t("languageEnglish")}</MenuItem>
<MenuItem value="nb-NO">{t("languageNorwegianBokmal")}</MenuItem>
</Select>
</FormControl>
</Stack>
@@ -598,7 +599,7 @@ export function ApplicationPackageDraftsSection({
setError(null);
onSaved?.();
} catch (err) {
setError(getApiErrorMessage(err, "Could not save the application drafts."));
setError(getApiErrorMessage(err, t("assetsDraftsSaveFailed")));
} finally {
setBusy(false);
}