develop-updateui #1
@@ -1,6 +1,6 @@
|
|||||||
import type { i18n as I18nInstance } from 'i18next';
|
import type { i18n as I18nInstance } from 'i18next';
|
||||||
|
|
||||||
import { getActiveI18nInstance } from '@/lib/translation';
|
import { getClientI18nInstance } from '@/lib/translation/client';
|
||||||
|
|
||||||
import { defaultLocale, supportedLocales, type SupportedLocale } from './constants';
|
import { defaultLocale, supportedLocales, type SupportedLocale } from './constants';
|
||||||
|
|
||||||
@@ -15,5 +15,5 @@ export const normalizeLocale = (locale?: string): SupportedLocale => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getActiveI18n = (): I18nInstance | undefined => {
|
export const getActiveI18n = (): I18nInstance | undefined => {
|
||||||
return getActiveI18nInstance();
|
return import.meta.env.SSR ? undefined : getClientI18nInstance();
|
||||||
};
|
};
|
||||||
|
|||||||
15
src/lib/translation/client.ts
Normal file
15
src/lib/translation/client.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import type { i18n as I18nInstance } from 'i18next';
|
||||||
|
|
||||||
|
import { createI18nInstance, initI18nInstance } from '@/lib/translation';
|
||||||
|
|
||||||
|
let clientI18n: I18nInstance | undefined;
|
||||||
|
|
||||||
|
export const createI18nForClient = async (language?: string) => {
|
||||||
|
if (!clientI18n) {
|
||||||
|
clientI18n = createI18nInstance(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return initI18nInstance(clientI18n, language, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getClientI18nInstance = () => clientI18n;
|
||||||
@@ -1,14 +1,11 @@
|
|||||||
import i18next, { type i18n as I18nInstance } from 'i18next';
|
import i18next, { type i18n as I18nInstance } from 'i18next';
|
||||||
import LanguageDetector from 'i18next-browser-languagedetector';
|
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||||
import I18NextHttpBackend from 'i18next-http-backend';
|
import I18NextHttpBackend from 'i18next-http-backend';
|
||||||
import { tryGetContext } from 'hono/context-storage';
|
|
||||||
|
|
||||||
import { defaultLocale, localeCookieKey, supportedLocales, type SupportedLocale } from '@/i18n/constants';
|
import { defaultLocale, localeCookieKey, supportedLocales, type SupportedLocale } from '@/i18n/constants';
|
||||||
|
|
||||||
const runtimeNamespace = 'translation';
|
const runtimeNamespace = 'translation';
|
||||||
|
|
||||||
let clientI18n: I18nInstance | undefined;
|
|
||||||
|
|
||||||
const normalizeLanguage = (language?: string): SupportedLocale => {
|
const normalizeLanguage = (language?: string): SupportedLocale => {
|
||||||
if (!language) return defaultLocale;
|
if (!language) return defaultLocale;
|
||||||
const normalized = language.toLowerCase().split('-')[0] as SupportedLocale;
|
const normalized = language.toLowerCase().split('-')[0] as SupportedLocale;
|
||||||
@@ -23,18 +20,22 @@ const getLoadPath = () => {
|
|||||||
return '/locales/{{lng}}/{{lng}}.json';
|
return '/locales/{{lng}}/{{lng}}.json';
|
||||||
};
|
};
|
||||||
|
|
||||||
const createInstance = () => {
|
export const createI18nInstance = (forServer: boolean) => {
|
||||||
const instance = i18next.createInstance();
|
const instance = i18next.createInstance();
|
||||||
|
|
||||||
instance.use(I18NextHttpBackend);
|
instance.use(I18NextHttpBackend);
|
||||||
if (!import.meta.env.SSR) {
|
if (!forServer) {
|
||||||
instance.use(LanguageDetector);
|
instance.use(LanguageDetector);
|
||||||
}
|
}
|
||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
};
|
};
|
||||||
|
|
||||||
const initInstance = async (instance: I18nInstance, language?: string) => {
|
export const initI18nInstance = async (
|
||||||
|
instance: I18nInstance,
|
||||||
|
language?: string,
|
||||||
|
forServer: boolean = import.meta.env.SSR,
|
||||||
|
) => {
|
||||||
const lng = normalizeLanguage(language);
|
const lng = normalizeLanguage(language);
|
||||||
|
|
||||||
if (!instance.isInitialized) {
|
if (!instance.isInitialized) {
|
||||||
@@ -52,7 +53,7 @@ const initInstance = async (instance: I18nInstance, language?: string) => {
|
|||||||
backend: {
|
backend: {
|
||||||
loadPath: getLoadPath(),
|
loadPath: getLoadPath(),
|
||||||
},
|
},
|
||||||
...(import.meta.env.SSR
|
...(forServer
|
||||||
? {}
|
? {}
|
||||||
: {
|
: {
|
||||||
detection: {
|
detection: {
|
||||||
@@ -72,27 +73,3 @@ const initInstance = async (instance: I18nInstance, language?: string) => {
|
|||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createI18nForRuntime = async (language?: string) => {
|
|
||||||
if (import.meta.env.SSR) {
|
|
||||||
const serverI18n = await initInstance(createInstance(), language);
|
|
||||||
const context = tryGetContext<any>();
|
|
||||||
context?.set?.('i18n', serverI18n);
|
|
||||||
return serverI18n;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!clientI18n) {
|
|
||||||
clientI18n = createInstance();
|
|
||||||
}
|
|
||||||
|
|
||||||
return initInstance(clientI18n, language);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getActiveI18nInstance = () => {
|
|
||||||
if (!import.meta.env.SSR) {
|
|
||||||
return clientI18n;
|
|
||||||
}
|
|
||||||
|
|
||||||
const context = tryGetContext<any>();
|
|
||||||
return context?.get?.('i18n') as I18nInstance | undefined;
|
|
||||||
};
|
|
||||||
|
|||||||
16
src/main.ts
16
src/main.ts
@@ -5,8 +5,11 @@ import { createPinia } from 'pinia';
|
|||||||
import { createSSRApp } from 'vue';
|
import { createSSRApp } from 'vue';
|
||||||
import { RouterView } from 'vue-router';
|
import { RouterView } from 'vue-router';
|
||||||
|
|
||||||
|
import type { i18n as I18nInstance } from 'i18next';
|
||||||
import I18NextVue from 'i18next-vue';
|
import I18NextVue from 'i18next-vue';
|
||||||
import { createI18nForRuntime } from '@/lib/translation';
|
|
||||||
|
import { createI18nInstance, initI18nInstance } from '@/lib/translation';
|
||||||
|
import { createI18nForClient } from '@/lib/translation/client';
|
||||||
|
|
||||||
import { withErrorBoundary } from './lib/hoc/withErrorBoundary';
|
import { withErrorBoundary } from './lib/hoc/withErrorBoundary';
|
||||||
import createAppRouter from './routes';
|
import createAppRouter from './routes';
|
||||||
@@ -18,7 +21,7 @@ const getSerializedAppData = () => {
|
|||||||
return JSON.parse(document.getElementById('__APP_DATA__')?.innerText || '{}') as Record<string, any>;
|
return JSON.parse(document.getElementById('__APP_DATA__')?.innerText || '{}') as Record<string, any>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function createApp(lng: string = 'en') {
|
export async function createApp(lng: string = 'en', i18next?: I18nInstance) {
|
||||||
const pinia = createPinia();
|
const pinia = createPinia();
|
||||||
const app = createSSRApp(withErrorBoundary(RouterView));
|
const app = createSSRApp(withErrorBoundary(RouterView));
|
||||||
|
|
||||||
@@ -32,8 +35,13 @@ export async function createApp(lng: string = 'en') {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
app.use(pinia);
|
app.use(pinia);
|
||||||
const i18next = await createI18nForRuntime(lng);
|
const runtimeI18n = import.meta.env.SSR
|
||||||
app.use(I18NextVue, { i18next });
|
? (i18next ?? createI18nInstance(true))
|
||||||
|
: await createI18nForClient(lng);
|
||||||
|
if (import.meta.env.SSR) {
|
||||||
|
await initI18nInstance(runtimeI18n, lng, true);
|
||||||
|
}
|
||||||
|
app.use(I18NextVue, { i18next: runtimeI18n });
|
||||||
app.use(PiniaColada, {
|
app.use(PiniaColada, {
|
||||||
pinia,
|
pinia,
|
||||||
plugins: [
|
plugins: [
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { renderSSRHead } from '@unhead/vue/server';
|
|||||||
import { streamText } from 'hono/streaming';
|
import { streamText } from 'hono/streaming';
|
||||||
import { renderToWebStream } from 'vue/server-renderer';
|
import { renderToWebStream } from 'vue/server-renderer';
|
||||||
|
|
||||||
|
import { localeCookieKey } from '@/i18n';
|
||||||
|
import { createI18nInstance, initI18nInstance } from '@/lib/translation';
|
||||||
import { buildBootstrapScript } from '@/lib/manifest';
|
import { buildBootstrapScript } from '@/lib/manifest';
|
||||||
import { createApp } from '@/main';
|
import { createApp } from '@/main';
|
||||||
import { htmlEscape } from '@/server/utils/htmlEscape';
|
import { htmlEscape } from '@/server/utils/htmlEscape';
|
||||||
@@ -25,7 +27,10 @@ export function registerSSRRoutes(app: Hono) {
|
|||||||
const nonce = crypto.randomUUID();
|
const nonce = crypto.randomUUID();
|
||||||
const url = new URL(c.req.url);
|
const url = new URL(c.req.url);
|
||||||
const lang = c.get("language")
|
const lang = c.get("language")
|
||||||
const { app: vueApp, router, head, pinia, bodyClass, queryCache } = await createApp(lang);
|
const localeFromCookie = parseCookie(c.req.header('cookie'), localeCookieKey);
|
||||||
|
const i18next = createI18nInstance(true);
|
||||||
|
await initI18nInstance(i18next, localeFromCookie ?? lang, true);
|
||||||
|
const { app: vueApp, router, head, pinia, bodyClass, queryCache } = await createApp(localeFromCookie ?? lang, i18next);
|
||||||
|
|
||||||
vueApp.provide("honoContext", c);
|
vueApp.provide("honoContext", c);
|
||||||
|
|
||||||
@@ -44,7 +49,7 @@ export function registerSSRRoutes(app: Hono) {
|
|||||||
const appStream = renderToWebStream(vueApp, ctx);
|
const appStream = renderToWebStream(vueApp, ctx);
|
||||||
|
|
||||||
// HTML Head
|
// HTML Head
|
||||||
await stream.write(`<!DOCTYPE html><html lang='${lang}'><head>`);
|
await stream.write(`<!DOCTYPE html><html lang='${i18next.resolvedLanguage ?? lang}'><head>`);
|
||||||
await stream.write("<base href='" + url.origin + "'/>");
|
await stream.write("<base href='" + url.origin + "'/>");
|
||||||
|
|
||||||
// SSR Head tags
|
// SSR Head tags
|
||||||
@@ -74,7 +79,7 @@ export function registerSSRRoutes(app: Hono) {
|
|||||||
Object.assign(ctx, {
|
Object.assign(ctx, {
|
||||||
$p: pinia.state.value,
|
$p: pinia.state.value,
|
||||||
$colada: serializeQueryCache(queryCache),
|
$colada: serializeQueryCache(queryCache),
|
||||||
$locale: lang,
|
$locale: i18next.resolvedLanguage ?? lang,
|
||||||
});
|
});
|
||||||
|
|
||||||
// App data script
|
// App data script
|
||||||
|
|||||||
Reference in New Issue
Block a user