From 0ac7636c74e74fcac77cf263355410a6ab50edf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=A5=E6=B5=A9?= Date: Wed, 22 Oct 2025 18:31:44 +0800 Subject: [PATCH] =?UTF-8?q?feat(i18n):=20=E9=9B=86=E6=88=90=20vue-i18n=20?= =?UTF-8?q?=E5=B9=B6=E9=85=8D=E7=BD=AE=E5=A4=9A=E8=AF=AD=E8=A8=80=E6=94=AF?= =?UTF-8?q?=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit 15c9509318dd7d48c081c4ae4846598d9affb344) --- .husky/pre-commit | 1 + .vscode/settings.json | 5 ++ package.json | 2 + pnpm-lock.yaml | 107 +++++++++++++++++++++++++++++ src/locales/en-US.json | 10 +++ src/locales/zh-CN.json | 10 +++ src/pages/demos/i18n-demo.page.vue | 34 +++++++++ typed-router.d.ts | 13 ++++ vite.config.plugins.ts | 4 +- 9 files changed, 184 insertions(+), 2 deletions(-) create mode 100644 src/locales/en-US.json create mode 100644 src/locales/zh-CN.json create mode 100644 src/pages/demos/i18n-demo.page.vue diff --git a/.husky/pre-commit b/.husky/pre-commit index f1780a6..49d73da 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,5 @@ # 此钩子在执行 git commit 命令时,在创建提交之前运行。 echo "🧹 [Pre-commit] 正在运行 lint-staged..." time pnpm exec lint-staged +time pnpm run lint:vue-i18n-extract echo "🧹 [Pre-commit] lint-staged 完成!" diff --git a/.vscode/settings.json b/.vscode/settings.json index 54e0f92..0adeccd 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -18,6 +18,11 @@ }, "editor.defaultFormatter": "esbenp.prettier-vscode", + "i18n-ally.localesPaths": ["src/locales"], + "i18n-ally.sourceLanguage": "zh-CN", // 翻译源语言 (源文件) 根据此语言文件翻译其他语言文件的变量和内容 + "i18n-ally.displayLanguage": "zh-CN", // 显示语言 (显示文件/翻译文件) + "i18n-ally.keystyle": "nested", + "typescript.tsdk": "node_modules/typescript/lib", "typescript.preferences.autoImportFileExcludePatterns": ["vue-router/auto$"] } diff --git a/package.json b/package.json index 5044aa2..bce4a28 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "format:prettier": "prettier --write src/", "type-check": "vue-tsc --build", "lint": "run-s lint:*", + "lint:vue-i18n-extract": "vue-i18n-extract report --vueFiles './src/**/*.?(ts|tsx|vue)' --languageFiles './src/locales/*.?(json|yml|yaml|js)' --ci", "lint:stylelint": "stylelint \"**/*.{css,less,scss,vue}\" --fix --ignore-path .gitignore", "lint:oxlint": "oxlint . --fix -D correctness --ignore-path .gitignore", "lint:eslint": "eslint . --fix", @@ -121,6 +122,7 @@ "vite-plugin-vue-meta-layouts": "^0.6.1", "vite-plugin-webfont-dl": "^3.11.1", "vitest": "^3.2.4", + "vue-i18n-extract": "^2.0.7", "vue-macros": "3.1.1", "vue-tsc": "^3.1.0", "wrangler": "^4.37.1" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7c25507..a943ada 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -246,6 +246,9 @@ importers: vitest: specifier: ^3.2.4 version: 3.2.4(@types/node@22.18.11)(happy-dom@20.0.1)(jiti@2.6.1)(jsdom@27.0.0(postcss@8.5.6))(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1) + vue-i18n-extract: + specifier: ^2.0.7 + version: 2.0.7 vue-macros: specifier: 3.1.1 version: 3.1.1(@vueuse/core@13.9.0(vue@3.5.22(typescript@5.9.2)))(esbuild@0.25.11)(rollup@4.52.4)(typescript@5.9.2)(vite@7.1.10(@types/node@22.18.11)(jiti@2.6.1)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1))(vue-tsc@3.1.1(typescript@5.9.2))(vue@3.5.22(typescript@5.9.2)) @@ -2816,6 +2819,10 @@ packages: commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + commander@6.2.1: + resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} + engines: {node: '>= 6'} + compare-func@2.0.0: resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} @@ -2995,6 +3002,10 @@ packages: dot-case@3.0.4: resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} + dot-object@2.1.5: + resolution: {integrity: sha512-xHF8EP4XH/Ba9fvAF2LDd5O3IITVolerVV6xvkxoM8zlGEiCUrggpAnHyOoKJKCrhvPcGATFAUwIujj7bRG5UA==} + hasBin: true + dot-prop@5.3.0: resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} engines: {node: '>=8'} @@ -3301,6 +3312,9 @@ packages: resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} engines: {node: '>= 6'} + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -3361,6 +3375,15 @@ packages: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + deprecated: Glob versions prior to v9 are no longer supported + global-directory@4.0.1: resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==} engines: {node: '>=18'} @@ -3503,6 +3526,13 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} @@ -3569,6 +3599,10 @@ packages: resolution: {integrity: sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==} engines: {node: '>=8'} + is-valid-glob@1.0.0: + resolution: {integrity: sha512-AhiROmoEFDSsjx8hW+5sGwgKVIORcXnrlAx/R0ZSeaPw70Vw0CqkGBBhHGL58Uox2eXnU1AnvXJl1XlyedO5bA==} + engines: {node: '>=0.10.0'} + is-what@4.1.16: resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} engines: {node: '>=12.13'} @@ -3921,6 +3955,10 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + minimatch@9.0.1: resolution: {integrity: sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==} engines: {node: '>=16 || 14 >=14.17'} @@ -4029,6 +4067,9 @@ packages: ohash@2.0.11: resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + onetime@7.0.0: resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} engines: {node: '>=18'} @@ -4108,6 +4149,10 @@ packages: resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -5228,6 +5273,10 @@ packages: vue-flow-layout@0.2.0: resolution: {integrity: sha512-zKgsWWkXq0xrus7H4Mc+uFs1ESrmdTXlO0YNbR6wMdPaFvosL3fMB8N7uTV308UhGy9UvTrGhIY7mVz9eN+L0Q==} + vue-i18n-extract@2.0.7: + resolution: {integrity: sha512-i1NW5R58S720iQ1BEk+6ILo3hT6UA8mtYNNolSH4rt9345qvXdvA6GHy2+jHozdDAKHwlu9VvS/+vIMKs1UYQw==} + hasBin: true + vue-i18n@11.1.12: resolution: {integrity: sha512-BnstPj3KLHLrsqbVU2UOrPmr0+Mv11bsUZG0PyCOzsawCivk8W00GMXHeVUWIDOgNaScCuZah47CZFE+Wnl8mw==} engines: {node: '>= 16'} @@ -5346,6 +5395,9 @@ packages: resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} engines: {node: '>=18'} + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + write-file-atomic@5.0.1: resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -7924,6 +7976,8 @@ snapshots: commander@2.20.3: {} + commander@6.2.1: {} + compare-func@2.0.0: dependencies: array-ify: 1.0.0 @@ -8085,6 +8139,11 @@ snapshots: no-case: 3.0.4 tslib: 2.8.1 + dot-object@2.1.5: + dependencies: + commander: 6.2.1 + glob: 7.2.3 + dot-prop@5.3.0: dependencies: is-obj: 2.0.0 @@ -8454,6 +8513,8 @@ snapshots: hasown: 2.0.2 mime-types: 2.1.35 + fs.realpath@1.0.0: {} + fsevents@2.3.2: optional: true @@ -8517,6 +8578,23 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + glob@8.1.0: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.6 + once: 1.4.0 + global-directory@4.0.1: dependencies: ini: 4.1.1 @@ -8656,6 +8734,13 @@ snapshots: imurmurhash@0.1.4: {} + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + ini@1.3.8: {} ini@4.1.1: {} @@ -8700,6 +8785,8 @@ snapshots: dependencies: text-extensions: 2.4.0 + is-valid-glob@1.0.0: {} + is-what@4.1.16: {} is-wsl@3.1.0: @@ -9050,6 +9137,10 @@ snapshots: dependencies: brace-expansion: 1.1.12 + minimatch@5.1.6: + dependencies: + brace-expansion: 2.0.2 + minimatch@9.0.1: dependencies: brace-expansion: 2.0.2 @@ -9164,6 +9255,10 @@ snapshots: ohash@2.0.11: {} + once@1.4.0: + dependencies: + wrappy: 1.0.2 + onetime@7.0.0: dependencies: mimic-function: 5.0.1 @@ -9290,6 +9385,8 @@ snapshots: path-exists@5.0.0: {} + path-is-absolute@1.0.1: {} + path-key@3.1.1: {} path-key@4.0.0: {} @@ -10417,6 +10514,14 @@ snapshots: vue-flow-layout@0.2.0: {} + vue-i18n-extract@2.0.7: + dependencies: + cac: 6.7.14 + dot-object: 2.1.5 + glob: 8.1.0 + is-valid-glob: 1.0.0 + js-yaml: 4.1.0 + vue-i18n@11.1.12(vue@3.5.22(typescript@5.9.2)): dependencies: '@intlify/core-base': 11.1.12 @@ -10592,6 +10697,8 @@ snapshots: string-width: 7.2.0 strip-ansi: 7.1.2 + wrappy@1.0.2: {} + write-file-atomic@5.0.1: dependencies: imurmurhash: 0.1.4 diff --git a/src/locales/en-US.json b/src/locales/en-US.json new file mode 100644 index 0000000..dd704a3 --- /dev/null +++ b/src/locales/en-US.json @@ -0,0 +1,10 @@ +{ + "page": { + "i18n-demo": { + "title": "Vue I18n Demo", + "current-language": "Current Language", + "change-language": "Change Language", + "hello": "Hello, {name}!" + } + } +} diff --git a/src/locales/zh-CN.json b/src/locales/zh-CN.json new file mode 100644 index 0000000..13221ef --- /dev/null +++ b/src/locales/zh-CN.json @@ -0,0 +1,10 @@ +{ + "page": { + "i18n-demo": { + "title": "Vue I18n 示例", + "current-language": "当前语言", + "change-language": "切换语言", + "hello": "你好, {name}!" + } + } +} \ No newline at end of file diff --git a/src/pages/demos/i18n-demo.page.vue b/src/pages/demos/i18n-demo.page.vue new file mode 100644 index 0000000..d7594ea --- /dev/null +++ b/src/pages/demos/i18n-demo.page.vue @@ -0,0 +1,34 @@ + + + diff --git a/typed-router.d.ts b/typed-router.d.ts index 0359e02..9275fde 100644 --- a/typed-router.d.ts +++ b/typed-router.d.ts @@ -58,6 +58,13 @@ declare module 'vue-router/auto-routes' { Record, | never >, + 'DemosI18nDemo': RouteRecordInfo< + 'DemosI18nDemo', + '/demos/i18n-demo', + Record, + Record, + | never + >, 'DemosWebsocketDemo': RouteRecordInfo< 'DemosWebsocketDemo', '/demos/websocket-demo', @@ -115,6 +122,12 @@ declare module 'vue-router/auto-routes' { views: | never } + 'src/pages/demos/i18n-demo.page.vue': { + routes: + | 'DemosI18nDemo' + views: + | never + } 'src/pages/demos/websocket-demo.page.vue': { routes: | 'DemosWebsocketDemo' diff --git a/vite.config.plugins.ts b/vite.config.plugins.ts index 9c1d20a..d53ccd3 100644 --- a/vite.config.plugins.ts +++ b/vite.config.plugins.ts @@ -1,5 +1,5 @@ import { cloudflare } from '@cloudflare/vite-plugin'; -import VueI18n from '@intlify/unplugin-vue-i18n/vite'; +import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite'; import { PrimeVueResolver } from '@primevue/auto-import-resolver'; import { VantResolver } from '@vant/auto-import-resolver'; import vue from '@vitejs/plugin-vue'; @@ -146,7 +146,7 @@ export function Plugins({ }), // https://github.com/intlify/bundle-tools/tree/main/packages/unplugin-vue-i18n - VueI18n({ + VueI18nPlugin({ /* options */ // locale messages resource pre-compile option include: [path.resolve(import.meta.dirname, './src/locales/**')],