diff --git a/src/app.d.ts b/src/app.d.ts index 11a7753..d7e42ae 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -1,5 +1,6 @@ // See https://svelte.dev/docs/kit/types#app.d.ts +import type { Locale } from "$i18n/runtime"; import type { UserRole } from "$lib/server/auth/authorization-service"; import type { JWTPayload } from "jose"; @@ -16,6 +17,7 @@ declare global { role: UserRole; tenantId?: string | null; }; + locale?: Locale; } // interface PageData {} // interface PageState {} diff --git a/src/routes/(pages)/+layout.server.ts b/src/routes/(pages)/+layout.server.ts new file mode 100644 index 0000000..0f9c939 --- /dev/null +++ b/src/routes/(pages)/+layout.server.ts @@ -0,0 +1,7 @@ +import type { LayoutServerLoad } from "./$types"; + +export const load: LayoutServerLoad = async ({ locals }) => { + return { + locale: locals.locale, + }; +}; diff --git a/src/routes/(pages)/+layout.svelte b/src/routes/(pages)/+layout.svelte index 29709d6..33391c3 100644 --- a/src/routes/(pages)/+layout.svelte +++ b/src/routes/(pages)/+layout.svelte @@ -3,10 +3,17 @@ import { Toaster } from "$lib/components/ui/sonner/index.js"; import { ModeWatcher, setMode } from "mode-watcher"; import "../../app.css"; + import { setLocale } from "$i18n/runtime"; + + let { data, children } = $props(); $effect(() => { if (!browser) return; + if (data.locale) { + setLocale(data.locale); + } + const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); const handler = () => { const newMode = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; @@ -16,8 +23,6 @@ mediaQuery.addEventListener("change", handler); return () => mediaQuery.removeEventListener("change", handler); }); - - let { children } = $props(); diff --git a/src/routes/(pages)/page.svelte.test.ts b/src/routes/(pages)/page.svelte.test.ts index f47f961..ae95885 100644 --- a/src/routes/(pages)/page.svelte.test.ts +++ b/src/routes/(pages)/page.svelte.test.ts @@ -2,6 +2,7 @@ import { describe, test, expect, vi, beforeEach } from "vitest"; import "@testing-library/jest-dom/vitest"; import { render, screen } from "@testing-library/svelte"; import Page from "./+page.svelte"; +import type { Locale } from "$i18n/runtime"; describe("/+page.svelte", () => { beforeEach(() => { @@ -15,6 +16,7 @@ describe("/+page.svelte", () => { test("should render h1", async () => { const mockData = { + locale: "en" as Locale, streamed: { isEnvOk: Promise.resolve(true), }, diff --git a/src/server-hooks/i18nHandle.ts b/src/server-hooks/i18nHandle.ts index 8368a14..472a137 100644 --- a/src/server-hooks/i18nHandle.ts +++ b/src/server-hooks/i18nHandle.ts @@ -1,5 +1,11 @@ -import type { Handle } from "@sveltejs/kit"; +import { + extractLocaleFromCookie, + extractLocaleFromHeader, + setLocale, + type Locale, +} from "$i18n/runtime"; import { paraglideMiddleware } from "$i18n/server"; +import type { Handle } from "@sveltejs/kit"; /** * SvelteKit server hook that handles i18n localization @@ -10,6 +16,16 @@ import { paraglideMiddleware } from "$i18n/server"; * @returns {Promise} The response with applied headers and rate limiting */ export const i18nHandle: Handle = ({ event, resolve }) => { + // Determine browser locale and use it + const cookieLocale = extractLocaleFromCookie(); + let locale: Locale | undefined = cookieLocale as Locale; + + if (!locale) { + locale = extractLocaleFromHeader(event.request); + setLocale(locale); + event.locals.locale = locale; + } + return paraglideMiddleware(event.request, ({ request: localizedRequest, locale }) => { event.request = localizedRequest; return resolve(event, {