feat(i18n): localise application workspace

This commit is contained in:
cesnimda
2026-08-28 20:04:11 +02:00
parent cc01540446
commit a57b2fe58c
5 changed files with 372 additions and 124 deletions
@@ -302,14 +302,14 @@ function OverviewSection({ overview, onGo, onReload, onEdit }: {
onReload: () => void;
onEdit: () => void;
}) {
const { t } = useI18n();
const { language, t } = useI18n();
const stats = useMemo(() => overview ? [
{ icon: <DescriptionOutlinedIcon fontSize="small" />, label: "CV", value: overview.cv.variantName ?? (overview.cv.hasTailoredCvText ? "Tailored text" : "Not prepared"), ok: !!overview.cv.variantId || overview.cv.hasTailoredCvText, go: "cv" as const },
{ icon: <MailOutlineIcon fontSize="small" />, label: "Cover letter", value: overview.hasCoverLetter ? "Ready" : "Not written", ok: overview.hasCoverLetter, go: "cover-letter" as const },
{ icon: <FolderOutlinedIcon fontSize="small" />, label: "Documents", value: overview.documentCount ? `${overview.documentCount} attached` : "None", ok: overview.documentCount > 0, go: "overview" as const },
{ icon: <AutoFixHighIcon fontSize="small" />, label: "AI suggestions", value: overview.aiInteractionCount ? `${overview.aiInteractionCount} saved` : "None yet", ok: overview.aiInteractionCount > 0, go: "analysis" as const },
{ icon: <ChecklistIcon fontSize="small" />, label: "Checklist", value: overview.checklistProgress ? `${overview.checklistProgress.completed}/${overview.checklistProgress.total} done` : "—", ok: (overview.checklistProgress?.percent ?? 0) === 100, go: "overview" as const },
] : [], [overview]);
{ icon: <DescriptionOutlinedIcon fontSize="small" />, label: t("workspaceCv"), value: overview.cv.variantName ?? (overview.cv.hasTailoredCvText ? t("workspaceTailoredText") : t("workspaceNotPrepared")), ok: !!overview.cv.variantId || overview.cv.hasTailoredCvText, go: "cv" as const },
{ icon: <MailOutlineIcon fontSize="small" />, label: t("jobDetailsCoverLetter"), value: overview.hasCoverLetter ? t("workspaceReady") : t("workspaceNotWritten"), ok: overview.hasCoverLetter, go: "cover-letter" as const },
{ icon: <FolderOutlinedIcon fontSize="small" />, label: t("workspaceDocuments"), value: overview.documentCount ? t("workspaceAttachedCount", { count: overview.documentCount }) : t("assetsNone"), ok: overview.documentCount > 0, go: "overview" as const },
{ icon: <AutoFixHighIcon fontSize="small" />, label: t("workspaceAiSuggestions"), value: overview.aiInteractionCount ? t("workspaceSavedCount", { count: overview.aiInteractionCount }) : t("workspaceNoneYet"), ok: overview.aiInteractionCount > 0, go: "analysis" as const },
{ icon: <ChecklistIcon fontSize="small" />, label: t("workspaceChecklist"), value: overview.checklistProgress ? t("workspaceDoneCount", { completed: overview.checklistProgress.completed, total: overview.checklistProgress.total }) : "—", ok: (overview.checklistProgress?.percent ?? 0) === 100, go: "overview" as const },
] : [], [overview, t]);
if (!overview) {
return <Stack spacing={2}>{[0, 1].map((i) => <Skeleton key={i} variant="rounded" height={120} />)}</Stack>;
@@ -361,7 +361,7 @@ function OverviewSection({ overview, onGo, onReload, onEdit }: {
{overview.recentActivity.map((a, i) => (
<Stack key={i} direction="row" spacing={1} justifyContent="space-between">
<Typography variant="body2"><strong>{a.type}</strong>{a.detail ? `${a.detail}` : ""}</Typography>
<Typography variant="caption" color="text.secondary">{new Date(a.at).toLocaleDateString()}</Typography>
<Typography variant="caption" color="text.secondary">{new Date(a.at).toLocaleDateString(language === "nb" ? "nb-NO" : "en")}</Typography>
</Stack>
))}
</Stack>
@@ -408,26 +408,27 @@ function workspaceSectionLabel(t: (key: any) => string, section: WorkspaceSectio
}
function JobDetailsSection({ overview, onEdit }: { overview: WorkspaceOverview | null; onEdit: () => void }) {
const { language, t } = useI18n();
if (!overview) return <Skeleton variant="rounded" height={200} />;
const rows: [string, string][] = [
["Company", overview.company ?? "—"],
["Location", overview.location ?? "—"],
["Country", overview.countryCode ?? "—"],
["Source", overview.source ?? "—"],
["Salary", overview.salary ?? "—"],
["Status", overview.status],
["Discovered", overview.savedAt ? new Date(overview.savedAt).toLocaleDateString() : "—"],
["Applied", overview.dateApplied ? new Date(overview.dateApplied).toLocaleDateString() : "—"],
["Deadline", overview.deadline ? new Date(overview.deadline).toLocaleDateString() : "—"],
["Follow-up", overview.followUpAt ? new Date(overview.followUpAt).toLocaleDateString() : "Not scheduled"],
["Next action", overview.nextAction ?? "—"],
[t("company"), overview.company ?? "—"],
[t("location"), overview.location ?? "—"],
[t("workspaceCountry"), overview.countryCode ?? "—"],
[t("workspaceSource"), overview.source ?? "—"],
[t("jobDetailsSalary"), overview.salary ?? "—"],
[t("addJobModalStatus"), statusLabel(t, overview.status)],
[t("workspaceDiscovered"), overview.savedAt ? new Date(overview.savedAt).toLocaleDateString(language === "nb" ? "nb-NO" : "en") : "—"],
[t("workspaceApplied"), overview.dateApplied ? new Date(overview.dateApplied).toLocaleDateString(language === "nb" ? "nb-NO" : "en") : "—"],
[t("jobDetailsDeadline"), overview.deadline ? new Date(overview.deadline).toLocaleDateString(language === "nb" ? "nb-NO" : "en") : "—"],
[t("intelligenceCategoryFollowUp"), overview.followUpAt ? new Date(overview.followUpAt).toLocaleDateString(language === "nb" ? "nb-NO" : "en") : t("workspaceNotScheduled")],
[t("workspaceNextAction"), overview.nextAction ?? "—"],
];
return (
<Stack spacing={2}>
<Paper sx={{ p: 2.5, borderRadius: 3 }}>
<Stack direction="row" alignItems="center" justifyContent="space-between" gap={1} sx={{ mb: 1.5 }}>
<Typography variant="subtitle1" sx={{ fontWeight: 800 }}>Application information</Typography>
<Button size="small" startIcon={<EditOutlinedIcon />} onClick={onEdit}>Edit</Button>
<Typography variant="subtitle1" sx={{ fontWeight: 800 }}>{t("workspaceApplicationInfo")}</Typography>
<Button size="small" startIcon={<EditOutlinedIcon />} onClick={onEdit}>{t("workspaceEdit")}</Button>
</Stack>
<Box sx={{ display: "grid", gridTemplateColumns: { xs: "1fr", sm: "repeat(2, minmax(0, 1fr))", lg: "repeat(4, minmax(0, 1fr))" }, gap: 1.5 }}>
{rows.map(([k, v]) => (
@@ -444,29 +445,29 @@ function JobDetailsSection({ overview, onEdit }: { overview: WorkspaceOverview |
) : null}
{overview.notes ? (
<Box sx={{ mt: 2 }}>
<Typography variant="caption" color="text.secondary" sx={{ fontWeight: 700 }}>Notes</Typography>
<Typography variant="caption" color="text.secondary" sx={{ fontWeight: 700 }}>{t("workspaceNotes")}</Typography>
<Typography variant="body2" sx={{ mt: 0.5, whiteSpace: "pre-wrap", overflowWrap: "anywhere" }}>{overview.notes}</Typography>
</Box>
) : null}
</Paper>
<Paper sx={{ p: { xs: 2, sm: 2.5 }, borderRadius: 3 }}>
<Typography variant="subtitle1" sx={{ fontWeight: 800, mb: 1.5 }}>Job description</Typography>
<Typography variant="subtitle1" sx={{ fontWeight: 800, mb: 1.5 }}>{t("workspaceJobDescription")}</Typography>
{!overview.hasJobDescription ? (
<Alert severity="warning" sx={{ borderRadius: 2 }} action={<Button color="inherit" size="small" onClick={onEdit}>Add advert</Button>}>
No advert text saved. Analysis and matching need the job description.
<Alert severity="warning" sx={{ borderRadius: 2 }} action={<Button color="inherit" size="small" onClick={onEdit}>{t("workspaceAddAdvert")}</Button>}>
{t("workspaceNoJobDescription")}
</Alert>
) : (
<Stack spacing={2.5}>
{overview.translatedDescription ? (
<Box>
<Typography variant="overline" color="text.secondary">Translated advert</Typography>
<Typography variant="overline" color="text.secondary">{t("workspaceTranslatedAdvert")}</Typography>
<Typography sx={{ mt: 0.5, whiteSpace: "pre-wrap", overflowWrap: "anywhere", lineHeight: 1.7 }}>{overview.translatedDescription}</Typography>
</Box>
) : null}
{overview.description ? (
<Box>
{overview.translatedDescription ? <Typography variant="overline" color="text.secondary">Original advert{overview.descriptionLanguage ? ` · ${overview.descriptionLanguage.toUpperCase()}` : ""}</Typography> : null}
{overview.translatedDescription ? <Typography variant="overline" color="text.secondary">{t("workspaceOriginalAdvert")}{overview.descriptionLanguage ? ` · ${overview.descriptionLanguage.toUpperCase()}` : ""}</Typography> : null}
<Typography sx={{ mt: 0.5, whiteSpace: "pre-wrap", overflowWrap: "anywhere", lineHeight: 1.7 }}>{overview.description}</Typography>
</Box>
) : null}