fix(i18n): fall back to Accept-Language and add a language switcher to onboarding

New visitors always got the English default because locale resolution
only ever read the NEXT_LOCALE cookie — never set until a user visits
a page with the LanguageSwitcher, which only lives in the authenticated
app Sidebar. /onboarding/pet runs before that, so a German browser
landed in English with no way to change it (reported live after
deploying the upload-consent step from Gitea #3).

Adds an Accept-Language fallback in src/i18n/request.ts (only consulted
when no cookie is set) and renders the existing LanguageSwitcher on the
onboarding page so users can override the guess.
This commit is contained in:
admin committed 2026-08-11 10:27:58 +02:00
1 parent b6610ca7a7
commit 4d9727f0d1
2 files changed
+65 -50

No files matched your search

+42 -46
View File
@@ -9,6 +9,7 @@ import { Button } from "@/components/ui/button";
import { useActivePet } from "@/context/ActivePetContext";
import { OnboardingGuidelines } from "@/components/legal/OnboardingGuidelines";
import { UploadConsent } from "@/components/legal/UploadConsent";
import { LanguageSwitcher } from "@/components/i18n/LanguageSwitcher";
/**
* Onboarding pet page — /onboarding/pet
@@ -32,55 +33,41 @@ export default function OnboardingPetPage() {
const [guidelinesAcknowledged, setGuidelinesAcknowledged] = useState(false);
const [uploadConsentGiven, setUploadConsentGiven] = useState(false);
let content: React.ReactNode;
if (!guidelinesAcknowledged) {
return (
<main className="min-h-screen flex items-center justify-center bg-background px-4">
<OnboardingGuidelines onContinue={() => setGuidelinesAcknowledged(true)} />
</main>
);
}
if (!uploadConsentGiven) {
return (
<main className="min-h-screen flex items-center justify-center bg-background px-4">
<UploadConsent onContinue={() => setUploadConsentGiven(true)} />
</main>
);
}
if (createdPetId) {
return (
<main className="min-h-screen flex items-center justify-center bg-background px-4">
<div className="w-full max-w-[512px] p-8 flex flex-col items-center gap-6">
<div className="text-center">
<h1 className="text-xl font-semibold mb-2">{t("addPhoto")}</h1>
<p className="text-sm text-muted-foreground">{t("addPhotoDesc")}</p>
</div>
<AvatarUpload petId={createdPetId} onUploadComplete={() => {}} />
<div className="flex flex-col gap-2 w-full">
<Button
className="w-full"
onClick={() => router.push(`/pets/${createdPetId}`)}
>
{t("done")}
</Button>
<Button
variant="ghost"
className="w-full text-muted-foreground"
onClick={() => router.push(`/pets/${createdPetId}`)}
>
{t("skipForNow")}
</Button>
</div>
content = <OnboardingGuidelines onContinue={() => setGuidelinesAcknowledged(true)} />;
} else if (!uploadConsentGiven) {
content = <UploadConsent onContinue={() => setUploadConsentGiven(true)} />;
} else if (createdPetId) {
content = (
<div className="w-full max-w-[512px] p-8 flex flex-col items-center gap-6">
<div className="text-center">
<h1 className="text-xl font-semibold mb-2">{t("addPhoto")}</h1>
<p className="text-sm text-muted-foreground">{t("addPhotoDesc")}</p>
</div>
</main>
);
}
return (
<main className="min-h-screen flex items-center justify-center bg-background px-4">
<AvatarUpload petId={createdPetId} onUploadComplete={() => {}} />
<div className="flex flex-col gap-2 w-full">
<Button
className="w-full"
onClick={() => router.push(`/pets/${createdPetId}`)}
>
{t("done")}
</Button>
<Button
variant="ghost"
className="w-full text-muted-foreground"
onClick={() => router.push(`/pets/${createdPetId}`)}
>
{t("skipForNow")}
</Button>
</div>
</div>
);
} else {
content = (
<div className="w-full max-w-[512px] p-8">
<h1 className="text-xl font-semibold mb-2">{t("addFirstPet")}</h1>
<p className="text-sm text-muted-foreground mb-8">{t("addFirstPetDesc")}</p>
@@ -92,6 +79,15 @@ export default function OnboardingPetPage() {
}}
/>
</div>
);
}
return (
<main className="relative min-h-screen flex items-center justify-center bg-background px-4">
<div className="absolute top-4 right-4">
<LanguageSwitcher />
</div>
{content}
</main>
);
}