feat(i18n): 添加国际化支持,更新语言切换功能
All checks were successful
/ build-and-deploy-to-vercel (push) Successful in 2m20s
/ depcheck (push) Successful in 2m27s
/ playwright (push) Successful in 2m1s

This commit is contained in:
严浩
2024-11-06 17:44:32 +08:00
parent e2f60c32a3
commit 40752e555f
6 changed files with 94 additions and 17 deletions

2
.npmrc
View File

@ -5,7 +5,7 @@ registry=https://mirrors.cloud.tencent.com/npm/
registry=https://mirrors.huaweicloud.com/repository/npm/ registry=https://mirrors.huaweicloud.com/repository/npm/
# https://pnpm.io/zh/npmrc#node-mirrorltreleasedir # https://pnpm.io/zh/npmrc#node-mirrorltreleasedir
# use-node-version=20.18.0 use-node-version=22.11.0
node-mirror:release=https://npmmirror.com/mirrors/node/ # pnpm config set node-mirror:release=https://npmmirror.com/mirrors/node/ node-mirror:release=https://npmmirror.com/mirrors/node/ # pnpm config set node-mirror:release=https://npmmirror.com/mirrors/node/
node-mirror:rc=https://npmmirror.com/mirrors/node-rc/ node-mirror:rc=https://npmmirror.com/mirrors/node-rc/
node-mirror:nightly=https://npmmirror.com/mirrors/node-nightly/ node-mirror:nightly=https://npmmirror.com/mirrors/node-nightly/

View File

@ -24,7 +24,9 @@
] ]
}, },
"pnpm": { "pnpm": {
"patchedDependencies": {} "overrides": {
"import-meta-resolve": "^4.1.0"
}
}, },
"dependencies": { "dependencies": {
"@alova/adapter-axios": "^2.0.9", "@alova/adapter-axios": "^2.0.9",
@ -49,6 +51,7 @@
"vant": "^4.9.8", "vant": "^4.9.8",
"vite-plugin-webfont-dl": "^3.9.5", "vite-plugin-webfont-dl": "^3.9.5",
"vue": "^3.5.12", "vue": "^3.5.12",
"vue-i18n": "10",
"vue-page-stack": "^3.2.0", "vue-page-stack": "^3.2.0",
"vue-router": "^4.4.5" "vue-router": "^4.4.5"
}, },

53
pnpm-lock.yaml generated
View File

@ -4,6 +4,9 @@ settings:
autoInstallPeers: true autoInstallPeers: true
excludeLinksFromLockfile: false excludeLinksFromLockfile: false
overrides:
import-meta-resolve: ^4.1.0
importers: importers:
.: .:
@ -74,6 +77,9 @@ importers:
vue: vue:
specifier: ^3.5.12 specifier: ^3.5.12
version: 3.5.12(typescript@5.6.3) version: 3.5.12(typescript@5.6.3)
vue-i18n:
specifier: '10'
version: 10.0.4(vue@3.5.12(typescript@5.6.3))
vue-page-stack: vue-page-stack:
specifier: ^3.2.0 specifier: ^3.2.0
version: 3.2.0(vue-router@4.4.5(vue@3.5.12(typescript@5.6.3)))(vue@3.5.12(typescript@5.6.3)) version: 3.2.0(vue-router@4.4.5(vue@3.5.12(typescript@5.6.3)))(vue@3.5.12(typescript@5.6.3))
@ -966,6 +972,18 @@ packages:
'@iconify/utils@2.1.33': '@iconify/utils@2.1.33':
resolution: {integrity: sha512-jP9h6v/g0BIZx0p7XGJJVtkVnydtbgTgt9mVNcGDYwaa7UhdHdI9dvoq+gKj9sijMSJKxUPEG2JyjsgXjxL7Kw==} resolution: {integrity: sha512-jP9h6v/g0BIZx0p7XGJJVtkVnydtbgTgt9mVNcGDYwaa7UhdHdI9dvoq+gKj9sijMSJKxUPEG2JyjsgXjxL7Kw==}
'@intlify/core-base@10.0.4':
resolution: {integrity: sha512-GG428DkrrWCMhxRMRQZjuS7zmSUzarYcaHJqG9VB8dXAxw4iQDoKVQ7ChJRB6ZtsCsX3Jse1PEUlHrJiyQrOTg==}
engines: {node: '>= 16'}
'@intlify/message-compiler@10.0.4':
resolution: {integrity: sha512-AFbhEo10DP095/45EauinQJ5hJ3rJUmuuqltGguvc3WsvezZN+g8qNHLGWKu60FHQVizMrQY7VJ+zVlBXlQQkQ==}
engines: {node: '>= 16'}
'@intlify/shared@10.0.4':
resolution: {integrity: sha512-ukFn0I01HsSgr3VYhYcvkTCLS7rGa0gw4A4AMpcy/A9xx/zRJy7PS2BElMXLwUazVFMAr5zuiTk3MQeoeGXaJg==}
engines: {node: '>= 16'}
'@isaacs/cliui@8.0.2': '@isaacs/cliui@8.0.2':
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3149,9 +3167,6 @@ packages:
import-from-string@0.0.4: import-from-string@0.0.4:
resolution: {integrity: sha512-ZmtWHOGv55OEFb3HxfQH4L4vAR7g3HUm82N5LmvXugiXlaZ1j/epItoUDjQ+gJ+MjNl+apczmCnqGFq8q2CM6w==} resolution: {integrity: sha512-ZmtWHOGv55OEFb3HxfQH4L4vAR7g3HUm82N5LmvXugiXlaZ1j/epItoUDjQ+gJ+MjNl+apczmCnqGFq8q2CM6w==}
import-meta-resolve@3.1.1:
resolution: {integrity: sha512-qeywsE/KC3w9Fd2ORrRDUw6nS/nLwZpXgfrOc2IILvZYnCaEMd+D56Vfg9k4G29gIeVi3XKql1RQatME8iYsiw==}
import-meta-resolve@4.1.0: import-meta-resolve@4.1.0:
resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==} resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==}
@ -4889,6 +4904,12 @@ packages:
vue-flow-layout@0.0.5: vue-flow-layout@0.0.5:
resolution: {integrity: sha512-lZlqQ/Se1trGMtBMneZDWaiQiQBuxU8ivZ+KpJMem5zKROFpzuPq9KqyWABbSYbxq0qhqZs1I4DBwrY041rtOA==} resolution: {integrity: sha512-lZlqQ/Se1trGMtBMneZDWaiQiQBuxU8ivZ+KpJMem5zKROFpzuPq9KqyWABbSYbxq0qhqZs1I4DBwrY041rtOA==}
vue-i18n@10.0.4:
resolution: {integrity: sha512-1xkzVxqBLk2ZFOmeI+B5r1J7aD/WtNJ4j9k2mcFcQo5BnOmHBmD7z4/oZohh96AAaRZ4Q7mNQvxc9h+aT+Md3w==}
engines: {node: '>= 16'}
peerDependencies:
vue: ^3.0.0
vue-page-stack@3.2.0: vue-page-stack@3.2.0:
resolution: {integrity: sha512-MSv1usz6BdfyIH1JLd+9RgQPl42lMl3k9VGnXPq90odG31Y8l62gFjZaVSy/URJSc0Ya46TBX4FwrpJ4odon9A==} resolution: {integrity: sha512-MSv1usz6BdfyIH1JLd+9RgQPl42lMl3k9VGnXPq90odG31Y8l62gFjZaVSy/URJSc0Ya46TBX4FwrpJ4odon9A==}
peerDependencies: peerDependencies:
@ -5687,6 +5708,18 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@intlify/core-base@10.0.4':
dependencies:
'@intlify/message-compiler': 10.0.4
'@intlify/shared': 10.0.4
'@intlify/message-compiler@10.0.4':
dependencies:
'@intlify/shared': 10.0.4
source-map-js: 1.2.1
'@intlify/shared@10.0.4': {}
'@isaacs/cliui@8.0.2': '@isaacs/cliui@8.0.2':
dependencies: dependencies:
string-width: 5.1.2 string-width: 5.1.2
@ -8384,12 +8417,9 @@ snapshots:
import-from-string@0.0.4: import-from-string@0.0.4:
dependencies: dependencies:
esbuild: 0.19.12 esbuild: 0.19.12
import-meta-resolve: 3.1.1 import-meta-resolve: 4.1.0
import-meta-resolve@3.1.1: {} import-meta-resolve@4.1.0: {}
import-meta-resolve@4.1.0:
optional: true
importx@0.4.4: importx@0.4.4:
dependencies: dependencies:
@ -10276,6 +10306,13 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- typescript - typescript
vue-i18n@10.0.4(vue@3.5.12(typescript@5.6.3)):
dependencies:
'@intlify/core-base': 10.0.4
'@intlify/shared': 10.0.4
'@vue/devtools-api': 6.6.4
vue: 3.5.12(typescript@5.6.3)
vue-page-stack@3.2.0(vue-router@4.4.5(vue@3.5.12(typescript@5.6.3)))(vue@3.5.12(typescript@5.6.3)): vue-page-stack@3.2.0(vue-router@4.4.5(vue@3.5.12(typescript@5.6.3)))(vue@3.5.12(typescript@5.6.3)):
dependencies: dependencies:
'@vue/shared': 3.5.11 '@vue/shared': 3.5.11

View File

@ -4,6 +4,7 @@ import 'virtual:uno.css';
import { createHead } from '@unhead/vue'; import { createHead } from '@unhead/vue';
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'; import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
import { createI18n } from 'vue-i18n';
import { DataLoaderPlugin } from 'unplugin-vue-router/data-loaders'; import { DataLoaderPlugin } from 'unplugin-vue-router/data-loaders';
import App from './App.vue'; import App from './App.vue';
@ -29,7 +30,25 @@ async function init() {
// Register the plugin before the router // Register the plugin before the router
.use(DataLoaderPlugin, { router }) .use(DataLoaderPlugin, { router })
// adding the router will trigger the initial navigation // adding the router will trigger the initial navigation
.use(router); .use(router)
.use(
createI18n({
locale: 'zh_CN', // 默认显示语言
fallbackLocale: 'zh_CN',
messages: {
en_US: {
message: {
hello: 'hello',
},
},
zh_CN: {
message: {
hello: '你好',
},
},
},
}),
);
app.config.globalProperties.$__DEV__ = $__DEV__; app.config.globalProperties.$__DEV__ = $__DEV__;
app.mount('#app'); app.mount('#app');
} }

View File

@ -15,15 +15,23 @@ definePage({
}); });
import type { FunctionalComponent } from 'vue'; import type { FunctionalComponent } from 'vue';
import { useI18n } from 'vue-i18n';
const { locale } = useI18n();
const handleLanguageChange = (event: Event) => {
const select = event.target as HTMLSelectElement;
locale.value = select.value;
};
// https://cn.vuejs.org/guide/extras/render-function#typing-functional-components // https://cn.vuejs.org/guide/extras/render-function#typing-functional-components
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const FComponent: FunctionalComponent<{ prop: string }> = (props, context) => ( const FComponent: FunctionalComponent<{ prop: string }> = (props, context) => (
<> // <>
<div border="1 solid pink" text="pink"> <div border="1 solid pink" text="pink">
<span>This is a functional component with prop: {JSON.stringify(props)}</span> <span>This is a functional component with prop: {JSON.stringify(props)}</span>
</div> </div>
</> // </>
); );
</script> </script>
@ -36,7 +44,16 @@ const FComponent: FunctionalComponent<{ prop: string }> = (props, context) => (
<li><router-link :to="{ name: '中文页面' }">中文-页面.page.vue</router-link></li> <li><router-link :to="{ name: '中文页面' }">中文-页面.page.vue</router-link></li>
<li><router-link :to="{ name: 'Api' }">Api</router-link></li> <li><router-link :to="{ name: 'Api' }">Api</router-link></li>
</ul> </ul>
<FComponent prop="Hello World" />
<div b="1px solid pink" mt-8>
<select :value="locale" @change="handleLanguageChange">
<option value="zh_CN">中文</option>
<option value="en_US">English</option>
</select>
<h1>{{ $t('message.hello') }}</h1>
</div>
<FComponent prop="Hello World" style="margin-top: 8px"></FComponent>
<div text-orange></div> <div text-orange></div>
<div b="1px solid pink" mt-8> <div b="1px solid pink" mt-8>

1
typed-router.d.ts vendored
View File

@ -19,6 +19,7 @@ declare module 'vue-router/auto-routes' {
*/ */
export interface RouteNamedMap { export interface RouteNamedMap {
'中文页面': RouteRecordInfo<'中文页面', '/中文-页面', Record<never, never>, Record<never, never>>, '中文页面': RouteRecordInfo<'中文页面', '/中文-页面', Record<never, never>, Record<never, never>>,
'A': RouteRecordInfo<'A', '/a', Record<never, never>, Record<never, never>>,
'AA': RouteRecordInfo<'AA', '/a/a', Record<never, never>, Record<never, never>>, 'AA': RouteRecordInfo<'AA', '/a/a', Record<never, never>, Record<never, never>>,
'Api': RouteRecordInfo<'Api', '/api', Record<never, never>, Record<never, never>>, 'Api': RouteRecordInfo<'Api', '/api', Record<never, never>, Record<never, never>>,
'DataLoadersId': RouteRecordInfo<'DataLoadersId', '/data-loaders/:id', { id: ParamValue<true> }, { id: ParamValue<false> }>, 'DataLoadersId': RouteRecordInfo<'DataLoadersId', '/data-loaders/:id', { id: ParamValue<true> }, { id: ParamValue<false> }>,