diff --git a/.env b/.env
index 77ad0d3..b48df03 100644
--- a/.env
+++ b/.env
@@ -5,7 +5,7 @@ VITE_APP_NAME=Nova - Admin
 # 路由模式
 VITE_ROUTE_MODE = web
 # 权限路由模式: static | dynamic
-VITE_AUTH_ROUTE_MODE=dynamic
+VITE_AUTH_ROUTE_MODE=static
 
 # 设置登陆后跳转地址
 VITE_HOME_PATH = /dashboard/workbench
diff --git a/.vscode/settings.json b/.vscode/settings.json
index abd048b..b20e46b 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -65,5 +65,14 @@
     "jsonc",
     "yaml",
     "toml"
-  ]
+  ],
+  "i18n-ally.displayLanguage": "zh",
+  // "i18n-ally.enabledParsers": ["ts"],
+  "i18n-ally.enabledFrameworks": ["vue"],
+  "i18n-ally.editor.preferEditor": true,
+  "i18n-ally.keystyle": "nested",
+  "i18n-ally.localesPaths": [
+    "locales"
+  ],
+  "commentTranslate.source": "Google"
 }
diff --git a/build/plugins.ts b/build/plugins.ts
index 0e18d54..3ef7498 100644
--- a/build/plugins.ts
+++ b/build/plugins.ts
@@ -27,7 +27,7 @@ export function createVitePlugins(env: ImportMetaEnv) {
 
     // auto import api of lib
     AutoImport({
-      imports: ['vue', 'vue-router', 'pinia', '@vueuse/core'],
+      imports: ['vue', 'vue-router', 'pinia', '@vueuse/core', 'vue-i18n'],
       include: [
         /\.[tj]sx?$/,
         /\.vue$/,
diff --git a/locales/en.json b/locales/en.json
new file mode 100644
index 0000000..83b2af8
--- /dev/null
+++ b/locales/en.json
@@ -0,0 +1,119 @@
+{
+  "common": {
+    "cancel": "Cancel",
+    "confirm": "Confirm",
+    "close": "Closure",
+    "reload": "Refresh"
+  },
+  "app": {
+    "loginOut": "Login out",
+    "loginOutContent": "Confirm to log out of current account?",
+    "loginOutTitle": "Sign out",
+    "userCenter": "Personal center",
+    "lignt": "Light",
+    "dark": "Dark",
+    "system": "System",
+    "backTop": "Back to top",
+    "toggleSider": "Toggle sidebar",
+    "BreadcrumbIcon": "Breadcrumbs icon",
+    "blackAndWhite": "Black and white mode",
+    "bottomCopyright": "Bottom copyright",
+    "breadcrumb": "Bread crumbs",
+    "colorWeak": "Color Weakness Mode",
+    "interfaceDisplay": "Interface display",
+    "logoDisplay": "LOGO display",
+    "messages": "Messages",
+    "multitab": "Display multiple tabs",
+    "notifications": "Notify",
+    "notificationsTips": "Notification",
+    "pageTransition": "Page transition",
+    "reset": "Reset",
+    "resetSettingContent": "Confirm to reset all settings?",
+    "resetSettingMeaasge": "Reset successful",
+    "resetSettingTitle": "Reset settings",
+    "searchPlaceholder": "Search page/path",
+    "setting": "Setting",
+    "systemSetting": "System settings",
+    "themeColor": "Theme color",
+    "themeSetting": "Theme settings",
+    "todos": "Todos",
+    "toggleFullScreen": "Toggle full screen",
+    "topProgress": "Top progress",
+    "transitionFadeBottom": "Bottom fade",
+    "transitionFadeScale": "Scale fade",
+    "transitionFadeSlide": "Side fade",
+    "transitionNull": "No transition",
+    "transitionSoft": "Soft",
+    "transitionZoomFade": "Expand fade out",
+    "transitionZoomOut": "Zoom out",
+    "watermake": "Watermark",
+    "closeOther": "Close other",
+    "closeAll": "Close all",
+    "closeLeft": "Close left",
+    "closeRight": "Close right"
+  },
+  "login": {
+    "signInTitle": "Login",
+    "accountRuleTip": "Please enter account",
+    "passwordRuleTip": "Please enter password",
+    "or": "Or",
+    "rememberMe": "Remember me",
+    "forgotPassword": "Forget the password?",
+    "signIn": "Sign in",
+    "signUp": "Sign up",
+    "noAccountText": "Don't have an account?",
+    "accountPlaceholder": "Enter the account number",
+    "checkPasswordPlaceholder": "Please enter password again",
+    "checkPasswordRuleTip": "Please confirm password again",
+    "haveAccountText": "Do you have an account?",
+    "passwordPlaceholder": "Enter password",
+    "readAndAgree": "I have read and agree",
+    "registerTitle": "Register",
+    "userAgreement": "User Agreement",
+    "resetPassword": "Reset password",
+    "resetPasswordPlaceholder": "Enter account/mobile phone number",
+    "resetPasswordRuleTip": "Please enter your account/mobile phone number",
+    "resetPasswordTitle": "Reset"
+  },
+  "route": {
+    "appRoot": "Home",
+    "cardList": "Card list",
+    "commonList": "Common list",
+    "dashboard": "Dashboard",
+    "demo": "Function example",
+    "fetch": "Request example",
+    "list": "List",
+    "monitor": "Monitoring",
+    "test": "Multi-level menu",
+    "test2": "Multi-level menu subpage",
+    "test2Detail": "Details page of multi-level menu",
+    "test3": "multi-level menu",
+    "test4": "Multi-level menu 3-1",
+    "workbench": "Workbench",
+    "QRCode": "QR code",
+    "about": "About",
+    "clipboard": "Clipboard",
+    "demo403": "403",
+    "demo404": "404",
+    "demo500": "500",
+    "dictionarySetting": "Dictionary settings",
+    "docments": "Document",
+    "docmentsVite": "Vite",
+    "docmentsVue": "Vue",
+    "docmentsVueuse": "VueUse (external link)",
+    "echarts": "Echarts",
+    "editor": "Editor",
+    "editorMd": "MarkDown editor",
+    "editorRich": "Rich text editor",
+    "error": "Exception page",
+    "icons": "Icon",
+    "justSuper": "Supervisible",
+    "map": "Map",
+    "menuSetting": "Menu Settings",
+    "permission": "Permissions",
+    "permissionDemo": "Permissions example",
+    "setting": "System settings",
+    "userCenter": "Personal Center",
+    "accountSetting": "User settings"
+  }
+}
diff --git a/locales/zh.json b/locales/zh.json
new file mode 100644
index 0000000..b2ef18e
--- /dev/null
+++ b/locales/zh.json
@@ -0,0 +1,119 @@
+{
+  "common": {
+    "confirm": "确认",
+    "cancel": "取消",
+    "reload": "刷新",
+    "close": "关闭"
+  },
+  "app": {
+    "loginOut": "退出登录",
+    "loginOutTitle": "退出登录",
+    "loginOutContent": "确认退出当前账号?",
+    "userCenter": "个人中心",
+    "lignt": "浅色",
+    "dark": "深色",
+    "system": "跟随系统",
+    "backTop": "返回顶部",
+    "toggleSider": "切换侧边栏",
+    "toggleFullScreen": "切换全屏",
+    "notificationsTips": "消息通知",
+    "notifications": "通知",
+    "messages": "消息",
+    "todos": "待办",
+    "searchPlaceholder": "搜索页面/路径",
+    "resetSettingTitle": "重置设置",
+    "resetSettingContent": "确认重置所有设置?",
+    "resetSettingMeaasge": "重置成功",
+    "reset": "重置",
+    "setting": "设置",
+    "themeSetting": "主题设置",
+    "colorWeak": "色弱模式",
+    "blackAndWhite": "黑白模式",
+    "themeColor": "主题色",
+    "pageTransition": "页面过渡",
+    "transitionNull": "无过渡",
+    "transitionFadeSlide": "侧边淡出",
+    "transitionFadeBottom": "底边淡出",
+    "transitionFadeScale": "收缩淡出",
+    "transitionZoomFade": "扩大淡出",
+    "transitionZoomOut": "收缩",
+    "transitionSoft": "柔和",
+    "systemSetting": "系统设置",
+    "interfaceDisplay": "界面显示",
+    "logoDisplay": "LOGO显示",
+    "topProgress": "顶部进度",
+    "multitab": "多页签显示",
+    "bottomCopyright": "底部版权",
+    "breadcrumb": "面包屑",
+    "BreadcrumbIcon": "面包屑图标",
+    "watermake": "水印",
+    "closeOther": "关闭其他",
+    "closeLeft": "关闭左侧",
+    "closeRight": "关闭右侧",
+    "closeAll": "全部关闭"
+  },
+  "login": {
+    "signInTitle": "登录",
+    "accountPlaceholder": "输入账号",
+    "passwordPlaceholder": "输入密码",
+    "accountRuleTip": "请输入账户",
+    "passwordRuleTip": "请输入密码",
+    "or": "其他",
+    "signIn": "登录",
+    "rememberMe": "记住我",
+    "forgotPassword": "忘记密码?",
+    "signUp": "注册",
+    "noAccountText": "你没有账户?",
+    "haveAccountText": "已有账号?",
+    "checkPasswordRuleTip": "请再次确认密码",
+    "registerTitle": "注册",
+    "checkPasswordPlaceholder": "请再次输入密码",
+    "readAndAgree": "我已阅读并同意",
+    "userAgreement": "用户协议",
+    "resetPasswordTitle": "重置密码",
+    "resetPasswordPlaceholder": "输入账号/手机号码",
+    "resetPasswordRuleTip": "请输入账号/手机号码",
+    "resetPassword": "重置密码"
+  },
+  "route": {
+    "appRoot": "首页",
+    "dashboard": "仪表盘",
+    "workbench": "工作台",
+    "monitor": "监控页",
+    "test": "多级菜单演示",
+    "test2": "多级菜单子页",
+    "test2Detail": "多级菜单的详情页",
+    "test3": "多级菜单",
+    "test4": "多级菜单3-1",
+    "list": "列表页",
+    "commonList": "常用列表",
+    "cardList": "卡片列表",
+    "demo": "功能示例",
+    "fetch": "请求示例",
+    "echarts": "Echarts示例",
+    "map": "地图",
+    "editor": "编辑器",
+    "editorMd": "MarkDown编辑器",
+    "editorRich": "富文本编辑器",
+    "clipboard": "剪贴板",
+    "icons": "图标",
+    "QRCode": "二维码",
+    "docments": "文档",
+    "docmentsVue": "Vue",
+    "docmentsVite": "Vite",
+    "docmentsVueuse": "VueUse(外链)",
+    "permission": "权限",
+    "permissionDemo": "权限示例",
+    "justSuper": "super可见",
+    "error": "异常页",
+    "demo403": "403",
+    "demo404": "404",
+    "demo500": "500",
+    "setting": "系统设置",
+    "accountSetting": "用户设置",
+    "dictionarySetting": "字典设置",
+    "menuSetting": "菜单设置",
+    "userCenter": "个人中心",
+    "about": "关于"
+  }
+}
diff --git a/package.json b/package.json
index 42ed094..b1de248 100644
--- a/package.json
+++ b/package.json
@@ -62,6 +62,7 @@
     "qs": "^6.12.0",
     "radash": "^12.1.0",
     "vue": "^3.4.21",
+    "vue-i18n": "^9.11.0",
     "vue-router": "^4.3.0"
   },
   "devDependencies": {
diff --git a/src/components/common/CommonWrapper.vue b/src/components/common/CommonWrapper.vue
index bb7d27b..f752769 100644
--- a/src/components/common/CommonWrapper.vue
+++ b/src/components/common/CommonWrapper.vue
@@ -18,7 +18,6 @@
 <style scoped>
   .el {
     color: var(--n-text-color);
-    background-color: var(--card-color);
     transition: 0.3s var(--cubic-bezier-ease-in-out);
   }
   .el:hover {
diff --git a/src/components/common/DarkModeSwitch.vue b/src/components/common/DarkModeSwitch.vue
index 5208a0d..fb92ee1 100644
--- a/src/components/common/DarkModeSwitch.vue
+++ b/src/components/common/DarkModeSwitch.vue
@@ -3,30 +3,35 @@ import { NFlex, NText } from 'naive-ui'
 import { useAppStore } from '@/store'
 import { renderIcon } from '@/utils'
 
+const { t } = useI18n()
+
 const appStore = useAppStore()
-const options = [
-  {
-    label: 'Light',
-    value: 'light',
-    icon: 'icon-park-outline:sun-one',
-  },
-  {
-    label: 'Dark',
-    value: 'dark',
-    icon: 'icon-park-outline:moon',
-  },
-  {
-    label: 'System',
-    value: 'auto',
-    icon: 'icon-park-outline:laptop-computer',
-  },
-]
+
+const options = computed(() => {
+  return [
+    {
+      label: t('app.lignt'),
+      value: 'light',
+      icon: 'icon-park-outline:sun-one',
+    },
+    {
+      label: t('app.dark'),
+      value: 'dark',
+      icon: 'icon-park-outline:moon',
+    },
+    {
+      label: t('app.system'),
+      value: 'auto',
+      icon: 'icon-park-outline:laptop-computer',
+    },
+  ]
+})
 
 function renderLabel(option: any) {
   return h(NFlex, { align: 'center' }, {
     default: () => [
       renderIcon(option.icon)(),
-      h(NText, { depth: 3 }, { default: () => option.value }),
+      option.label,
     ],
   })
 }
diff --git a/src/components/common/langsSwitch.vue b/src/components/common/langsSwitch.vue
new file mode 100644
index 0000000..8cd1d33
--- /dev/null
+++ b/src/components/common/langsSwitch.vue
@@ -0,0 +1,25 @@
+<script setup lang="ts">
+import { useAppStore } from '@/store'
+
+const appStore = useAppStore()
+const options = [
+  {
+    label: 'English',
+    value: 'en',
+  },
+  {
+    label: '中文',
+    value: 'zh',
+  },
+]
+</script>
+
+<template>
+  <n-popselect :value="appStore.lang" :options="options" trigger="click" @update:value="appStore.setAppLang">
+    <CommonWrapper>
+      <icon-park-outline-translate />
+    </CommonWrapper>
+  </n-popselect>
+</template>
+
+<style scoped></style>
diff --git a/src/layouts/BasicLayout/index.vue b/src/layouts/BasicLayout/index.vue
index d6b3d67..d729acc 100644
--- a/src/layouts/BasicLayout/index.vue
+++ b/src/layouts/BasicLayout/index.vue
@@ -54,6 +54,7 @@ const appStore = useAppStore()
             <Notices />
             <FullScreen />
             <DarkModeSwitch />
+            <LangsSwitch />
             <Setting />
             <UserCenter />
           </div>
diff --git a/src/layouts/components/common/BackTop.vue b/src/layouts/components/common/BackTop.vue
index 84eb8db..109a4e8 100644
--- a/src/layouts/components/common/BackTop.vue
+++ b/src/layouts/components/common/BackTop.vue
@@ -8,9 +8,7 @@
           <icon-park-outline-to-top />
         </div>
       </template>
-      <span>返回顶部</span>
+      <span>{{ $t('app.backTop') }}</span>
     </n-tooltip>
   </n-back-top>
 </template>
-
-<style scoped></style>
diff --git a/src/layouts/components/header/Breadcrumb.vue b/src/layouts/components/header/Breadcrumb.vue
index 7010357..ec9518f 100644
--- a/src/layouts/components/header/Breadcrumb.vue
+++ b/src/layouts/components/header/Breadcrumb.vue
@@ -22,7 +22,7 @@ const appStore = useAppStore()
       @click="router.push(item.path)"
     >
       <nova-icon v-if="appStore.showBreadcrumbIcon" :icon="item.meta.icon" />
-      <span class="whitespace-nowrap">{{ item.meta.title }}</span>
+      <span class="whitespace-nowrap">{{ $t(`route.${String(item.name)}`, item.meta.title) }}</span>
     </n-el>
   </TransitionGroup>
 </template>
@@ -34,7 +34,6 @@ const appStore = useAppStore()
 }
 
 .list-move,
-/* 对移动中的元素应用的过渡 */
 .list-enter-active,
 .list-leave-active {
   transition: all 0.3s ease;
diff --git a/src/layouts/components/header/CollapaseButton.vue b/src/layouts/components/header/CollapaseButton.vue
index e41d308..f39ef0b 100644
--- a/src/layouts/components/header/CollapaseButton.vue
+++ b/src/layouts/components/header/CollapaseButton.vue
@@ -12,7 +12,7 @@ const appStore = useAppStore()
         <icon-park-outline-menu-fold v-else />
       </CommonWrapper>
     </template>
-    <span>切换侧边栏</span>
+    <span>{{ $t('app.toggleSider') }}</span>
   </n-tooltip>
 </template>
 
diff --git a/src/layouts/components/header/FullScreen.vue b/src/layouts/components/header/FullScreen.vue
index 3dd5f65..9409809 100644
--- a/src/layouts/components/header/FullScreen.vue
+++ b/src/layouts/components/header/FullScreen.vue
@@ -12,7 +12,7 @@ const appStore = useAppStore()
         <icon-park-outline-full-screen-two v-else />
       </CommonWrapper>
     </template>
-    <span>全屏</span>
+    <span>{{ $t('app.toggleFullScreen') }}</span>
   </n-tooltip>
 </template>
 
diff --git a/src/layouts/components/header/Notices.vue b/src/layouts/components/header/Notices.vue
index 78101e0..eacfab4 100644
--- a/src/layouts/components/header/Notices.vue
+++ b/src/layouts/components/header/Notices.vue
@@ -81,11 +81,10 @@ const MassageData = ref<Message.List[]>([
 ])
 const currentTab = ref(0)
 function handleRead(id: number) {
-  // MassageData.value[currentTab.value].list[index].isRead = true
   const data = MassageData.value.find(i => i.id === id)
   if (data)
     data.isRead = true
-  window.$message.success(`已读id: ${id}`)
+  window.$message.success(`id: ${id}`)
 }
 const massageCount = computed(() => {
   return MassageData.value.filter(i => !i.isRead).length
@@ -106,14 +105,14 @@ const groupMessage = computed(() => {
             </n-badge>
           </CommonWrapper>
         </template>
-        <span>消息通知</span>
+        <span>{{ $t('app.notificationsTips') }}</span>
       </n-tooltip>
     </template>
     <n-tabs v-model:value="currentTab" type="line" animated justify-content="space-evenly" class="w-390px">
       <n-tab-pane :name="0">
         <template #tab>
           <n-space class="w-130px" justify="center">
-            通知
+            {{ $t('app.notifications') }}
             <n-badge type="info" :value="groupMessage[0]?.filter(i => !i.isRead).length" :max="99" />
           </n-space>
         </template>
@@ -122,7 +121,7 @@ const groupMessage = computed(() => {
       <n-tab-pane :name="1">
         <template #tab>
           <n-space class="w-130px" justify="center">
-            消息
+            {{ $t('app.messages') }}
             <n-badge type="warning" :value="groupMessage[1]?.filter(i => !i.isRead).length" :max="99" />
           </n-space>
         </template>
@@ -131,7 +130,7 @@ const groupMessage = computed(() => {
       <n-tab-pane :name="2">
         <template #tab>
           <n-space class="w-130px" justify="center">
-            待办
+            {{ $t('app.todos') }}
             <n-badge type="error" :value="groupMessage[2]?.filter(i => !i.isRead).length" :max="99" />
           </n-space>
         </template>
diff --git a/src/layouts/components/header/Search.vue b/src/layouts/components/header/Search.vue
index c844cc7..e840ec2 100644
--- a/src/layouts/components/header/Search.vue
+++ b/src/layouts/components/header/Search.vue
@@ -6,16 +6,18 @@ import { renderIcon } from '@/utils'
 const routeStore = useRouteStore()
 const searchValue = ref('')
 
+const { t } = useI18n()
+
 const options = computed(() => {
   return routeStore.rowRoutes.filter((item) => {
     const conditions = [
-      item['meta.title']?.includes(searchValue.value),
+      t(`route.${String(item.name)}`, item['meta.title'] || item.name)?.includes(searchValue.value),
       item.path?.includes(searchValue.value),
     ]
     return conditions.some(condition => condition)
   }).map((item) => {
     return {
-      label: item['meta.title'],
+      label: t(`route.${String(item.name)}`, item['meta.title'] || item.name),
       value: item.path,
       icon: item['meta.icon'],
     }
@@ -44,7 +46,7 @@ function handleSelect(value: string) {
   <n-auto-complete
     v-model:value="searchValue" class="w-20em m-r-1em" :input-props="{
       autocomplete: 'disabled',
-    }" :options="options" :render-label="renderLabel" placeholder="搜索页面/路径" clearable @select="handleSelect"
+    }" :options="options" :render-label="renderLabel" :placeholder="$t('app.searchPlaceholder')" clearable @select="handleSelect"
   >
     <template #prefix>
       <n-icon>
diff --git a/src/layouts/components/header/Setting.vue b/src/layouts/components/header/Setting.vue
index c284d50..8d5e6bc 100644
--- a/src/layouts/components/header/Setting.vue
+++ b/src/layouts/components/header/Setting.vue
@@ -3,40 +3,45 @@ import { useAppStore } from '@/store'
 
 const appStore = useAppStore()
 
+const { t } = useI18n()
+
 const drawerActive = ref(false)
 function openSetting() {
   drawerActive.value = !drawerActive.value
 }
-const transitionSelectorOptions = [
-  {
-    label: '无',
-    value: '',
-  },
-  {
-    label: '侧滑',
-    value: 'fade-slide',
-  },
-  {
-    label: '下滑',
-    value: 'fade-bottom',
-  },
-  {
-    label: '收缩',
-    value: 'fade-scale',
-  },
-  {
-    label: '扩张',
-    value: 'zoom-fade',
-  },
-  {
-    label: '坍缩',
-    value: 'zoom-out',
-  },
-  {
-    label: '柔和',
-    value: 'fade',
-  },
-]
+
+const transitionSelectorOptions = computed(() => {
+  return [
+    {
+      label: t('app.transitionNull'),
+      value: '',
+    },
+    {
+      label: t('app.transitionFadeSlide'),
+      value: 'fade-slide',
+    },
+    {
+      label: t('app.transitionFadeBottom'),
+      value: 'fade-bottom',
+    },
+    {
+      label: t('app.transitionFadeScale'),
+      value: 'fade-scale',
+    },
+    {
+      label: t('app.transitionZoomFade'),
+      value: 'zoom-fade',
+    },
+    {
+      label: t('app.transitionZoomOut'),
+      value: 'zoom-out',
+    },
+    {
+      label: t('app.transitionSoft'),
+      value: 'fade',
+    },
+  ]
+})
 
 const palette = [
   '#ffb8b8',
@@ -59,13 +64,13 @@ const palette = [
 
 function resetSetting() {
   window.$dialog.warning({
-    title: '重置所有设置',
-    content: '你确定重置所有设置?',
-    positiveText: '确定',
-    negativeText: '取消',
+    title: t('app.resetSettingTitle'),
+    content: t('app.resetSettingContent'),
+    positiveText: t('common.confirm'),
+    negativeText: t('common.cancel'),
     onPositiveClick: () => {
       appStore.resetAlltheme()
-      window.$message.success('重置成功')
+      window.$message.success(t('app.resetSettingMeaasge'))
     },
   })
 }
@@ -77,65 +82,65 @@ function resetSetting() {
       <CommonWrapper @click="openSetting">
         <div>
           <icon-park-outline-setting-two />
-          <n-drawer v-model:show="drawerActive" :width="300">
-            <n-drawer-content title="系统设置" closable>
+          <n-drawer v-model:show="drawerActive" :width="360">
+            <n-drawer-content :title="t('app.systemSetting')" closable>
               <n-space vertical>
-                <n-divider>主题设置</n-divider>
+                <n-divider>{{ $t('app.themeSetting') }}</n-divider>
                 <n-space justify="space-between">
-                  色弱模式
+                  {{ $t('app.colorWeak') }}
                   <n-switch :value="appStore.colorWeak" @update:value="appStore.toggleColorWeak" />
                 </n-space>
                 <n-space justify="space-between">
-                  灰色模式
+                  {{ $t('app.blackAndWhite') }}
                   <n-switch :value="appStore.grayMode" @update:value="appStore.toggleGrayMode" />
                 </n-space>
                 <n-space align="center" justify="space-between">
-                  主题色
+                  {{ $t('app.themeColor') }}
                   <n-color-picker
                     v-model:value="appStore.primaryColor"
-                    class="w-7em" :swatches="palette" :show-alpha="false"
+                    class="w-10em" :swatches="palette"
                     @update:value="appStore.setPrimaryColor"
                   />
                 </n-space>
                 <n-space align="center" justify="space-between">
-                  切换动效
-                  <n-select v-model:value="appStore.transitionAnimation" class="w-7em" :options="transitionSelectorOptions" @update:value="appStore.reloadPage" />
+                  {{ $t('app.pageTransition') }}
+                  <n-select v-model:value="appStore.transitionAnimation" class="w-10em" :options="transitionSelectorOptions" @update:value="appStore.reloadPage" />
                 </n-space>
 
-                <n-divider>界面显示</n-divider>
+                <n-divider>{{ $t('app.interfaceDisplay') }}</n-divider>
                 <n-space justify="space-between">
-                  LOGO显示
+                  {{ $t('app.logoDisplay') }}
                   <n-switch v-model:value="appStore.showLogo" />
                 </n-space>
                 <n-space justify="space-between">
-                  顶部进度
+                  {{ $t('app.topProgress') }}
                   <n-switch v-model:value="appStore.showProgress" />
                 </n-space>
                 <n-space justify="space-between">
-                  多页签显示
+                  {{ $t('app.multitab') }}
                   <n-switch v-model:value="appStore.showTabs" />
                 </n-space>
                 <n-space justify="space-between">
-                  底部标签显示
+                  {{ $t('app.bottomCopyright') }}
                   <n-switch v-model:value="appStore.showFooter" />
                 </n-space>
                 <n-space justify="space-between">
-                  面包屑
+                  {{ $t('app.breadcrumb') }}
                   <n-switch v-model:value="appStore.showBreadcrumb" />
                 </n-space>
                 <n-space justify="space-between">
-                  面包屑图标
+                  {{ $t('app.BreadcrumbIcon') }}
                   <n-switch v-model:value="appStore.showBreadcrumbIcon" />
                 </n-space>
                 <n-space justify="space-between">
-                  水印
+                  {{ $t('app.watermake') }}
                   <n-switch v-model:value="appStore.showWatermark" />
                 </n-space>
               </n-space>
 
               <template #footer>
                 <n-button type="error" @click="resetSetting">
-                  重置设置
+                  {{ $t('app.reset') }}
                 </n-button>
               </template>
             </n-drawer-content>
@@ -143,8 +148,6 @@ function resetSetting() {
         </div>
       </CommonWrapper>
     </template>
-    <span>设置</span>
+    <span>{{ $t('app.setting') }}</span>
   </n-tooltip>
 </template>
-
-<style scoped></style>
diff --git a/src/layouts/components/header/UserCenter.vue b/src/layouts/components/header/UserCenter.vue
index 71e02d1..9351fcb 100644
--- a/src/layouts/components/header/UserCenter.vue
+++ b/src/layouts/components/header/UserCenter.vue
@@ -2,46 +2,50 @@
 import { renderIcon } from '@/utils/icon'
 import { useAuthStore } from '@/store'
 
+const { t } = useI18n()
+
 const { userInfo, resetAuthStore } = useAuthStore()
 const router = useRouter()
 
-const options = [
-  {
-    label: '个人中心',
-    key: 'userCenter',
-    icon: renderIcon('carbon:user-avatar-filled-alt'),
-  },
-  {
-    type: 'divider',
-    key: 'd1',
-  },
-  {
-    label: 'Github',
-    key: 'guthub',
-    icon: renderIcon('icon-park-outline:github'),
-  },
-  {
-    label: 'gitee',
-    key: 'gitee',
-    icon: renderIcon('simple-icons:gitee'),
-  },
-  {
-    type: 'divider',
-    key: 'd1',
-  },
-  {
-    label: '退出登录',
-    key: 'loginOut',
-    icon: renderIcon('icon-park-outline:logout'),
-  },
-]
+const options = computed(() => {
+  return [
+    {
+      label: t('app.userCenter'),
+      key: 'userCenter',
+      icon: renderIcon('carbon:user-avatar-filled-alt'),
+    },
+    {
+      type: 'divider',
+      key: 'd1',
+    },
+    {
+      label: 'Github',
+      key: 'guthub',
+      icon: renderIcon('icon-park-outline:github'),
+    },
+    {
+      label: 'Gitee',
+      key: 'gitee',
+      icon: renderIcon('simple-icons:gitee'),
+    },
+    {
+      type: 'divider',
+      key: 'd1',
+    },
+    {
+      label: t('app.loginOut'),
+      key: 'loginOut',
+      icon: renderIcon('icon-park-outline:logout'),
+    },
+  ]
+})
 function handleSelect(key: string | number) {
   if (key === 'loginOut') {
     window.$dialog?.info({
-      title: '退出登录',
-      content: '确认退出当前账号?',
-      positiveText: '确定',
-      negativeText: '取消',
+      title: t('app.loginOutTitle'),
+      content: t('app.loginOutContent'),
+      positiveText: t('common.confirm'),
+      negativeText: t('common.cancel'),
       onPositiveClick: () => {
         resetAuthStore()
       },
diff --git a/src/layouts/components/tab/DropTabs.vue b/src/layouts/components/tab/DropTabs.vue
new file mode 100644
index 0000000..1335f02
--- /dev/null
+++ b/src/layouts/components/tab/DropTabs.vue
@@ -0,0 +1,39 @@
+<script setup lang="ts">
+import { useTabStore } from '@/store'
+import { renderIcon } from '@/utils'
+
+const tabStore = useTabStore()
+
+const { t } = useI18n()
+
+function renderDropTabsLabel(option: any) {
+  return t(`route.${String(option.name)}`, option.meta.title)
+}
+function renderDropTabsIcon(option: any) {
+  return renderIcon(option.meta.icon)!()
+}
+
+const router = useRouter()
+function handleDropTabs(key: string, option: any) {
+  router.push(option.path)
+}
+</script>
+
+<template>
+  <n-dropdown
+    :options="tabStore.allTabs"
+    :render-label="renderDropTabsLabel"
+    :render-icon="renderDropTabsIcon"
+    trigger="click"
+    size="small"
+    @select="handleDropTabs"
+  >
+    <CommonWrapper>
+      <icon-park-outline-application-menu />
+    </CommonWrapper>
+  </n-dropdown>
+</template>
+
+<style scoped>
+
+</style>
diff --git a/src/layouts/components/tab/Reload.vue b/src/layouts/components/tab/Reload.vue
index 75631fe..aae2f83 100644
--- a/src/layouts/components/tab/Reload.vue
+++ b/src/layouts/components/tab/Reload.vue
@@ -21,7 +21,7 @@ function handleReload() {
         <icon-park-outline-refresh :class="{ 'animate-spin': loading }" />
       </CommonWrapper>
     </template>
-    <span>刷新页面</span>
+    <span>{{ $t('common.reload') }}</span>
   </n-tooltip>
 </template>
 
diff --git a/src/layouts/components/tab/TabBar.vue b/src/layouts/components/tab/TabBar.vue
index 3855e4a..76e537f 100644
--- a/src/layouts/components/tab/TabBar.vue
+++ b/src/layouts/components/tab/TabBar.vue
@@ -1,6 +1,7 @@
 <script setup lang="ts">
 import type { RouteLocationNormalized } from 'vue-router'
 import Reload from './Reload.vue'
+import DropTabs from './DropTabs.vue'
 import { renderIcon } from '@/utils'
 import { useAppStore, useTabStore } from '@/store'
 
@@ -14,38 +15,41 @@ function handleTab(route: RouteLocationNormalized) {
 function handleClose(path: string) {
   tabStore.closeTab(path)
 }
-const options = [
-  {
-    label: '刷新',
-    key: 'reload',
-    icon: renderIcon('icon-park-outline:redo'),
-  },
-  {
-    label: '关闭',
-    key: 'closeCurrent',
-    icon: renderIcon('icon-park-outline:close'),
-  },
-  {
-    label: '关闭其他',
-    key: 'closeOther',
-    icon: renderIcon('icon-park-outline:delete-four'),
-  },
-  {
-    label: '关闭左侧',
-    key: 'closeLeft',
-    icon: renderIcon('icon-park-outline:to-left'),
-  },
-  {
-    label: '关闭右侧',
-    key: 'closeRight',
-    icon: renderIcon('icon-park-outline:to-right'),
-  },
-  {
-    label: '全部关闭',
-    key: 'closeAll',
-    icon: renderIcon('icon-park-outline:fullwidth'),
-  },
-]
+const { t } = useI18n()
+const options = computed(() => {
+  return [
+    {
+      label: t('common.reload'),
+      key: 'reload',
+      icon: renderIcon('icon-park-outline:redo'),
+    },
+    {
+      label: t('common.close'),
+      key: 'closeCurrent',
+      icon: renderIcon('icon-park-outline:close'),
+    },
+    {
+      label: t('app.closeOther'),
+      key: 'closeOther',
+      icon: renderIcon('icon-park-outline:delete-four'),
+    },
+    {
+      label: t('app.closeLeft'),
+      key: 'closeLeft',
+      icon: renderIcon('icon-park-outline:to-left'),
+    },
+    {
+      label: t('app.closeRight'),
+      key: 'closeRight',
+      icon: renderIcon('icon-park-outline:to-right'),
+    },
+    {
+      label: t('app.closeAll'),
+      key: 'closeAll',
+      icon: renderIcon('icon-park-outline:fullwidth'),
+    },
+  ]
+})
 const showDropdown = ref(false)
 const x = ref(0)
 const y = ref(0)
@@ -91,17 +95,6 @@ function handleContextMenu(e: MouseEvent, route: RouteLocationNormalized) {
 function onClickoutside() {
   showDropdown.value = false
 }
-
-function renderDropTabsLabel(option: any) {
-  return option.meta.title
-}
-function renderDropTabsIcon(option: any) {
-  return renderIcon(option.meta.icon)!()
-}
-
-function handleDropTabs(key: string, option: any) {
-  router.push(option.path)
-}
 </script>
 
 <template>
@@ -119,7 +112,9 @@ function handleDropTabs(key: string, option: any) {
         :name="item.path"
         @click="router.push(item.path)"
       >
-        {{ item.meta.title }}
+        <div class="flex-x-center gap-2">
+          <nova-icon :icon="item.meta.icon" /> {{ $t(`route.${String(item.name)}`, item.meta.title) }}
+        </div>
       </n-tab>
       <n-tab
         v-for="item in tabStore.tabs"
@@ -130,23 +125,12 @@ function handleDropTabs(key: string, option: any) {
         @contextmenu="handleContextMenu($event, item)"
       >
         <div class="flex-x-center gap-2">
-          <nova-icon :icon="item.meta.icon" /> {{ item.meta.title }}
+          <nova-icon :icon="item.meta.icon" /> {{ $t(`route.${String(item.name)}`, item.meta.title) }}
         </div>
       </n-tab>
       <template #suffix>
         <Reload />
-        <n-dropdown
-          :options="tabStore.allTabs"
-          :render-label="renderDropTabsLabel"
-          :render-icon="renderDropTabsIcon"
-          trigger="click"
-          size="small"
-          @select="handleDropTabs"
-        >
-          <CommonWrapper>
-            <icon-park-outline-application-menu />
-          </CommonWrapper>
-        </n-dropdown>
+        <DropTabs />
       </template>
     </n-tabs>
     <n-dropdown
@@ -162,4 +146,4 @@ function handleDropTabs(key: string, option: any) {
   </div>
 </template>
 
-<style scoped></style>
+<style scoped></style>./DropTabs.vue
diff --git a/src/modules/i18n.ts b/src/modules/i18n.ts
new file mode 100644
index 0000000..ed3e0c7
--- /dev/null
+++ b/src/modules/i18n.ts
@@ -0,0 +1,19 @@
+import { createI18n } from 'vue-i18n'
+import type { App } from 'vue'
+import en from '../../locales/en.json'
+import zh from '../../locales/zh.json'
+import { local } from '@/utils'
+
+export const i18n = createI18n({
+  legacy: false,
+  locale: local.get('lang') || 'zh', // 默认显示语言
+  fallbackLocale: 'en',
+  messages: {
+    zh,
+    en,
+  },
+})
+
+export function install(app: App) {
+  app.use(i18n)
+}
diff --git a/src/router/routes.static.ts b/src/router/routes.static.ts
index 3854085..9971d0a 100644
--- a/src/router/routes.static.ts
+++ b/src/router/routes.static.ts
@@ -10,7 +10,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [
     'pid': null,
   },
   {
-    'name': 'dashboard_workbench',
+    'name': 'workbench',
     'path': '/dashboard/workbench',
     'meta.title': '工作台',
     'meta.requiresAuth': true,
@@ -21,7 +21,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [
     'pid': 1,
   },
   {
-    'name': 'dashboard_monitor',
+    'name': 'monitor',
     'path': '/dashboard/monitor',
     'meta.title': '监控页',
     'meta.requiresAuth': true,
@@ -51,7 +51,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [
     'pid': 4,
   },
   {
-    'name': 'test2_detail',
+    'name': 'test2Detail',
     'path': '/test/test2/detail',
     'meta.title': '多级菜单的详情页',
     'meta.requiresAuth': true,
@@ -93,7 +93,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [
     'pid': null,
   },
   {
-    'name': 'list_commonList',
+    'name': 'commonList',
     'path': '/list/commonList',
     'meta.title': '常用列表',
     'meta.requiresAuth': true,
@@ -103,7 +103,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [
     'pid': 10,
   },
   {
-    'name': 'list_cardList',
+    'name': 'cardList',
     'path': '/list/cardList',
     'meta.title': '卡片列表',
     'meta.requiresAuth': true,
@@ -113,8 +113,8 @@ export const staticRoutes: AppRoute.RowRoute[] = [
     'pid': 10,
   },
   {
-    'name': 'plugin',
-    'path': '/plugin',
+    'name': 'demo',
+    'path': '/demo',
     'meta.title': '功能示例',
     'meta.requiresAuth': true,
     'meta.icon': 'icon-park-outline:application-one',
@@ -124,38 +124,38 @@ export const staticRoutes: AppRoute.RowRoute[] = [
   },
   {
     'name': 'fetch',
-    'path': '/plugin/fetch',
-    'meta.title': '接口功能测试',
+    'path': '/demo/fetch',
+    'meta.title': '请求示例',
     'meta.requiresAuth': true,
     'meta.icon': 'icon-park-outline:international',
-    'componentPath': '/plugin/fetch/index.vue',
+    'componentPath': '/demo/fetch/index.vue',
     'id': 5,
     'pid': 13,
   },
   {
-    'name': 'plugin_echarts',
-    'path': '/plugin/echarts',
+    'name': 'echarts',
+    'path': '/demo/echarts',
     'meta.title': 'ECharts',
     'meta.requiresAuth': true,
     'meta.icon': 'icon-park-outline:chart-proportion',
-    'componentPath': '/plugin/echarts/index.vue',
+    'componentPath': '/demo/echarts/index.vue',
     'id': 15,
     'pid': 13,
   },
   {
-    'name': 'PluginMap',
-    'path': '/plugin/map',
+    'name': 'map',
+    'path': '/demo/map',
     'meta.title': '地图',
     'meta.requiresAuth': true,
     'meta.icon': 'carbon:map',
     'meta.keepAlive': true,
-    'componentPath': '/plugin/map/index.vue',
+    'componentPath': '/demo/map/index.vue',
     'id': 17,
     'pid': 13,
   },
   {
-    'name': 'plugin_editor',
-    'path': '/plugin/editor',
+    'name': 'editor',
+    'path': '/demo/editor',
     'meta.title': '编辑器',
     'meta.requiresAuth': true,
     'meta.icon': 'icon-park-outline:editor',
@@ -164,52 +164,52 @@ export const staticRoutes: AppRoute.RowRoute[] = [
     'pid': 13,
   },
   {
-    'name': 'plugin_md',
-    'path': '/plugin/editor/md',
+    'name': 'editorMd',
+    'path': '/demo/editor/md',
     'meta.title': 'MarkDown',
     'meta.requiresAuth': true,
     'meta.icon': 'ri:markdown-line',
-    'componentPath': '/plugin/editor/md/index.vue',
+    'componentPath': '/demo/editor/md/index.vue',
     'id': 19,
     'pid': 18,
   },
   {
-    'name': 'plugin_rich',
-    'path': '/plugin/editor/rich',
+    'name': 'editorRich',
+    'path': '/demo/editor/rich',
     'meta.title': '富文本',
     'meta.requiresAuth': true,
     'meta.icon': 'icon-park-outline:edit-one',
-    'componentPath': '/plugin/editor/rich/index.vue',
+    'componentPath': '/demo/editor/rich/index.vue',
     'id': 20,
     'pid': 18,
   },
   {
-    'name': 'plugin_clipboard',
-    'path': '/plugin/clipboard',
+    'name': 'clipboard',
+    'path': '/demo/clipboard',
     'meta.title': '剪贴板',
     'meta.requiresAuth': true,
     'meta.icon': 'icon-park-outline:clipboard',
-    'componentPath': '/plugin/clipboard/index.vue',
+    'componentPath': '/demo/clipboard/index.vue',
     'id': 21,
     'pid': 13,
   },
   {
-    'name': 'plugin_icons',
-    'path': '/plugin/icons',
+    'name': 'icons',
+    'path': '/demo/icons',
     'meta.title': '图标',
     'meta.requiresAuth': true,
     'meta.icon': 'icon-park-outline:winking-face-with-open-eyes',
-    'componentPath': '/plugin/icons/index.vue',
+    'componentPath': '/demo/icons/index.vue',
     'id': 22,
     'pid': 13,
   },
   {
-    'name': 'plugin_QRCode',
-    'path': '/plugin/QRCode',
+    'name': 'QRCode',
+    'path': '/demo/QRCode',
     'meta.title': '二维码',
     'meta.requiresAuth': true,
     'meta.icon': 'icon-park-outline:two-dimensional-code',
-    'componentPath': '/plugin/QRCode/index.vue',
+    'componentPath': '/demo/QRCode/index.vue',
     'id': 23,
     'pid': 13,
   },
@@ -224,9 +224,9 @@ export const staticRoutes: AppRoute.RowRoute[] = [
     'pid': null,
   },
   {
-    'name': 'docments_vue',
+    'name': 'docmentsVue',
     'path': '/docments/vue',
-    'meta.title': 'vue',
+    'meta.title': 'Vue',
     'meta.requiresAuth': true,
     'meta.icon': 'logos:vue',
     'componentPath': '/docments/vue/index.vue',
@@ -234,9 +234,9 @@ export const staticRoutes: AppRoute.RowRoute[] = [
     'pid': 24,
   },
   {
-    'name': 'docments_vite',
+    'name': 'docmentsVite',
     'path': '/docments/vite',
-    'meta.title': 'vite',
+    'meta.title': 'Vite',
     'meta.requiresAuth': true,
     'meta.icon': 'logos:vitejs',
     'componentPath': '/docments/vite/index.vue',
@@ -244,7 +244,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [
     'pid': 24,
   },
   {
-    'name': 'docments_vueuse',
+    'name': 'docmentsVueuse',
     'path': '/docments/vueuse',
     'meta.title': 'VueUse(外链)',
     'meta.requiresAuth': true,
@@ -257,7 +257,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [
   {
     'name': 'permission',
     'path': '/permission',
-    'meta.title': '权限示例',
+    'meta.title': '权限',
     'meta.requiresAuth': true,
     'meta.icon': 'icon-park-outline:people-safe',
     'componentPath': null,
@@ -265,7 +265,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [
     'pid': null,
   },
   {
-    'name': 'permission_permission',
+    'name': 'permissionDemo',
     'path': '/permission/permission',
     'meta.title': '权限示例',
     'meta.requiresAuth': true,
@@ -275,9 +275,9 @@ export const staticRoutes: AppRoute.RowRoute[] = [
     'pid': 28,
   },
   {
-    'name': 'permission_justSuper',
+    'name': 'justSuper',
     'path': '/permission/justSuper',
-    'meta.title': '超管super可见',
+    'meta.title': 'super可见',
     'meta.requiresAuth': true,
     'meta.roles': [
       'super',
@@ -300,7 +300,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [
   {
     'name': 'demo403',
     'path': '/error/403',
-    'meta.title': '403页',
+    'meta.title': '403',
     'meta.requiresAuth': true,
     'meta.icon': 'carbon:error',
     'meta.order': 3,
@@ -311,7 +311,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [
   {
     'name': 'demo404',
     'path': '/error/404',
-    'meta.title': '404页',
+    'meta.title': '404',
     'meta.requiresAuth': true,
     'meta.icon': 'icon-park-outline:error',
     'meta.order': 2,
@@ -322,7 +322,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [
   {
     'name': 'demo500',
     'path': '/error/500',
-    'meta.title': '500页',
+    'meta.title': '500',
     'meta.requiresAuth': true,
     'meta.icon': 'carbon:data-error',
     'meta.order': 1,
@@ -341,7 +341,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [
     'pid': null,
   },
   {
-    'name': 'setting_account',
+    'name': 'accountSetting',
     'path': '/setting/account',
     'meta.title': '用户设置',
     'meta.requiresAuth': true,
@@ -351,7 +351,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [
     'pid': 35,
   },
   {
-    'name': 'setting_dictionary',
+    'name': 'dictionarySetting',
     'path': '/setting/dictionary',
     'meta.title': '字典设置',
     'meta.requiresAuth': true,
@@ -361,7 +361,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [
     'pid': 35,
   },
   {
-    'name': 'setting_menu',
+    'name': 'menuSetting',
     'path': '/setting/menu',
     'meta.title': '菜单设置',
     'meta.requiresAuth': true,
diff --git a/src/store/app/index.ts b/src/store/app/index.ts
index d0cf436..03bd8b0 100644
--- a/src/store/app/index.ts
+++ b/src/store/app/index.ts
@@ -2,6 +2,7 @@ import type { GlobalThemeOverrides } from 'naive-ui'
 import chroma from 'chroma-js'
 import { set } from 'radash'
 import themeConfig from './theme.json'
+import { local, setLocale } from '@/utils'
 
 type TransitionAnimation = '' | 'fade-slide' | 'fade-bottom' | 'fade-scale' | 'zoom-fade' | 'zoom-out'
 
@@ -17,6 +18,7 @@ export const useAppStore = defineStore('app-store', {
   state: () => {
     return {
       footerText: 'Copyright © 2024 chansee97',
+      lang: 'zh',
       theme: themeConfig as GlobalThemeOverrides,
       primaryColor: themeConfig.common.primaryColor,
       collapsed: false,
@@ -65,6 +67,11 @@ export const useAppStore = defineStore('app-store', {
       // 重置所有配色
       this.setPrimaryColor(this.primaryColor)
     },
+    setAppLang(lang: App.lang) {
+      setLocale(lang)
+      local.set('lang', lang)
+      this.lang = lang
+    },
     /* 设置主题色 */
     setPrimaryColor(color: string) {
       const brightenColor = chroma(color).brighten(1).hex()
diff --git a/src/store/route.ts b/src/store/route.ts
index 75ea41d..4274b88 100644
--- a/src/store/route.ts
+++ b/src/store/route.ts
@@ -3,7 +3,7 @@ import { RouterLink } from 'vue-router'
 import { h } from 'vue'
 import { clone, construct, min } from 'radash'
 import type { RouteRecordRaw } from 'vue-router'
-import { arrayToTree, local, renderIcon } from '@/utils'
+import { $t, arrayToTree, local, renderIcon } from '@/utils'
 import { router } from '@/router'
 import { fetchUserRoutes } from '@/service'
 import { staticRoutes } from '@/router/routes.static'
@@ -81,9 +81,9 @@ export const useRouteStore = defineStore('route-store', {
                             path: item.path,
                           },
                         },
-                        { default: () => item.meta.title },
+                        { default: () => $t(`route.${String(item.name)}`, item.meta.title) },
                       )
-                  : item.meta.title,
+                  : $t(`route.${String(item.name)}`, item.meta.title),
             key: item.path,
             icon: item.meta.icon ? renderIcon(item.meta.icon) : undefined,
           }
@@ -142,7 +142,7 @@ export const useRouteStore = defineStore('route-store', {
         redirect: import.meta.env.VITE_HOME_PATH,
         component: BasicLayout,
         meta: {
-          title: '首页',
+          title: '',
           icon: 'icon-park-outline:home',
         },
         children: [],
diff --git a/src/typings/global.d.ts b/src/typings/global.d.ts
index af687ad..d2f4ed2 100644
--- a/src/typings/global.d.ts
+++ b/src/typings/global.d.ts
@@ -19,6 +19,13 @@ declare namespace NaiveUI {
   type ThemeColor = 'default' | 'error' | 'primary' | 'info' | 'success' | 'warning'
 }
 
+declare module '~icons/*' {
+  import type { FunctionalComponent, SVGAttributes } from 'vue'
+
+  const component: FunctionalComponent<SVGAttributes>
+  export default component
+}
+
 declare namespace Storage {
   interface Session {
     demoKey: string
@@ -33,5 +40,11 @@ declare namespace Storage {
     refreshToken: string
     /* 存储登录账号 */
     loginAccount: any
+    /* 存储当前语言 */
+    lang: App.lang
   }
 }
+
+declare namespace App {
+  type lang = 'zh' | 'en'
+}
diff --git a/src/typings/route.d.ts b/src/typings/route.d.ts
index 7c2edf8..85c4a22 100644
--- a/src/typings/route.d.ts
+++ b/src/typings/route.d.ts
@@ -23,6 +23,8 @@ declare namespace AppRoute {
     withoutTab?: boolean
     /** 当前路由是否会被固定在Tab中,用于一些常驻页面 */
     pinTab?: boolean
+    /** 当前路由i18n标识 */
+    i18nKey?: string
   }
   /** 单个路由的类型结构(动态路由模式:后端返回此类型结构的路由) */
   interface baseRoute {
diff --git a/src/utils/i18n.ts b/src/utils/i18n.ts
new file mode 100644
index 0000000..7cc9411
--- /dev/null
+++ b/src/utils/i18n.ts
@@ -0,0 +1,7 @@
+import { i18n } from '@/modules/i18n'
+
+export function setLocale(locale: App.lang) {
+  i18n.global.locale.value = locale
+}
+
+export const $t = i18n.global.t
diff --git a/src/utils/index.ts b/src/utils/index.ts
index c8551d7..d9bedb9 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -1,3 +1,4 @@
 export * from './icon'
 export * from './storage'
 export * from './array'
+export * from './i18n'
diff --git a/src/views/plugin/QRCode/index.vue b/src/views/demo/QRCode/index.vue
similarity index 100%
rename from src/views/plugin/QRCode/index.vue
rename to src/views/demo/QRCode/index.vue
diff --git a/src/views/plugin/clipboard/index.vue b/src/views/demo/clipboard/index.vue
similarity index 100%
rename from src/views/plugin/clipboard/index.vue
rename to src/views/demo/clipboard/index.vue
diff --git a/src/views/plugin/echarts/index.vue b/src/views/demo/echarts/index.vue
similarity index 100%
rename from src/views/plugin/echarts/index.vue
rename to src/views/demo/echarts/index.vue
diff --git a/src/views/plugin/editor/md/index.vue b/src/views/demo/editor/md/index.vue
similarity index 100%
rename from src/views/plugin/editor/md/index.vue
rename to src/views/demo/editor/md/index.vue
diff --git a/src/views/plugin/editor/rich/index.vue b/src/views/demo/editor/rich/index.vue
similarity index 100%
rename from src/views/plugin/editor/rich/index.vue
rename to src/views/demo/editor/rich/index.vue
diff --git a/src/views/plugin/fetch/components/Delete.vue b/src/views/demo/fetch/components/Delete.vue
similarity index 100%
rename from src/views/plugin/fetch/components/Delete.vue
rename to src/views/demo/fetch/components/Delete.vue
diff --git a/src/views/plugin/fetch/components/DownLoad.vue b/src/views/demo/fetch/components/DownLoad.vue
similarity index 100%
rename from src/views/plugin/fetch/components/DownLoad.vue
rename to src/views/demo/fetch/components/DownLoad.vue
diff --git a/src/views/plugin/fetch/components/DownLoadWithProgress.vue b/src/views/demo/fetch/components/DownLoadWithProgress.vue
similarity index 100%
rename from src/views/plugin/fetch/components/DownLoadWithProgress.vue
rename to src/views/demo/fetch/components/DownLoadWithProgress.vue
diff --git a/src/views/plugin/fetch/components/Env.vue b/src/views/demo/fetch/components/Env.vue
similarity index 100%
rename from src/views/plugin/fetch/components/Env.vue
rename to src/views/demo/fetch/components/Env.vue
diff --git a/src/views/plugin/fetch/components/FailedRequest.vue b/src/views/demo/fetch/components/FailedRequest.vue
similarity index 100%
rename from src/views/plugin/fetch/components/FailedRequest.vue
rename to src/views/demo/fetch/components/FailedRequest.vue
diff --git a/src/views/plugin/fetch/components/FailedResponse.vue b/src/views/demo/fetch/components/FailedResponse.vue
similarity index 100%
rename from src/views/plugin/fetch/components/FailedResponse.vue
rename to src/views/demo/fetch/components/FailedResponse.vue
diff --git a/src/views/plugin/fetch/components/FailedResponseWithoutTip.vue b/src/views/demo/fetch/components/FailedResponseWithoutTip.vue
similarity index 100%
rename from src/views/plugin/fetch/components/FailedResponseWithoutTip.vue
rename to src/views/demo/fetch/components/FailedResponseWithoutTip.vue
diff --git a/src/views/plugin/fetch/components/FormPost.vue b/src/views/demo/fetch/components/FormPost.vue
similarity index 100%
rename from src/views/plugin/fetch/components/FormPost.vue
rename to src/views/demo/fetch/components/FormPost.vue
diff --git a/src/views/plugin/fetch/components/Get.vue b/src/views/demo/fetch/components/Get.vue
similarity index 100%
rename from src/views/plugin/fetch/components/Get.vue
rename to src/views/demo/fetch/components/Get.vue
diff --git a/src/views/plugin/fetch/components/NoToken.vue b/src/views/demo/fetch/components/NoToken.vue
similarity index 100%
rename from src/views/plugin/fetch/components/NoToken.vue
rename to src/views/demo/fetch/components/NoToken.vue
diff --git a/src/views/plugin/fetch/components/Post.vue b/src/views/demo/fetch/components/Post.vue
similarity index 100%
rename from src/views/plugin/fetch/components/Post.vue
rename to src/views/demo/fetch/components/Post.vue
diff --git a/src/views/plugin/fetch/components/Put.vue b/src/views/demo/fetch/components/Put.vue
similarity index 100%
rename from src/views/plugin/fetch/components/Put.vue
rename to src/views/demo/fetch/components/Put.vue
diff --git a/src/views/plugin/fetch/components/RefreshToken.vue b/src/views/demo/fetch/components/RefreshToken.vue
similarity index 100%
rename from src/views/plugin/fetch/components/RefreshToken.vue
rename to src/views/demo/fetch/components/RefreshToken.vue
diff --git a/src/views/plugin/fetch/components/TokenExpiration.vue b/src/views/demo/fetch/components/TokenExpiration.vue
similarity index 100%
rename from src/views/plugin/fetch/components/TokenExpiration.vue
rename to src/views/demo/fetch/components/TokenExpiration.vue
diff --git a/src/views/plugin/fetch/components/Transform.vue b/src/views/demo/fetch/components/Transform.vue
similarity index 100%
rename from src/views/plugin/fetch/components/Transform.vue
rename to src/views/demo/fetch/components/Transform.vue
diff --git a/src/views/plugin/fetch/components/UseRequest.vue b/src/views/demo/fetch/components/UseRequest.vue
similarity index 100%
rename from src/views/plugin/fetch/components/UseRequest.vue
rename to src/views/demo/fetch/components/UseRequest.vue
diff --git a/src/views/plugin/fetch/index.vue b/src/views/demo/fetch/index.vue
similarity index 100%
rename from src/views/plugin/fetch/index.vue
rename to src/views/demo/fetch/index.vue
diff --git a/src/views/plugin/icons/index.vue b/src/views/demo/icons/index.vue
similarity index 100%
rename from src/views/plugin/icons/index.vue
rename to src/views/demo/icons/index.vue
diff --git a/src/views/plugin/map/components/AMap.vue b/src/views/demo/map/components/AMap.vue
similarity index 100%
rename from src/views/plugin/map/components/AMap.vue
rename to src/views/demo/map/components/AMap.vue
diff --git a/src/views/plugin/map/components/BMap.vue b/src/views/demo/map/components/BMap.vue
similarity index 100%
rename from src/views/plugin/map/components/BMap.vue
rename to src/views/demo/map/components/BMap.vue
diff --git a/src/views/plugin/map/index.vue b/src/views/demo/map/index.vue
similarity index 100%
rename from src/views/plugin/map/index.vue
rename to src/views/demo/map/index.vue
diff --git a/src/views/login/components/Login/index.vue b/src/views/login/components/Login/index.vue
index 53cc8a9..b9276e2 100644
--- a/src/views/login/components/Login/index.vue
+++ b/src/views/login/components/Login/index.vue
@@ -10,18 +10,22 @@ const authStore = useAuthStore()
 function toOtherForm(type: any) {
   emit('update:modelValue', type)
 }
-const rules = {
-  account: {
-    required: true,
-    trigger: 'blur',
-    message: '请输入账户',
-  },
-  pwd: {
-    required: true,
-    trigger: 'blur',
-    message: '请输入密码',
-  },
-}
+
+const { t } = useI18n()
+const rules = computed(() => {
+  return {
+    account: {
+      required: true,
+      trigger: 'blur',
+      message: t('login.accountRuleTip'),
+    },
+    pwd: {
+      required: true,
+      trigger: 'blur',
+      message: t('login.passwordRuleTip'),
+    },
+  }
+})
 const formValue = ref({
   account: 'super',
   pwd: '123456',
@@ -46,6 +50,9 @@ function handleLogin() {
     isLoading.value = false
   })
 }
+onMounted(() => {
+  checkUserAccount()
+})
 function checkUserAccount() {
   const loginAccount = local.get('loginAccount')
   if (!loginAccount)
@@ -54,20 +61,19 @@ function checkUserAccount() {
   formValue.value = loginAccount
   isRemember.value = true
 }
-checkUserAccount()
 </script>
 
 <template>
   <div>
     <n-h2 depth="3" class="text-center">
-      登录
+      {{ $t('login.signInTitle') }}
     </n-h2>
     <n-form ref="formRef" :rules="rules" :model="formValue" :show-label="false" size="large">
       <n-form-item path="account">
-        <n-input v-model:value="formValue.account" clearable placeholder="输入账号" />
+        <n-input v-model:value="formValue.account" clearable :placeholder="$t('login.accountPlaceholder')" />
       </n-form-item>
       <n-form-item path="pwd">
-        <n-input v-model:value="formValue.pwd" type="password" placeholder="输入密码" clearable show-password-on="click">
+        <n-input v-model:value="formValue.pwd" type="password" :placeholder="$t('login.passwordPlaceholder')" clearable show-password-on="click">
           <template #password-invisible-icon>
             <icon-park-outline-preview-close-one />
           </template>
@@ -79,22 +85,25 @@ checkUserAccount()
       <n-space vertical :size="20">
         <div class="flex-y-center justify-between">
           <n-checkbox v-model:checked="isRemember">
-            记住我
+            {{ $t('login.rememberMe') }}
           </n-checkbox>
           <n-button type="primary" text @click="toOtherForm('resetPwd')">
-            忘记密码?
+            {{ $t('login.forgotPassword') }}
           </n-button>
         </div>
         <n-button block type="primary" size="large" :loading="isLoading" :disabled="isLoading" @click="handleLogin">
-          登录
-        </n-button>
-        <n-button type="primary" text @click="toOtherForm('register')">
-          立即注册
+          {{ $t('login.signIn') }}
         </n-button>
+        <n-flex>
+          <n-text>{{ $t('login.noAccountText') }}</n-text>
+          <n-button type="primary" text @click="toOtherForm('register')">
+            {{ $t('login.signUp') }}
+          </n-button>
+        </n-flex>
       </n-space>
     </n-form>
     <n-divider>
-      <span op-80>其他登录</span>
+      <span op-80>{{ $t('login.or') }}</span>
     </n-divider>
     <n-space justify="center">
       <n-button circle>
diff --git a/src/views/login/components/Register/index.vue b/src/views/login/components/Register/index.vue
index e87aafd..69f6e30 100644
--- a/src/views/login/components/Register/index.vue
+++ b/src/views/login/components/Register/index.vue
@@ -3,21 +3,23 @@ const emit = defineEmits(['update:modelValue'])
 function toLogin() {
   emit('update:modelValue', 'login')
 }
+const { t } = useI18n()
+
 const rules = {
   account: {
     required: true,
     trigger: 'blur',
-    message: '请输入账户',
+    message: t('login.accountRuleTip'),
   },
   pwd: {
     required: true,
     trigger: 'blur',
-    message: '请输入密码',
+    message: t('login.passwordRuleTip'),
   },
   rePwd: {
     required: true,
     trigger: 'blur',
-    message: '请再次确认密码',
+    message: t('login.checkPasswordRuleTip'),
   },
 }
 const formValue = ref({
@@ -34,7 +36,7 @@ function handleRegister() {}
 <template>
   <div>
     <n-h2 depth="3" class="text-center">
-      注册
+      {{ $t('login.registerTitle') }}
     </n-h2>
     <n-form
       :rules="rules"
@@ -46,14 +48,14 @@ function handleRegister() {}
         <n-input
           v-model:value="formValue.account"
           clearable
-          placeholder="输入账号"
+          :placeholder="$t('login.accountPlaceholder')"
         />
       </n-form-item>
       <n-form-item path="pwd">
         <n-input
           v-model:value="formValue.pwd"
           type="password"
-          placeholder="输入密码"
+          :placeholder="$t('login.passwordPlaceholder')"
           clearable
           show-password-on="click"
         >
@@ -69,7 +71,7 @@ function handleRegister() {}
         <n-input
           v-model:value="formValue.rePwd"
           type="password"
-          placeholder="请再次输入密码"
+          :placeholder="$t('login.checkPasswordPlaceholder')"
           clearable
           show-password-on="click"
         >
@@ -88,17 +90,11 @@ function handleRegister() {}
           class="w-full"
         >
           <n-checkbox v-model:checked="isRead">
-            我已阅读并同意 <n-button
+            {{ $t('login.readAndAgree') }} <n-button
               type="primary"
               text
             >
-              用户协议
-            </n-button> 及
-            <n-button
-              type="primary"
-              text
-            >
-              xx社区规范
+              {{ $t('login.userAgreement') }}
             </n-button>
           </n-checkbox>
           <n-button
@@ -106,16 +102,18 @@ function handleRegister() {}
             type="primary"
             @click="handleRegister"
           >
-            立即注册
-          </n-button>
-          <n-button
-            tertiary
-            block
-            type="primary"
-            @click="toLogin"
-          >
-            已有账号?去登录
+            {{ $t('login.signUp') }}
           </n-button>
+          <n-flex justify="center">
+            <n-text>{{ $t('login.haveAccountText') }}</n-text>
+            <n-button
+              text
+              type="primary"
+              @click="toLogin"
+            >
+              {{ $t('login.signIn') }}
+            </n-button>
+          </n-flex>
         </n-space>
       </n-form-item>
     </n-form>
diff --git a/src/views/login/components/ResetPwd/index.vue b/src/views/login/components/ResetPwd/index.vue
index a721da3..6eb49fe 100644
--- a/src/views/login/components/ResetPwd/index.vue
+++ b/src/views/login/components/ResetPwd/index.vue
@@ -3,26 +3,33 @@ const emit = defineEmits(['update:modelValue'])
 function toLogin() {
   emit('update:modelValue', 'login')
 }
-const rules = {
-  account: {
-    required: true,
-    trigger: 'blur',
-    message: '请输入账号/手机号码',
-  },
-}
+const { t } = useI18n()
+
+const rules = computed(() => {
+  return {
+    account: {
+      required: true,
+      trigger: 'blur',
+      message: t('login.resetPasswordRuleTip'),
+    },
+  }
+})
 const formValue = ref({
   account: '',
 })
-
-function handleRegister() {}
+const formRef = ref<FormInst | null>(null)
+function handleRegister() {
+  formRef.value?.validate()
+}
 </script>
 
 <template>
   <div>
     <n-h2 depth="3" class="text-center">
-      重置密码
+      {{ $t('login.resetPasswordTitle') }}
     </n-h2>
     <n-form
+      ref="formRef"
       :rules="rules"
       :model="formValue"
       :show-label="false"
@@ -32,7 +39,7 @@ function handleRegister() {}
         <n-input
           v-model:value="formValue.account"
           clearable
-          placeholder="账号/手机号码"
+          :placeholder="$t('login.resetPasswordPlaceholder')"
         />
       </n-form-item>
       <n-form-item>
@@ -46,16 +53,18 @@ function handleRegister() {}
             type="primary"
             @click="handleRegister"
           >
-            重置密码
-          </n-button>
-          <n-button
-            tertiary
-            block
-            type="primary"
-            @click="toLogin"
-          >
-            已有账号?去登录
+            {{ $t('login.resetPassword') }}
           </n-button>
+          <n-flex justify="center">
+            <n-text>{{ $t('login.haveAccountText') }}</n-text>
+            <n-button
+              text
+              type="primary"
+              @click="toLogin"
+            >
+              {{ $t('login.signIn') }}
+            </n-button>
+          </n-flex>
         </n-space>
       </n-form-item>
     </n-form>
diff --git a/src/views/login/index.vue b/src/views/login/index.vue
index 7312d98..f4b4163 100644
--- a/src/views/login/index.vue
+++ b/src/views/login/index.vue
@@ -16,6 +16,7 @@ const appName = import.meta.env.VITE_APP_NAME
   <n-el class="wh-full flex-center" style="background-color: var(--body-color);">
     <div class="fixed top-40px right-40px text-lg">
       <DarkModeSwitch />
+      <LangsSwitch />
     </div>
     <n-el
       class="p-4xl h-full w-full sm:w-450px sm:h-700px"