feat(i18n): persist Bokmal preference globally

This commit is contained in:
cesnimda
2026-08-28 12:44:02 +02:00
parent b6dcc7c760
commit 5805c1a621
21 changed files with 359 additions and 66 deletions
@@ -16,6 +16,7 @@ import {
import { cvBuilderApi } from "../cvBuilder";
import { aiWorkspaceApi } from "../aiWorkspace";
import { useAccountPlan } from "../accountPlan";
import { useI18n } from "../i18n/I18nProvider";
// Phase 5.4 — Application Assets sections for the workspace.
//
@@ -417,10 +418,11 @@ const COVER_LETTER_ACTIONS = [
function CoverLetterAiAssistant({ jobId, currentText, onApply }: { jobId: number; currentText: string; onApply: (text: string, action: string) => void }) {
const { canUseAi } = useAccountPlan();
const { language: uiLanguage, t } = useI18n();
const { data: cv, loading: loadingCv } = useAsset<ApplicationCv>(() => applicationAssetsApi.cv(jobId), [jobId]);
const [action, setAction] = useState("generate");
const [mode, setMode] = useState("professional");
const [language, setLanguage] = useState<"en" | "nb-NO">("en");
const [language, setLanguage] = useState<"en" | "nb-NO">(() => uiLanguage === "nb" ? "nb-NO" : "en");
const [instructions, setInstructions] = useState("");
const [suggestion, setSuggestion] = useState("");
const [busy, setBusy] = useState(false);
@@ -449,17 +451,17 @@ function CoverLetterAiAssistant({ jobId, currentText, onApply }: { jobId: number
const hasCv = !!cv?.attachedVariantId;
return (
<Shell
title="AI writing assistant"
subtitle="Uses this job and its linked CV. Suggestions never overwrite your document."
title={t("coverAiTitle")}
subtitle={t("coverAiSubtitle")}
loading={loadingCv}
error={null}
>
<Stack spacing={2}>
{!hasCv ? (
<Alert severity="info">Select a CV before generating a tailored cover letter.</Alert>
<Alert severity="info">{t("coverAiSelectCv")}</Alert>
) : (
<Alert severity="success" variant="outlined" sx={{ py: 0.5 }}>
Using <strong>{cv?.attachedVariantName}</strong> and this application's full job advert and analysis.
{t("coverAiUsing")} <strong>{cv?.attachedVariantName}</strong>
</Alert>
)}
<Stack direction={{ xs: "column", sm: "row" }} spacing={1.5}>
@@ -478,15 +480,15 @@ function CoverLetterAiAssistant({ jobId, currentText, onApply }: { jobId: number
</Select>
</FormControl>
<FormControl size="small" sx={{ minWidth: 190 }}>
<InputLabel>Document language</InputLabel>
<Select label="Document language" value={language} onChange={(event) => setLanguage(event.target.value as "en" | "nb-NO")}>
<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>
</Select>
</FormControl>
</Stack>
<TextField
label="Additional instructions"
label={t("coverAiAdditionalInstructions")}
placeholder="For example: Focus on my .NET experience and keep it concise."
value={instructions}
onChange={(event) => setInstructions(event.target.value)}
@@ -508,17 +510,17 @@ function CoverLetterAiAssistant({ jobId, currentText, onApply }: { jobId: number
{suggestion && (
<Box sx={{ display: "grid", gridTemplateColumns: { xs: "1fr", lg: "1fr 1fr" }, gap: 1.5 }}>
<Paper variant="outlined" sx={{ p: 2, minWidth: 0 }}>
<Typography variant="overline" color="text.secondary">Current</Typography>
<Typography variant="overline" color="text.secondary">{t("coverAiCurrent")}</Typography>
<Typography variant="body2" sx={{ whiteSpace: "pre-wrap", overflowWrap: "anywhere" }}>
{currentText || "No current draft"}
</Typography>
</Paper>
<Paper variant="outlined" sx={{ p: 2, minWidth: 0, borderColor: "primary.main" }}>
<Typography variant="overline" color="primary">Suggestion</Typography>
<Typography variant="overline" color="primary">{t("coverAiSuggestion")}</Typography>
<Typography variant="body2" sx={{ whiteSpace: "pre-wrap", overflowWrap: "anywhere" }}>{suggestion}</Typography>
<Stack direction="row" spacing={1} sx={{ mt: 2 }}>
<Button size="small" variant="contained" onClick={() => { onApply(suggestion, action); setSuggestion(""); }}>Apply to editor</Button>
<Button size="small" onClick={() => setSuggestion("")}>Reject</Button>
<Button size="small" variant="contained" onClick={() => { onApply(suggestion, action); setSuggestion(""); }}>{t("coverAiApply")}</Button>
<Button size="small" onClick={() => setSuggestion("")}>{t("coverAiReject")}</Button>
</Stack>
</Paper>
</Box>