fix: fix ssr teleports and i18n
This commit is contained in:
parent
c187239881
commit
b574f9d263
|
@ -17,6 +17,7 @@
|
|||
</head>
|
||||
<body>
|
||||
<div id="app"><!--ssr-outlet--></div>
|
||||
<div id="teleported"><!--teleports--></div>
|
||||
<script type="module" src="/src/entry-client.ts"></script>
|
||||
|
||||
<script>
|
||||
|
|
|
@ -13,6 +13,26 @@ const APP_PORT = 1007
|
|||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
// Inject teleports in template
|
||||
const injectTeleports = (html: string, teleports: {
|
||||
'#teleported': string
|
||||
}) => {
|
||||
// if (teleports) {
|
||||
// for (const [target, content] of Object.entries(teleports)) {
|
||||
// if (['head', 'body', 'html'].includes(target)) {
|
||||
// const replacement = `</${target}>`
|
||||
// html = html.replace(replacement, content + replacement)
|
||||
// } else {
|
||||
// const replacement = ` id="${target.replace('#', '')}">`
|
||||
// html = html.replace(replacement, replacement + content)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return html
|
||||
return html.replace('<!--teleports-->', teleports['#teleported'])
|
||||
}
|
||||
|
||||
;(async () => {
|
||||
const app = new Koa()
|
||||
|
||||
|
@ -21,9 +41,18 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|||
appType: 'custom',
|
||||
})
|
||||
|
||||
// 解析accept-language
|
||||
function parseAcceptLanguage(acceptLanguage: string) {
|
||||
const languages = acceptLanguage.split(',')
|
||||
const language = languages[0]
|
||||
const country = language.split('-')[1]
|
||||
return { language, country }
|
||||
}
|
||||
|
||||
app.use(koaConnect(vite.middlewares))
|
||||
|
||||
app.use(async (ctx) => {
|
||||
const { language, country } = parseAcceptLanguage(ctx.request.headers['accept-language'] as string)
|
||||
try {
|
||||
let template = fs.readFileSync(
|
||||
path.resolve(__dirname, 'index.html'),
|
||||
|
@ -34,9 +63,13 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|||
|
||||
const { render } = await vite.ssrLoadModule('/src/entry-server.ts')
|
||||
|
||||
const [renderedHtml, renderedPinia, renderedLinks] = await render(ctx, {})
|
||||
const [renderedHtml, renderedPinia, renderedLinks, renderedTeleports] =
|
||||
await render(ctx, {}, {
|
||||
language,
|
||||
country,
|
||||
})
|
||||
|
||||
const html = template
|
||||
const html = injectTeleports(template, renderedTeleports)
|
||||
.replace('<!--preload-links-->', renderedLinks)
|
||||
.replace('<!--ssr-outlet-->', renderedHtml)
|
||||
.replace('__pinia', renderedPinia)
|
||||
|
|
|
@ -18,21 +18,23 @@ const handleConfirm = () => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport to="body" :disabled="showAlert">
|
||||
<Teleport to="#teleported" :disabled="showAlert">
|
||||
<Transition name="alert">
|
||||
<div v-if="showAlert" class="mask">
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h3>{{ $t(`${alertMsg}`) }}</h3>
|
||||
</div>
|
||||
<div>
|
||||
<div v-if="showAlert" class="mask">
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h3>{{ $t(`${alertMsg}`) }}</h3>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<button v-if="isShowCancel" class="button" @click="handleClose">
|
||||
{{ $tm('ComponentAlert.cancel') }}
|
||||
</button>
|
||||
<button class="button" @click="handleConfirm">
|
||||
{{ $tm('ComponentAlert.confirm') }}
|
||||
</button>
|
||||
<div class="footer">
|
||||
<button v-if="isShowCancel" class="button" @click="handleClose">
|
||||
{{ $tm('ComponentAlert.cancel') }}
|
||||
</button>
|
||||
<button class="button" @click="handleConfirm">
|
||||
{{ $tm('ComponentAlert.confirm') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -15,35 +15,37 @@ const handleClose = () => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport to="body" :disabled="showInfo">
|
||||
<Teleport to="#teleported" :disabled="showInfo">
|
||||
<Transition
|
||||
enter-active-class="animate__animated animate__fadeInUp animate__faster"
|
||||
leave-active-class="animate__animated animate__fadeOutDown animate__faster"
|
||||
>
|
||||
<div class="container" v-if="showInfo">
|
||||
<Transition
|
||||
enter-active-class="animate__animated animate__swing"
|
||||
appear
|
||||
>
|
||||
<div class="lass">
|
||||
<span>{{ name }}</span>
|
||||
<div>
|
||||
<div class="container" v-if="showInfo">
|
||||
<Transition
|
||||
enter-active-class="animate__animated animate__swing"
|
||||
appear
|
||||
>
|
||||
<div class="lass">
|
||||
<span>{{ name }}</span>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<div class="avatar">
|
||||
<img :src="loli" />
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<div class="avatar">
|
||||
<img :src="loli" />
|
||||
</div>
|
||||
<Transition
|
||||
enter-active-class="animate__animated animate__bounceInRight animate__faster"
|
||||
appear
|
||||
>
|
||||
<!-- A ha ha ha! You probably didn't expect that this was inspired by しゅがてん!-Sugarfull tempering- -->
|
||||
<div class="info">{{ `「 ${$t(`${infoMsg}`)} 」` }}</div>
|
||||
</Transition>
|
||||
|
||||
<Transition
|
||||
enter-active-class="animate__animated animate__bounceInRight animate__faster"
|
||||
appear
|
||||
>
|
||||
<!-- A ha ha ha! You probably didn't expect that this was inspired by しゅがてん!-Sugarfull tempering- -->
|
||||
<div class="info">{{ `「 ${$t(`${infoMsg}`)} 」` }}</div>
|
||||
</Transition>
|
||||
|
||||
<div class="close" @click="handleClose">
|
||||
<Icon icon="line-md:close" />
|
||||
<div class="close" @click="handleClose">
|
||||
<Icon icon="line-md:close" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
|
|
|
@ -119,65 +119,67 @@ const handleCloseCapture = () => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport to="body" :disabled="isShowCapture">
|
||||
<Teleport to="#teleported" :disabled="isShowCapture">
|
||||
<Transition name="capture">
|
||||
<!-- Mask -->
|
||||
<div
|
||||
class="mask"
|
||||
@keydown="checkKeyPress($event)"
|
||||
tabindex="0"
|
||||
v-if="isShowCapture"
|
||||
>
|
||||
<div class="validate">
|
||||
<!-- Title -->
|
||||
<div class="title">
|
||||
<!-- <span>{{ `❮` }}</span> -->
|
||||
<h2>{{ $tm('AlertInfo.capture.title') }}</h2>
|
||||
<!-- <span>{{ `❯` }}</span> -->
|
||||
</div>
|
||||
<p class="question">{{ currentQuestion.text }}</p>
|
||||
|
||||
<!-- Options -->
|
||||
<div class="select">
|
||||
<label
|
||||
v-for="(option, index) in currentQuestion.options"
|
||||
:key="index"
|
||||
>
|
||||
<input type="radio" v-model="userAnswers" :value="option" />
|
||||
{{ option }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Submit buttons -->
|
||||
<div class="btn">
|
||||
<button @click="submitAnswer">
|
||||
{{ $tm('AlertInfo.capture.submit') }}
|
||||
</button>
|
||||
<button @click="handleCloseCapture">
|
||||
{{ $tm('AlertInfo.capture.close') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Hints -->
|
||||
<!-- tabindex allows this element to be focused on the page -->
|
||||
<div class="hint-container">
|
||||
<div v-if="isShowHint" class="hint">
|
||||
<div>{{ $tm('AlertInfo.capture.hint1') }}</div>
|
||||
<div>
|
||||
{{ $tm('AlertInfo.capture.hint2') }}
|
||||
<span>kun</span>
|
||||
{{ $tm('AlertInfo.capture.hint3') }}
|
||||
</div>
|
||||
<div>
|
||||
<!-- Mask -->
|
||||
<div
|
||||
class="mask"
|
||||
@keydown="checkKeyPress($event)"
|
||||
tabindex="0"
|
||||
v-if="isShowCapture"
|
||||
>
|
||||
<div class="validate">
|
||||
<!-- Title -->
|
||||
<div class="title">
|
||||
<!-- <span>{{ `❮` }}</span> -->
|
||||
<h2>{{ $tm('AlertInfo.capture.title') }}</h2>
|
||||
<!-- <span>{{ `❯` }}</span> -->
|
||||
</div>
|
||||
<div v-if="isShowAnswer" class="answer">
|
||||
<div>{{ $tm('AlertInfo.capture.hint4') }}</div>
|
||||
<a
|
||||
href="https://github.com/KUN1007/kun-galgame-vue/tree/remove-server/src/components/capture"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
<p class="question">{{ currentQuestion.text }}</p>
|
||||
|
||||
<!-- Options -->
|
||||
<div class="select">
|
||||
<label
|
||||
v-for="(option, index) in currentQuestion.options"
|
||||
:key="index"
|
||||
>
|
||||
{{ $tm('AlertInfo.capture.answer') }}
|
||||
</a>
|
||||
<input type="radio" v-model="userAnswers" :value="option" />
|
||||
{{ option }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Submit buttons -->
|
||||
<div class="btn">
|
||||
<button @click="submitAnswer">
|
||||
{{ $tm('AlertInfo.capture.submit') }}
|
||||
</button>
|
||||
<button @click="handleCloseCapture">
|
||||
{{ $tm('AlertInfo.capture.close') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Hints -->
|
||||
<!-- tabindex allows this element to be focused on the page -->
|
||||
<div class="hint-container">
|
||||
<div v-if="isShowHint" class="hint">
|
||||
<div>{{ $tm('AlertInfo.capture.hint1') }}</div>
|
||||
<div>
|
||||
{{ $tm('AlertInfo.capture.hint2') }}
|
||||
<span>kun</span>
|
||||
{{ $tm('AlertInfo.capture.hint3') }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isShowAnswer" class="answer">
|
||||
<div>{{ $tm('AlertInfo.capture.hint4') }}</div>
|
||||
<a
|
||||
href="https://github.com/KUN1007/kun-galgame-vue/tree/remove-server/src/components/capture"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{{ $tm('AlertInfo.capture.answer') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { createApp } from './main'
|
||||
import { createKUNGalgameRouter } from './router'
|
||||
import { setupPinia } from './store'
|
||||
import i18n from '@/language/i18n'
|
||||
import createI18n from '@/language/i18n'
|
||||
import '@/styles/index.scss'
|
||||
|
||||
const router = createKUNGalgameRouter()
|
||||
|
@ -9,6 +9,8 @@ const pinia = setupPinia()
|
|||
|
||||
const { app } = createApp()
|
||||
|
||||
export const i18n = createI18n()
|
||||
|
||||
app.use(router).use(pinia).use(i18n)
|
||||
|
||||
if (window.__pinia) {
|
||||
|
|
|
@ -2,7 +2,7 @@ import { createApp } from './main'
|
|||
|
||||
import { createKUNGalgameRouter } from './router'
|
||||
import { setupPinia } from './store'
|
||||
import i18n from '@/language/i18n'
|
||||
import createI18n from '@/language/i18n'
|
||||
|
||||
import { renderToString } from '@vue/server-renderer'
|
||||
|
||||
|
@ -46,9 +46,11 @@ const renderPreloadLinks = (
|
|||
|
||||
export const render = async (
|
||||
ctx: ParameterizedContext,
|
||||
manifest: Record<string, string[]>
|
||||
): Promise<[string, string, string]> => {
|
||||
manifest: Record<string, string[]>,
|
||||
options: { language: string; country: string }
|
||||
) => {
|
||||
const { app } = createApp()
|
||||
const { language, country } = options
|
||||
|
||||
// router
|
||||
const router = createKUNGalgameRouter()
|
||||
|
@ -63,7 +65,9 @@ export const render = async (
|
|||
const renderedPinia = JSON.stringify(pinia.state.value)
|
||||
|
||||
// i18n
|
||||
app.use(i18n)
|
||||
app.use(createI18n(
|
||||
language.includes('zh') ? 'zh' : 'en',
|
||||
))
|
||||
|
||||
const renderCtx: { modules?: string[] } = {}
|
||||
|
||||
|
@ -71,5 +75,9 @@ export const render = async (
|
|||
|
||||
const renderedLinks = renderPreloadLinks(renderCtx.modules, manifest)
|
||||
|
||||
return [renderedHtml, renderedPinia, renderedLinks]
|
||||
const renderedTeleports = renderCtx.teleports as {
|
||||
'#teleported': string
|
||||
}
|
||||
|
||||
return [renderedHtml, renderedPinia, renderedLinks, renderedTeleports]
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { createI18n } from 'vue-i18n'
|
||||
import { createI18n as _createI18n } from 'vue-i18n'
|
||||
// 读取本地存储中的语言配置
|
||||
import { KUNGalgameLanguage } from '@/utils/getDefaultEnv'
|
||||
|
||||
|
@ -6,8 +6,8 @@ import { KUNGalgameLanguage } from '@/utils/getDefaultEnv'
|
|||
import zh from './zh'
|
||||
import en from './en'
|
||||
|
||||
const i18n = createI18n({
|
||||
locale: KUNGalgameLanguage,
|
||||
const createI18n = (language?: string) => _createI18n({
|
||||
locale: language || KUNGalgameLanguage,
|
||||
legacy: false,
|
||||
messages: {
|
||||
zh,
|
||||
|
@ -15,4 +15,4 @@ const i18n = createI18n({
|
|||
},
|
||||
})
|
||||
|
||||
export default i18n
|
||||
export default createI18n
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Router } from 'vue-router'
|
||||
import { createPermission } from './permission'
|
||||
import i18n from '@/language/i18n'
|
||||
import { i18n } from '@/entry-client'
|
||||
|
||||
const createPageTitle = (router: Router) => {
|
||||
router.beforeEach((to) => {
|
||||
|
|
|
@ -5,26 +5,28 @@ import vue from '@vitejs/plugin-vue'
|
|||
import { visualizer } from 'rollup-plugin-visualizer'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue(), visualizer() as PluginOption],
|
||||
/* Set the 'src' alias to '@' */
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
export default defineConfig(({ mode, ssrBuild, command }) => {
|
||||
return {
|
||||
plugins: [vue(), visualizer() as PluginOption],
|
||||
/* Set the 'src' alias to '@' */
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
},
|
||||
},
|
||||
},
|
||||
esbuild: {
|
||||
drop: ['console', 'debugger'],
|
||||
},
|
||||
build: {
|
||||
// Dist dir name
|
||||
assetsDir: 'kun',
|
||||
},
|
||||
server: { host: '127.0.0.1', port: 1007 },
|
||||
// Suppress i18n warnings
|
||||
define: {
|
||||
__VUE_I18N_FULL_INSTALL__: true,
|
||||
__VUE_I18N_LEGACY_API__: false,
|
||||
__INTLIFY_PROD_DEVTOOLS__: false,
|
||||
},
|
||||
esbuild: command === 'serve' ? {} : {
|
||||
drop: ['console', 'debugger']
|
||||
},
|
||||
build: {
|
||||
// Dist dir name
|
||||
assetsDir: 'kun',
|
||||
},
|
||||
server: { host: '127.0.0.1', port: 1007 },
|
||||
// Suppress i18n warnings
|
||||
define: {
|
||||
__VUE_I18N_FULL_INSTALL__: true,
|
||||
__VUE_I18N_LEGACY_API__: false,
|
||||
__INTLIFY_PROD_DEVTOOLS__: false,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue