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:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user