remove vue-i18n
This commit is contained in:
@@ -9,8 +9,7 @@ const readAppData = () => {
|
||||
|
||||
async function render() {
|
||||
const appData = readAppData();
|
||||
const { app, router, queryCache, pinia } = createApp(appData.$locale);
|
||||
|
||||
const { app, router, queryCache, pinia } = await createApp(appData.$locale);
|
||||
pinia.use(PiniaSharedState({ enable: true, initialize: true }));
|
||||
hydrateQueryCache(queryCache, appData.$colada || {});
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
export const supportedLocales = ['en', 'vi'] as const;
|
||||
|
||||
export type SupportedLocale = (typeof supportedLocales)[number];
|
||||
|
||||
export const defaultLocale: SupportedLocale = 'en';
|
||||
|
||||
export const localeCookieKey = 'lang';
|
||||
@@ -1,75 +0,0 @@
|
||||
import { createI18n as createVueI18n } from 'vue-i18n';
|
||||
import type { SupportedLocale } from './constants';
|
||||
import { defaultLocale, supportedLocales } from './constants';
|
||||
import en from './messages/en';
|
||||
import vi from './messages/vi';
|
||||
|
||||
export const i18nMessages = {
|
||||
en,
|
||||
vi,
|
||||
} as const;
|
||||
|
||||
let activeI18n: ReturnType<typeof createI18n> | null = null;
|
||||
|
||||
const normalizeLocaleToken = (locale?: string | null): string | undefined => {
|
||||
if (!locale) return undefined;
|
||||
return locale
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace('_', '-');
|
||||
};
|
||||
|
||||
export const toSupportedLocale = (locale?: string | null): SupportedLocale | undefined => {
|
||||
const normalized = normalizeLocaleToken(locale);
|
||||
if (!normalized) return undefined;
|
||||
|
||||
const direct = supportedLocales.find(item => item === normalized);
|
||||
if (direct) return direct;
|
||||
|
||||
const base = normalized.split('-')[0];
|
||||
return supportedLocales.find(item => item === base);
|
||||
};
|
||||
|
||||
export const normalizeLocale = (locale?: string | null): SupportedLocale => {
|
||||
return toSupportedLocale(locale) ?? defaultLocale;
|
||||
};
|
||||
|
||||
export const resolveLocaleFromAcceptLanguage = (acceptLanguage?: string | null): SupportedLocale | undefined => {
|
||||
if (!acceptLanguage) return undefined;
|
||||
|
||||
const candidates = acceptLanguage
|
||||
.split(',')
|
||||
.map((part) => {
|
||||
const [rawLocale, ...params] = part.trim().split(';');
|
||||
const qParam = params.find(param => param.trim().startsWith('q='));
|
||||
const quality = qParam ? Number.parseFloat(qParam.split('=')[1] ?? '1') : 1;
|
||||
return {
|
||||
locale: rawLocale,
|
||||
quality: Number.isFinite(quality) ? quality : 1,
|
||||
};
|
||||
})
|
||||
.sort((a, b) => b.quality - a.quality);
|
||||
|
||||
for (const candidate of candidates) {
|
||||
const matched = toSupportedLocale(candidate.locale);
|
||||
if (matched) return matched;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const createI18n = (initialLocale?: string | null) => {
|
||||
const locale = normalizeLocale(initialLocale);
|
||||
const i18n = createVueI18n({
|
||||
legacy: false,
|
||||
locale,
|
||||
fallbackLocale: defaultLocale,
|
||||
messages: i18nMessages,
|
||||
});
|
||||
activeI18n = i18n;
|
||||
return i18n;
|
||||
};
|
||||
|
||||
export const getActiveI18n = () => activeI18n;
|
||||
|
||||
export type AppI18n = ReturnType<typeof createI18n>;
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
37
src/lib/translation/index.ts
Normal file
37
src/lib/translation/index.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import i18next from "i18next";
|
||||
import I18NextHttpBackend from "i18next-http-backend";
|
||||
import LanguageDetector from "i18next-browser-languagedetector";
|
||||
const i18n = i18next.createInstance();
|
||||
|
||||
i18n
|
||||
.use(I18NextHttpBackend)
|
||||
.use(LanguageDetector)
|
||||
.init({
|
||||
supportedLngs: ["en", "vi"],
|
||||
fallbackLng: "en",
|
||||
defaultNS: "common",
|
||||
ns: [
|
||||
"common",
|
||||
"app",
|
||||
"auth",
|
||||
"nav",
|
||||
"settings",
|
||||
"pageHeader",
|
||||
"confirm",
|
||||
"toast",
|
||||
"overview",
|
||||
"video",
|
||||
"notification",
|
||||
"upload",
|
||||
"home",
|
||||
"legal",
|
||||
"notFound",
|
||||
],
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
backend: {
|
||||
loadPath: "/locales/{{lng}}/{{ns}}.json", // dynamic fetch JSON
|
||||
},
|
||||
});
|
||||
export default i18n;
|
||||
19
src/main.ts
19
src/main.ts
@@ -4,7 +4,10 @@ import { createHead as SSRHead } from '@unhead/vue/server';
|
||||
import { createPinia } from 'pinia';
|
||||
import { createSSRApp } from 'vue';
|
||||
import { RouterView } from 'vue-router';
|
||||
import { createI18n, normalizeLocale } from './i18n';
|
||||
|
||||
import I18NextVue from 'i18next-vue';
|
||||
import i18next from '@/lib/translation';
|
||||
|
||||
import { withErrorBoundary } from './lib/hoc/withErrorBoundary';
|
||||
import createAppRouter from './routes';
|
||||
|
||||
@@ -15,18 +18,13 @@ const getSerializedAppData = () => {
|
||||
return JSON.parse(document.getElementById('__APP_DATA__')?.innerText || '{}') as Record<string, any>;
|
||||
};
|
||||
|
||||
export function createApp(initialLocale?: string | null) {
|
||||
export async function createApp(lng: string = 'en') {
|
||||
const pinia = createPinia();
|
||||
const app = createSSRApp(withErrorBoundary(RouterView));
|
||||
|
||||
const head = import.meta.env.SSR ? SSRHead() : CSRHead();
|
||||
const appData = !import.meta.env.SSR ? getSerializedAppData() : ({} as Record<string, any>);
|
||||
|
||||
const resolvedInitialLocale = initialLocale
|
||||
?? (!import.meta.env.SSR ? appData.$locale : undefined)
|
||||
?? undefined;
|
||||
|
||||
const i18n = createI18n(normalizeLocale(resolvedInitialLocale));
|
||||
|
||||
app.use(head);
|
||||
app.directive('nh', {
|
||||
created(el) {
|
||||
@@ -34,7 +32,8 @@ export function createApp(initialLocale?: string | null) {
|
||||
}
|
||||
});
|
||||
app.use(pinia);
|
||||
app.use(i18n);
|
||||
await i18next.init({lng});
|
||||
app.use(I18NextVue, {i18next});
|
||||
app.use(PiniaColada, {
|
||||
pinia,
|
||||
plugins: [
|
||||
@@ -62,5 +61,5 @@ export function createApp(initialLocale?: string | null) {
|
||||
}
|
||||
}
|
||||
|
||||
return { app, router, head, pinia, bodyClass, queryCache, i18n };
|
||||
return { app, router, head, pinia, bodyClass, queryCache };
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
{{ content[route.name as keyof typeof content.value]?.title || '' }}
|
||||
</h2>
|
||||
<vue-head :input="{
|
||||
title: content.value[route.name as keyof typeof content.value]?.headTitle || t('app.name'),
|
||||
title: content[route.name as keyof typeof content.value]?.headTitle || t('app.name'),
|
||||
meta: [
|
||||
{ name: 'description', content: content.value[route.name as keyof typeof content.value]?.subtitle || '' }
|
||||
{ name: 'description', content: content[route.name as keyof typeof content.value]?.subtitle || '' }
|
||||
]
|
||||
}" />
|
||||
</div>
|
||||
@@ -23,12 +23,12 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { useTranslation } from 'i18next-vue';
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const content = computed(() => ({
|
||||
login: {
|
||||
|
||||
@@ -2,9 +2,16 @@ import { contextStorage } from 'hono/context-storage';
|
||||
import { cors } from 'hono/cors';
|
||||
import isMobile from 'is-mobile';
|
||||
import type { Hono } from 'hono';
|
||||
import { languageDetector } from 'hono/language';
|
||||
|
||||
export function setupMiddlewares(app: Hono) {
|
||||
app.use('*', contextStorage());
|
||||
app.use('*', languageDetector({
|
||||
supportedLanguages: ['vi', 'en'],
|
||||
fallbackLanguage: 'en',
|
||||
lookupCookie: 'i18next',
|
||||
lookupFromHeaderKey: 'accept-language',
|
||||
order: ['cookie', 'header'],
|
||||
}) ,contextStorage());
|
||||
|
||||
app.use(cors(), async (c, next) => {
|
||||
c.set("fetch", app.request.bind(app));
|
||||
|
||||
@@ -3,12 +3,10 @@ import { renderSSRHead } from '@unhead/vue/server';
|
||||
import { streamText } from 'hono/streaming';
|
||||
import { renderToWebStream } from 'vue/server-renderer';
|
||||
|
||||
import { createApp } from '@/main';
|
||||
import { defaultLocale, localeCookieKey } from '@/i18n/constants';
|
||||
import { normalizeLocale, resolveLocaleFromAcceptLanguage } from '@/i18n';
|
||||
import { useAuthStore } from '@/stores/auth';
|
||||
import { buildBootstrapScript } from '@/lib/manifest';
|
||||
import { createApp } from '@/main';
|
||||
import { htmlEscape } from '@/server/utils/htmlEscape';
|
||||
import { useAuthStore } from '@/stores/auth';
|
||||
import type { Hono } from 'hono';
|
||||
|
||||
const parseCookie = (cookieHeader: string | undefined, key: string): string | undefined => {
|
||||
@@ -22,22 +20,12 @@ const parseCookie = (cookieHeader: string | undefined, key: string): string | un
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const resolveLocaleFromAuthUser = (authUser: unknown): string | undefined => {
|
||||
if (!authUser || typeof authUser !== 'object') return undefined;
|
||||
const maybeLanguage = (authUser as any).language ?? (authUser as any).locale;
|
||||
return typeof maybeLanguage === 'string' ? maybeLanguage : undefined;
|
||||
};
|
||||
|
||||
export function registerSSRRoutes(app: Hono) {
|
||||
app.get("*", async (c) => {
|
||||
const nonce = crypto.randomUUID();
|
||||
const url = new URL(c.req.url);
|
||||
|
||||
const cookieLocaleRaw = parseCookie(c.req.header('cookie'), localeCookieKey);
|
||||
const acceptLocale = resolveLocaleFromAcceptLanguage(c.req.header('accept-language'));
|
||||
const bootstrapLocale = normalizeLocale(cookieLocaleRaw ?? acceptLocale ?? defaultLocale);
|
||||
|
||||
const { app: vueApp, router, head, pinia, bodyClass, queryCache, i18n } = createApp(bootstrapLocale);
|
||||
const lang = c.get("language")
|
||||
const { app: vueApp, router, head, pinia, bodyClass, queryCache } = await createApp(lang);
|
||||
|
||||
vueApp.provide("honoContext", c);
|
||||
|
||||
@@ -45,10 +33,6 @@ export function registerSSRRoutes(app: Hono) {
|
||||
auth.$reset();
|
||||
await auth.init();
|
||||
|
||||
const userPreferredLocale = resolveLocaleFromAuthUser(auth.user);
|
||||
const resolvedLocale = normalizeLocale(userPreferredLocale ?? cookieLocaleRaw ?? acceptLocale ?? defaultLocale);
|
||||
i18n.global.locale.value = resolvedLocale;
|
||||
|
||||
await router.push(url.pathname);
|
||||
await router.isReady();
|
||||
|
||||
@@ -60,7 +44,7 @@ export function registerSSRRoutes(app: Hono) {
|
||||
const appStream = renderToWebStream(vueApp, ctx);
|
||||
|
||||
// HTML Head
|
||||
await stream.write(`<!DOCTYPE html><html lang='${resolvedLocale}'><head>`);
|
||||
await stream.write(`<!DOCTYPE html><html lang='${lang}'><head>`);
|
||||
await stream.write("<base href='" + url.origin + "'/>");
|
||||
|
||||
// SSR Head tags
|
||||
@@ -90,7 +74,7 @@ export function registerSSRRoutes(app: Hono) {
|
||||
Object.assign(ctx, {
|
||||
$p: pinia.state.value,
|
||||
$colada: serializeQueryCache(queryCache),
|
||||
$locale: resolvedLocale,
|
||||
$locale: lang,
|
||||
});
|
||||
|
||||
// App data script
|
||||
|
||||
Reference in New Issue
Block a user