From 22bf50f87798864cf53a481bc167aff2b8bd3a60 Mon Sep 17 00:00:00 2001 From: ghostubborn Date: Wed, 1 Apr 2026 15:22:14 +0800 Subject: [PATCH] feat(i18n): set up vue-i18n with dynamic locale loading --- frontend/index.html | 2 +- frontend/package-lock.json | 66 ++++++++++++++++++++++++++++++++++++++ frontend/package.json | 1 + frontend/src/i18n/index.js | 27 ++++++++++++++++ frontend/src/main.js | 2 ++ frontend/vite.config.js | 6 ++++ 6 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 frontend/src/i18n/index.js diff --git a/frontend/index.html b/frontend/index.html index 009c924..f607099 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,5 +1,5 @@ - + diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 8c4fa71..3edf339 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -11,6 +11,7 @@ "axios": "^1.13.2", "d3": "^7.9.0", "vue": "^3.5.24", + "vue-i18n": "^9.14.5", "vue-router": "^4.6.3" }, "devDependencies": { @@ -506,6 +507,50 @@ "node": ">=18" } }, + "node_modules/@intlify/core-base": { + "version": "9.14.5", + "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.14.5.tgz", + "integrity": "sha512-5ah5FqZG4pOoHjkvs8mjtv+gPKYU0zCISaYNjBNNqYiaITxW8ZtVih3GS/oTOqN8d9/mDLyrjD46GBApNxmlsA==", + "license": "MIT", + "dependencies": { + "@intlify/message-compiler": "9.14.5", + "@intlify/shared": "9.14.5" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/message-compiler": { + "version": "9.14.5", + "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.14.5.tgz", + "integrity": "sha512-IHzgEu61/YIpQV5Pc3aRWScDcnFKWvQA9kigcINcCBXN8mbW+vk9SK+lDxA6STzKQsVJxUPg9ACC52pKKo3SVQ==", + "license": "MIT", + "dependencies": { + "@intlify/shared": "9.14.5", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/shared": { + "version": "9.14.5", + "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.14.5.tgz", + "integrity": "sha512-9gB+E53BYuAEMhbCAxVgG38EZrk59sxBtv3jSizNL2hEWlgjBjAw1AwpLHtNaeda12pe6W20OGEa0TwuMSRbyQ==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -2035,6 +2080,27 @@ } } }, + "node_modules/vue-i18n": { + "version": "9.14.5", + "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.14.5.tgz", + "integrity": "sha512-0jQ9Em3ymWngyiIkj0+c/k7WgaPO+TNzjKSNq9BvBQaKJECqn9cd9fL4tkDhB5G1QBskGl9YxxbDAhgbFtpe2g==", + "deprecated": "v9 and v10 no longer supported. please migrate to v11. about maintenance status, see https://vue-i18n.intlify.dev/guide/maintenance.html", + "license": "MIT", + "dependencies": { + "@intlify/core-base": "9.14.5", + "@intlify/shared": "9.14.5", + "@vue/devtools-api": "^6.5.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, "node_modules/vue-router": { "version": "4.6.3", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.3.tgz", diff --git a/frontend/package.json b/frontend/package.json index f7e995a..7e05e89 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,6 +12,7 @@ "axios": "^1.13.2", "d3": "^7.9.0", "vue": "^3.5.24", + "vue-i18n": "^9.14.5", "vue-router": "^4.6.3" }, "devDependencies": { diff --git a/frontend/src/i18n/index.js b/frontend/src/i18n/index.js new file mode 100644 index 0000000..aa26553 --- /dev/null +++ b/frontend/src/i18n/index.js @@ -0,0 +1,27 @@ +import { createI18n } from 'vue-i18n' +import languages from '../../../locales/languages.json' + +const localeFiles = import.meta.glob('../../../locales/!(languages).json', { eager: true }) + +const messages = {} +const availableLocales = [] + +for (const path in localeFiles) { + const key = path.match(/\/([^/]+)\.json$/)[1] + if (languages[key]) { + messages[key] = localeFiles[path].default + availableLocales.push({ key, label: languages[key].label }) + } +} + +const savedLocale = localStorage.getItem('locale') || 'zh' + +const i18n = createI18n({ + legacy: false, + locale: savedLocale, + fallbackLocale: 'zh', + messages +}) + +export { availableLocales } +export default i18n diff --git a/frontend/src/main.js b/frontend/src/main.js index c8e37b0..cc3d101 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -1,9 +1,11 @@ import { createApp } from 'vue' import App from './App.vue' import router from './router' +import i18n from './i18n' const app = createApp(App) app.use(router) +app.use(i18n) app.mount('#app') diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 7cec1a7..f88fdea 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -1,9 +1,15 @@ import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' +import path from 'path' // https://vite.dev/config/ export default defineConfig({ plugins: [vue()], + resolve: { + alias: { + '@locales': path.resolve(__dirname, '../locales') + } + }, server: { port: 3000, open: true,