feat: locale system

This commit is contained in:
2026-02-02 10:00:51 +08:00
parent 9c82e12144
commit 1f59e81c52
11 changed files with 102 additions and 11 deletions

View File

@@ -19,6 +19,7 @@
"reka-ui": "^2.8.0",
"tailwind-merge": "^3.4.0",
"vue": "^3.5.17",
"vue-i18n": "11",
"vue-router": "^4.5.1",
},
"devDependencies": {
@@ -209,6 +210,12 @@
"@internationalized/number": ["@internationalized/number@3.6.5", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-6hY4Kl4HPBvtfS62asS/R22JzNNy8vi/Ssev7x6EobfCp+9QIB2hKvI2EtbdJ0VSQacxVNtqhE/NmF/NZ0gm6g=="],
"@intlify/core-base": ["@intlify/core-base@11.2.8", "", { "dependencies": { "@intlify/message-compiler": "11.2.8", "@intlify/shared": "11.2.8" } }, "sha512-nBq6Y1tVkjIUsLsdOjDSJj4AsjvD0UG3zsg9Fyc+OivwlA/oMHSKooUy9tpKj0HqZ+NWFifweHavdljlBLTwdA=="],
"@intlify/message-compiler": ["@intlify/message-compiler@11.2.8", "", { "dependencies": { "@intlify/shared": "11.2.8", "source-map-js": "^1.0.2" } }, "sha512-A5n33doOjmHsBtCN421386cG1tWp5rpOjOYPNsnpjIJbQ4POF0QY2ezhZR9kr0boKwaHjbOifvyQvHj2UTrDFQ=="],
"@intlify/shared": ["@intlify/shared@11.2.8", "", {}, "sha512-l6e4NZyUgv8VyXXH4DbuucFOBmxLF56C/mqh2tvApbzl2Hrhi1aTDcuv5TKdxzfHYmpO3UB0Cz04fgDT9vszfw=="],
"@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="],
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
@@ -1149,6 +1156,8 @@
"vue-eslint-parser": ["vue-eslint-parser@10.2.0", "", { "dependencies": { "debug": "^4.4.0", "eslint-scope": "^8.2.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.6.0", "semver": "^7.6.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0" } }, "sha512-CydUvFOQKD928UzZhTp4pr2vWz1L+H99t7Pkln2QSPdvmURT0MoC4wUccfCnuEaihNsu9aYYyk+bep8rlfkUXw=="],
"vue-i18n": ["vue-i18n@11.2.8", "", { "dependencies": { "@intlify/core-base": "11.2.8", "@intlify/shared": "11.2.8", "@vue/devtools-api": "^6.5.0" }, "peerDependencies": { "vue": "^3.0.0" } }, "sha512-vJ123v/PXCZntd6Qj5Jumy7UBmIuE92VrtdX+AXr+1WzdBHojiBxnAxdfctUFL+/JIN+VQH4BhsfTtiGsvVObg=="],
"vue-router": ["vue-router@4.5.1", "", { "dependencies": { "@vue/devtools-api": "^6.6.4" }, "peerDependencies": { "vue": "^3.2.0" } }, "sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw=="],
"vue-tsc": ["vue-tsc@2.2.12", "", { "dependencies": { "@volar/typescript": "2.4.15", "@vue/language-core": "2.2.12" }, "peerDependencies": { "typescript": ">=5.0.0" }, "bin": { "vue-tsc": "./bin/vue-tsc.js" } }, "sha512-P7OP77b2h/Pmk+lZdJ0YWs+5tJ6J2+uOQPo7tlBnY44QqQSPYvS0qVT4wqDJgwrZaLe47etJLLQRFia71GYITw=="],
@@ -1237,6 +1246,8 @@
"vue-eslint-parser/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"vue-i18n/@vue/devtools-api": ["@vue/devtools-api@6.6.4", "", {}, "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="],
"vue-router/@vue/devtools-api": ["@vue/devtools-api@6.6.4", "", {}, "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],

View File

@@ -30,6 +30,7 @@
"reka-ui": "^2.8.0",
"tailwind-merge": "^3.4.0",
"vue": "^3.5.17",
"vue-i18n": "11",
"vue-router": "^4.5.1"
},
"devDependencies": {

View File

@@ -1,4 +1,12 @@
<script lang="ts" setup></script>
<script lang="ts" setup>
import { useI18n } from "vue-i18n";
import { useLanguageStore } from "./stores/LanguageStore";
onMounted(() => {
// 加载 i18n
useI18n().locale.value = useLanguageStore().language;
});
</script>
<template>
<div>

View File

@@ -1,8 +1,15 @@
<script setup lang="ts">
import { useGlobalLanguageHook } from "@/hooks/globalLanguageHook";
import { useI18n } from "vue-i18n";
const { changeGlobalLanguage, curGlobalLanguage, optionalLanguages } =
useGlobalLanguageHook();
const { locale, t } = useI18n();
const onClickChangeLocal = (newLanguage: string) => {
changeGlobalLanguage(newLanguage, locale);
};
</script>
<template>
@@ -41,7 +48,7 @@ const { changeGlobalLanguage, curGlobalLanguage, optionalLanguages } =
class="dropdown-content bg-base-200 text-base-content rounded-box top-px mt-14 w-56 overflow-y-auto border-[length:var(--border)] border-white/5 shadow-2xl outline-[length:var(--border)] outline-black/5"
>
<ul class="menu menu-sm w-full">
<li class="menu-title text-xs">语言</li>
<li class="menu-title text-xs">{{ t("nav.locale") }}</li>
<li
v-for="optionalLanguage in optionalLanguages"
:key="optionalLanguage.label"
@@ -50,7 +57,7 @@ const { changeGlobalLanguage, curGlobalLanguage, optionalLanguages } =
:class="[
curGlobalLanguage === optionalLanguage.label ? 'menu-active' : '',
]"
@click="changeGlobalLanguage(optionalLanguage.label)"
@click="onClickChangeLocal(optionalLanguage.label)"
>
<span class="pe-4 font-mono font-bold opacity-40">
{{ optionalLanguage.label }}

View File

@@ -1,5 +1,8 @@
<script setup lang="ts">
import { useGlobalThemeHook } from "@/hooks/globalThemeHook";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const { changeGlobalTheme, curGlobalTheme, optionalThemes } =
useGlobalThemeHook();
@@ -31,7 +34,7 @@ const { changeGlobalTheme, curGlobalTheme, optionalThemes } =
tabindex="-1"
class="dropdown-content bg-base-300 rounded-box z-1 w-52 p-2 shadow-2xl max-h-84 overflow-auto mt-6"
>
<li class="menu-title text-xs my-1">主题</li>
<li class="menu-title text-xs my-1">{{ t("nav.theme") }}</li>
<li
v-for="optionalTheme in optionalThemes"
:key="optionalTheme"

View File

@@ -1,6 +1,9 @@
<script setup lang="ts">
import ChangeLanguageDropdownButton from "@/components/button/ChangeLanguageDropdownButton.vue";
import ChangeThemeDropdownButton from "@/components/button/ChangeThemeDropdownButton.vue";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
</script>
<template>
@@ -27,16 +30,24 @@ import ChangeThemeDropdownButton from "@/components/button/ChangeThemeDropdownBu
tabindex="-1"
class="menu dropdown-content bg-base-100 rounded-box z-1 mt-5 w-52 p-2 shadow"
>
<li><a>首页</a></li>
<li><a>关于</a></li>
<li>
<a>{{ t("nav.home") }}</a>
</li>
<li>
<a>{{ t("nav.about") }}</a>
</li>
</ul>
</div>
<a class="btn btn-ghost text-xl">daisyUI</a>
<a class="btn btn-ghost text-xl">Hucky</a>
</div>
<div class="navbar-center hidden lg:flex">
<ul class="menu menu-horizontal px-1">
<li><a>首页</a></li>
<li><a>关于</a></li>
<li>
<a>{{ t("nav.home") }}</a>
</li>
<li>
<a>{{ t("nav.about") }}</a>
</li>
</ul>
</div>
<div class="navbar-end">

View File

@@ -3,6 +3,7 @@ import {
GlobalLanguage,
languageMap,
} from "@/stores/LanguageStore";
import { Locale } from "vue-i18n";
const languageStore = useLanguageStore();
@@ -16,8 +17,12 @@ export const optionalLanguages = Object.entries(languageMap).map(
// 一定不要通过改变 curGlobalLanguage 来改变语言,而要通过 changeGlobalLanguage 来改变语言
const curGlobalLanguage = computed(() => languageStore.language);
function changeGlobalLanguage(language: GlobalLanguage) {
function changeGlobalLanguage(
language: GlobalLanguage,
locale: WritableComputedRef<string>,
) {
languageStore.setLanguage(language);
locale.value = language;
}
export const useGlobalLanguageHook = () => ({

10
src/i18n/en_US.ts Normal file
View File

@@ -0,0 +1,10 @@
import { messagesInterface } from ".";
export const en_USMessages: messagesInterface = {
nav: {
home: "Home",
about: "About",
theme: "Theme",
locale: "Locale",
},
};

24
src/i18n/index.ts Normal file
View File

@@ -0,0 +1,24 @@
import { createI18n } from "vue-i18n";
import { zh_CNMessages } from "./zh_CN";
import { en_USMessages } from "./en_US";
export interface messagesInterface {
nav: {
home: string;
about: string;
theme: string;
locale: string;
};
}
const i18n = createI18n({
legacy: false,
locale: "ZH",
fallbackLocale: "EN",
messages: {
ZH: zh_CNMessages,
EN: en_USMessages,
},
});
export default i18n;

10
src/i18n/zh_CN.ts Normal file
View File

@@ -0,0 +1,10 @@
import { messagesInterface } from ".";
export const zh_CNMessages: messagesInterface = {
nav: {
home: "首页",
about: "关于",
theme: "主题",
locale: "语言",
},
};

View File

@@ -2,10 +2,11 @@ import { createApp } from "vue";
import "./style.css";
import App from "./App.vue";
import router from "./router";
import i18n from "./i18n";
import { createPinia } from "pinia";
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);
createApp(App).use(router).use(pinia).mount("#app");
createApp(App).use(router).use(pinia).use(i18n).mount("#app");