mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-12-20 14:29:24 +08:00
feat: Optimize i18n by dynamically loading module (#10759)
* feat: Optimize internationalization support, dynamically load language packs and update language settings * feat: Refactor i18n initialization to load initial messages dynamically
This commit is contained in:
parent
1ef4803954
commit
ff36b65af4
5 changed files with 152 additions and 95 deletions
|
|
@ -1,34 +1,94 @@
|
||||||
import { createI18n } from 'vue-i18n';
|
import { createI18n } from 'vue-i18n';
|
||||||
import zh from './modules/zh';
|
|
||||||
import zhHant from './modules/zh-Hant';
|
type LocaleMessage = Record<string, unknown>;
|
||||||
import en from './modules/en';
|
type LocaleLoader = () => Promise<{ default: LocaleMessage }>;
|
||||||
import ptBr from './modules/pt-br';
|
|
||||||
import ja from './modules/ja';
|
const DEFAULT_LOCALE = 'en';
|
||||||
import ru from './modules/ru';
|
const STORAGE_KEY = 'lang';
|
||||||
import ms from './modules/ms';
|
|
||||||
import ko from './modules/ko';
|
const LOCALE_LOADERS: Record<string, LocaleLoader> = {
|
||||||
import tr from './modules/tr';
|
zh: () => import('./modules/zh'),
|
||||||
import esES from './modules/es-es';
|
'zh-Hant': () => import('./modules/zh-Hant'),
|
||||||
|
en: () => import('./modules/en'),
|
||||||
|
'pt-BR': () => import('./modules/pt-br'),
|
||||||
|
ja: () => import('./modules/ja'),
|
||||||
|
ru: () => import('./modules/ru'),
|
||||||
|
ms: () => import('./modules/ms'),
|
||||||
|
ko: () => import('./modules/ko'),
|
||||||
|
tr: () => import('./modules/tr'),
|
||||||
|
'es-ES': () => import('./modules/es-es'),
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStoredLocale = () => {
|
||||||
|
if (typeof window === 'undefined') return DEFAULT_LOCALE;
|
||||||
|
return localStorage.getItem(STORAGE_KEY) || DEFAULT_LOCALE;
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialLocale = getStoredLocale();
|
||||||
|
|
||||||
|
const loadedLocales = new Set<string>();
|
||||||
|
|
||||||
|
export const loadLocaleMessages = async (locale: string) => {
|
||||||
|
const targetLocale = LOCALE_LOADERS[locale] ? locale : DEFAULT_LOCALE;
|
||||||
|
if (loadedLocales.has(targetLocale)) {
|
||||||
|
return targetLocale;
|
||||||
|
}
|
||||||
|
const loader = LOCALE_LOADERS[targetLocale];
|
||||||
|
if (!loader) {
|
||||||
|
return targetLocale;
|
||||||
|
}
|
||||||
|
const messagesModule = await loader();
|
||||||
|
const messages = messagesModule.default || {};
|
||||||
|
if (!i18n) {
|
||||||
|
return targetLocale;
|
||||||
|
}
|
||||||
|
i18n.global.setLocaleMessage(targetLocale, messages);
|
||||||
|
loadedLocales.add(targetLocale);
|
||||||
|
return targetLocale;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getInitialMessages = async (): Promise<Record<string, LocaleMessage>> => {
|
||||||
|
const loader = LOCALE_LOADERS[initialLocale];
|
||||||
|
if (!loader) {
|
||||||
|
return { [initialLocale]: {} };
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const messagesModule = await loader();
|
||||||
|
const messages = messagesModule.default || {};
|
||||||
|
loadedLocales.add(initialLocale);
|
||||||
|
return { [initialLocale]: messages };
|
||||||
|
} catch {
|
||||||
|
return { [initialLocale]: {} };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialMessages = await getInitialMessages();
|
||||||
|
|
||||||
const i18n = createI18n({
|
const i18n = createI18n({
|
||||||
legacy: false,
|
legacy: false,
|
||||||
missingWarn: false,
|
missingWarn: false,
|
||||||
locale: localStorage.getItem('lang') || 'en',
|
fallbackWarn: false,
|
||||||
fallbackLocale: 'en',
|
locale: initialLocale,
|
||||||
|
fallbackLocale: DEFAULT_LOCALE,
|
||||||
globalInjection: true,
|
globalInjection: true,
|
||||||
messages: {
|
messages: initialMessages,
|
||||||
zh,
|
|
||||||
'zh-Hant': zhHant,
|
|
||||||
en,
|
|
||||||
'pt-BR': ptBr,
|
|
||||||
ja,
|
|
||||||
ru,
|
|
||||||
ms,
|
|
||||||
ko,
|
|
||||||
tr,
|
|
||||||
'es-ES': esES,
|
|
||||||
},
|
|
||||||
warnHtmlMessage: false,
|
warnHtmlMessage: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const ensureFallbackLocale = async () => {
|
||||||
|
const fallback = i18n.global.fallbackLocale.value || DEFAULT_LOCALE;
|
||||||
|
if (typeof fallback === 'string') {
|
||||||
|
await loadLocaleMessages(fallback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setActiveLocale = async (locale: string) => {
|
||||||
|
const loaded = await loadLocaleMessages(locale);
|
||||||
|
i18n.global.locale.value = loaded;
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
localStorage.setItem(STORAGE_KEY, loaded);
|
||||||
|
}
|
||||||
|
return loaded;
|
||||||
|
};
|
||||||
|
|
||||||
export default i18n;
|
export default i18n;
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ for (const path in styleModule) {
|
||||||
}
|
}
|
||||||
|
|
||||||
import router from '@/routers/index';
|
import router from '@/routers/index';
|
||||||
import i18n from '@/lang/index';
|
import i18n, { ensureFallbackLocale, loadLocaleMessages } from '@/lang/index';
|
||||||
import pinia from '@/store/index';
|
import pinia from '@/store/index';
|
||||||
import SvgIcon from './components/svg-icon/svg-icon.vue';
|
import SvgIcon from './components/svg-icon/svg-icon.vue';
|
||||||
import Components from '@/components';
|
import Components from '@/components';
|
||||||
|
|
@ -24,10 +24,15 @@ import * as Icons from '@element-plus/icons-vue';
|
||||||
|
|
||||||
import directives from '@/directives/index';
|
import directives from '@/directives/index';
|
||||||
|
|
||||||
|
const bootstrap = async () => {
|
||||||
|
const currentLocale = i18n.global.locale.value;
|
||||||
|
|
||||||
|
await Promise.all([loadLocaleMessages(currentLocale), ensureFallbackLocale()]);
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
app.component('SvgIcon', SvgIcon);
|
app.component('SvgIcon', SvgIcon);
|
||||||
app.use(ElementPlus);
|
app.use(ElementPlus);
|
||||||
app.use(Fit2CloudPlus, { locale: i18n.global.messages.value[localStorage.getItem('lang') || 'zh'] });
|
app.use(Fit2CloudPlus, { locale: i18n.global.getLocaleMessage(currentLocale) });
|
||||||
|
|
||||||
Object.keys(Icons).forEach((key) => {
|
Object.keys(Icons).forEach((key) => {
|
||||||
app.component(key, Icons[key as keyof typeof Icons]);
|
app.component(key, Icons[key as keyof typeof Icons]);
|
||||||
|
|
@ -40,3 +45,6 @@ app.use(Components);
|
||||||
app.use(directives);
|
app.use(directives);
|
||||||
|
|
||||||
app.mount('#app');
|
app.mount('#app');
|
||||||
|
};
|
||||||
|
|
||||||
|
bootstrap();
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { defineStore } from 'pinia';
|
||||||
import piniaPersistConfig from '@/config/pinia-persist';
|
import piniaPersistConfig from '@/config/pinia-persist';
|
||||||
import { GlobalState, ThemeConfigProp } from '../interface';
|
import { GlobalState, ThemeConfigProp } from '../interface';
|
||||||
import { DeviceType } from '@/enums/app';
|
import { DeviceType } from '@/enums/app';
|
||||||
import i18n from '@/lang';
|
import i18n, { setActiveLocale } from '@/lang';
|
||||||
|
|
||||||
const GlobalStore = defineStore({
|
const GlobalStore = defineStore({
|
||||||
id: 'GlobalState',
|
id: 'GlobalState',
|
||||||
|
|
@ -48,7 +48,7 @@ const GlobalStore = defineStore({
|
||||||
isMasterProductPro: false,
|
isMasterProductPro: false,
|
||||||
isOffLine: false,
|
isOffLine: false,
|
||||||
|
|
||||||
masterAlias: i18n.global.t('xpack.node.master'),
|
masterAlias: '',
|
||||||
currentNode: 'local',
|
currentNode: 'local',
|
||||||
currentNodeAddr: '',
|
currentNodeAddr: '',
|
||||||
}),
|
}),
|
||||||
|
|
@ -79,9 +79,10 @@ const GlobalStore = defineStore({
|
||||||
setCsrfToken(token: string) {
|
setCsrfToken(token: string) {
|
||||||
this.csrfToken = token;
|
this.csrfToken = token;
|
||||||
},
|
},
|
||||||
updateLanguage(language: any) {
|
async updateLanguage(language: string) {
|
||||||
this.language = language;
|
const activeLocale = await setActiveLocale(language);
|
||||||
localStorage.setItem('lang', language);
|
this.language = activeLocale;
|
||||||
|
return activeLocale;
|
||||||
},
|
},
|
||||||
setThemeConfig(themeConfig: ThemeConfigProp) {
|
setThemeConfig(themeConfig: ThemeConfigProp) {
|
||||||
this.themeConfig = themeConfig;
|
this.themeConfig = themeConfig;
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@
|
||||||
<el-dropdown-item command="ko">한국어</el-dropdown-item>
|
<el-dropdown-item command="ko">한국어</el-dropdown-item>
|
||||||
<el-dropdown-item command="ru">Русский</el-dropdown-item>
|
<el-dropdown-item command="ru">Русский</el-dropdown-item>
|
||||||
<el-dropdown-item command="ms">Bahasa Melayu</el-dropdown-item>
|
<el-dropdown-item command="ms">Bahasa Melayu</el-dropdown-item>
|
||||||
<el-dropdown-item command="Tr">Turkish</el-dropdown-item>
|
<el-dropdown-item command="tr">Turkish</el-dropdown-item>
|
||||||
</el-dropdown-menu>
|
</el-dropdown-menu>
|
||||||
</template>
|
</template>
|
||||||
</el-dropdown>
|
</el-dropdown>
|
||||||
|
|
@ -199,7 +199,6 @@ const themeConfig = computed(() => globalStore.themeConfig);
|
||||||
const globalStore = GlobalStore();
|
const globalStore = GlobalStore();
|
||||||
const menuStore = MenuStore();
|
const menuStore = MenuStore();
|
||||||
const tabsStore = TabsStore();
|
const tabsStore = TabsStore();
|
||||||
const usei18n = useI18n();
|
|
||||||
|
|
||||||
const errAuthInfo = ref(false);
|
const errAuthInfo = ref(false);
|
||||||
const errCaptcha = ref(false);
|
const errCaptcha = ref(false);
|
||||||
|
|
@ -276,32 +275,24 @@ const loading = ref<boolean>(false);
|
||||||
const mfaShow = ref<boolean>(false);
|
const mfaShow = ref<boolean>(false);
|
||||||
const dropdownText = ref('中文(简体)');
|
const dropdownText = ref('中文(简体)');
|
||||||
|
|
||||||
function handleCommand(command: string) {
|
const languageLabelMap: Record<string, string> = {
|
||||||
loginForm.language = command;
|
zh: '中文(简体)',
|
||||||
usei18n.locale.value = command;
|
en: 'English',
|
||||||
globalStore.updateLanguage(command);
|
'pt-BR': 'Português (Brasil)',
|
||||||
if (command === 'zh') {
|
'zh-Hant': '中文(繁體)',
|
||||||
dropdownText.value = '中文(简体)';
|
ko: '한국어',
|
||||||
} else if (command === 'en') {
|
ja: '日本語',
|
||||||
dropdownText.value = 'English';
|
ru: 'Русский',
|
||||||
} else if (command === 'pt-BR') {
|
ms: 'Bahasa Melayu',
|
||||||
dropdownText.value = 'Português (Brasil)';
|
tr: 'Turkish',
|
||||||
} else if (command === 'zh-Hant') {
|
'es-ES': 'España - Español',
|
||||||
dropdownText.value = '中文(繁體)';
|
};
|
||||||
} else if (command === 'ko') {
|
|
||||||
dropdownText.value = '한국어';
|
const handleCommand = async (command: string) => {
|
||||||
} else if (command === 'ja') {
|
const activeLocale = await globalStore.updateLanguage(command);
|
||||||
dropdownText.value = '日本語';
|
loginForm.language = activeLocale;
|
||||||
} else if (command === 'ru') {
|
dropdownText.value = languageLabelMap[activeLocale] || languageLabelMap.zh;
|
||||||
dropdownText.value = 'Русский';
|
};
|
||||||
} else if (command === 'ms') {
|
|
||||||
dropdownText.value = 'Bahasa Melayu';
|
|
||||||
} else if (command === 'tr') {
|
|
||||||
dropdownText.value = 'Turkish';
|
|
||||||
} else if (command === 'es-ES') {
|
|
||||||
dropdownText.value = 'España - Español';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const agreeWithLogin = () => {
|
const agreeWithLogin = () => {
|
||||||
open.value = false;
|
open.value = false;
|
||||||
|
|
@ -421,18 +412,16 @@ const getSetting = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await getLoginSetting();
|
const res = await getLoginSetting();
|
||||||
isDemo.value = res.data.isDemo;
|
isDemo.value = res.data.isDemo;
|
||||||
loginForm.language = res.data.language;
|
const language = res.data.language || loginForm.language;
|
||||||
handleCommand(loginForm.language);
|
await handleCommand(language);
|
||||||
isIntl.value = res.data.isIntl;
|
isIntl.value = res.data.isIntl;
|
||||||
isFxplay.value = res.data.isFxplay;
|
isFxplay.value = res.data.isFxplay;
|
||||||
globalStore.isFxplay = isFxplay.value;
|
globalStore.isFxplay = isFxplay.value;
|
||||||
globalStore.isOffLine = res.data.isOffLine;
|
globalStore.isOffLine = res.data.isOffLine;
|
||||||
|
|
||||||
document.title = res.data.panelName;
|
document.title = res.data.panelName;
|
||||||
i18n.locale.value = res.data.language;
|
|
||||||
i18n.warnHtmlMessage = false;
|
i18n.warnHtmlMessage = false;
|
||||||
globalStore.setOpenMenuTabs(res.data.menuTabs === 'Enable');
|
globalStore.setOpenMenuTabs(res.data.menuTabs === 'Enable');
|
||||||
globalStore.updateLanguage(res.data.language);
|
|
||||||
globalStore.setThemeConfig({ ...themeConfig.value, theme: res.data.theme, panelName: res.data.panelName });
|
globalStore.setThemeConfig({ ...themeConfig.value, theme: res.data.theme, panelName: res.data.panelName });
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -508,11 +508,10 @@ const onSave = async (key: string, val: any) => {
|
||||||
key: key,
|
key: key,
|
||||||
value: val + '',
|
value: val + '',
|
||||||
};
|
};
|
||||||
await updateSetting(param)
|
try {
|
||||||
.then(() => {
|
await updateSetting(param);
|
||||||
if (key === 'Language') {
|
if (key === 'Language') {
|
||||||
i18n.global.locale.value = val;
|
await globalStore.updateLanguage(val);
|
||||||
globalStore.updateLanguage(val);
|
|
||||||
location.reload();
|
location.reload();
|
||||||
}
|
}
|
||||||
if (key === 'Theme') {
|
if (key === 'Theme') {
|
||||||
|
|
@ -523,11 +522,11 @@ const onSave = async (key: string, val: any) => {
|
||||||
}
|
}
|
||||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
search();
|
search();
|
||||||
|
} catch (error) {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
})
|
return;
|
||||||
.catch(() => {
|
}
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue