فهرست منبع

wip: multi-language support

vben 4 سال پیش
والد
کامیت
737b1b190c
59فایلهای تغییر یافته به همراه512 افزوده شده و 272 حذف شده
  1. 6 0
      CHANGELOG.zh_CN.md
  2. 1 0
      package.json
  3. 12 9
      src/App.vue
  4. 3 0
      src/components/Application/index.ts
  5. 53 0
      src/components/Application/src/AppLocalPicker.vue
  6. 2 2
      src/components/Dropdown/index.ts
  7. 9 9
      src/components/Dropdown/src/Dropdown.tsx
  8. 6 1
      src/components/Dropdown/src/props.ts
  9. 2 0
      src/components/Dropdown/src/types.ts
  10. 0 16
      src/hooks/web/useI18n.ts
  11. 66 13
      src/hooks/web/useLocale.ts
  12. 0 4
      src/hooks/web/useMessage.tsx
  13. 1 2
      src/layouts/default/setting/SettingDrawer.tsx
  14. 2 2
      src/layouts/logo/index.vue
  15. 2 2
      src/layouts/page/index.tsx
  16. 14 0
      src/locales/index.ts
  17. 18 0
      src/locales/lang/en/sys/errorLog.ts
  18. 12 0
      src/locales/lang/en/sys/exception.ts
  19. 6 0
      src/locales/lang/en/sys/lock.ts
  20. 13 0
      src/locales/lang/en/sys/login.ts
  21. 0 3
      src/locales/lang/en/system/basic.ts
  22. 0 3
      src/locales/lang/en/system/login.ts
  23. 0 3
      src/locales/lang/ru/routes/menus/dashboard.ts
  24. 0 3
      src/locales/lang/ru/system/basic.ts
  25. 0 7
      src/locales/lang/ru/system/login.ts
  26. 0 3
      src/locales/lang/zhCN/system/basic.ts
  27. 0 3
      src/locales/lang/zhCN/system/login.ts
  28. 0 0
      src/locales/lang/zh_CN/routes/menus/dashboard.ts
  29. 18 0
      src/locales/lang/zh_CN/sys/errorLog.ts
  30. 11 0
      src/locales/lang/zh_CN/sys/exception.ts
  31. 6 0
      src/locales/lang/zh_CN/sys/lock.ts
  32. 13 0
      src/locales/lang/zh_CN/sys/login.ts
  33. 1 1
      src/locales/types.ts
  34. 3 3
      src/router/guard/index.ts
  35. 2 2
      src/router/guard/permissionGuard.ts
  36. 0 4
      src/router/menus/modules/demo/feat.ts
  37. 0 8
      src/router/routes/modules/demo/feat.ts
  38. 11 10
      src/settings/projectSetting.ts
  39. 10 8
      src/settings/use/index.ts
  40. 36 0
      src/settings/use/useLocaleSetting.ts
  41. 1 3
      src/setup/directives/permission.ts
  42. 1 1
      src/setup/directives/repeatClick.ts
  43. 3 4
      src/setup/error-handle/index.ts
  44. 11 12
      src/setup/i18n/index.ts
  45. 22 1
      src/store/modules/app.ts
  46. 3 3
      src/store/modules/error.ts
  47. 3 15
      src/store/modules/user.ts
  48. 12 18
      src/types/config.d.ts
  49. 2 2
      src/utils/helper/envHelper.ts
  50. 2 2
      src/utils/http/axios/index.ts
  51. 0 38
      src/views/demo/feat/i18n/index.vue
  52. 7 1
      src/views/sys/error-log/DetailModal.vue
  53. 10 6
      src/views/sys/error-log/data.tsx
  54. 23 9
      src/views/sys/error-log/index.vue
  55. 17 10
      src/views/sys/exception/Exception.tsx
  56. 21 15
      src/views/sys/lock/index.vue
  57. 24 10
      src/views/sys/login/Login.vue
  58. 6 1
      vite.config.ts
  59. 5 0
      yarn.lock

+ 6 - 0
CHANGELOG.zh_CN.md

@@ -1,3 +1,9 @@
+## Wip
+
+### 🎫 Chores
+
+- 移除 messageSetting 配置
+
 ## 2.0.0-rc.11 (2020-11-18)
 
 ### ✨ Features

+ 1 - 0
package.json

@@ -26,6 +26,7 @@
     "ant-design-vue": "2.0.0-beta.15",
     "apexcharts": "3.22.0",
     "axios": "^0.21.0",
+    "crypto-es": "^1.2.6",
     "echarts": "^4.9.0",
     "lodash-es": "^4.17.15",
     "mockjs": "^1.1.0",

+ 12 - 9
src/App.vue

@@ -1,5 +1,9 @@
 <template>
-  <ConfigProvider v-bind="lockEvent" :locale="zhCN" :transform-cell-text="transformCellText">
+  <ConfigProvider
+    v-bind="lockEvent"
+    :locale="antConfigLocale"
+    :transform-cell-text="transformCellText"
+  >
     <router-view />
   </ConfigProvider>
 </template>
@@ -7,16 +11,12 @@
 <script lang="ts">
   import { defineComponent } from 'vue';
   import { ConfigProvider } from 'ant-design-vue';
-  import { createBreakpointListen } from '/@/hooks/event/useBreakpoint';
-
-  import zhCN from 'ant-design-vue/es/locale/zh_CN';
-  import moment from 'moment';
-  import 'moment/dist/locale/zh-cn';
 
   import { getConfigProvider, initAppConfigStore } from '/@/setup/App';
-  import { useLockPage } from '/@/hooks/web/useLockPage';
 
-  moment.locale('zh-cn');
+  import { useLockPage } from '/@/hooks/web/useLockPage';
+  import { useLocale } from '/@/hooks/web/useLocale';
+  import { createBreakpointListen } from '/@/hooks/event/useBreakpoint';
 
   export default defineComponent({
     name: 'App',
@@ -34,9 +34,12 @@
       // Create a lock screen monitor
       const lockEvent = useLockPage();
 
+      // support Multi-language
+      const { antConfigLocale } = useLocale();
+
       return {
         transformCellText,
-        zhCN,
+        antConfigLocale,
         lockEvent,
       };
     },

+ 3 - 0
src/components/Application/index.ts

@@ -0,0 +1,3 @@
+import AppLocalPicker from './src/AppLocalPicker.vue';
+
+export { AppLocalPicker };

+ 53 - 0
src/components/Application/src/AppLocalPicker.vue

@@ -0,0 +1,53 @@
+<template>
+  <Dropdown
+    :trigger="['click']"
+    :dropMenuList="localeList"
+    :selectedKeys="selectedKeys"
+    @menuEvent="handleMenuEvent"
+  >
+    <GlobalOutlined class="app-locale" />
+  </Dropdown>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, watchEffect, unref } from 'vue';
+
+  import { Dropdown, DropMenu } from '/@/components/Dropdown';
+  import { GlobalOutlined } from '@ant-design/icons-vue';
+
+  import { useLocale } from '/@/hooks/web/useLocale';
+  import { useLocaleSetting } from '/@/settings/use/useLocaleSetting';
+
+  import { LocaleType } from '/@/locales/types';
+
+  export default defineComponent({
+    name: 'AppLocalPicker',
+    components: { GlobalOutlined, Dropdown },
+    setup() {
+      const { localeList } = useLocaleSetting();
+      const selectedKeys = ref<string[]>([]);
+
+      const { changeLocale, getLang } = useLocale();
+
+      watchEffect(() => {
+        selectedKeys.value = [unref(getLang)];
+      });
+
+      function toggleLocale(lang: LocaleType | string) {
+        changeLocale(lang as LocaleType);
+        selectedKeys.value = [lang as string];
+      }
+
+      function handleMenuEvent(menu: DropMenu) {
+        toggleLocale(menu.event as string);
+      }
+
+      return { localeList, handleMenuEvent, selectedKeys };
+    },
+  });
+</script>
+
+<style lang="less" scoped>
+  .app-locale {
+    cursor: pointer;
+  }
+</style>

+ 2 - 2
src/components/Dropdown/index.ts

@@ -1,2 +1,2 @@
-export { default as Dropdown } from './Dropdown';
-export * from './types';
+export { default as Dropdown } from './src/Dropdown';
+export * from './src/types';

+ 9 - 9
src/components/Dropdown/Dropdown.tsx → src/components/Dropdown/src/Dropdown.tsx

@@ -1,32 +1,33 @@
 import { defineComponent, computed, unref } from 'vue';
-import { Dropdown, Menu } from 'ant-design-vue';
+import { Dropdown, Menu, Divider } from 'ant-design-vue';
 
 import Icon from '/@/components/Icon/index';
 
 import { basicDropdownProps } from './props';
 import { getSlot } from '/@/utils/helper/tsxHelper';
+import { Trigger } from './types';
 
 export default defineComponent({
   name: 'Dropdown',
   props: basicDropdownProps,
+  emits: ['menuEvent'],
   setup(props, { slots, emit, attrs }) {
     const getMenuList = computed(() => props.dropMenuList);
 
     function handleClickMenu({ key }: any) {
-      const menu = unref(getMenuList)[key];
+      const menu = unref(getMenuList).find((item) => item.event === key);
       emit('menuEvent', menu);
     }
 
     function renderMenus() {
       return (
-        <Menu onClick={handleClickMenu}>
+        <Menu onClick={handleClickMenu} selectedKeys={props.selectedKeys}>
           {() => (
             <>
               {unref(getMenuList).map((item, index) => {
-                const { disabled, icon, text, divider } = item;
-
+                const { disabled, icon, text, divider, event } = item;
                 return [
-                  <Menu.Item key={`${index}`} disabled={disabled}>
+                  <Menu.Item key={`${event}`} disabled={disabled}>
                     {() => (
                       <>
                         {icon && <Icon icon={icon} />}
@@ -34,8 +35,7 @@ export default defineComponent({
                       </>
                     )}
                   </Menu.Item>,
-                  // @ts-ignore
-                  divider && <Menu.Divider key={`d-${index}`} />,
+                  divider && <Divider key={`d-${index}`} />,
                 ];
               })}
             </>
@@ -45,7 +45,7 @@ export default defineComponent({
     }
 
     return () => (
-      <Dropdown trigger={props.trigger as any} {...attrs}>
+      <Dropdown trigger={props.trigger as Trigger[]} {...attrs}>
         {{
           default: () => <span>{getSlot(slots)}</span>,
           overlay: () => renderMenus(),

+ 6 - 1
src/components/Dropdown/props.ts → src/components/Dropdown/src/props.ts

@@ -1,4 +1,5 @@
 import type { PropType } from 'vue';
+import type { DropMenu } from './types';
 
 export const dropdownProps = {
   /**
@@ -15,7 +16,11 @@ export const dropdownProps = {
 };
 export const basicDropdownProps = Object.assign({}, dropdownProps, {
   dropMenuList: {
-    type: Array as PropType<any[]>,
+    type: Array as PropType<DropMenu[]>,
+    default: () => [],
+  },
+  selectedKeys: {
+    type: Array as PropType<string[]>,
     default: () => [],
   },
 });

+ 2 - 0
src/components/Dropdown/types.ts → src/components/Dropdown/src/types.ts

@@ -6,3 +6,5 @@ export interface DropMenu {
   disabled?: boolean;
   divider?: boolean;
 }
+
+export type Trigger = 'click' | 'hover' | 'contextMenu';

+ 0 - 16
src/hooks/web/useI18n.ts

@@ -1,16 +0,0 @@
-import { createI18n } from 'vue-i18n';
-import { ref, watch } from 'vue';
-import type { I18nOptions } from 'vue-i18n';
-export function useI18n(options?: I18nOptions) {
-  const i18n = createI18n(options);
-
-  const localeRef = ref(i18n.global.locale);
-
-  watch(localeRef, () => {
-    i18n.global.locale = localeRef.value as any;
-  });
-  return {
-    t: i18n.global.t,
-    localeRef,
-  };
-}

+ 66 - 13
src/hooks/web/useLocale.ts

@@ -1,21 +1,74 @@
+/**
+ * Multi-language related operations
+ */
 import type { LocaleType } from '/@/locales/types';
-import { appStore } from '/@/store/modules/app';
+
+import { unref, ref } from 'vue';
+
+import { getI18n } from '/@/setup/i18n';
+
+import { useLocaleSetting } from '/@/settings/use/useLocaleSetting';
+
+import moment from 'moment';
+
+import 'moment/dist/locale/zh-cn';
+
+moment.locale('zh-cn');
+
+const antConfigLocaleRef = ref<any>(null);
 
 export function useLocale() {
-  /**
-   *
-   */
-  function getLocale(): string {
-    return appStore.getProjectConfig.locale;
+  const { getLang, getLocale, setLocale: setLocalSetting } = useLocaleSetting();
+
+  // Switching the language will change the locale of useI18n
+  // And submit to configuration modification
+  function changeLocale(lang: LocaleType): void {
+    (getI18n().global.locale as any).value = lang;
+    setLocalSetting({ lang });
+    // i18n.global.setLocaleMessage(locale, messages);
+
+    antConfigLocaleRef.value = { a: 1 };
+    switch (lang) {
+      // Simplified Chinese
+      case 'zh_CN':
+        import('ant-design-vue/es/locale/zh_CN').then((locale) => {
+          antConfigLocaleRef.value = locale.default;
+        });
+
+        moment.locale('cn');
+        break;
+      // English
+      case 'en':
+        import('ant-design-vue/es/locale/en_US').then((locale) => {
+          antConfigLocaleRef.value = locale.default;
+        });
+        moment.locale('en-us');
+        break;
+
+      // other
+      default:
+        break;
+    }
   }
 
-  /**
-   *
-   * @param locale
-   */
-  async function changeLocale(locale: LocaleType): Promise<void> {
-    appStore.commitProjectConfigState({ locale: locale });
+  // initialization
+  function setupLocale() {
+    const lang = unref(getLang);
+    lang && changeLocale(lang);
   }
 
-  return { getLocale, changeLocale };
+  return {
+    setupLocale,
+    getLocale,
+    getLang,
+    changeLocale,
+    antConfigLocale: antConfigLocaleRef,
+  };
+}
+
+/**
+ * For non-setup use
+ */
+export function useExternalI18n() {
+  return getI18n().global;
 }

+ 0 - 4
src/hooks/web/useMessage.tsx

@@ -3,7 +3,6 @@ import type { ModalFunc, ModalFuncProps } from 'ant-design-vue/lib/modal/Modal';
 import { Modal, message as Message, notification } from 'ant-design-vue';
 import { InfoCircleFilled, CheckCircleFilled, CloseCircleFilled } from '@ant-design/icons-vue';
 
-import { useSetting } from '/@/hooks/core/useSetting';
 import { ArgsProps, ConfigProps } from 'ant-design-vue/lib/notification';
 
 export interface NotifyApi {
@@ -33,8 +32,6 @@ interface ConfirmOptions {
   warning: ModalFunc;
 }
 
-const { projectSetting } = useSetting();
-
 function getIcon(iconType: string) {
   if (iconType === 'warning') {
     return <InfoCircleFilled class="modal-icon-warning" />;
@@ -60,7 +57,6 @@ function createConfirm(options: ModalOptionsEx): ConfirmOptions {
   const opt: ModalFuncProps = {
     centered: true,
     icon: getIcon(iconType),
-    ...projectSetting.messageSetting,
     ...options,
   };
   return Modal.confirm(opt) as any;

+ 1 - 2
src/layouts/default/setting/SettingDrawer.tsx

@@ -5,7 +5,6 @@ import Button from '/@/components/Button/index.vue';
 import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum';
 import { CopyOutlined, RedoOutlined, CheckOutlined } from '@ant-design/icons-vue';
 import { appStore } from '/@/store/modules/app';
-import { userStore } from '/@/store/modules/user';
 import { ProjectConfig } from '/@/types/config';
 
 import { useMessage } from '/@/hooks/web/useMessage';
@@ -97,7 +96,7 @@ export default defineComponent({
 
     function handleClearAndRedo() {
       localStorage.clear();
-      userStore.resumeAllState();
+      appStore.resumeAllState();
       location.reload();
     }
 

+ 2 - 2
src/layouts/logo/index.vue

@@ -7,7 +7,7 @@
 <script lang="ts">
   import { computed, defineComponent, PropType, ref, watch } from 'vue';
   // hooks
-  import { useSetting } from '/@/hooks/core/useSetting';
+  import { useGlobSetting } from '/@/settings/use';
   import { useTimeoutFn } from '/@/hooks/core/useTimeout';
   import { useGo } from '/@/hooks/web/usePage';
 
@@ -30,7 +30,7 @@
     },
     setup(props) {
       const showRef = ref<boolean>(!!props.showTitle);
-      const { globSetting } = useSetting();
+      const globSetting = useGlobSetting();
       const go = useGo();
 
       function handleGoHome() {

+ 2 - 2
src/layouts/page/index.tsx

@@ -4,7 +4,7 @@ import { RouterView, RouteLocation } from 'vue-router';
 import FrameLayout from '/@/layouts/iframe/index.vue';
 
 import { useTransition } from './useTransition';
-import { useSetting } from '/@/hooks/core/useSetting';
+import { useProjectSetting } from '/@/settings/use';
 
 import { tabStore } from '/@/store/modules/tab';
 import { appStore } from '/@/store/modules/app';
@@ -29,7 +29,7 @@ export default defineComponent({
       const { on: transitionOn } = useTransition();
       on = transitionOn;
     }
-    const { projectSetting } = useSetting();
+    const projectSetting = useProjectSetting();
     return () => {
       const {
         routerTransition,

+ 14 - 0
src/locales/index.ts

@@ -1,3 +1,17 @@
 import messages from 'globby?locale!/@/locales/lang/**/*.@(ts)';
 
+import type { DropMenu } from '/@/components/Dropdown';
+
+// locale list
+export const localeList: DropMenu[] = [
+  {
+    text: '简体中文',
+    event: 'zh_CN',
+  },
+  {
+    text: 'English',
+    event: 'en',
+  },
+];
+
 export default messages;

+ 18 - 0
src/locales/lang/en/sys/errorLog.ts

@@ -0,0 +1,18 @@
+export default {
+  tableTitle: 'Error log list',
+  tableColumnType: 'Type',
+  tableColumnDate: 'Time',
+  tableColumnFile: 'File',
+  tableColumnMsg: 'Error message',
+  tableColumnStackMsg: 'Stack info',
+
+  tableActionDesc: 'Details',
+
+  modalTitle: 'Error details',
+
+  fireVueError: 'Fire vue error',
+  fireResourceError: 'Fire resource error',
+  fireAjaxError: 'Fire ajax error',
+
+  enableMessage: 'Only effective when useErrorHandle=true in `/src/settings/projectSetting.ts`.',
+};

+ 12 - 0
src/locales/lang/en/sys/exception.ts

@@ -0,0 +1,12 @@
+export default {
+  backLogin: 'Back Login',
+  backHome: 'Back Home',
+  redo: 'Refresh',
+  subTitle403: "Sorry, you don't have access to this page.",
+  subTitle404: 'Sorry, the page you visited does not exist.',
+  subTitle500: 'Sorry, the server is reporting an error.',
+  noDataTitle: 'No data on the current page.',
+  networkErrorTitle: 'Network Error',
+  networkErrorSubTitle:
+    'Sorry,Your network connection has been disconnected, please check your network!',
+};

+ 6 - 0
src/locales/lang/en/sys/lock.ts

@@ -0,0 +1,6 @@
+export default {
+  alert: 'Lock screen password error',
+  backToLogin: 'Back to login',
+  entry: 'Enter the system',
+  placeholder: 'Please enter the lock screen password or user password',
+};

+ 13 - 0
src/locales/lang/en/sys/login.ts

@@ -0,0 +1,13 @@
+export default {
+  loginButton: 'Login',
+  autoLogin: 'AutoLogin',
+  forgetPassword: 'Forget Password',
+
+  // notify
+  loginSuccessTitle: 'Login successful',
+  loginSuccessDesc: 'Welcome back',
+
+  // placeholder
+  accountPlaceholder: 'Please input Username',
+  passwordPlaceholder: 'Please input Password',
+};

+ 0 - 3
src/locales/lang/en/system/basic.ts

@@ -1,3 +0,0 @@
-export default {
-  some: 'Get Out',
-};

+ 0 - 3
src/locales/lang/en/system/login.ts

@@ -1,3 +0,0 @@
-export default {
-  button: 'Login',
-};

+ 0 - 3
src/locales/lang/ru/routes/menus/dashboard.ts

@@ -1,3 +0,0 @@
-export default {
-  someentry: 'some text',
-};

+ 0 - 3
src/locales/lang/ru/system/basic.ts

@@ -1,3 +0,0 @@
-export default {
-  some: 'Get Out',
-};

+ 0 - 7
src/locales/lang/ru/system/login.ts

@@ -1,7 +0,0 @@
-export default {
-  button: 'Login',
-  validation: {
-    account: 'Required Field account',
-    password: 'Required Field password',
-  },
-};

+ 0 - 3
src/locales/lang/zhCN/system/basic.ts

@@ -1,3 +0,0 @@
-export default {
-  some: '出去',
-};

+ 0 - 3
src/locales/lang/zhCN/system/login.ts

@@ -1,3 +0,0 @@
-export default {
-  button: '登录',
-};

+ 0 - 0
src/locales/lang/zhCN/routes/menus/dashboard.ts → src/locales/lang/zh_CN/routes/menus/dashboard.ts


+ 18 - 0
src/locales/lang/zh_CN/sys/errorLog.ts

@@ -0,0 +1,18 @@
+export default {
+  tableTitle: '错误日志列表',
+  tableColumnType: '类型',
+  tableColumnDate: '时间',
+  tableColumnFile: '文件',
+  tableColumnMsg: '错误信息',
+  tableColumnStackMsg: 'stack信息',
+
+  tableActionDesc: '详情',
+
+  modalTitle: '错误详情',
+
+  fireVueError: '点击触发vue错误',
+  fireResourceError: '点击触发资源加载错误',
+  fireAjaxError: '点击触发ajax错误',
+
+  enableMessage: '只在`/src/settings/projectSetting.ts` 内的useErrorHandle=true时生效.',
+};

+ 11 - 0
src/locales/lang/zh_CN/sys/exception.ts

@@ -0,0 +1,11 @@
+export default {
+  backLogin: '返回登录',
+  backHome: '返回首页',
+  redo: '刷新',
+  subTitle403: '抱歉,您无权访问此页面。',
+  subTitle404: '抱歉,您访问的页面不存在。',
+  subTitle500: '抱歉,服务器报告错误。',
+  noDataTitle: '当前页无数据',
+  networkErrorTitle: '网络错误',
+  networkErrorSubTitle: '抱歉,您的网络连接已断开,请检查您的网络!',
+};

+ 6 - 0
src/locales/lang/zh_CN/sys/lock.ts

@@ -0,0 +1,6 @@
+export default {
+  alert: '锁屏密码错误',
+  backToLogin: '返回登录',
+  entry: '进入系统',
+  placeholder: '请输入锁屏密码或者用户密码',
+};

+ 13 - 0
src/locales/lang/zh_CN/sys/login.ts

@@ -0,0 +1,13 @@
+export default {
+  loginButton: '登录',
+  autoLogin: '自动登录',
+  forgetPassword: '忘记密码',
+
+  // notify
+  loginSuccessTitle: '登录成功',
+  loginSuccessDesc: '欢迎回来',
+
+  // placeholder
+  accountPlaceholder: '请输入账号',
+  passwordPlaceholder: '请输入密码',
+};

+ 1 - 1
src/locales/types.ts

@@ -1 +1 @@
-export type LocaleType = 'zhCN' | 'en' | 'ru' | 'ja';
+export type LocaleType = 'zh_CN' | 'en' | 'ru' | 'ja';

+ 3 - 3
src/router/guard/index.ts

@@ -6,7 +6,7 @@ import { createProgressGuard } from './progressGuard';
 import { createPermissionGuard } from './permissionGuard';
 import { createPageLoadingGuard } from './pageLoadingGuard';
 
-import { useSetting } from '/@/hooks/core/useSetting';
+import { useGlobSetting, useProjectSetting } from '/@/settings/use';
 
 import { getIsOpenTab, setCurrentTo } from '/@/utils/helper/routeHelper';
 import { setTitle } from '/@/utils/browser';
@@ -14,9 +14,9 @@ import { AxiosCanceler } from '/@/utils/http/axios/axiosCancel';
 
 import { tabStore } from '/@/store/modules/tab';
 
-const { projectSetting, globSetting } = useSetting();
+const globSetting = useGlobSetting();
 export function createGuard(router: Router) {
-  const { openNProgress, closeMessageOnSwitch, removeAllHttpPending } = projectSetting;
+  const { openNProgress, closeMessageOnSwitch, removeAllHttpPending } = useProjectSetting();
   let axiosCanceler: AxiosCanceler | null;
   if (removeAllHttpPending) {
     axiosCanceler = new AxiosCanceler();

+ 2 - 2
src/router/guard/permissionGuard.ts

@@ -1,6 +1,6 @@
 import type { Router, RouteRecordRaw } from 'vue-router';
 
-import { userStore } from '/@/store/modules/user';
+import { appStore } from '/@/store/modules/app';
 import { permissionStore } from '/@/store/modules/permission';
 
 import { PageEnum } from '/@/enums/pageEnum';
@@ -72,7 +72,7 @@ export function createPermissionGuard(router: Router) {
   router.afterEach((to) => {
     // Just enter the login page and clear the authentication information
     if (to.path === LOGIN_PATH) {
-      userStore.resumeAllState();
+      appStore.resumeAllState();
     }
   });
 }

+ 0 - 4
src/router/menus/modules/demo/feat.ts

@@ -36,10 +36,6 @@ const menu: MenuModule = {
         name: '图片预览',
       },
       {
-        path: 'i18n',
-        name: '国际化',
-      },
-      {
         path: 'copy',
         name: '剪切板',
       },

+ 0 - 8
src/router/routes/modules/demo/feat.ts

@@ -81,14 +81,6 @@ const feat: AppRouteModule = {
       },
     },
     {
-      path: '/i18n',
-      name: 'I18nDemo',
-      component: () => import('/@/views/demo/feat/i18n/index.vue'),
-      meta: {
-        title: '国际化',
-      },
-    },
-    {
       path: '/watermark',
       name: 'WatermarkDemo',
       component: () => import('/@/views/demo/feat/watermark/index.vue'),

+ 11 - 10
src/settings/projectSetting.ts

@@ -7,7 +7,16 @@ import { isProdMode } from '/@/utils/env';
 
 // ! You need to clear the browser cache after the change
 const setting: ProjectConfig = {
-  locale: 'en',
+  // locale setting
+  locale: {
+    // Locales
+    lang: 'zh_CN',
+    // Default locale
+    fallback: 'zh_CN',
+    // available Locales
+    availableLocales: ['zh_CN', 'en'],
+  },
+
   // color
   // TODO 主题色
   themeColor: primaryColor,
@@ -87,15 +96,7 @@ const setting: ProjectConfig = {
     // 开启手风琴模式,只显示一个菜单
     accordion: true,
   },
-  // 消息配置
-  messageSetting: {
-    // 弹窗title
-    title: '操作提示',
-    // 取消按钮的文子,
-    cancelText: '取消',
-    // 确认按钮的文字
-    okText: '确定',
-  },
+
   // 多标签
   multiTabsSetting: {
     // 开启

+ 10 - 8
src/hooks/core/useSetting.ts → src/settings/use/index.ts

@@ -1,10 +1,10 @@
-import type { ProjectConfig, GlobConfig, SettingWrap, GlobEnvConfig } from '/@/types/config';
+import type { ProjectConfig, GlobConfig, GlobEnvConfig } from '/@/types/config';
 
 import getProjectSetting from '/@/settings/projectSetting';
 
-import { getGlobEnvConfig, isDevMode } from '/@/utils/env';
 import { getShortName } from '../../../build/getShortName';
 import { warn } from '/@/utils/log';
+import { getGlobEnvConfig, isDevMode } from '/@/utils/env';
 
 const reg = /[a-zA-Z\_]*/;
 
@@ -12,6 +12,7 @@ const ENV_NAME = getShortName(import.meta.env);
 const ENV = ((isDevMode()
   ? getGlobEnvConfig()
   : window[ENV_NAME as any]) as unknown) as GlobEnvConfig;
+
 const {
   VITE_GLOB_APP_TITLE,
   VITE_GLOB_API_URL,
@@ -25,7 +26,7 @@ if (!reg.test(VITE_GLOB_APP_SHORT_NAME)) {
   );
 }
 
-export const useSetting = (): SettingWrap => {
+export const useGlobSetting = (): Readonly<GlobConfig> => {
   // Take global configuration
   const glob: Readonly<GlobConfig> = {
     title: VITE_GLOB_APP_TITLE,
@@ -33,9 +34,10 @@ export const useSetting = (): SettingWrap => {
     shortName: VITE_GLOB_APP_SHORT_NAME,
     urlPrefix: VITE_GLOB_API_URL_PREFIX,
   };
-  const projectSetting: Readonly<ProjectConfig> = getProjectSetting;
-  return {
-    globSetting: glob as Readonly<GlobConfig>,
-    projectSetting,
-  };
+  return glob as Readonly<GlobConfig>;
+};
+
+export const useProjectSetting = (): ProjectConfig => {
+  // TODO computed
+  return getProjectSetting;
 };

+ 36 - 0
src/settings/use/useLocaleSetting.ts

@@ -0,0 +1,36 @@
+import type { LocaleSetting } from '/@/types/config';
+
+import { computed } from 'vue';
+import { appStore } from '/@/store/modules/app';
+
+import getProjectSetting from '/@/settings/projectSetting';
+import { localeList } from '/@/locales';
+
+export function useLocaleSetting() {
+  // Get locale configuration
+  const getLocale = computed(() => {
+    return appStore.getProjectConfig.locale || getProjectSetting.locale;
+  });
+
+  // get current language
+  const getLang = computed(() => {
+    return getLocale.value.lang;
+  });
+
+  // get Available Locales
+  const getAvailableLocales = computed((): string[] => {
+    return getLocale.value.availableLocales;
+  });
+
+  // get Fallback Locales
+  const getFallbackLocale = computed((): string => {
+    return getLocale.value.fallback;
+  });
+
+  // Set locale configuration
+  function setLocale(locale: Partial<LocaleSetting>): void {
+    appStore.commitProjectConfigState({ locale });
+  }
+
+  return { getLocale, getLang, localeList, setLocale, getAvailableLocales, getFallbackLocale };
+}

+ 1 - 3
src/setup/directives/permission.ts

@@ -14,9 +14,7 @@ function isAuth(el: Element, binding: any) {
   const value = binding.value;
   if (!value) return;
   if (!hasPermission(value)) {
-    if (el.parentNode) {
-      el.parentNode.removeChild(el);
-    }
+    el.parentNode?.removeChild(el);
   }
 }
 

+ 1 - 1
src/setup/directives/repeatClick.ts

@@ -9,7 +9,7 @@ const repeatDirective: Directive = {
   beforeMount(el: Element, binding: DirectiveBinding<any>) {
     let interval: Nullable<IntervalHandle> = null;
     let startTime = 0;
-    const handler = (): void => binding.value && binding.value();
+    const handler = (): void => binding?.value();
     const clear = (): void => {
       if (Date.now() - startTime < 100) {
         handler();

+ 3 - 4
src/setup/error-handle/index.ts

@@ -3,7 +3,7 @@
  */
 
 import { errorStore, ErrorInfo } from '/@/store/modules/error';
-import { useSetting } from '/@/hooks/core/useSetting';
+import { useProjectSetting } from '/@/settings/use';
 import { ErrorTypeEnum } from '/@/enums/exceptionEnum';
 import { App } from 'vue';
 
@@ -89,7 +89,7 @@ export function scriptErrorHandler(
   const errorInfo: Partial<ErrorInfo> = {};
   colno = colno || (window.event && (window.event as any).errorCharacter) || 0;
   errorInfo.message = event as string;
-  if (error && error.stack) {
+  if (error?.stack) {
     errorInfo.stack = error.stack;
   } else {
     errorInfo.stack = '';
@@ -160,8 +160,7 @@ function registerResourceErrorHandler() {
  * @param app
  */
 export function setupErrorHandle(app: App) {
-  const { projectSetting } = useSetting();
-  const { useErrorHandle } = projectSetting;
+  const { useErrorHandle } = useProjectSetting();
   if (!useErrorHandle) return;
   // Vue exception monitoring;
   app.config.errorHandler = vueErrorHandler;

+ 11 - 12
src/setup/i18n/index.ts

@@ -1,19 +1,20 @@
-import type { App } from 'vue';
-import type { I18n, Locale, I18nOptions } from 'vue-i18n';
+import { App, unref } from 'vue';
+import type { I18n, I18nOptions } from 'vue-i18n';
 
 import { createI18n } from 'vue-i18n';
 import localeMessages from '/@/locales';
 import { useLocale } from '/@/hooks/web/useLocale';
+import { useLocaleSetting } from '/@/settings/use/useLocaleSetting';
 
-const { getLocale } = useLocale();
+const { setupLocale } = useLocale();
 
+const { getLang, getAvailableLocales, getFallbackLocale } = useLocaleSetting();
 const localeData: I18nOptions = {
   legacy: false,
-  locale: getLocale(),
-  // TODO: setting fallback inside settings
-  fallbackLocale: 'en',
+  locale: unref(getLang),
+  fallbackLocale: unref(getFallbackLocale),
   messages: localeMessages,
-  // availableLocales: ['ru'],
+  availableLocales: unref(getAvailableLocales),
   sync: true, //If you don’t want to inherit locale from global scope, you need to set sync of i18n component option to false.
   silentTranslationWarn: false, // true - warning off
   silentFallbackWarn: true,
@@ -24,12 +25,10 @@ let i18n: I18n;
 // setup i18n instance with glob
 export function setupI18n(app: App) {
   i18n = createI18n(localeData) as I18n;
-  setI18nLanguage(getLocale());
+  setupLocale();
   app.use(i18n);
 }
 
-export function setI18nLanguage(locale: Locale): void {
-  // @ts-ignore
-  i18n.global.locale.value = locale;
-  // i18n.global.setLocaleMessage(locale, messages);
+export function getI18n(): I18n {
+  return i18n;
 }

+ 22 - 1
src/store/modules/app.ts

@@ -6,9 +6,19 @@ import store from '/@/store';
 import { PROJ_CFG_KEY, LOCK_INFO_KEY } from '/@/enums/cacheEnum';
 
 import { hotModuleUnregisterModule } from '/@/utils/helper/vuexHelper';
-import { setLocal, getLocal, removeLocal } from '/@/utils/helper/persistent';
+import {
+  setLocal,
+  getLocal,
+  removeLocal,
+  clearSession,
+  clearLocal,
+} from '/@/utils/helper/persistent';
 import { deepMerge } from '/@/utils';
 
+import { resetRouter } from '/@/router';
+import { permissionStore } from './permission';
+import { tabStore } from './tab';
+
 import { userStore } from './user';
 
 export interface LockInfo {
@@ -78,6 +88,17 @@ class App extends VuexModule {
   }
 
   @Action
+  async resumeAllState() {
+    resetRouter();
+    clearSession();
+    clearLocal();
+
+    permissionStore.commitResetState();
+    tabStore.commitResetState();
+    userStore.commitResetState();
+  }
+
+  @Action
   public async setPageLoadingAction(loading: boolean): Promise<void> {
     if (loading) {
       clearTimeout(timeId);

+ 3 - 3
src/store/modules/error.ts

@@ -4,7 +4,7 @@ import { VuexModule, getModule, Module, Mutation, Action } from 'vuex-module-dec
 
 import { formatToDateTime } from '/@/utils/dateUtil';
 import { ErrorTypeEnum } from '/@/enums/exceptionEnum';
-import { useSetting } from '/@/hooks/core/useSetting';
+import { useProjectSetting } from '/@/settings/use';
 
 export interface ErrorInfo {
   type: ErrorTypeEnum;
@@ -16,6 +16,7 @@ export interface ErrorInfo {
   url: string;
   time?: string;
 }
+
 export interface ErrorState {
   errorInfoState: ErrorInfo[] | null;
   errorListCountState: number;
@@ -56,8 +57,7 @@ class Error extends VuexModule implements ErrorState {
 
   @Action
   setupErrorHandle(error: any) {
-    const { projectSetting } = useSetting();
-    const { useErrorHandle } = projectSetting;
+    const { useErrorHandle } = useProjectSetting();
     if (!useErrorHandle) return;
 
     const errInfo: Partial<ErrorInfo> = {

+ 3 - 15
src/store/modules/user.ts

@@ -15,13 +15,11 @@ import { ROLES_KEY, TOKEN_KEY, USER_INFO_KEY } from '/@/enums/cacheEnum';
 
 import { useMessage } from '/@/hooks/web/useMessage';
 
-import router, { resetRouter } from '/@/router';
-import { permissionStore } from './permission';
-import { tabStore } from './tab';
+import router from '/@/router';
 
 import { loginApi, getUserInfoById } from '/@/api/sys/user';
 
-import { setLocal, getLocal, clearSession, clearLocal } from '/@/utils/helper/persistent';
+import { setLocal, getLocal } from '/@/utils/helper/persistent';
 // import { FULL_PAGE_NOT_FOUND_ROUTE } from '/@/router/constant';
 
 export type UserInfo = Omit<GetUserInfoByUserIdModel, 'roles'>;
@@ -52,7 +50,7 @@ class User extends VuexModule {
   }
 
   @Mutation
-  resetState(): void {
+  commitResetState(): void {
     this.userInfoState = null;
     this.tokenState = '';
     this.roleListState = [];
@@ -128,16 +126,6 @@ class User extends VuexModule {
     goLogin && router.push(PageEnum.BASE_LOGIN);
   }
 
-  @Action
-  async resumeAllState() {
-    resetRouter();
-    clearSession();
-    clearLocal();
-    permissionStore.commitResetState();
-    tabStore.commitResetState();
-    this.resetState();
-  }
-
   /**
    * @description: Confirm before logging out
    */

+ 12 - 18
src/types/config.d.ts

@@ -2,13 +2,7 @@
 import { MenuTypeEnum, MenuModeEnum, TriggerEnum } from '/@/enums/menuEnum';
 import { ContentEnum, PermissionModeEnum, ThemeEnum, RouterTransitionEnum } from '/@/enums/appEnum';
 import type { LocaleType } from '/@/locales/types';
-export interface MessageSetting {
-  title: string;
-  // 取消按钮的文字,
-  cancelText: string;
-  // 确认按钮的文字
-  okText: string;
-}
+
 export interface MenuSetting {
   collapsed: boolean;
   collapsedShowTitle: boolean;
@@ -54,8 +48,18 @@ export interface HeaderSetting {
   // 显示消息中心按钮
   showNotice: boolean;
 }
+
+export interface LocaleSetting {
+  // Current language
+  lang: LocaleType;
+  // default language
+  fallback: LocaleType;
+  // available Locales
+  availableLocales: LocaleType[];
+}
+
 export interface ProjectConfig {
-  locale: LocaleType;
+  locale: LocaleSetting;
   // header背景色
   headerBgColor: string;
   // 左侧菜单背景色
@@ -81,8 +85,6 @@ export interface ProjectConfig {
   // menuType: MenuTypeEnum;
   menuSetting: MenuSetting;
 
-  messageSetting: MessageSetting;
-
   // 多标签页设置
   multiTabsSetting: MultiTabsSetting;
   // pageLayout是否开启keep-alive
@@ -133,12 +135,6 @@ export interface GlobEnvConfig {
   VITE_GLOB_APP_SHORT_NAME: string;
 }
 
-//  修改配置
-export type SetProjectSettingFn = <T extends keyof ProjectConfig>(
-  key: T,
-  value: ProjectConfig[T]
-) => void;
-
 interface GlobWrap {
   globSetting: Readonly<GlobConfig>;
 }
@@ -146,5 +142,3 @@ interface GlobWrap {
 interface ProjectSettingWrap {
   projectSetting: Readonly<ProjectConfig>;
 }
-
-export type SettingWrap = GlobWrap & ProjectSettingWrap;

+ 2 - 2
src/utils/helper/envHelper.ts

@@ -1,7 +1,7 @@
 import { getEnv } from '/@/utils/env';
-import { useSetting } from '/@/hooks/core/useSetting';
+import { useGlobSetting } from '/@/settings/use';
 import pkg from '../../../package.json';
-const { globSetting } = useSetting();
+const globSetting = useGlobSetting();
 
 // Generate cache key according to version
 export const getStorageShortName = () => {

+ 2 - 2
src/utils/http/axios/index.ts

@@ -10,7 +10,7 @@ import { AxiosTransform } from './axiosTransform';
 
 import { checkStatus } from './checkStatus';
 
-import { useSetting } from '/@/hooks/core/useSetting';
+import { useGlobSetting } from '/@/settings/use';
 import { useMessage } from '/@/hooks/web/useMessage';
 
 import { RequestEnum, ResultEnum, ContentTypeEnum } from '/@/enums/httpEnum';
@@ -21,7 +21,7 @@ import { setObjToUrlParams, deepMerge } from '/@/utils';
 import { errorStore } from '/@/store/modules/error';
 import { errorResult } from './const';
 
-const { globSetting } = useSetting();
+const globSetting = useGlobSetting();
 const prefix = globSetting.urlPrefix;
 const { createMessage, createErrorModal } = useMessage();
 

+ 0 - 38
src/views/demo/feat/i18n/index.vue

@@ -1,38 +0,0 @@
-<template>
-  <div class="p-4">
-    <Alert message="国际化方式,没有进行全局国际化,有需要可以自行处理。" type="info" />
-    <Divider />
-    国际化信息: {{ t('hello') }}
-    <Divider />
-    <a-button :type="localeRef === 'zhCN' ? 'primary' : 'default'" @click="localeRef = 'zhCN'">
-      中文
-    </a-button>
-    <a-button :type="localeRef === 'en' ? 'primary' : 'default'" @click="localeRef = 'en'">
-      英文
-    </a-button>
-    <Divider />
-  </div>
-</template>
-<script lang="ts">
-  import { defineComponent } from 'vue';
-  import { Alert, Divider } from 'ant-design-vue';
-
-  import { useI18n } from '/@/hooks/web/useI18n';
-  export default defineComponent({
-    components: { Alert, Divider },
-    setup() {
-      const { t, localeRef } = useI18n({
-        locale: 'zhCN',
-        messages: {
-          en: {
-            hello: 'hello',
-          },
-          zhCN: {
-            hello: '你好',
-          },
-        },
-      });
-      return { localeRef, t };
-    },
-  });
-</script>

+ 7 - 1
src/views/sys/error-log/DetailModal.vue

@@ -1,13 +1,16 @@
 <template>
-  <BasicModal :width="800" title="错误详情" v-bind="$attrs">
+  <BasicModal :width="800" :title="t('sys.errorLog.tableActionDesc')" v-bind="$attrs">
     <Description :data="info" @register="register" />
   </BasicModal>
 </template>
 <script lang="ts">
   import { defineComponent, PropType } from 'vue';
+  import { useI18n } from 'vue-i18n';
+
   import { BasicModal } from '/@/components/Modal/index';
   import { ErrorInfo } from '/@/store/modules/error';
   import { Description, useDescription } from '/@/components/Description/index';
+
   import { getDescSchema } from './data';
 
   export default defineComponent({
@@ -20,12 +23,15 @@
       },
     },
     setup() {
+      const { t } = useI18n();
       const [register] = useDescription({
         column: 2,
         schema: getDescSchema(),
       });
       return {
         register,
+        useI18n,
+        t,
       };
     },
   });

+ 10 - 6
src/views/sys/error-log/data.tsx

@@ -2,11 +2,15 @@ import { Tag } from 'ant-design-vue';
 import { BasicColumn } from '/@/components/Table/index';
 import { ErrorTypeEnum } from '/@/enums/exceptionEnum';
 
+import { useExternalI18n } from '/@/hooks/web/useLocale';
+
+const { t } = useExternalI18n();
+
 export function getColumns(): BasicColumn[] {
   return [
     {
       dataIndex: 'type',
-      title: '类型',
+      title: t('sys.errorLog.tableColumnType'),
       width: 80,
       customRender: ({ text }) => {
         const color =
@@ -24,17 +28,17 @@ export function getColumns(): BasicColumn[] {
     },
     {
       dataIndex: 'url',
-      title: '地址',
+      title: 'URL',
       width: 200,
     },
     {
       dataIndex: 'time',
-      title: '时间',
+      title: t('sys.errorLog.tableColumnDate'),
       width: 160,
     },
     {
       dataIndex: 'file',
-      title: '文件',
+      title: t('sys.errorLog.tableColumnFile'),
       width: 200,
     },
     {
@@ -44,12 +48,12 @@ export function getColumns(): BasicColumn[] {
     },
     {
       dataIndex: 'message',
-      title: '错误信息',
+      title: t('sys.errorLog.tableColumnMsg'),
       width: 300,
     },
     {
       dataIndex: 'stack',
-      title: 'stack信息',
+      title: t('sys.errorLog.tableColumnStackMsg'),
       width: 300,
     },
   ];

+ 23 - 9
src/views/sys/error-log/index.vue

@@ -6,12 +6,22 @@
     <DetailModal :info="rowInfoRef" @register="registerModal" />
     <BasicTable @register="register" class="error-handle-table">
       <template #toolbar>
-        <a-button @click="fireVueError" type="primary"> 点击触发vue错误 </a-button>
-        <a-button @click="fireResourceError" type="primary"> 点击触发resource错误 </a-button>
-        <a-button @click="fireAjaxError" type="primary"> 点击触发ajax错误 </a-button>
+        <a-button @click="fireVueError" type="primary">
+          {{ t('sys.errorLog.fireVueError') }}
+        </a-button>
+        <a-button @click="fireResourceError" type="primary">
+          {{ t('sys.errorLog.fireResourceError') }}
+        </a-button>
+        <a-button @click="fireAjaxError" type="primary">
+          {{ t('sys.errorLog.fireAjaxError') }}
+        </a-button>
       </template>
       <template #action="{ record }">
-        <TableAction :actions="[{ label: '详情', onClick: handleDetail.bind(null, record) }]" />
+        <TableAction
+          :actions="[
+            { label: t('sys.errorLog.tableActionDesc'), onClick: handleDetail.bind(null, record) },
+          ]"
+        />
       </template>
     </BasicTable>
   </div>
@@ -21,10 +31,11 @@
   import { defineComponent, watch, ref, nextTick } from 'vue';
 
   import DetailModal from './DetailModal.vue';
+  import { BasicTable, useTable, TableAction } from '/@/components/Table/index';
+
   import { useModal } from '/@/components/Modal/index';
   import { useMessage } from '/@/hooks/web/useMessage';
-
-  import { BasicTable, useTable, TableAction } from '/@/components/Table/index';
+  import { useI18n } from 'vue-i18n';
 
   import { errorStore, ErrorInfo } from '/@/store/modules/error';
 
@@ -42,12 +53,14 @@
       const rowInfoRef = ref<ErrorInfo>();
       const imgListRef = ref<string[]>([]);
 
+      const { t } = useI18n();
+
       const [register, { setTableData }] = useTable({
-        title: '错误日志列表',
+        title: t('sys.errorLog.tableTitle'),
         columns: getColumns(),
         actionColumn: {
           width: 80,
-          title: '操作',
+          title: 'Action',
           dataIndex: 'action',
           slots: { customRender: 'action' },
         },
@@ -67,7 +80,7 @@
       );
       const { createMessage } = useMessage();
       if (isDevMode()) {
-        createMessage.info('只在`/src/settings/projectSetting.ts` 内的useErrorHandle=true时生效!');
+        createMessage.info(t('sys.errorLog.enableMessage'));
       }
       // 查看详情
       function handleDetail(row: ErrorInfo) {
@@ -96,6 +109,7 @@
         fireAjaxError,
         imgListRef,
         rowInfoRef,
+        t,
       };
     },
   });

+ 17 - 10
src/views/sys/exception/Exception.tsx

@@ -12,6 +12,7 @@ import { useRoute } from 'vue-router';
 
 import { useGo, useRedo } from '/@/hooks/web/usePage';
 import { PageEnum } from '/@/enums/pageEnum';
+import { useI18n } from 'vue-i18n';
 
 import './exception.less';
 interface MapValue {
@@ -47,9 +48,12 @@ export default defineComponent({
   },
   setup(props) {
     const statusMapRef = ref(new Map<string | number, MapValue>());
+
     const { query } = useRoute();
     const go = useGo();
     const redo = useRedo();
+    const { t } = useI18n();
+
     const getStatus = computed(() => {
       const { status: routeStatus } = query;
       const { status } = props;
@@ -62,41 +66,44 @@ export default defineComponent({
       }
     );
 
+    const backLoginI18n = t('sys.exception.backLogin');
+    const backHomeI18n = t('sys.exception.backHome');
+
     unref(statusMapRef).set(ExceptionEnum.PAGE_NOT_ACCESS, {
       title: '403',
       status: `${ExceptionEnum.PAGE_NOT_ACCESS}`,
-      subTitle: `Sorry, you don't have access to this page.!`,
-      btnText: props.full ? 'Back Login' : 'Back Home',
+      subTitle: t('sys.exception.subTitle403'),
+      btnText: props.full ? backLoginI18n : backHomeI18n,
       handler: () => (props.full ? go(PageEnum.BASE_LOGIN) : go()),
     });
 
     unref(statusMapRef).set(ExceptionEnum.PAGE_NOT_FOUND, {
       title: '404',
       status: `${ExceptionEnum.PAGE_NOT_FOUND}`,
-      subTitle: `Sorry, the page you visited does not exist.`,
-      btnText: props.full ? 'Back Login' : 'Back Home',
+      subTitle: t('sys.exception.subTitle404'),
+      btnText: props.full ? backLoginI18n : backHomeI18n,
       handler: () => (props.full ? go(PageEnum.BASE_LOGIN) : go()),
     });
 
     unref(statusMapRef).set(ExceptionEnum.ERROR, {
       title: '500',
       status: `${ExceptionEnum.ERROR}`,
-      subTitle: `Sorry, the server is reporting an error.`,
-      btnText: 'Back Home',
+      subTitle: t('sys.exception.subTitle500'),
+      btnText: backHomeI18n,
       handler: () => go(),
     });
 
     unref(statusMapRef).set(ExceptionEnum.PAGE_NOT_DATA, {
-      title: 'No data on the current page',
+      title: t('sys.exception.noDataTitle'),
       subTitle: '',
-      btnText: 'Refresh',
+      btnText: t('sys.exception.redo'),
       handler: () => redo(),
       icon: notDataImg,
     });
 
     unref(statusMapRef).set(ExceptionEnum.NET_WORK_ERROR, {
-      title: 'Network Error',
-      subTitle: 'Sorry,Your network connection has been disconnected, please check your network!',
+      title: t('sys.exception.networkErrorTitle'),
+      subTitle: t('sys.exception.networkErrorSubTitle'),
       btnText: 'Refresh',
       handler: () => redo(),
       icon: netWorkImg,

+ 21 - 15
src/views/sys/lock/index.vue

@@ -6,36 +6,38 @@
         <p class="lock-page__header-name">{{ realName }}</p>
       </div>
       <BasicForm @register="register" v-if="!getIsNotPwd" />
-      <Alert v-if="errMsgRef" type="error" message="锁屏密码错误" banner />
+      <Alert v-if="errMsgRef" type="error" :message="t('sys.lock.alert')" banner />
       <div class="lock-page__footer">
         <a-button type="default" class="mt-2 mr-2" @click="goLogin" v-if="!getIsNotPwd">
-          返回登录
+          {{ t('sys.lock.backToLogin') }}
         </a-button>
         <a-button type="primary" class="mt-2" @click="unLock(!getIsNotPwd)" :loading="loadingRef">
-          进入系统
+          {{ t('sys.lock.entry') }}
         </a-button>
       </div>
     </div>
   </div>
 </template>
 <script lang="ts">
-  // 组件相关
   import { defineComponent, ref, computed } from 'vue';
   import { Alert } from 'ant-design-vue';
-  // hook
+
   import { BasicForm, useForm } from '/@/components/Form';
 
   import { userStore } from '/@/store/modules/user';
   import { appStore } from '/@/store/modules/app';
+
+  import { useI18n } from 'vue-i18n';
+
   export default defineComponent({
     name: 'LockPage',
     components: { Alert, BasicForm },
 
     setup() {
-      // 获取配置文件
-      // 样式前缀
       const loadingRef = ref(false);
       const errMsgRef = ref(false);
+
+      const { t } = useI18n();
       const [register, { validateFields }] = useForm({
         showActionButtonGroup: false,
         schemas: [
@@ -45,7 +47,7 @@
             component: 'InputPassword',
             componentProps: {
               style: { width: '100%' },
-              placeholder: '请输入锁屏密码或者用户密码',
+              placeholder: t('sys.lock.placeholder'),
             },
             rules: [{ required: true }],
           },
@@ -55,6 +57,14 @@
         const { realName } = userStore.getUserInfoState || {};
         return realName;
       });
+
+      const getIsNotPwd = computed(() => {
+        if (!appStore.getLockInfo) {
+          return true;
+        }
+        return appStore.getLockInfo.pwd === undefined;
+      });
+
       /**
        * @description: unLock
        */
@@ -76,17 +86,12 @@
           loadingRef.value = false;
         }
       }
+
       function goLogin() {
         userStore.loginOut(true);
         appStore.resetLockInfo();
       }
-      const getIsNotPwd = computed(() => {
-        if (!appStore.getLockInfo) {
-          return true;
-        }
-        return appStore.getLockInfo.pwd === undefined;
-      });
-      // 账号密码登录
+
       return {
         register,
         getIsNotPwd,
@@ -95,6 +100,7 @@
         unLock,
         errMsgRef,
         loadingRef,
+        t,
       };
     },
   });

+ 24 - 10
src/views/sys/login/Login.vue

@@ -4,6 +4,7 @@
     <div class="login-form-wrap">
       <div class="login-form mx-6">
         <div class="login-form__content px-2 py-10">
+          <AppLocalPicker class="login-form__locale" />
           <header>
             <img :src="logo" class="mr-4" />
             <h1>{{ title }}</h1>
@@ -29,13 +30,15 @@
               <a-col :span="12">
                 <a-form-item>
                   <!-- No logic, you need to deal with it yourself -->
-                  <a-checkbox v-model:checked="autoLogin" size="small">自动登录</a-checkbox>
+                  <a-checkbox v-model:checked="autoLogin" size="small">{{
+                    t('sys.login.autoLogin')
+                  }}</a-checkbox>
                 </a-form-item>
               </a-col>
               <a-col :span="12">
                 <a-form-item :style="{ 'text-align': 'right' }">
                   <!-- No logic, you need to deal with it yourself -->
-                  <a-button type="link" size="small">忘记密码</a-button>
+                  <a-button type="link" size="small">{{ t('sys.login.forgetPassword') }}</a-button>
                 </a-form-item>
               </a-col>
             </a-row>
@@ -47,7 +50,7 @@
                 :block="true"
                 @click="login"
                 :loading="formState.loading"
-                >{{ t('system.login.button') }}</a-button
+                >{{ t('sys.login.loginButton') }}</a-button
               >
             </a-form-item>
           </a-form>
@@ -61,6 +64,7 @@
   import { Checkbox } from 'ant-design-vue';
 
   import Button from '/@/components/Button/index.vue';
+  import { AppLocalPicker } from '/@/components/Application';
   // import { BasicDragVerify, DragVerifyActionType } from '/@/components/Verify/index';
 
   import { userStore } from '/@/store/modules/user';
@@ -68,7 +72,7 @@
 
   // import { appStore } from '/@/store/modules/app';
   import { useMessage } from '/@/hooks/web/useMessage';
-  import { useSetting } from '/@/hooks/core/useSetting';
+  import { useGlobSetting } from '/@/settings/use';
   import logo from '/@/assets/images/logo.png';
 
   export default defineComponent({
@@ -76,14 +80,16 @@
       //  BasicDragVerify,
       AButton: Button,
       ACheckbox: Checkbox,
+      AppLocalPicker,
     },
     setup() {
       const formRef = ref<any>(null);
       const autoLoginRef = ref(false);
       // const verifyRef = ref<RefInstanceType<DragVerifyActionType>>(null);
 
-      const { globSetting } = useSetting();
+      const globSetting = useGlobSetting();
       const { notification } = useMessage();
+      const { t } = useI18n();
 
       // const openLoginVerifyRef = computed(() => appStore.getProjectConfig.openLoginVerify);
 
@@ -97,8 +103,10 @@
       });
 
       const formRules = reactive({
-        account: [{ required: true, message: '请输入账号', trigger: 'blur' }],
-        password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
+        account: [{ required: true, message: t('sys.login.accountPlaceholder'), trigger: 'blur' }],
+        password: [
+          { required: true, message: t('sys.login.passwordPlaceholder'), trigger: 'blur' },
+        ],
         // verify: unref(openLoginVerifyRef) ? [{ required: true, message: '请通过验证码校验' }] : [],
       });
 
@@ -123,8 +131,8 @@
           );
           if (userInfo) {
             notification.success({
-              message: '登录成功',
-              description: `欢迎回来: ${userInfo.realName}`,
+              message: t('sys.login.loginSuccessTitle'),
+              description: `${t('sys.login.loginSuccessDesc')}: ${userInfo.realName}`,
               duration: 3,
             });
           }
@@ -134,7 +142,6 @@
           formState.loading = false;
         }
       }
-      const { t } = useI18n();
       return {
         formRef,
         // verifyRef,
@@ -195,7 +202,14 @@
         .respond-to(xlarge, { width: 540px; right:0});
       }
 
+      &__locale {
+        position: absolute;
+        top: 10px;
+        right: 10px;
+      }
+
       &__content {
+        position: relative;
         width: 100%;
         height: 100%;
         border: 1px solid #999;

+ 6 - 1
vite.config.ts

@@ -126,7 +126,12 @@ const viteConfig: UserConfig = {
   },
   // The package will be recompiled using rollup, and the new package compiled into the esm module specification will be put into node_modules/.vite_opt_cache
   optimizeDeps: {
-    include: ['echarts/map/js/china', 'ant-design-vue/es/locale/zh_CN', '@ant-design/icons-vue'],
+    include: [
+      'echarts/map/js/china',
+      'ant-design-vue/es/locale/zh_CN',
+      'ant-design-vue/es/locale/en_US',
+      '@ant-design/icons-vue',
+    ],
   },
 
   // Local cross-domain proxy

+ 5 - 0
yarn.lock

@@ -2926,6 +2926,11 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2:
     shebang-command "^2.0.0"
     which "^2.0.1"
 
+crypto-es@^1.2.6:
+  version "1.2.6"
+  resolved "https://registry.npmjs.org/crypto-es/-/crypto-es-1.2.6.tgz#468f3573a5d7b82e3b63b0004f55f905a6d3b12c"
+  integrity sha512-PQnrovdr5ibmOxqAh/Vy+A30RokHom7kb9Z61EPwfASfbcJCrCG4+vNNegmebNVHiXvS7WjYpHDePxnE/biEbA==
+
 crypto-random-string@^1.0.0:
   version "1.0.0"
   resolved "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e"