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
@@ -21,6 +21,7 @@ import { useI18n } from "../i18n/I18nProvider";
// One loader for all three sections: same fetch/loading/empty/error shape, so the sections stay
// consistent and each one is just its own rendering.
function useIntelligence<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);
@@ -38,7 +39,7 @@ function useIntelligence<T>(load: () => Promise<T>, deps: React.DependencyList)
setError(null);
})
.catch((err) => {
if (!cancelled) setError(getApiErrorMessage(err, "Could not load this section."));
if (!cancelled) setError(getApiErrorMessage(err, t("intelligenceLoadFailed")));
})
.finally(() => {
if (!cancelled) setLoading(false);
@@ -46,7 +47,7 @@ function useIntelligence<T>(load: () => Promise<T>, deps: React.DependencyList)
return () => {
cancelled = true;
};
}, [run]);
}, [run, t]);
return { data, error, loading };
}
@@ -110,7 +111,7 @@ function Bullets({ label, values }: { label: string; values: string[] }) {
// ---------- Timeline ----------
export function ApplicationTimeline({ jobId }: { jobId: number }) {
const { t } = useI18n();
const { language, t } = useI18n();
const [category, setCategory] = useState<string>("");
const { data, error, loading } = useIntelligence<Timeline>(
() => applicationIntelligenceApi.timeline(jobId, category || undefined),
@@ -129,7 +130,7 @@ export function ApplicationTimeline({ jobId }: { jobId: number }) {
<Stack key={m.id} direction="row" spacing={1} justifyContent="space-between" alignItems="baseline">
<Typography variant="body2" sx={{ fontWeight: 600 }}>{m.summary}</Typography>
<Typography variant="caption" color="text.secondary">
{new Date(m.at).toLocaleDateString()}
{new Date(m.at).toLocaleDateString(language === "nb" ? "nb-NO" : "en")}
</Typography>
</Stack>
))}
@@ -267,7 +268,7 @@ export function ApplicationMatch({ jobId }: { jobId: number }) {
<SectionShell
title={t("workspaceCvMatch")}
subtitle={data?.selectedCvName
? `${t("workspaceComparingCv")} ${data.selectedCvName}`
? t("workspaceComparingCv", { name: data.selectedCvName })
: t("workspaceSelectCvMatch")}
loading={loading}
error={error}
@@ -286,7 +287,7 @@ export function ApplicationMatch({ jobId }: { jobId: number }) {
<Box>
<Stack direction="row" justifyContent="space-between" alignItems="baseline">
<Typography variant="h4" sx={{ fontWeight: 900 }}>{data?.score}%</Typography>
<Chip size="small" label={data?.band} variant="outlined" />
<Chip size="small" label={matchBandLabel(t, data?.band)} variant="outlined" />
</Stack>
<LinearProgress
variant="determinate"
@@ -369,3 +370,10 @@ function timelineCategoryLabel(t: (key: any) => string, category: string): strin
};
return keys[category] ? t(keys[category]) : category;
}
function matchBandLabel(t: (key: any) => string, band?: string): string {
if (!band) return t("matchScoreBand_Unknown");
return ["Strong", "Partial", "Low", "Unknown"].includes(band)
? t(`matchScoreBand_${band}`)
: band;
}