locale中英文
This commit is contained in:
1 parent
57fda355e6
commit
1e125254eb
3 files changed
+89
-6
No files matched your search
@@ -1,6 +1,8 @@
|
|||||||
import { createI18n } from 'vue-i18n'
|
import { createI18n } from 'vue-i18n'
|
||||||
import type { I18nOptions } from 'vue-i18n';
|
import type { I18nOptions } from 'vue-i18n';
|
||||||
import type { App } from 'vue';
|
import type { App } from 'vue';
|
||||||
|
import { set } from 'lodash-es';
|
||||||
|
import type { LocaleEntity } from '@/data/auto/localeEntity';
|
||||||
|
|
||||||
//这儿使用await,需要如下两步处理
|
//这儿使用await,需要如下两步处理
|
||||||
//npm install vite-plugin-top-level-await -D
|
//npm install vite-plugin-top-level-await -D
|
||||||
@@ -36,5 +38,38 @@ export async function setupI18n(app: App) {
|
|||||||
app.use(i18n);
|
app.use(i18n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将后端 LocaleService.FindAll() 返回的 locale 表内容合并到对应语言的翻译中,
|
||||||
|
* 作为本地 JSON 翻译文件(lang/en、lang/zh-cn)的补充。
|
||||||
|
* 数据映射规则:
|
||||||
|
* Lang -> 语言 key(如 zh-cn / en)
|
||||||
|
* GroupName -> 顶层分组(对应 json 文件名的 key,如 layout / login / request)
|
||||||
|
* Name -> 点分隔的翻译 key(如 header.logout)
|
||||||
|
* Value -> 翻译文本
|
||||||
|
* 需在应用启动后调用;内部使用动态 import,避免与 utils/request 形成循环依赖。
|
||||||
|
*/
|
||||||
|
export async function mergeDbLocaleMessages() {
|
||||||
|
try {
|
||||||
|
const { default: localeService } = await import('@/service/security/localeService');
|
||||||
|
const list = await localeService.FindAll();
|
||||||
|
if (!list || list.length === 0) return;
|
||||||
|
|
||||||
export default i18n;
|
// 按语言分组,并将 GroupName.Name 铺平成嵌套对象
|
||||||
|
const grouped: Record<string, Record<string, any>> = {};
|
||||||
|
list.forEach((item: LocaleEntity) => {
|
||||||
|
if (!item.Lang || !item.Name || !item.Value) return;
|
||||||
|
const key = item.GroupName ? `${item.GroupName}.${item.Name}` : item.Name;
|
||||||
|
if (!grouped[item.Lang]) grouped[item.Lang] = {};
|
||||||
|
set(grouped[item.Lang], key, item.Value);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 合并进对应语言的翻译,覆盖同名 key
|
||||||
|
Object.entries(grouped).forEach(([lang, messages]) => {
|
||||||
|
i18n.global.mergeLocaleMessage(lang, messages);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('加载数据库翻译失败,使用本地翻译文件', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default i18n;
|
||||||
@@ -5,6 +5,7 @@ import App from "./App.vue";
|
|||||||
import { setupStore } from "./store";
|
import { setupStore } from "./store";
|
||||||
//使用了Top await
|
//使用了Top await
|
||||||
import { setupRouter } from "./router";
|
import { setupRouter } from "./router";
|
||||||
|
import { mergeDbLocaleMessages } from "./locales";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
setupAntd,
|
setupAntd,
|
||||||
@@ -32,8 +33,11 @@ function setupPlugins() {
|
|||||||
async function setupApp() {
|
async function setupApp() {
|
||||||
// 挂载pinia状态管理
|
// 挂载pinia状态管理
|
||||||
setupStore(app);
|
setupStore(app);
|
||||||
|
// 并行加载数据库 locale 表内容作为中英文翻译补充
|
||||||
|
const dbLocale = mergeDbLocaleMessages();
|
||||||
// 挂载路由
|
// 挂载路由
|
||||||
await setupRouter(app);
|
await setupRouter(app);
|
||||||
|
await dbLocale;
|
||||||
|
|
||||||
app.mount("#app");
|
app.mount("#app");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,25 @@
|
|||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-button type="primary" html-type="submit" size="large" block> 登录 </a-button>
|
<a-button type="primary" html-type="submit" size="large" block> 登录 </a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<div class="culture-switch">
|
||||||
|
<a-button
|
||||||
|
type="text"
|
||||||
|
size="large"
|
||||||
|
:class="{ 'culture-active': currentLocale === 'zh-cn' }"
|
||||||
|
@click="changeLocale('zh-cn')"
|
||||||
|
>
|
||||||
|
{{ t('CulturePolicy.zh-cn') }}
|
||||||
|
</a-button>
|
||||||
|
<a-divider type="vertical" />
|
||||||
|
<a-button
|
||||||
|
type="text"
|
||||||
|
size="large"
|
||||||
|
:class="{ 'culture-active': currentLocale === 'en' }"
|
||||||
|
@click="changeLocale('en')"
|
||||||
|
>
|
||||||
|
{{ t('CulturePolicy.en') }}
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
<!-- Biometric (WebAuthn) — only visible if the user has a stored
|
<!-- Biometric (WebAuthn) — only visible if the user has a stored
|
||||||
credential on this device. The credential id is read from
|
credential on this device. The credential id is read from
|
||||||
localStorage (key: biometric_<username>). -->
|
localStorage (key: biometric_<username>). -->
|
||||||
@@ -47,6 +66,8 @@
|
|||||||
style="width: 80px; margin-top: 16px; margin-left: 24px">
|
style="width: 80px; margin-top: 16px; margin-left: 24px">
|
||||||
首页
|
首页
|
||||||
</a-button>
|
</a-button>
|
||||||
|
|
||||||
|
|
||||||
</a-form>
|
</a-form>
|
||||||
</div>
|
</div>
|
||||||
</a-spin>
|
</a-spin>
|
||||||
@@ -73,6 +94,7 @@ const { t } = i18n.global;
|
|||||||
//const props = defineProps(['id'])
|
//const props = defineProps(['id'])
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const userLoginProfile = useLoginProfileStore();
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
loading: false,
|
loading: false,
|
||||||
@@ -82,6 +104,16 @@ const state = reactive({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 当前语言(默认取本地存储,其次中文)
|
||||||
|
const currentLocale = ref<string>(userLoginProfile.getLocale() ?? "zh-cn");
|
||||||
|
|
||||||
|
// 切换中英文并持久化
|
||||||
|
const changeLocale = (locale: string) => {
|
||||||
|
currentLocale.value = locale;
|
||||||
|
userLoginProfile.setLocale(locale);
|
||||||
|
(i18n.global.locale as any).value = locale;
|
||||||
|
};
|
||||||
|
|
||||||
const onRegister = () => {
|
const onRegister = () => {
|
||||||
router.push({ path: "/register" });
|
router.push({ path: "/register" });
|
||||||
};
|
};
|
||||||
@@ -101,7 +133,7 @@ const handleSubmit = async () => {
|
|||||||
UserMobile: state.LoginData.UserMobile,
|
UserMobile: state.LoginData.UserMobile,
|
||||||
UserPassword: "",
|
UserPassword: "",
|
||||||
Guid: uuidv4(),
|
Guid: uuidv4(),
|
||||||
Culture: "zh-cn",
|
Culture: currentLocale.value,
|
||||||
};
|
};
|
||||||
|
|
||||||
let key = await authService.GetRsaPublicKey();
|
let key = await authService.GetRsaPublicKey();
|
||||||
@@ -114,8 +146,6 @@ const handleSubmit = async () => {
|
|||||||
|
|
||||||
let profile: LoginProfile = await authService.Login(data);
|
let profile: LoginProfile = await authService.Login(data);
|
||||||
|
|
||||||
const userLoginProfile = useLoginProfileStore();
|
|
||||||
|
|
||||||
userLoginProfile.updateLoginProfile(profile);
|
userLoginProfile.updateLoginProfile(profile);
|
||||||
|
|
||||||
router.push({ path: "/dashboard" });
|
router.push({ path: "/dashboard" });
|
||||||
@@ -248,8 +278,6 @@ const onBiometricLogin = async () => {
|
|||||||
|
|
||||||
// 4. Persist the JWT locally and navigate. installProfile is the
|
// 4. Persist the JWT locally and navigate. installProfile is the
|
||||||
// same call LoginView makes after a successful password login.
|
// same call LoginView makes after a successful password login.
|
||||||
const userLoginProfile = useLoginProfileStore();
|
|
||||||
|
|
||||||
userLoginProfile.updateLoginProfile(profile);
|
userLoginProfile.updateLoginProfile(profile);
|
||||||
|
|
||||||
router.push({ path: "/dashboard" });
|
router.push({ path: "/dashboard" });
|
||||||
@@ -354,5 +382,21 @@ const onBiometricLogin = async () => {
|
|||||||
padding-left: 6px;
|
padding-left: 6px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.culture-switch {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 16px;
|
||||||
|
|
||||||
|
.ant-btn-text {
|
||||||
|
color: #08c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.culture-active {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1677ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
Reference in new issue
Block a user