mirror of
https://github.com/halo-dev/halo.git
synced 2025-12-14 16:24:01 +08:00
Add support for async language pack loading (#7931)
#### What type of PR is this? /area ui /kind improvement /milestone 2.22.x #### What this PR does / why we need it: This PR supports asynchronously loading the required language packs, which can significantly improve the initial page load speed. before: <img width="732" height="285" alt="image" src="https://github.com/user-attachments/assets/6d682010-751a-4c07-8651-8c6ade8a5f2e" /> after: <img width="748" height="280" alt="image" src="https://github.com/user-attachments/assets/1c22d295-5ba9-423b-b941-3e7d536e1660" /> #### Does this PR introduce a user-facing change? ```release-note Console 和 UC 支持异步加载所需语言包,提升首屏加载速度。 ```
This commit is contained in:
@@ -1,25 +1,22 @@
|
||||
import { consoleApiClient } from "@halo-dev/api-client";
|
||||
import { createPinia } from "pinia";
|
||||
import type { DirectiveBinding } from "vue";
|
||||
import { createApp } from "vue";
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
// setup
|
||||
import { getBrowserLanguage, i18n, setupI18n } from "@/locales";
|
||||
import { setLanguage, setupI18n } from "@/locales";
|
||||
import { setupApiClient } from "@/setup/setupApiClient";
|
||||
import { setupComponents } from "@/setup/setupComponents";
|
||||
import "@/setup/setupStyles";
|
||||
// core modules
|
||||
import { setupApiClient } from "@/setup/setupApiClient";
|
||||
import { setupVueQuery } from "@/setup/setupVueQuery";
|
||||
import { useRoleStore } from "@/stores/role";
|
||||
import { getCookie } from "@/utils/cookie";
|
||||
import {
|
||||
setupCoreModules,
|
||||
setupPluginModules,
|
||||
} from "@console/setup/setupModules";
|
||||
import { useThemeStore } from "@console/stores/theme";
|
||||
import { consoleApiClient } from "@halo-dev/api-client";
|
||||
import { stores, utils } from "@halo-dev/ui-shared";
|
||||
import "core-js/es/object/has-own";
|
||||
import { createPinia } from "pinia";
|
||||
import type { DirectiveBinding } from "vue";
|
||||
import { createApp } from "vue";
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
@@ -74,13 +71,11 @@ async function initApp() {
|
||||
const currentUserStore = stores.currentUser();
|
||||
await currentUserStore.fetchCurrentUser();
|
||||
|
||||
// set locale
|
||||
i18n.global.locale.value = getCookie("language") || getBrowserLanguage();
|
||||
utils.date.setLocale(i18n.global.locale.value);
|
||||
|
||||
const globalInfoStore = stores.globalInfo();
|
||||
await globalInfoStore.fetchGlobalInfo();
|
||||
|
||||
await setLanguage();
|
||||
|
||||
if (currentUserStore.isAnonymous) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
<script lang="ts" setup>
|
||||
import HasPermission from "@/components/permission/HasPermission.vue";
|
||||
import StickyBlock from "@/components/sticky-block/StickyBlock.vue";
|
||||
import { setLanguage } from "@/locales";
|
||||
import type { FormKitSchemaCondition, FormKitSchemaNode } from "@formkit/core";
|
||||
import type { Setting } from "@halo-dev/api-client";
|
||||
import { consoleApiClient } from "@halo-dev/api-client";
|
||||
import { Toast, VButton, VLoading } from "@halo-dev/components";
|
||||
import { stores, utils } from "@halo-dev/ui-shared";
|
||||
import { stores } from "@halo-dev/ui-shared";
|
||||
import { useQuery, useQueryClient } from "@tanstack/vue-query";
|
||||
import { computed, inject, ref, toRaw, type Ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t, locale } = useI18n();
|
||||
const { t } = useI18n();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const group = inject<Ref<string>>("activeTab", ref("basic"));
|
||||
@@ -56,9 +57,8 @@ const handleSaveConfigMap = async (data: Record<string, unknown>) => {
|
||||
|
||||
if (group.value === "basic") {
|
||||
const language = data.language;
|
||||
locale.value = language as string;
|
||||
document.cookie = `language=${language}; path=/; SameSite=Lax; Secure`;
|
||||
utils.date.setLocale(locale.value);
|
||||
await setLanguage(language as string);
|
||||
}
|
||||
|
||||
Toast.success(t("core.common.toast.save_success"));
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { getBrowserLanguage, i18n, locales } from "@/locales";
|
||||
import { useLocalStorage } from "@vueuse/core";
|
||||
import { watch } from "vue";
|
||||
import MdiTranslate from "~icons/mdi/translate";
|
||||
|
||||
// setup locale
|
||||
const currentLocale = useLocalStorage(
|
||||
"locale",
|
||||
getBrowserLanguage() || locales[0].code
|
||||
);
|
||||
|
||||
watch(
|
||||
() => currentLocale.value,
|
||||
(value) => {
|
||||
i18n.global.locale.value = value;
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<label
|
||||
for="locale"
|
||||
class="block flex-shrink-0 text-sm font-medium text-gray-600"
|
||||
>
|
||||
<MdiTranslate />
|
||||
</label>
|
||||
<select
|
||||
id="locale"
|
||||
v-model="currentLocale"
|
||||
class="block appearance-none rounded-md border-0 py-1.5 pl-3 pr-10 text-sm text-gray-800 outline-none ring-1 ring-inset ring-gray-200 focus:!ring-1 focus:!ring-primary"
|
||||
>
|
||||
<template v-for="locale in locales">
|
||||
<option v-if="locale.name" :key="locale.code" :value="locale.code">
|
||||
{{ locale.name }}
|
||||
</option>
|
||||
</template>
|
||||
</select>
|
||||
</template>
|
||||
@@ -1,67 +1,115 @@
|
||||
import { getCookie } from "@/utils/cookie";
|
||||
import { utils } from "@halo-dev/ui-shared";
|
||||
import type { App } from "vue";
|
||||
import { createI18n } from "vue-i18n";
|
||||
// @ts-ignore
|
||||
import en from "./en.yaml";
|
||||
// @ts-ignore
|
||||
import es from "./es.yaml";
|
||||
// @ts-ignore
|
||||
import zhCN from "./zh-CN.yaml";
|
||||
// @ts-ignore
|
||||
import zhTW from "./zh-TW.yaml";
|
||||
|
||||
export const locales = [
|
||||
interface LocaleConfig {
|
||||
code: string[];
|
||||
file: string;
|
||||
}
|
||||
|
||||
export const SUPPORTED_LOCALES: LocaleConfig[] = [
|
||||
{
|
||||
code: "en",
|
||||
package: en,
|
||||
hidden: true,
|
||||
code: ["en"],
|
||||
file: "en.yaml",
|
||||
},
|
||||
{
|
||||
name: "English",
|
||||
code: "en-US",
|
||||
package: en,
|
||||
code: ["es"],
|
||||
file: "es.yaml",
|
||||
},
|
||||
{
|
||||
name: "Español",
|
||||
code: "es",
|
||||
package: es,
|
||||
code: ["zh-CN", "zh"],
|
||||
file: "zh-CN.yaml",
|
||||
},
|
||||
{
|
||||
name: "简体中文",
|
||||
code: "zh-CN",
|
||||
package: zhCN,
|
||||
},
|
||||
{
|
||||
code: "zh",
|
||||
package: zhCN,
|
||||
},
|
||||
{
|
||||
name: "正體中文",
|
||||
code: "zh-TW",
|
||||
package: zhTW,
|
||||
code: ["zh-TW"],
|
||||
file: "zh-TW.yaml",
|
||||
},
|
||||
];
|
||||
|
||||
const messages = locales.reduce((acc, cur) => {
|
||||
acc[cur.code] = cur.package;
|
||||
return acc;
|
||||
}, {});
|
||||
const localeModules = import.meta.glob<{ default: Record<string, unknown> }>(
|
||||
"./*.yaml",
|
||||
{ eager: false }
|
||||
);
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: "en",
|
||||
fallbackLocale: "en",
|
||||
messages,
|
||||
});
|
||||
|
||||
export function getBrowserLanguage(): string {
|
||||
const browserLanguage = navigator.language;
|
||||
const language = messages[browserLanguage]
|
||||
? browserLanguage
|
||||
: browserLanguage.split("-")[0];
|
||||
return language in messages ? language : "zh-CN";
|
||||
export function getEnvironmentLanguage(): string {
|
||||
return getCookie("language") || navigator.language;
|
||||
}
|
||||
|
||||
export function setupI18n(app: App) {
|
||||
export function getLocaleDefinition(
|
||||
language: string
|
||||
): LocaleConfig | undefined {
|
||||
const locale = SUPPORTED_LOCALES.find((locale) =>
|
||||
locale.code.includes(language)
|
||||
);
|
||||
|
||||
if (locale) {
|
||||
return locale;
|
||||
}
|
||||
|
||||
const code = language.split("-")[0];
|
||||
return SUPPORTED_LOCALES.find((locale) => locale.code.includes(code));
|
||||
}
|
||||
|
||||
export async function setLanguage(_language?: string): Promise<void> {
|
||||
const language = _language || getEnvironmentLanguage();
|
||||
|
||||
if (!i18n.global.availableLocales.includes(language)) {
|
||||
const locale = getLocaleDefinition(language);
|
||||
if (locale) {
|
||||
try {
|
||||
const localeLoader = localeModules[`./${locale.file}`];
|
||||
if (!localeLoader) {
|
||||
throw new Error(`Locale file ${locale.file} not found`);
|
||||
}
|
||||
const messages = await localeLoader();
|
||||
i18n.global.setLocaleMessage(language, messages.default || messages);
|
||||
} catch (error) {
|
||||
console.error(`Failed to load locale file for ${language}:`, error);
|
||||
await loadFallbackLocale();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
console.warn(`Locale not found for ${language}, using fallback`);
|
||||
await loadFallbackLocale();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
i18n.global.locale.value = language;
|
||||
utils.date.setLocale(language);
|
||||
}
|
||||
|
||||
async function loadFallbackLocale(): Promise<void> {
|
||||
const fallback = i18n.global.fallbackLocale.value as string;
|
||||
|
||||
if (!i18n.global.availableLocales.includes(fallback)) {
|
||||
const fallbackLocale = getLocaleDefinition(fallback);
|
||||
if (fallbackLocale) {
|
||||
try {
|
||||
const localeLoader = localeModules[`./${fallbackLocale.file}`];
|
||||
if (!localeLoader) {
|
||||
throw new Error(
|
||||
`Fallback locale file ${fallbackLocale.file} not found`
|
||||
);
|
||||
}
|
||||
const messages = await localeLoader();
|
||||
i18n.global.setLocaleMessage(fallback, messages.default || messages);
|
||||
} catch (error) {
|
||||
console.error(`Failed to load fallback locale file:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
i18n.global.locale.value = fallback;
|
||||
}
|
||||
|
||||
export function setupI18n(app: App): void {
|
||||
app.use(i18n);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { getBrowserLanguage, i18n, setupI18n } from "@/locales";
|
||||
import { setLanguage, setupI18n } from "@/locales";
|
||||
import { setupApiClient } from "@/setup/setupApiClient";
|
||||
import { setupComponents } from "@/setup/setupComponents";
|
||||
import "@/setup/setupStyles";
|
||||
import { setupVueQuery } from "@/setup/setupVueQuery";
|
||||
import { useRoleStore } from "@/stores/role";
|
||||
import { getCookie } from "@/utils/cookie";
|
||||
import { consoleApiClient } from "@halo-dev/api-client";
|
||||
import { stores, utils } from "@halo-dev/ui-shared";
|
||||
import router from "@uc/router";
|
||||
@@ -62,13 +61,11 @@ async function initApp() {
|
||||
const currentUserStore = stores.currentUser();
|
||||
await currentUserStore.fetchCurrentUser();
|
||||
|
||||
// set locale
|
||||
i18n.global.locale.value = getCookie("language") || getBrowserLanguage();
|
||||
utils.date.setLocale(i18n.global.locale.value);
|
||||
|
||||
const globalInfoStore = stores.globalInfo();
|
||||
await globalInfoStore.fetchGlobalInfo();
|
||||
|
||||
await setLanguage();
|
||||
|
||||
if (currentUserStore.isAnonymous) {
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user