From 82f2858cdebd255953c4be23fc79328a54864c99 Mon Sep 17 00:00:00 2001
From: Azalea <22280294+hykilpikonna@users.noreply.github.com>
Date: Sun, 23 Nov 2025 00:34:03 +0800
Subject: [PATCH] [+] i18n SSR
---
src/app.d.ts | 16 +++++++-----
src/app.html | 2 +-
src/hooks.server.ts | 22 ++++++++++++++++
src/lib/i18n/index.ts | 50 +++++++++++-------------------------
src/routes/+layout.server.ts | 6 ++---
src/routes/+layout.svelte | 2 ++
6 files changed, 52 insertions(+), 46 deletions(-)
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) => {