[+] i18n SSR

This commit is contained in:
2025-11-23 00:34:03 +08:00
parent dd73599bb3
commit 82f2858cde
6 changed files with 52 additions and 46 deletions
+9 -7
View File
@@ -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 {};
+1 -1
View File
@@ -1,5 +1,5 @@
<!doctype html>
<html lang="zh-CN">
<html lang="%lang%">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
+22
View File
@@ -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)
});
};
+15 -35
View File
@@ -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<Lang, typeof ZH> = {
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<typeof ZH>(I18N_KEY) || msgs['en']
}
export const setLanguage = (lang: Lang) => {
document.cookie = `lang=${lang}; path=/; max-age=31536000`
location.reload()
}
export {}
+3 -3
View File
@@ -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 }
}
}
+2
View File
@@ -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) => {