[+] i18n SSR
This commit is contained in:
Vendored
+9
-7
@@ -1,13 +1,15 @@
|
|||||||
// See https://svelte.dev/docs/kit/types#app.d.ts
|
// See https://svelte.dev/docs/kit/types#app.d.ts
|
||||||
// for information about these interfaces
|
// for information about these interfaces
|
||||||
declare global {
|
declare global {
|
||||||
namespace App {
|
namespace App {
|
||||||
// interface Error {}
|
// interface Error {}
|
||||||
// interface Locals {}
|
interface Locals {
|
||||||
// interface PageData {}
|
lang: 'en' | 'zh'
|
||||||
// interface PageState {}
|
}
|
||||||
// interface Platform {}
|
// interface PageData {}
|
||||||
}
|
// interface PageState {}
|
||||||
|
// interface Platform {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {};
|
export {};
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="zh-CN">
|
<html lang="%lang%">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
|||||||
@@ -1,3 +1,25 @@
|
|||||||
import { checkAudioSeparator } from '$lib/server/separator';
|
import { checkAudioSeparator } from '$lib/server/separator';
|
||||||
|
import type { Handle } from '@sveltejs/kit';
|
||||||
|
|
||||||
checkAudioSeparator().catch(e => console.error('Audio separator check failed:', e));
|
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
@@ -1,7 +1,11 @@
|
|||||||
|
import { getContext, setContext } from 'svelte'
|
||||||
import EN from "./en"
|
import EN from "./en"
|
||||||
import ZH from "./zh"
|
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'
|
type Lang = 'en' | 'zh'
|
||||||
|
|
||||||
@@ -10,44 +14,20 @@ const msgs: Record<Lang, typeof ZH> = {
|
|||||||
zh: ZH
|
zh: ZH
|
||||||
}
|
}
|
||||||
|
|
||||||
let lang: Lang = 'en'
|
const I18N_KEY = Symbol('i18n')
|
||||||
|
|
||||||
// Infer language from browser
|
export const initI18n = (lang: Lang) => {
|
||||||
if (browser && navigator.language.startsWith('zh')) {
|
setContext(I18N_KEY, msgs[lang])
|
||||||
lang = 'zh'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getI18n = () => msgs[lang]
|
export const getI18n = () => {
|
||||||
|
return getContext<typeof ZH>(I18N_KEY) || msgs['en']
|
||||||
// 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 setLanguage = (lang: Lang) => {
|
||||||
|
document.cookie = `lang=${lang}; path=/; max-age=31536000`
|
||||||
|
location.reload()
|
||||||
|
}
|
||||||
|
|
||||||
export {}
|
export {}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { createUser, login } from '$lib/server/user'
|
import { createUser, login } from '$lib/server/user'
|
||||||
import type { LayoutServerLoad } from './$types'
|
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')
|
let session = cookies.get('session')
|
||||||
|
|
||||||
const registUA = request.headers.get('user-agent') || ''
|
const registUA = request.headers.get('user-agent') || ''
|
||||||
@@ -20,7 +20,7 @@ export const load: LayoutServerLoad = async ({ cookies, request }) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const user = structuredClone(await login(session))
|
const user = structuredClone(await login(session))
|
||||||
return { user }
|
return { user, lang: locals.lang }
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Invalid session, create new
|
// Invalid session, create new
|
||||||
session = await createUser(registUA)
|
session = await createUser(registUA)
|
||||||
@@ -32,6 +32,6 @@ export const load: LayoutServerLoad = async ({ cookies, request }) => {
|
|||||||
maxAge: 60 * 60 * 24 * 365
|
maxAge: 60 * 60 * 24 * 365
|
||||||
})
|
})
|
||||||
const user = structuredClone(await login(session))
|
const user = structuredClone(await login(session))
|
||||||
return { user }
|
return { user, lang: locals.lang }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,10 +6,12 @@
|
|||||||
import "../style/material.scss"
|
import "../style/material.scss"
|
||||||
import '@unocss/reset/normalize.css'
|
import '@unocss/reset/normalize.css'
|
||||||
import '@unocss/reset/tailwind-v4.css'
|
import '@unocss/reset/tailwind-v4.css'
|
||||||
|
import { initI18n } from "$lib/i18n"
|
||||||
import type { LayoutProps } from "./$types"
|
import type { LayoutProps } from "./$types"
|
||||||
import { onNavigate } from '$app/navigation'
|
import { onNavigate } from '$app/navigation'
|
||||||
|
|
||||||
let { data, children }: LayoutProps = $props()
|
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
|
// This function is called when the user navigates to a new page, it will start the view transition
|
||||||
onNavigate((navigation) => {
|
onNavigate((navigation) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user