diff --git a/src/app.d.ts b/src/app.d.ts index da08e6d..6ef9db7 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -1,13 +1,15 @@ // See https://svelte.dev/docs/kit/types#app.d.ts // for information about these interfaces declare global { - namespace App { - // interface Error {} - // interface Locals {} - // interface PageData {} - // interface PageState {} - // interface Platform {} - } + namespace App { + // interface Error {} + interface Locals { + lang: 'en' | 'zh' + } + // interface PageData {} + // interface PageState {} + // interface Platform {} + } } export {}; diff --git a/src/app.html b/src/app.html index 0ea778b..ef0f393 100644 --- a/src/app.html +++ b/src/app.html @@ -1,5 +1,5 @@ - + diff --git a/src/hooks.server.ts b/src/hooks.server.ts index d795584..3cadebf 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -1,3 +1,25 @@ import { checkAudioSeparator } from '$lib/server/separator'; +import type { Handle } from '@sveltejs/kit'; checkAudioSeparator().catch(e => console.error('Audio separator check failed:', e)); + +export const handle: Handle = async ({ event, resolve }) => { + const langCookie = event.cookies.get('lang'); + if (langCookie === 'zh' || langCookie === 'en') { + event.locals.lang = langCookie; + } else { + const acceptLanguage = event.request.headers.get('accept-language'); + // Simple check: if zh is present, prefer it (unless en is weighted higher, but let's keep it simple for now) + // A better parser would parse q-values. + // For now, let's assume if 'zh' is in the header, the user likely understands Chinese. + if (acceptLanguage && acceptLanguage.includes('zh')) { + event.locals.lang = 'zh'; + } else { + event.locals.lang = 'en'; + } + } + + return resolve(event, { + transformPageChunk: ({ html }) => html.replace('%lang%', event.locals.lang) + }); +}; diff --git a/src/lib/i18n/index.ts b/src/lib/i18n/index.ts index fb68ff8..1d92106 100644 --- a/src/lib/i18n/index.ts +++ b/src/lib/i18n/index.ts @@ -1,7 +1,11 @@ +import { getContext, setContext } from 'svelte' import EN from "./en" import ZH from "./zh" -import { browser } from '$app/environment' + +// i18n related files: +// src\hooks.server.ts +// src\routes\+layout.server.ts type Lang = 'en' | 'zh' @@ -10,44 +14,20 @@ const msgs: Record = { zh: ZH } -let lang: Lang = 'en' +const I18N_KEY = Symbol('i18n') -// Infer language from browser -if (browser && navigator.language.startsWith('zh')) { - lang = 'zh' +export const initI18n = (lang: Lang) => { + setContext(I18N_KEY, msgs[lang]) } -export const getI18n = () => msgs[lang] - -// export function ts(key: string, variables?: { [index: string]: any }) { -// return t(key as keyof LocalizedMessages, variables) -// } - -// /** -// * Load the translation for the given key -// * -// * @param key -// * @param variables -// */ -// export function t(key: keyof LocalizedMessages, variables?: { [index: string]: any }) { -// // Check if the key exists -// let msg = getI18n()[key] -// if (!msg) { -// // Check if the key exists in English -// if (!(msg = getI18n()[key])) { -// msg = key -// console.error(`ERROR!! Missing translation reference entry (English) for ${key}`) -// } -// else console.warn(`Missing translation for ${key} in ${lang}`) -// } -// // Replace variables -// if (variables) { -// return msg.replace(/\${(.*?)}/g, (_: string, v: string | number) => variables[v] + "") -// } -// return msg -// } -// Object.assign(window, { t }) +export const getI18n = () => { + return getContext(I18N_KEY) || msgs['en'] +} +export const setLanguage = (lang: Lang) => { + document.cookie = `lang=${lang}; path=/; max-age=31536000` + location.reload() +} export {} diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts index 38dc587..331d9bf 100644 --- a/src/routes/+layout.server.ts +++ b/src/routes/+layout.server.ts @@ -1,7 +1,7 @@ import { createUser, login } from '$lib/server/user' import type { LayoutServerLoad } from './$types' -export const load: LayoutServerLoad = async ({ cookies, request }) => { +export const load: LayoutServerLoad = async ({ cookies, request, locals }) => { let session = cookies.get('session') const registUA = request.headers.get('user-agent') || '' @@ -20,7 +20,7 @@ export const load: LayoutServerLoad = async ({ cookies, request }) => { try { const user = structuredClone(await login(session)) - return { user } + return { user, lang: locals.lang } } catch (e) { // Invalid session, create new session = await createUser(registUA) @@ -32,6 +32,6 @@ export const load: LayoutServerLoad = async ({ cookies, request }) => { maxAge: 60 * 60 * 24 * 365 }) const user = structuredClone(await login(session)) - return { user } + return { user, lang: locals.lang } } } \ No newline at end of file diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 76f5360..7863452 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -6,10 +6,12 @@ import "../style/material.scss" import '@unocss/reset/normalize.css' import '@unocss/reset/tailwind-v4.css' + import { initI18n } from "$lib/i18n" import type { LayoutProps } from "./$types" import { onNavigate } from '$app/navigation' let { data, children }: LayoutProps = $props() + initI18n(data.lang || 'en') // This function is called when the user navigates to a new page, it will start the view transition onNavigate((navigation) => {