From c4be2eb46e46c59c70e0a29a5d6b4901f2cb3333 Mon Sep 17 00:00:00 2001 From: Marcel Date: Thu, 19 Mar 2026 18:55:45 +0100 Subject: [PATCH] feat(i18n): detect browser language as default locale On first visit (no PARAGLIDE_LOCALE cookie), parse the Accept-Language request header and set the cookie to the best matching supported locale (de/en/es). The user's manual choice via the switcher always takes precedence since the detection is skipped when the cookie exists. Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/hooks.server.ts | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/frontend/src/hooks.server.ts b/frontend/src/hooks.server.ts index 6271507e..fc5d3a71 100644 --- a/frontend/src/hooks.server.ts +++ b/frontend/src/hooks.server.ts @@ -2,9 +2,34 @@ import { redirect, type Handle, type HandleFetch } from '@sveltejs/kit'; import { paraglideMiddleware } from '$lib/paraglide/server'; import { sequence } from '@sveltejs/kit/hooks'; import { env } from 'process'; +import { cookieName, cookieMaxAge, locales } from '$lib/paraglide/runtime'; const PUBLIC_PATHS = ['/login', '/logout']; +function detectLocale(acceptLanguage: string): string | null { + const preferred = acceptLanguage + .split(',') + .map((part) => { + const [lang, q] = part.trim().split(';q='); + return { lang: lang.trim().split('-')[0].toLowerCase(), q: q ? parseFloat(q) : 1 }; + }) + .sort((a, b) => b.q - a.q); + for (const { lang } of preferred) { + if ((locales as readonly string[]).includes(lang)) return lang; + } + return null; +} + +const handleLocaleDetection: Handle = ({ event, resolve }) => { + if (!event.cookies.get(cookieName)) { + const locale = detectLocale(event.request.headers.get('accept-language') ?? ''); + if (locale) { + event.cookies.set(cookieName, locale, { path: '/', sameSite: 'lax', maxAge: cookieMaxAge }); + } + } + return resolve(event); +}; + const handleAuth: Handle = async ({ event, resolve }) => { const isPublic = PUBLIC_PATHS.some((p) => event.url.pathname.startsWith(p)); if (!isPublic && !event.locals.user) { @@ -74,4 +99,4 @@ export const handleFetch: HandleFetch = async ({ event, request, fetch }) => { return fetch(request); }; -export const handle = sequence(userGroup, handleAuth, handleParaglide); +export const handle = sequence(userGroup, handleAuth, handleLocaleDetection, handleParaglide);