Parcourir la source

wip: support multilingual configuration

vben il y a 4 ans
Parent
commit
8882d4e7ea

+ 0 - 96
build/vite/plugin/context/transform.ts

@@ -1,96 +0,0 @@
-// Modified from
-// https://github.com/luxueyan/vite-transform-globby-import/blob/master/src/index.ts
-
-// TODO Currently, it is not possible to monitor file addition and deletion. The content has been changed, the cache problem?
-import { join } from 'path';
-import { lstatSync } from 'fs';
-import glob from 'glob';
-import { createResolver, Resolver } from 'vite/dist/node/resolver.js';
-import { Transform } from 'vite/dist/node/transform.js';
-
-const modulesDir: string = join(process.cwd(), '/node_modules/');
-
-interface SharedConfig {
-  root?: string;
-  alias?: Record<string, string>;
-  resolvers?: Resolver[];
-}
-
-function template(template: string) {
-  return (data: { [x: string]: any }) => {
-    return template.replace(/#([^#]+)#/g, (_, g1) => data[g1] || g1);
-  };
-}
-
-const globbyTransform = function (config: SharedConfig): Transform {
-  const resolver = createResolver(
-    config.root || process.cwd(),
-    config.resolvers || [],
-    config.alias || {}
-  );
-  const cache = new Map();
-
-  const urlMap = new Map();
-  return {
-    test({ path }) {
-      const filePath = path.replace('\u0000', ''); // why some path startsWith '\u0000'?
-      try {
-        return (
-          !filePath.startsWith(modulesDir) &&
-          /\.(vue|js|jsx|ts|tsx)$/.test(filePath) &&
-          lstatSync(filePath).isFile()
-        );
-      } catch {
-        return false;
-      }
-    },
-    transform({ code, path, isBuild }) {
-      let result = cache.get(path);
-      if (!result) {
-        const reg = /import\s+([\w\s{}*]+)\s+from\s+(['"])globby(\?path)?!([^'"]+)\2/g;
-        const match = code.match(reg);
-        if (!match) return code;
-        const lastImport = urlMap.get(path);
-        if (lastImport && match) {
-          code = code.replace(lastImport, match[0]);
-        }
-        result = code.replace(reg, (_, g1, g2, g3, g4) => {
-          const filePath = path.replace('\u0000', ''); // why some path startsWith '\u0000'?
-          // resolve path
-          const resolvedFilePath = g4.startsWith('.')
-            ? resolver.resolveRelativeRequest(filePath, g4)
-            : { pathname: resolver.requestToFile(g4) };
-          const files = glob.sync(resolvedFilePath.pathname, { dot: true });
-          let templateStr = 'import #name# from #file#'; // import default
-          let name = g1;
-          const m = g1.match(/\{\s*(\w+)(\s+as\s+(\w+))?\s*\}/); // import module
-          const m2 = g1.match(/\*\s+as\s+(\w+)/); // import * as all module
-          if (m) {
-            templateStr = `import { ${m[1]} as #name# } from #file#`;
-            name = m[3] || m[1];
-          } else if (m2) {
-            templateStr = 'import * as #name# from #file#';
-            name = m2[1];
-          }
-          const temRender = template(templateStr);
-
-          const groups: Array<string>[] = [];
-          const replaceFiles = files.map((f, i) => {
-            const file = g2 + resolver.fileToRequest(f) + g2;
-            groups.push([name + i, file]);
-            return temRender({ name: name + i, file });
-          });
-          urlMap.set(path, replaceFiles.join('\n'));
-          return (
-            replaceFiles.join('\n') +
-            (g3 ? '\n' + groups.map((v) => `${v[0]}._path = ${v[1]}`).join('\n') : '') +
-            `\nconst ${name} = { ${groups.map((v) => v[0]).join(',')} }\n`
-          );
-        });
-        if (isBuild) cache.set(path, result);
-      }
-      return result;
-    },
-  };
-};
-export default globbyTransform;

+ 1 - 3
build/vite/plugin/dynamicImport/index.ts → build/vite/plugin/transform/dynamic-import/index.ts

@@ -1,7 +1,6 @@
 // Used to import all files under `src/views`
-
 // The built-in dynamic import of vite cannot meet the needs of importing all files under views
-
+// Special usage ,Only for this project
 import glob from 'glob';
 import { Transform } from 'vite/dist/node/transform.js';
 
@@ -28,7 +27,6 @@ const dynamicImportTransform = function (env: any = {}): Transform {
         return code;
       }
 
-      // if (!isBuild) return code;
       // Only convert the dir
       try {
         const files = glob.sync('src/views/**/**.{vue,tsx}', { cwd: process.cwd() });

+ 201 - 0
build/vite/plugin/transform/globby/index.ts

@@ -0,0 +1,201 @@
+// Modified from
+// https://github.com/luxueyan/vite-transform-globby-import/blob/master/src/index.ts
+
+// TODO Deleting files requires re-running the project
+import { join } from 'path';
+import { lstatSync } from 'fs';
+import glob from 'glob';
+import globrex from 'globrex';
+import dotProp from 'dot-prop';
+import { createResolver, Resolver } from 'vite/dist/node/resolver.js';
+import { Transform } from 'vite/dist/node/transform.js';
+
+const modulesDir: string = join(process.cwd(), '/node_modules/');
+
+interface SharedConfig {
+  root?: string;
+  alias?: Record<string, string>;
+  resolvers?: Resolver[];
+
+  includes?: string[];
+}
+
+function template(template: string) {
+  return (data: { [x: string]: any }) => {
+    return template.replace(/#([^#]+)#/g, (_, g1) => data[g1] || g1);
+  };
+}
+
+// TODO support hmr
+function hmr(isBuild = false) {
+  if (isBuild) return '';
+  return `
+  if (import.meta.hot) {
+    import.meta.hot.accept();
+  }`;
+}
+
+// handle includes
+function fileInclude(includes: string | string[] | undefined, filePath: string) {
+  return !includes || !Array.isArray(includes)
+    ? true
+    : includes.some((item) => filePath.startsWith(item));
+}
+
+// Bare exporter
+function compareString(modify: any, data: string[][]) {
+  return modify ? '\n' + data.map((v) => `${v[0]}._path = ${v[1]}`).join('\n') : '';
+}
+
+function varTemplate(data: string[][], name: string) {
+  //prepare deep data (for locales)
+  let deepData: Record<string, object | string> = {};
+  let hasDeepData = false;
+
+  //data modify
+  data.map((v) => {
+    //check for has deep data
+    if (v[0].includes('/')) {
+      hasDeepData = true;
+    }
+
+    // lastKey is a data
+    let pathValue = v[0].replace(/\//g, '.').split('.');
+    let lastKey: string | undefined = pathValue.pop();
+
+    let deepValue: Record<any, any> = {};
+    if (lastKey) {
+      deepValue[lastKey.replace('_' + pathValue[0], '')] = lastKey;
+    }
+
+    // Set Deep Value
+    deepValue = Object.assign(deepValue, dotProp.get(deepData, pathValue.join('.')));
+    dotProp.set(deepData, pathValue.join('.'), deepValue);
+  });
+
+  if (hasDeepData) {
+    return `const ${name} = ` + JSON.stringify(deepData).replace(/\"|\'/g, '');
+  }
+
+  return `const ${name} = { ${data.map((v) => v[0]).join(',')} }`;
+}
+
+const globTransform = function (config: SharedConfig): Transform {
+  const resolver = createResolver(
+    config.root || process.cwd(),
+    config.resolvers || [],
+    config.alias || {}
+  );
+  const { includes } = config;
+  const cache = new Map();
+  const urlMap = new Map();
+  return {
+    test({ path }) {
+      const filePath = path.replace('\u0000', ''); // why some path startsWith '\u0000'?
+
+      try {
+        return (
+          !filePath.startsWith(modulesDir) &&
+          /\.(vue|js|jsx|ts|tsx)$/.test(filePath) &&
+          fileInclude(includes, filePath) &&
+          lstatSync(filePath).isFile()
+        );
+      } catch {
+        return false;
+      }
+    },
+    transform({ code, path, isBuild }) {
+      let result = cache.get(path);
+      if (!result) {
+        const reg = /import\s+([\w\s{}*]+)\s+from\s+(['"])globby(\?locale)?(\?path)?!([^'"]+)\2/g;
+        const match = code.match(reg);
+        if (!match) return code;
+        const lastImport = urlMap.get(path);
+        if (lastImport && match) {
+          code = code.replace(lastImport, match[0]);
+        }
+        result = code.replace(
+          reg,
+          (
+            _,
+            // variable to export
+            exportName,
+            // bare export or not
+            bareExporter,
+            // is locale import
+            isLocale,
+            // inject _path attr
+            injectPath,
+            // path export
+            globPath
+          ) => {
+            const filePath = path.replace('\u0000', ''); // why some path startsWith '\u0000'?
+            // resolve path
+
+            const resolvedFilePath = globPath.startsWith('.')
+              ? resolver.resolveRelativeRequest(filePath, globPath)
+              : { pathname: resolver.requestToFile(globPath) };
+
+            const files = glob.sync(resolvedFilePath.pathname, { dot: true });
+
+            let templateStr = 'import #name# from #file#'; // import default
+            let name = exportName;
+            const m = exportName.match(/\{\s*(\w+)(\s+as\s+(\w+))?\s*\}/); // import module
+            const m2 = exportName.match(/\*\s+as\s+(\w+)/); // import * as all module
+            if (m) {
+              templateStr = `import { ${m[1]} as #name# } from #file#`;
+              name = m[3] || m[1];
+            } else if (m2) {
+              templateStr = 'import * as #name# from #file#';
+              name = m2[1];
+            }
+
+            const templateRender = template(templateStr);
+
+            const groups: Array<string>[] = [];
+            const replaceFiles = files.map((f, i) => {
+              const fileNameWithAlias = resolver.fileToRequest(f);
+
+              const file = bareExporter + fileNameWithAlias + bareExporter;
+
+              if (isLocale) {
+                const globrexRes = globrex(globPath, { extended: true, globstar: true });
+
+                // Get segments for files like an en/system ch/modules for:
+                // ['en', 'system'] ['ch', 'modules']
+                const matchedGroups = globrexRes.regex.exec(fileNameWithAlias);
+
+                if (matchedGroups && matchedGroups.length) {
+                  const matchedSegments = matchedGroups[1]; //first everytime "Full Match"
+                  const name = matchedGroups[2] + '_' + matchedSegments.split('/').shift();
+                  //send deep way like an (en/modules/system/dashboard) into groups
+                  groups.push([matchedSegments + name, file]);
+                  return templateRender({
+                    name,
+                    file,
+                  });
+                }
+              } else {
+                groups.push([name + i, file]);
+                return templateRender({ name: name + i, file });
+              }
+            });
+            // save in memory used result
+            const filesJoined = replaceFiles.join('\n');
+
+            urlMap.set(path, filesJoined);
+            return [
+              filesJoined,
+              compareString(injectPath, groups),
+              varTemplate(groups, name),
+              '',
+            ].join('\n');
+          }
+        );
+        if (isBuild) cache.set(path, result);
+      }
+      return `${result}${hmr(isBuild)}`;
+    },
+  };
+};
+export default globTransform;

+ 5 - 2
package.json

@@ -22,7 +22,7 @@
   },
   "dependencies": {
     "@iconify/iconify": "^2.0.0-rc.2",
-    "@vueuse/core": "4.0.0-beta.41",
+    "@vueuse/core": "4.0.0-rc.3",
     "ant-design-vue": "2.0.0-beta.15",
     "apexcharts": "3.22.0",
     "axios": "^0.21.0",
@@ -35,7 +35,7 @@
     "qrcode": "^1.4.4",
     "vditor": "^3.6.2",
     "vue": "^3.0.2",
-    "vue-i18n": "^9.0.0-beta.7",
+    "vue-i18n": "^9.0.0-beta.8",
     "vue-router": "^4.0.0-rc.3",
     "vuex": "^4.0.0-rc.1",
     "vuex-module-decorators": "^1.0.1",
@@ -50,6 +50,7 @@
     "@purge-icons/generated": "^0.4.1",
     "@types/echarts": "^4.9.1",
     "@types/fs-extra": "^9.0.4",
+    "@types/globrex": "^0.1.0",
     "@types/koa-static": "^4.0.1",
     "@types/lodash-es": "^4.17.3",
     "@types/mockjs": "^1.0.3",
@@ -68,6 +69,7 @@
     "conventional-changelog-cli": "^2.1.1",
     "conventional-changelog-custom-config": "^0.3.1",
     "cross-env": "^7.0.2",
+    "dot-prop": "^6.0.0",
     "dotenv": "^8.2.0",
     "eslint": "^7.13.0",
     "eslint-config-prettier": "^6.15.0",
@@ -75,6 +77,7 @@
     "eslint-plugin-vue": "^7.1.0",
     "esno": "^0.2.4",
     "fs-extra": "^9.0.1",
+    "globrex": "^0.1.2",
     "husky": "^4.3.0",
     "koa-static": "^5.0.0",
     "less": "^3.12.2",

+ 21 - 0
src/hooks/web/useLocale.ts

@@ -0,0 +1,21 @@
+import type { LocaleType } from '/@/locales/types';
+import { appStore } from '/@/store/modules/app';
+
+export function useLocale() {
+  /**
+   *
+   */
+  function getLocale(): string {
+    return appStore.getProjectConfig.locale;
+  }
+
+  /**
+   *
+   * @param locale
+   */
+  async function changeLocale(locale: LocaleType): Promise<void> {
+    appStore.commitProjectConfigState({ locale: locale });
+  }
+
+  return { getLocale, changeLocale };
+}

+ 3 - 0
src/locales/index.ts

@@ -0,0 +1,3 @@
+import messages from 'globby?locale!/@/locales/lang/**/*.@(ts)';
+
+export default messages;

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

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

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

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

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

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

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

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

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

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

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

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

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

@@ -0,0 +1,3 @@
+export default {
+  someentry: '一些文本',
+};

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

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

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

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

+ 1 - 0
src/locales/types.ts

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

+ 6 - 0
src/main.ts

@@ -5,6 +5,7 @@ import { setupStore } from '/@/store';
 import { setupAntd } from '/@/setup/ant-design-vue';
 import { setupErrorHandle } from '/@/setup/error-handle';
 import { setupGlobDirectives } from '/@/setup/directives';
+import { setupI18n } from '/@/setup/i18n';
 
 import { setupProdMockServer } from '../mock/_createProductionServer';
 import { setApp } from '/@/setup/App';
@@ -15,11 +16,16 @@ import { isDevMode, isProdMode, isUseMock } from '/@/utils/env';
 
 import '/@/design/index.less';
 
+import '/@/locales/index';
+
 const app = createApp(App);
 
 // Configure component library
 setupAntd(app);
 
+// Multilingual configuration
+setupI18n(app);
+
 // Configure routing
 setupRouter(app);
 

+ 1 - 0
src/settings/projectSetting.ts

@@ -7,6 +7,7 @@ import { isProdMode } from '/@/utils/env';
 
 // ! You need to clear the browser cache after the change
 const setting: ProjectConfig = {
+  locale: 'en',
   // color
   // TODO 主题色
   themeColor: primaryColor,

+ 35 - 0
src/setup/i18n/index.ts

@@ -0,0 +1,35 @@
+import type { App } from 'vue';
+import type { I18n, Locale, I18nOptions } from 'vue-i18n';
+
+import { createI18n } from 'vue-i18n';
+import localeMessages from '/@/locales';
+import { useLocale } from '/@/hooks/web/useLocale';
+
+const { getLocale } = useLocale();
+
+const localeData: I18nOptions = {
+  legacy: false,
+  locale: getLocale(),
+  // TODO: setting fallback inside settings
+  fallbackLocale: 'en',
+  messages: localeMessages,
+  // availableLocales: ['ru'],
+  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,
+};
+
+let i18n: I18n;
+
+// setup i18n instance with glob
+export function setupI18n(app: App) {
+  i18n = createI18n(localeData) as I18n;
+  setI18nLanguage(getLocale());
+  app.use(i18n);
+}
+
+export function setI18nLanguage(locale: Locale): void {
+  // @ts-ignore
+  i18n.global.locale.value = locale;
+  // i18n.global.setLocaleMessage(locale, messages);
+}

+ 6 - 3
src/store/index.ts

@@ -1,16 +1,19 @@
 import type { App } from 'vue';
-import { createStore, createLogger, Plugin } from 'vuex';
+import {
+  createStore,
+  // createLogger, Plugin
+} from 'vuex';
 import { config } from 'vuex-module-decorators';
 import { isDevMode } from '/@/utils/env';
 
 config.rawError = true;
 const isDev = isDevMode();
-const plugins: Plugin<any>[] = isDev ? [createLogger()] : [];
+// const plugins: Plugin<any>[] = isDev ? [createLogger()] : [];
 
 const store = createStore({
   // modules: {},
   strict: isDev,
-  plugins,
+  // plugins,
 });
 
 export function setupStore(app: App<Element>) {

+ 2 - 1
src/types/config.d.ts

@@ -1,7 +1,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;
   // 取消按钮的文字,
@@ -55,6 +55,7 @@ export interface HeaderSetting {
   showNotice: boolean;
 }
 export interface ProjectConfig {
+  locale: LocaleType;
   // header背景色
   headerBgColor: string;
   // 左侧菜单背景色

+ 2 - 0
src/types/module.d.ts

@@ -4,4 +4,6 @@ declare module 'globby!/@/router/routes/modules/**/*.@(ts)';
 
 declare module 'globby!/@/router/menus/modules/**/*.@(ts)';
 
+declare module 'globby?locale!/@/locales/lang/**/*.@(ts)';
+
 declare const React: string;

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

@@ -11,14 +11,14 @@
 
           <a-form class="mx-auto mt-10" :model="formData" :rules="formRules" ref="formRef">
             <a-form-item name="account">
-              <a-input size="large" v-model:value="formData.account" placeholder="Username: vben" />
+              <a-input size="large" v-model:value="formData.account" placeholder="username: vben" />
             </a-form-item>
             <a-form-item name="password">
               <a-input-password
                 size="large"
                 visibilityToggle
                 v-model:value="formData.password"
-                placeholder="Password: 123456"
+                placeholder="password: 123456"
               />
             </a-form-item>
 
@@ -28,13 +28,13 @@
             <a-row>
               <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-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-form-item>
               </a-col>
@@ -47,7 +47,7 @@
                 :block="true"
                 @click="login"
                 :loading="formState.loading"
-                >登录</a-button
+                >{{ t('system.login.button') }}</a-button
               >
             </a-form-item>
           </a-form>
@@ -57,20 +57,15 @@
   </div>
 </template>
 <script lang="ts">
-  import {
-    defineComponent,
-    reactive,
-    ref,
-    unref,
-    toRaw,
-    // computed
-  } from 'vue';
+  import { defineComponent, reactive, ref, unref, toRaw } from 'vue';
   import { Checkbox } from 'ant-design-vue';
 
   import Button from '/@/components/Button/index.vue';
   // import { BasicDragVerify, DragVerifyActionType } from '/@/components/Verify/index';
 
   import { userStore } from '/@/store/modules/user';
+  import { useI18n } from 'vue-i18n';
+
   // import { appStore } from '/@/store/modules/app';
   import { useMessage } from '/@/hooks/web/useMessage';
   import { useSetting } from '/@/hooks/core/useSetting';
@@ -139,7 +134,7 @@
           formState.loading = false;
         }
       }
-
+      const { t } = useI18n();
       return {
         formRef,
         // verifyRef,
@@ -151,6 +146,7 @@
         // openLoginVerify: openLoginVerifyRef,
         title: globSetting && globSetting.title,
         logo,
+        t,
       };
     },
   });

+ 17 - 3
vite.config.ts

@@ -4,8 +4,9 @@ import { resolve } from 'path';
 
 import { modifyVars } from './build/config/lessModifyVars';
 import { createProxy } from './build/vite/proxy';
-import globbyTransform from './build/vite/plugin/context/transform';
-import dynamicImportTransform from './build/vite/plugin/dynamicImport/index';
+
+import globbyTransform from './build/vite/plugin/transform/globby';
+import dynamicImportTransform from './build/vite/plugin/transform/dynamic-import';
 
 import { isDevFn, loadEnv } from './build/utils';
 
@@ -111,6 +112,11 @@ const viteConfig: UserConfig = {
   },
   define: {
     __VERSION__: pkg.version,
+    // use vue-i18-next
+    // Suppress warning
+    __VUE_I18N_LEGACY_API__: false,
+    __VUE_I18N_FULL_INSTALL__: false,
+    __INTLIFY_PROD_DEVTOOLS__: false,
   },
   cssPreprocessOptions: {
     less: {
@@ -135,5 +141,13 @@ const viteConfig: UserConfig = {
 
 export default {
   ...viteConfig,
-  transforms: [globbyTransform(viteConfig), dynamicImportTransform(viteEnv)],
+  transforms: [
+    globbyTransform({
+      resolvers: viteConfig.resolvers,
+      root: viteConfig.root,
+      alias: viteConfig.alias,
+      includes: [resolve('src/router'), resolve('src/locales')],
+    }),
+    dynamicImportTransform(viteEnv),
+  ],
 } as UserConfig;

+ 30 - 13
yarn.lock

@@ -1323,6 +1323,11 @@
   dependencies:
     "@types/node" "*"
 
+"@types/globrex@^0.1.0":
+  version "0.1.0"
+  resolved "https://registry.npmjs.org/@types/globrex/-/globrex-0.1.0.tgz#baf4ac8e36947017612c01fde7c7b641dc0b6c89"
+  integrity sha512-aBkxDgp/UbnluE+CIT3V3PoNewwOlLCzXSF3ipD86Slv8xVjwxrDAfSGbsfGgMzPo/fEMPXc+gNUJbtiugwfoA==
+
 "@types/http-assert@*":
   version "1.5.1"
   resolved "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.1.tgz#d775e93630c2469c2f980fc27e3143240335db3b"
@@ -1732,18 +1737,18 @@
     vscode-languageserver-textdocument "^1.0.1"
     vscode-uri "^2.1.2"
 
-"@vueuse/core@4.0.0-beta.41":
-  version "4.0.0-beta.41"
-  resolved "https://registry.npmjs.org/@vueuse/core/-/core-4.0.0-beta.41.tgz#0058aed5ade75ae2866283498009ad5172cbae84"
-  integrity sha512-CgUih65PzYScorm1S4F93e6XXm+qxA8GrRLOSB1kXaqtP6vXedwkBxKkNEYNACx4reL4VEHqM/BrM6FajXkQUg==
+"@vueuse/core@4.0.0-rc.3":
+  version "4.0.0-rc.3"
+  resolved "https://registry.npmjs.org/@vueuse/core/-/core-4.0.0-rc.3.tgz#5381ca657e10df596cd7027fc5c96b2d4b3a090c"
+  integrity sha512-dQ/FZgo0z7kBFOvDWxuzaUrmuO8X1AlQk17e3PU1TVtG2Uu+mCvjPNbuvI2fjhTjl5rzPJawwoU2WZFj+nlFvw==
   dependencies:
-    "@vueuse/shared" "4.0.0-beta.41"
+    "@vueuse/shared" "4.0.0-rc.3"
     vue-demi latest
 
-"@vueuse/shared@4.0.0-beta.41":
-  version "4.0.0-beta.41"
-  resolved "https://registry.npmjs.org/@vueuse/shared/-/shared-4.0.0-beta.41.tgz#395782ea2e580f1fc9488d25c89bd09f70170b25"
-  integrity sha512-dqnuEPPC3OUJ6L6rhMiOCuPWIR698DtdwOydwCZBISsG2V6gZ2QFND6xtRwLib6/lhUMYVYPwIz3hPjlx7BIzw==
+"@vueuse/shared@4.0.0-rc.3":
+  version "4.0.0-rc.3"
+  resolved "https://registry.npmjs.org/@vueuse/shared/-/shared-4.0.0-rc.3.tgz#42fb56fed3779f3b8a17a82c16a364bad20d01b7"
+  integrity sha512-VY0x/XxpeTMHp/0FDiv1cgUUxkJGQl7liiM2AjR/J7+Ys/2Y2dijD5cAKViq9FGUPQQsOcLptMvMvUsDMoN4DA==
   dependencies:
     vue-demi latest
 
@@ -3222,6 +3227,13 @@ dot-prop@^5.1.0:
   dependencies:
     is-obj "^2.0.0"
 
+dot-prop@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.0.tgz#bd579fd704d970981c4b05de591db648959f2ebb"
+  integrity sha512-xCbB8IN3IT+tdgoEPOnJmYTNJDrygGFOmiQEiVa5eAD+JEB1vTgMNhVGRnN5Eex/6amck7cdcrixb1qN9Go+GQ==
+  dependencies:
+    is-obj "^2.0.0"
+
 dotenv-expand@^5.1.0:
   version "5.1.0"
   resolved "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0"
@@ -4092,6 +4104,11 @@ globjoin@^0.1.4:
   resolved "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz#2f4494ac8919e3767c5cbb691e9f463324285d43"
   integrity sha1-L0SUrIkZ43Z8XLtpHp9GMyQoXUM=
 
+globrex@^0.1.2:
+  version "0.1.2"
+  resolved "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098"
+  integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==
+
 gonzales-pe@^4.3.0:
   version "4.3.0"
   resolved "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-4.3.0.tgz#fe9dec5f3c557eead09ff868c65826be54d067b3"
@@ -8167,10 +8184,10 @@ vue-eslint-parser@^7.1.1:
     esquery "^1.0.1"
     lodash "^4.17.15"
 
-vue-i18n@^9.0.0-beta.7:
-  version "9.0.0-beta.7"
-  resolved "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.0.0-beta.7.tgz#f6fad5b4be218018aab4797f80dd2a95ee5236f9"
-  integrity sha512-hFl0XnV91P/4UyWvHYvdYxuk3GRnKIW9zXAm6hrUU4mOIwpqchi7jVQva2TJLr52Mpsu4zYXmzL1h5pgrKmCfQ==
+vue-i18n@^9.0.0-beta.8:
+  version "9.0.0-beta.8"
+  resolved "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.0.0-beta.8.tgz#92282d5b5e0e6f15cc04943ce11bf49db610468f"
+  integrity sha512-tViSN96jLi0AKvAVi4twcYYN5Ld++SqN1/140ua+YWm/iRbO2M0rAcsZ7e6/4LTm6Pd1ldSwWihSuv2bSQmlnw==
   dependencies:
     source-map "0.6.1"