Commit 8882d4e7eafabceb3c53cd5c22ad70ee166921c4
1 parent
3a651767
wip: support multilingual configuration
Showing
24 changed files
with
314 additions
and
77 deletions
build/vite/plugin/dynamicImport/index.ts renamed to build/vite/plugin/transform/dynamic-import/index.ts
1 | // Used to import all files under `src/views` | 1 | // Used to import all files under `src/views` |
2 | - | ||
3 | // The built-in dynamic import of vite cannot meet the needs of importing all files under views | 2 | // The built-in dynamic import of vite cannot meet the needs of importing all files under views |
4 | - | 3 | +// Special usage ,Only for this project |
5 | import glob from 'glob'; | 4 | import glob from 'glob'; |
6 | import { Transform } from 'vite/dist/node/transform.js'; | 5 | import { Transform } from 'vite/dist/node/transform.js'; |
7 | 6 | ||
@@ -28,7 +27,6 @@ const dynamicImportTransform = function (env: any = {}): Transform { | @@ -28,7 +27,6 @@ const dynamicImportTransform = function (env: any = {}): Transform { | ||
28 | return code; | 27 | return code; |
29 | } | 28 | } |
30 | 29 | ||
31 | - // if (!isBuild) return code; | ||
32 | // Only convert the dir | 30 | // Only convert the dir |
33 | try { | 31 | try { |
34 | const files = glob.sync('src/views/**/**.{vue,tsx}', { cwd: process.cwd() }); | 32 | const files = glob.sync('src/views/**/**.{vue,tsx}', { cwd: process.cwd() }); |
build/vite/plugin/context/transform.ts renamed to build/vite/plugin/transform/globby/index.ts
1 | // Modified from | 1 | // Modified from |
2 | // https://github.com/luxueyan/vite-transform-globby-import/blob/master/src/index.ts | 2 | // https://github.com/luxueyan/vite-transform-globby-import/blob/master/src/index.ts |
3 | 3 | ||
4 | -// TODO Currently, it is not possible to monitor file addition and deletion. The content has been changed, the cache problem? | 4 | +// TODO Deleting files requires re-running the project |
5 | import { join } from 'path'; | 5 | import { join } from 'path'; |
6 | import { lstatSync } from 'fs'; | 6 | import { lstatSync } from 'fs'; |
7 | import glob from 'glob'; | 7 | import glob from 'glob'; |
8 | +import globrex from 'globrex'; | ||
9 | +import dotProp from 'dot-prop'; | ||
8 | import { createResolver, Resolver } from 'vite/dist/node/resolver.js'; | 10 | import { createResolver, Resolver } from 'vite/dist/node/resolver.js'; |
9 | import { Transform } from 'vite/dist/node/transform.js'; | 11 | import { Transform } from 'vite/dist/node/transform.js'; |
10 | 12 | ||
@@ -14,6 +16,8 @@ interface SharedConfig { | @@ -14,6 +16,8 @@ interface SharedConfig { | ||
14 | root?: string; | 16 | root?: string; |
15 | alias?: Record<string, string>; | 17 | alias?: Record<string, string>; |
16 | resolvers?: Resolver[]; | 18 | resolvers?: Resolver[]; |
19 | + | ||
20 | + includes?: string[]; | ||
17 | } | 21 | } |
18 | 22 | ||
19 | function template(template: string) { | 23 | function template(template: string) { |
@@ -22,22 +26,78 @@ function template(template: string) { | @@ -22,22 +26,78 @@ function template(template: string) { | ||
22 | }; | 26 | }; |
23 | } | 27 | } |
24 | 28 | ||
25 | -const globbyTransform = function (config: SharedConfig): Transform { | 29 | +// TODO support hmr |
30 | +function hmr(isBuild = false) { | ||
31 | + if (isBuild) return ''; | ||
32 | + return ` | ||
33 | + if (import.meta.hot) { | ||
34 | + import.meta.hot.accept(); | ||
35 | + }`; | ||
36 | +} | ||
37 | + | ||
38 | +// handle includes | ||
39 | +function fileInclude(includes: string | string[] | undefined, filePath: string) { | ||
40 | + return !includes || !Array.isArray(includes) | ||
41 | + ? true | ||
42 | + : includes.some((item) => filePath.startsWith(item)); | ||
43 | +} | ||
44 | + | ||
45 | +// Bare exporter | ||
46 | +function compareString(modify: any, data: string[][]) { | ||
47 | + return modify ? '\n' + data.map((v) => `${v[0]}._path = ${v[1]}`).join('\n') : ''; | ||
48 | +} | ||
49 | + | ||
50 | +function varTemplate(data: string[][], name: string) { | ||
51 | + //prepare deep data (for locales) | ||
52 | + let deepData: Record<string, object | string> = {}; | ||
53 | + let hasDeepData = false; | ||
54 | + | ||
55 | + //data modify | ||
56 | + data.map((v) => { | ||
57 | + //check for has deep data | ||
58 | + if (v[0].includes('/')) { | ||
59 | + hasDeepData = true; | ||
60 | + } | ||
61 | + | ||
62 | + // lastKey is a data | ||
63 | + let pathValue = v[0].replace(/\//g, '.').split('.'); | ||
64 | + let lastKey: string | undefined = pathValue.pop(); | ||
65 | + | ||
66 | + let deepValue: Record<any, any> = {}; | ||
67 | + if (lastKey) { | ||
68 | + deepValue[lastKey.replace('_' + pathValue[0], '')] = lastKey; | ||
69 | + } | ||
70 | + | ||
71 | + // Set Deep Value | ||
72 | + deepValue = Object.assign(deepValue, dotProp.get(deepData, pathValue.join('.'))); | ||
73 | + dotProp.set(deepData, pathValue.join('.'), deepValue); | ||
74 | + }); | ||
75 | + | ||
76 | + if (hasDeepData) { | ||
77 | + return `const ${name} = ` + JSON.stringify(deepData).replace(/\"|\'/g, ''); | ||
78 | + } | ||
79 | + | ||
80 | + return `const ${name} = { ${data.map((v) => v[0]).join(',')} }`; | ||
81 | +} | ||
82 | + | ||
83 | +const globTransform = function (config: SharedConfig): Transform { | ||
26 | const resolver = createResolver( | 84 | const resolver = createResolver( |
27 | config.root || process.cwd(), | 85 | config.root || process.cwd(), |
28 | config.resolvers || [], | 86 | config.resolvers || [], |
29 | config.alias || {} | 87 | config.alias || {} |
30 | ); | 88 | ); |
89 | + const { includes } = config; | ||
31 | const cache = new Map(); | 90 | const cache = new Map(); |
32 | - | ||
33 | const urlMap = new Map(); | 91 | const urlMap = new Map(); |
34 | return { | 92 | return { |
35 | test({ path }) { | 93 | test({ path }) { |
36 | const filePath = path.replace('\u0000', ''); // why some path startsWith '\u0000'? | 94 | const filePath = path.replace('\u0000', ''); // why some path startsWith '\u0000'? |
95 | + | ||
37 | try { | 96 | try { |
38 | return ( | 97 | return ( |
39 | !filePath.startsWith(modulesDir) && | 98 | !filePath.startsWith(modulesDir) && |
40 | /\.(vue|js|jsx|ts|tsx)$/.test(filePath) && | 99 | /\.(vue|js|jsx|ts|tsx)$/.test(filePath) && |
100 | + fileInclude(includes, filePath) && | ||
41 | lstatSync(filePath).isFile() | 101 | lstatSync(filePath).isFile() |
42 | ); | 102 | ); |
43 | } catch { | 103 | } catch { |
@@ -47,50 +107,95 @@ const globbyTransform = function (config: SharedConfig): Transform { | @@ -47,50 +107,95 @@ const globbyTransform = function (config: SharedConfig): Transform { | ||
47 | transform({ code, path, isBuild }) { | 107 | transform({ code, path, isBuild }) { |
48 | let result = cache.get(path); | 108 | let result = cache.get(path); |
49 | if (!result) { | 109 | if (!result) { |
50 | - const reg = /import\s+([\w\s{}*]+)\s+from\s+(['"])globby(\?path)?!([^'"]+)\2/g; | 110 | + const reg = /import\s+([\w\s{}*]+)\s+from\s+(['"])globby(\?locale)?(\?path)?!([^'"]+)\2/g; |
51 | const match = code.match(reg); | 111 | const match = code.match(reg); |
52 | if (!match) return code; | 112 | if (!match) return code; |
53 | const lastImport = urlMap.get(path); | 113 | const lastImport = urlMap.get(path); |
54 | if (lastImport && match) { | 114 | if (lastImport && match) { |
55 | code = code.replace(lastImport, match[0]); | 115 | code = code.replace(lastImport, match[0]); |
56 | } | 116 | } |
57 | - result = code.replace(reg, (_, g1, g2, g3, g4) => { | ||
58 | - const filePath = path.replace('\u0000', ''); // why some path startsWith '\u0000'? | ||
59 | - // resolve path | ||
60 | - const resolvedFilePath = g4.startsWith('.') | ||
61 | - ? resolver.resolveRelativeRequest(filePath, g4) | ||
62 | - : { pathname: resolver.requestToFile(g4) }; | ||
63 | - const files = glob.sync(resolvedFilePath.pathname, { dot: true }); | ||
64 | - let templateStr = 'import #name# from #file#'; // import default | ||
65 | - let name = g1; | ||
66 | - const m = g1.match(/\{\s*(\w+)(\s+as\s+(\w+))?\s*\}/); // import module | ||
67 | - const m2 = g1.match(/\*\s+as\s+(\w+)/); // import * as all module | ||
68 | - if (m) { | ||
69 | - templateStr = `import { ${m[1]} as #name# } from #file#`; | ||
70 | - name = m[3] || m[1]; | ||
71 | - } else if (m2) { | ||
72 | - templateStr = 'import * as #name# from #file#'; | ||
73 | - name = m2[1]; | 117 | + result = code.replace( |
118 | + reg, | ||
119 | + ( | ||
120 | + _, | ||
121 | + // variable to export | ||
122 | + exportName, | ||
123 | + // bare export or not | ||
124 | + bareExporter, | ||
125 | + // is locale import | ||
126 | + isLocale, | ||
127 | + // inject _path attr | ||
128 | + injectPath, | ||
129 | + // path export | ||
130 | + globPath | ||
131 | + ) => { | ||
132 | + const filePath = path.replace('\u0000', ''); // why some path startsWith '\u0000'? | ||
133 | + // resolve path | ||
134 | + | ||
135 | + const resolvedFilePath = globPath.startsWith('.') | ||
136 | + ? resolver.resolveRelativeRequest(filePath, globPath) | ||
137 | + : { pathname: resolver.requestToFile(globPath) }; | ||
138 | + | ||
139 | + const files = glob.sync(resolvedFilePath.pathname, { dot: true }); | ||
140 | + | ||
141 | + let templateStr = 'import #name# from #file#'; // import default | ||
142 | + let name = exportName; | ||
143 | + const m = exportName.match(/\{\s*(\w+)(\s+as\s+(\w+))?\s*\}/); // import module | ||
144 | + const m2 = exportName.match(/\*\s+as\s+(\w+)/); // import * as all module | ||
145 | + if (m) { | ||
146 | + templateStr = `import { ${m[1]} as #name# } from #file#`; | ||
147 | + name = m[3] || m[1]; | ||
148 | + } else if (m2) { | ||
149 | + templateStr = 'import * as #name# from #file#'; | ||
150 | + name = m2[1]; | ||
151 | + } | ||
152 | + | ||
153 | + const templateRender = template(templateStr); | ||
154 | + | ||
155 | + const groups: Array<string>[] = []; | ||
156 | + const replaceFiles = files.map((f, i) => { | ||
157 | + const fileNameWithAlias = resolver.fileToRequest(f); | ||
158 | + | ||
159 | + const file = bareExporter + fileNameWithAlias + bareExporter; | ||
160 | + | ||
161 | + if (isLocale) { | ||
162 | + const globrexRes = globrex(globPath, { extended: true, globstar: true }); | ||
163 | + | ||
164 | + // Get segments for files like an en/system ch/modules for: | ||
165 | + // ['en', 'system'] ['ch', 'modules'] | ||
166 | + const matchedGroups = globrexRes.regex.exec(fileNameWithAlias); | ||
167 | + | ||
168 | + if (matchedGroups && matchedGroups.length) { | ||
169 | + const matchedSegments = matchedGroups[1]; //first everytime "Full Match" | ||
170 | + const name = matchedGroups[2] + '_' + matchedSegments.split('/').shift(); | ||
171 | + //send deep way like an (en/modules/system/dashboard) into groups | ||
172 | + groups.push([matchedSegments + name, file]); | ||
173 | + return templateRender({ | ||
174 | + name, | ||
175 | + file, | ||
176 | + }); | ||
177 | + } | ||
178 | + } else { | ||
179 | + groups.push([name + i, file]); | ||
180 | + return templateRender({ name: name + i, file }); | ||
181 | + } | ||
182 | + }); | ||
183 | + // save in memory used result | ||
184 | + const filesJoined = replaceFiles.join('\n'); | ||
185 | + | ||
186 | + urlMap.set(path, filesJoined); | ||
187 | + return [ | ||
188 | + filesJoined, | ||
189 | + compareString(injectPath, groups), | ||
190 | + varTemplate(groups, name), | ||
191 | + '', | ||
192 | + ].join('\n'); | ||
74 | } | 193 | } |
75 | - const temRender = template(templateStr); | ||
76 | - | ||
77 | - const groups: Array<string>[] = []; | ||
78 | - const replaceFiles = files.map((f, i) => { | ||
79 | - const file = g2 + resolver.fileToRequest(f) + g2; | ||
80 | - groups.push([name + i, file]); | ||
81 | - return temRender({ name: name + i, file }); | ||
82 | - }); | ||
83 | - urlMap.set(path, replaceFiles.join('\n')); | ||
84 | - return ( | ||
85 | - replaceFiles.join('\n') + | ||
86 | - (g3 ? '\n' + groups.map((v) => `${v[0]}._path = ${v[1]}`).join('\n') : '') + | ||
87 | - `\nconst ${name} = { ${groups.map((v) => v[0]).join(',')} }\n` | ||
88 | - ); | ||
89 | - }); | 194 | + ); |
90 | if (isBuild) cache.set(path, result); | 195 | if (isBuild) cache.set(path, result); |
91 | } | 196 | } |
92 | - return result; | 197 | + return `${result}${hmr(isBuild)}`; |
93 | }, | 198 | }, |
94 | }; | 199 | }; |
95 | }; | 200 | }; |
96 | -export default globbyTransform; | 201 | +export default globTransform; |
package.json
@@ -22,7 +22,7 @@ | @@ -22,7 +22,7 @@ | ||
22 | }, | 22 | }, |
23 | "dependencies": { | 23 | "dependencies": { |
24 | "@iconify/iconify": "^2.0.0-rc.2", | 24 | "@iconify/iconify": "^2.0.0-rc.2", |
25 | - "@vueuse/core": "4.0.0-beta.41", | 25 | + "@vueuse/core": "4.0.0-rc.3", |
26 | "ant-design-vue": "2.0.0-beta.15", | 26 | "ant-design-vue": "2.0.0-beta.15", |
27 | "apexcharts": "3.22.0", | 27 | "apexcharts": "3.22.0", |
28 | "axios": "^0.21.0", | 28 | "axios": "^0.21.0", |
@@ -35,7 +35,7 @@ | @@ -35,7 +35,7 @@ | ||
35 | "qrcode": "^1.4.4", | 35 | "qrcode": "^1.4.4", |
36 | "vditor": "^3.6.2", | 36 | "vditor": "^3.6.2", |
37 | "vue": "^3.0.2", | 37 | "vue": "^3.0.2", |
38 | - "vue-i18n": "^9.0.0-beta.7", | 38 | + "vue-i18n": "^9.0.0-beta.8", |
39 | "vue-router": "^4.0.0-rc.3", | 39 | "vue-router": "^4.0.0-rc.3", |
40 | "vuex": "^4.0.0-rc.1", | 40 | "vuex": "^4.0.0-rc.1", |
41 | "vuex-module-decorators": "^1.0.1", | 41 | "vuex-module-decorators": "^1.0.1", |
@@ -50,6 +50,7 @@ | @@ -50,6 +50,7 @@ | ||
50 | "@purge-icons/generated": "^0.4.1", | 50 | "@purge-icons/generated": "^0.4.1", |
51 | "@types/echarts": "^4.9.1", | 51 | "@types/echarts": "^4.9.1", |
52 | "@types/fs-extra": "^9.0.4", | 52 | "@types/fs-extra": "^9.0.4", |
53 | + "@types/globrex": "^0.1.0", | ||
53 | "@types/koa-static": "^4.0.1", | 54 | "@types/koa-static": "^4.0.1", |
54 | "@types/lodash-es": "^4.17.3", | 55 | "@types/lodash-es": "^4.17.3", |
55 | "@types/mockjs": "^1.0.3", | 56 | "@types/mockjs": "^1.0.3", |
@@ -68,6 +69,7 @@ | @@ -68,6 +69,7 @@ | ||
68 | "conventional-changelog-cli": "^2.1.1", | 69 | "conventional-changelog-cli": "^2.1.1", |
69 | "conventional-changelog-custom-config": "^0.3.1", | 70 | "conventional-changelog-custom-config": "^0.3.1", |
70 | "cross-env": "^7.0.2", | 71 | "cross-env": "^7.0.2", |
72 | + "dot-prop": "^6.0.0", | ||
71 | "dotenv": "^8.2.0", | 73 | "dotenv": "^8.2.0", |
72 | "eslint": "^7.13.0", | 74 | "eslint": "^7.13.0", |
73 | "eslint-config-prettier": "^6.15.0", | 75 | "eslint-config-prettier": "^6.15.0", |
@@ -75,6 +77,7 @@ | @@ -75,6 +77,7 @@ | ||
75 | "eslint-plugin-vue": "^7.1.0", | 77 | "eslint-plugin-vue": "^7.1.0", |
76 | "esno": "^0.2.4", | 78 | "esno": "^0.2.4", |
77 | "fs-extra": "^9.0.1", | 79 | "fs-extra": "^9.0.1", |
80 | + "globrex": "^0.1.2", | ||
78 | "husky": "^4.3.0", | 81 | "husky": "^4.3.0", |
79 | "koa-static": "^5.0.0", | 82 | "koa-static": "^5.0.0", |
80 | "less": "^3.12.2", | 83 | "less": "^3.12.2", |
src/hooks/web/useLocale.ts
0 → 100644
1 | +import type { LocaleType } from '/@/locales/types'; | ||
2 | +import { appStore } from '/@/store/modules/app'; | ||
3 | + | ||
4 | +export function useLocale() { | ||
5 | + /** | ||
6 | + * | ||
7 | + */ | ||
8 | + function getLocale(): string { | ||
9 | + return appStore.getProjectConfig.locale; | ||
10 | + } | ||
11 | + | ||
12 | + /** | ||
13 | + * | ||
14 | + * @param locale | ||
15 | + */ | ||
16 | + async function changeLocale(locale: LocaleType): Promise<void> { | ||
17 | + appStore.commitProjectConfigState({ locale: locale }); | ||
18 | + } | ||
19 | + | ||
20 | + return { getLocale, changeLocale }; | ||
21 | +} |
src/locales/index.ts
0 → 100644
src/locales/lang/en/routes/menus/dashboard.ts
0 → 100644
src/locales/lang/en/system/basic.ts
0 → 100644
src/locales/lang/en/system/login.ts
0 → 100644
src/locales/lang/ru/routes/menus/dashboard.ts
0 → 100644
src/locales/lang/ru/system/basic.ts
0 → 100644
src/locales/lang/ru/system/login.ts
0 → 100644
src/locales/lang/zhCN/routes/menus/dashboard.ts
0 → 100644
src/locales/lang/zhCN/system/basic.ts
0 → 100644
src/locales/lang/zhCN/system/login.ts
0 → 100644
src/locales/types.ts
0 → 100644
1 | +export type LocaleType = 'zhCN' | 'en' | 'ru' | 'ja'; |
src/main.ts
@@ -5,6 +5,7 @@ import { setupStore } from '/@/store'; | @@ -5,6 +5,7 @@ import { setupStore } from '/@/store'; | ||
5 | import { setupAntd } from '/@/setup/ant-design-vue'; | 5 | import { setupAntd } from '/@/setup/ant-design-vue'; |
6 | import { setupErrorHandle } from '/@/setup/error-handle'; | 6 | import { setupErrorHandle } from '/@/setup/error-handle'; |
7 | import { setupGlobDirectives } from '/@/setup/directives'; | 7 | import { setupGlobDirectives } from '/@/setup/directives'; |
8 | +import { setupI18n } from '/@/setup/i18n'; | ||
8 | 9 | ||
9 | import { setupProdMockServer } from '../mock/_createProductionServer'; | 10 | import { setupProdMockServer } from '../mock/_createProductionServer'; |
10 | import { setApp } from '/@/setup/App'; | 11 | import { setApp } from '/@/setup/App'; |
@@ -15,11 +16,16 @@ import { isDevMode, isProdMode, isUseMock } from '/@/utils/env'; | @@ -15,11 +16,16 @@ import { isDevMode, isProdMode, isUseMock } from '/@/utils/env'; | ||
15 | 16 | ||
16 | import '/@/design/index.less'; | 17 | import '/@/design/index.less'; |
17 | 18 | ||
19 | +import '/@/locales/index'; | ||
20 | + | ||
18 | const app = createApp(App); | 21 | const app = createApp(App); |
19 | 22 | ||
20 | // Configure component library | 23 | // Configure component library |
21 | setupAntd(app); | 24 | setupAntd(app); |
22 | 25 | ||
26 | +// Multilingual configuration | ||
27 | +setupI18n(app); | ||
28 | + | ||
23 | // Configure routing | 29 | // Configure routing |
24 | setupRouter(app); | 30 | setupRouter(app); |
25 | 31 |
src/settings/projectSetting.ts
@@ -7,6 +7,7 @@ import { isProdMode } from '/@/utils/env'; | @@ -7,6 +7,7 @@ import { isProdMode } from '/@/utils/env'; | ||
7 | 7 | ||
8 | // ! You need to clear the browser cache after the change | 8 | // ! You need to clear the browser cache after the change |
9 | const setting: ProjectConfig = { | 9 | const setting: ProjectConfig = { |
10 | + locale: 'en', | ||
10 | // color | 11 | // color |
11 | // TODO 主题色 | 12 | // TODO 主题色 |
12 | themeColor: primaryColor, | 13 | themeColor: primaryColor, |
src/setup/i18n/index.ts
0 → 100644
1 | +import type { App } from 'vue'; | ||
2 | +import type { I18n, Locale, I18nOptions } from 'vue-i18n'; | ||
3 | + | ||
4 | +import { createI18n } from 'vue-i18n'; | ||
5 | +import localeMessages from '/@/locales'; | ||
6 | +import { useLocale } from '/@/hooks/web/useLocale'; | ||
7 | + | ||
8 | +const { getLocale } = useLocale(); | ||
9 | + | ||
10 | +const localeData: I18nOptions = { | ||
11 | + legacy: false, | ||
12 | + locale: getLocale(), | ||
13 | + // TODO: setting fallback inside settings | ||
14 | + fallbackLocale: 'en', | ||
15 | + messages: localeMessages, | ||
16 | + // availableLocales: ['ru'], | ||
17 | + sync: true, //If you don’t want to inherit locale from global scope, you need to set sync of i18n component option to false. | ||
18 | + silentTranslationWarn: false, // true - warning off | ||
19 | + silentFallbackWarn: true, | ||
20 | +}; | ||
21 | + | ||
22 | +let i18n: I18n; | ||
23 | + | ||
24 | +// setup i18n instance with glob | ||
25 | +export function setupI18n(app: App) { | ||
26 | + i18n = createI18n(localeData) as I18n; | ||
27 | + setI18nLanguage(getLocale()); | ||
28 | + app.use(i18n); | ||
29 | +} | ||
30 | + | ||
31 | +export function setI18nLanguage(locale: Locale): void { | ||
32 | + // @ts-ignore | ||
33 | + i18n.global.locale.value = locale; | ||
34 | + // i18n.global.setLocaleMessage(locale, messages); | ||
35 | +} |
src/store/index.ts
1 | import type { App } from 'vue'; | 1 | import type { App } from 'vue'; |
2 | -import { createStore, createLogger, Plugin } from 'vuex'; | 2 | +import { |
3 | + createStore, | ||
4 | + // createLogger, Plugin | ||
5 | +} from 'vuex'; | ||
3 | import { config } from 'vuex-module-decorators'; | 6 | import { config } from 'vuex-module-decorators'; |
4 | import { isDevMode } from '/@/utils/env'; | 7 | import { isDevMode } from '/@/utils/env'; |
5 | 8 | ||
6 | config.rawError = true; | 9 | config.rawError = true; |
7 | const isDev = isDevMode(); | 10 | const isDev = isDevMode(); |
8 | -const plugins: Plugin<any>[] = isDev ? [createLogger()] : []; | 11 | +// const plugins: Plugin<any>[] = isDev ? [createLogger()] : []; |
9 | 12 | ||
10 | const store = createStore({ | 13 | const store = createStore({ |
11 | // modules: {}, | 14 | // modules: {}, |
12 | strict: isDev, | 15 | strict: isDev, |
13 | - plugins, | 16 | + // plugins, |
14 | }); | 17 | }); |
15 | 18 | ||
16 | export function setupStore(app: App<Element>) { | 19 | export function setupStore(app: App<Element>) { |
src/types/config.d.ts
1 | // 左侧菜单, 顶部菜单 | 1 | // 左侧菜单, 顶部菜单 |
2 | import { MenuTypeEnum, MenuModeEnum, TriggerEnum } from '/@/enums/menuEnum'; | 2 | import { MenuTypeEnum, MenuModeEnum, TriggerEnum } from '/@/enums/menuEnum'; |
3 | import { ContentEnum, PermissionModeEnum, ThemeEnum, RouterTransitionEnum } from '/@/enums/appEnum'; | 3 | import { ContentEnum, PermissionModeEnum, ThemeEnum, RouterTransitionEnum } from '/@/enums/appEnum'; |
4 | - | 4 | +import type { LocaleType } from '/@/locales/types'; |
5 | export interface MessageSetting { | 5 | export interface MessageSetting { |
6 | title: string; | 6 | title: string; |
7 | // 取消按钮的文字, | 7 | // 取消按钮的文字, |
@@ -55,6 +55,7 @@ export interface HeaderSetting { | @@ -55,6 +55,7 @@ export interface HeaderSetting { | ||
55 | showNotice: boolean; | 55 | showNotice: boolean; |
56 | } | 56 | } |
57 | export interface ProjectConfig { | 57 | export interface ProjectConfig { |
58 | + locale: LocaleType; | ||
58 | // header背景色 | 59 | // header背景色 |
59 | headerBgColor: string; | 60 | headerBgColor: string; |
60 | // 左侧菜单背景色 | 61 | // 左侧菜单背景色 |
src/types/module.d.ts
@@ -4,4 +4,6 @@ declare module 'globby!/@/router/routes/modules/**/*.@(ts)'; | @@ -4,4 +4,6 @@ declare module 'globby!/@/router/routes/modules/**/*.@(ts)'; | ||
4 | 4 | ||
5 | declare module 'globby!/@/router/menus/modules/**/*.@(ts)'; | 5 | declare module 'globby!/@/router/menus/modules/**/*.@(ts)'; |
6 | 6 | ||
7 | +declare module 'globby?locale!/@/locales/lang/**/*.@(ts)'; | ||
8 | + | ||
7 | declare const React: string; | 9 | declare const React: string; |
src/views/sys/login/Login.vue
@@ -11,14 +11,14 @@ | @@ -11,14 +11,14 @@ | ||
11 | 11 | ||
12 | <a-form class="mx-auto mt-10" :model="formData" :rules="formRules" ref="formRef"> | 12 | <a-form class="mx-auto mt-10" :model="formData" :rules="formRules" ref="formRef"> |
13 | <a-form-item name="account"> | 13 | <a-form-item name="account"> |
14 | - <a-input size="large" v-model:value="formData.account" placeholder="Username: vben" /> | 14 | + <a-input size="large" v-model:value="formData.account" placeholder="username: vben" /> |
15 | </a-form-item> | 15 | </a-form-item> |
16 | <a-form-item name="password"> | 16 | <a-form-item name="password"> |
17 | <a-input-password | 17 | <a-input-password |
18 | size="large" | 18 | size="large" |
19 | visibilityToggle | 19 | visibilityToggle |
20 | v-model:value="formData.password" | 20 | v-model:value="formData.password" |
21 | - placeholder="Password: 123456" | 21 | + placeholder="password: 123456" |
22 | /> | 22 | /> |
23 | </a-form-item> | 23 | </a-form-item> |
24 | 24 | ||
@@ -28,13 +28,13 @@ | @@ -28,13 +28,13 @@ | ||
28 | <a-row> | 28 | <a-row> |
29 | <a-col :span="12"> | 29 | <a-col :span="12"> |
30 | <a-form-item> | 30 | <a-form-item> |
31 | - <!-- 未做逻辑,需要自行处理 --> | 31 | + <!-- No logic, you need to deal with it yourself --> |
32 | <a-checkbox v-model:checked="autoLogin" size="small">自动登录</a-checkbox> | 32 | <a-checkbox v-model:checked="autoLogin" size="small">自动登录</a-checkbox> |
33 | </a-form-item> | 33 | </a-form-item> |
34 | </a-col> | 34 | </a-col> |
35 | <a-col :span="12"> | 35 | <a-col :span="12"> |
36 | <a-form-item :style="{ 'text-align': 'right' }"> | 36 | <a-form-item :style="{ 'text-align': 'right' }"> |
37 | - <!-- 未做逻辑,需要自行处理 --> | 37 | + <!-- No logic, you need to deal with it yourself --> |
38 | <a-button type="link" size="small">忘记密码</a-button> | 38 | <a-button type="link" size="small">忘记密码</a-button> |
39 | </a-form-item> | 39 | </a-form-item> |
40 | </a-col> | 40 | </a-col> |
@@ -47,7 +47,7 @@ | @@ -47,7 +47,7 @@ | ||
47 | :block="true" | 47 | :block="true" |
48 | @click="login" | 48 | @click="login" |
49 | :loading="formState.loading" | 49 | :loading="formState.loading" |
50 | - >登录</a-button | 50 | + >{{ t('system.login.button') }}</a-button |
51 | > | 51 | > |
52 | </a-form-item> | 52 | </a-form-item> |
53 | </a-form> | 53 | </a-form> |
@@ -57,20 +57,15 @@ | @@ -57,20 +57,15 @@ | ||
57 | </div> | 57 | </div> |
58 | </template> | 58 | </template> |
59 | <script lang="ts"> | 59 | <script lang="ts"> |
60 | - import { | ||
61 | - defineComponent, | ||
62 | - reactive, | ||
63 | - ref, | ||
64 | - unref, | ||
65 | - toRaw, | ||
66 | - // computed | ||
67 | - } from 'vue'; | 60 | + import { defineComponent, reactive, ref, unref, toRaw } from 'vue'; |
68 | import { Checkbox } from 'ant-design-vue'; | 61 | import { Checkbox } from 'ant-design-vue'; |
69 | 62 | ||
70 | import Button from '/@/components/Button/index.vue'; | 63 | import Button from '/@/components/Button/index.vue'; |
71 | // import { BasicDragVerify, DragVerifyActionType } from '/@/components/Verify/index'; | 64 | // import { BasicDragVerify, DragVerifyActionType } from '/@/components/Verify/index'; |
72 | 65 | ||
73 | import { userStore } from '/@/store/modules/user'; | 66 | import { userStore } from '/@/store/modules/user'; |
67 | + import { useI18n } from 'vue-i18n'; | ||
68 | + | ||
74 | // import { appStore } from '/@/store/modules/app'; | 69 | // import { appStore } from '/@/store/modules/app'; |
75 | import { useMessage } from '/@/hooks/web/useMessage'; | 70 | import { useMessage } from '/@/hooks/web/useMessage'; |
76 | import { useSetting } from '/@/hooks/core/useSetting'; | 71 | import { useSetting } from '/@/hooks/core/useSetting'; |
@@ -139,7 +134,7 @@ | @@ -139,7 +134,7 @@ | ||
139 | formState.loading = false; | 134 | formState.loading = false; |
140 | } | 135 | } |
141 | } | 136 | } |
142 | - | 137 | + const { t } = useI18n(); |
143 | return { | 138 | return { |
144 | formRef, | 139 | formRef, |
145 | // verifyRef, | 140 | // verifyRef, |
@@ -151,6 +146,7 @@ | @@ -151,6 +146,7 @@ | ||
151 | // openLoginVerify: openLoginVerifyRef, | 146 | // openLoginVerify: openLoginVerifyRef, |
152 | title: globSetting && globSetting.title, | 147 | title: globSetting && globSetting.title, |
153 | logo, | 148 | logo, |
149 | + t, | ||
154 | }; | 150 | }; |
155 | }, | 151 | }, |
156 | }); | 152 | }); |
vite.config.ts
@@ -4,8 +4,9 @@ import { resolve } from 'path'; | @@ -4,8 +4,9 @@ import { resolve } from 'path'; | ||
4 | 4 | ||
5 | import { modifyVars } from './build/config/lessModifyVars'; | 5 | import { modifyVars } from './build/config/lessModifyVars'; |
6 | import { createProxy } from './build/vite/proxy'; | 6 | import { createProxy } from './build/vite/proxy'; |
7 | -import globbyTransform from './build/vite/plugin/context/transform'; | ||
8 | -import dynamicImportTransform from './build/vite/plugin/dynamicImport/index'; | 7 | + |
8 | +import globbyTransform from './build/vite/plugin/transform/globby'; | ||
9 | +import dynamicImportTransform from './build/vite/plugin/transform/dynamic-import'; | ||
9 | 10 | ||
10 | import { isDevFn, loadEnv } from './build/utils'; | 11 | import { isDevFn, loadEnv } from './build/utils'; |
11 | 12 | ||
@@ -111,6 +112,11 @@ const viteConfig: UserConfig = { | @@ -111,6 +112,11 @@ const viteConfig: UserConfig = { | ||
111 | }, | 112 | }, |
112 | define: { | 113 | define: { |
113 | __VERSION__: pkg.version, | 114 | __VERSION__: pkg.version, |
115 | + // use vue-i18-next | ||
116 | + // Suppress warning | ||
117 | + __VUE_I18N_LEGACY_API__: false, | ||
118 | + __VUE_I18N_FULL_INSTALL__: false, | ||
119 | + __INTLIFY_PROD_DEVTOOLS__: false, | ||
114 | }, | 120 | }, |
115 | cssPreprocessOptions: { | 121 | cssPreprocessOptions: { |
116 | less: { | 122 | less: { |
@@ -135,5 +141,13 @@ const viteConfig: UserConfig = { | @@ -135,5 +141,13 @@ const viteConfig: UserConfig = { | ||
135 | 141 | ||
136 | export default { | 142 | export default { |
137 | ...viteConfig, | 143 | ...viteConfig, |
138 | - transforms: [globbyTransform(viteConfig), dynamicImportTransform(viteEnv)], | 144 | + transforms: [ |
145 | + globbyTransform({ | ||
146 | + resolvers: viteConfig.resolvers, | ||
147 | + root: viteConfig.root, | ||
148 | + alias: viteConfig.alias, | ||
149 | + includes: [resolve('src/router'), resolve('src/locales')], | ||
150 | + }), | ||
151 | + dynamicImportTransform(viteEnv), | ||
152 | + ], | ||
139 | } as UserConfig; | 153 | } as UserConfig; |
yarn.lock
@@ -1323,6 +1323,11 @@ | @@ -1323,6 +1323,11 @@ | ||
1323 | dependencies: | 1323 | dependencies: |
1324 | "@types/node" "*" | 1324 | "@types/node" "*" |
1325 | 1325 | ||
1326 | +"@types/globrex@^0.1.0": | ||
1327 | + version "0.1.0" | ||
1328 | + resolved "https://registry.npmjs.org/@types/globrex/-/globrex-0.1.0.tgz#baf4ac8e36947017612c01fde7c7b641dc0b6c89" | ||
1329 | + integrity sha512-aBkxDgp/UbnluE+CIT3V3PoNewwOlLCzXSF3ipD86Slv8xVjwxrDAfSGbsfGgMzPo/fEMPXc+gNUJbtiugwfoA== | ||
1330 | + | ||
1326 | "@types/http-assert@*": | 1331 | "@types/http-assert@*": |
1327 | version "1.5.1" | 1332 | version "1.5.1" |
1328 | resolved "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.1.tgz#d775e93630c2469c2f980fc27e3143240335db3b" | 1333 | resolved "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.1.tgz#d775e93630c2469c2f980fc27e3143240335db3b" |
@@ -1732,18 +1737,18 @@ | @@ -1732,18 +1737,18 @@ | ||
1732 | vscode-languageserver-textdocument "^1.0.1" | 1737 | vscode-languageserver-textdocument "^1.0.1" |
1733 | vscode-uri "^2.1.2" | 1738 | vscode-uri "^2.1.2" |
1734 | 1739 | ||
1735 | -"@vueuse/core@4.0.0-beta.41": | ||
1736 | - version "4.0.0-beta.41" | ||
1737 | - resolved "https://registry.npmjs.org/@vueuse/core/-/core-4.0.0-beta.41.tgz#0058aed5ade75ae2866283498009ad5172cbae84" | ||
1738 | - integrity sha512-CgUih65PzYScorm1S4F93e6XXm+qxA8GrRLOSB1kXaqtP6vXedwkBxKkNEYNACx4reL4VEHqM/BrM6FajXkQUg== | 1740 | +"@vueuse/core@4.0.0-rc.3": |
1741 | + version "4.0.0-rc.3" | ||
1742 | + resolved "https://registry.npmjs.org/@vueuse/core/-/core-4.0.0-rc.3.tgz#5381ca657e10df596cd7027fc5c96b2d4b3a090c" | ||
1743 | + integrity sha512-dQ/FZgo0z7kBFOvDWxuzaUrmuO8X1AlQk17e3PU1TVtG2Uu+mCvjPNbuvI2fjhTjl5rzPJawwoU2WZFj+nlFvw== | ||
1739 | dependencies: | 1744 | dependencies: |
1740 | - "@vueuse/shared" "4.0.0-beta.41" | 1745 | + "@vueuse/shared" "4.0.0-rc.3" |
1741 | vue-demi latest | 1746 | vue-demi latest |
1742 | 1747 | ||
1743 | -"@vueuse/shared@4.0.0-beta.41": | ||
1744 | - version "4.0.0-beta.41" | ||
1745 | - resolved "https://registry.npmjs.org/@vueuse/shared/-/shared-4.0.0-beta.41.tgz#395782ea2e580f1fc9488d25c89bd09f70170b25" | ||
1746 | - integrity sha512-dqnuEPPC3OUJ6L6rhMiOCuPWIR698DtdwOydwCZBISsG2V6gZ2QFND6xtRwLib6/lhUMYVYPwIz3hPjlx7BIzw== | 1748 | +"@vueuse/shared@4.0.0-rc.3": |
1749 | + version "4.0.0-rc.3" | ||
1750 | + resolved "https://registry.npmjs.org/@vueuse/shared/-/shared-4.0.0-rc.3.tgz#42fb56fed3779f3b8a17a82c16a364bad20d01b7" | ||
1751 | + integrity sha512-VY0x/XxpeTMHp/0FDiv1cgUUxkJGQl7liiM2AjR/J7+Ys/2Y2dijD5cAKViq9FGUPQQsOcLptMvMvUsDMoN4DA== | ||
1747 | dependencies: | 1752 | dependencies: |
1748 | vue-demi latest | 1753 | vue-demi latest |
1749 | 1754 | ||
@@ -3222,6 +3227,13 @@ dot-prop@^5.1.0: | @@ -3222,6 +3227,13 @@ dot-prop@^5.1.0: | ||
3222 | dependencies: | 3227 | dependencies: |
3223 | is-obj "^2.0.0" | 3228 | is-obj "^2.0.0" |
3224 | 3229 | ||
3230 | +dot-prop@^6.0.0: | ||
3231 | + version "6.0.0" | ||
3232 | + resolved "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.0.tgz#bd579fd704d970981c4b05de591db648959f2ebb" | ||
3233 | + integrity sha512-xCbB8IN3IT+tdgoEPOnJmYTNJDrygGFOmiQEiVa5eAD+JEB1vTgMNhVGRnN5Eex/6amck7cdcrixb1qN9Go+GQ== | ||
3234 | + dependencies: | ||
3235 | + is-obj "^2.0.0" | ||
3236 | + | ||
3225 | dotenv-expand@^5.1.0: | 3237 | dotenv-expand@^5.1.0: |
3226 | version "5.1.0" | 3238 | version "5.1.0" |
3227 | resolved "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" | 3239 | resolved "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" |
@@ -4092,6 +4104,11 @@ globjoin@^0.1.4: | @@ -4092,6 +4104,11 @@ globjoin@^0.1.4: | ||
4092 | resolved "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz#2f4494ac8919e3767c5cbb691e9f463324285d43" | 4104 | resolved "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz#2f4494ac8919e3767c5cbb691e9f463324285d43" |
4093 | integrity sha1-L0SUrIkZ43Z8XLtpHp9GMyQoXUM= | 4105 | integrity sha1-L0SUrIkZ43Z8XLtpHp9GMyQoXUM= |
4094 | 4106 | ||
4107 | +globrex@^0.1.2: | ||
4108 | + version "0.1.2" | ||
4109 | + resolved "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098" | ||
4110 | + integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg== | ||
4111 | + | ||
4095 | gonzales-pe@^4.3.0: | 4112 | gonzales-pe@^4.3.0: |
4096 | version "4.3.0" | 4113 | version "4.3.0" |
4097 | resolved "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-4.3.0.tgz#fe9dec5f3c557eead09ff868c65826be54d067b3" | 4114 | resolved "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-4.3.0.tgz#fe9dec5f3c557eead09ff868c65826be54d067b3" |
@@ -8167,10 +8184,10 @@ vue-eslint-parser@^7.1.1: | @@ -8167,10 +8184,10 @@ vue-eslint-parser@^7.1.1: | ||
8167 | esquery "^1.0.1" | 8184 | esquery "^1.0.1" |
8168 | lodash "^4.17.15" | 8185 | lodash "^4.17.15" |
8169 | 8186 | ||
8170 | -vue-i18n@^9.0.0-beta.7: | ||
8171 | - version "9.0.0-beta.7" | ||
8172 | - resolved "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.0.0-beta.7.tgz#f6fad5b4be218018aab4797f80dd2a95ee5236f9" | ||
8173 | - integrity sha512-hFl0XnV91P/4UyWvHYvdYxuk3GRnKIW9zXAm6hrUU4mOIwpqchi7jVQva2TJLr52Mpsu4zYXmzL1h5pgrKmCfQ== | 8187 | +vue-i18n@^9.0.0-beta.8: |
8188 | + version "9.0.0-beta.8" | ||
8189 | + resolved "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.0.0-beta.8.tgz#92282d5b5e0e6f15cc04943ce11bf49db610468f" | ||
8190 | + integrity sha512-tViSN96jLi0AKvAVi4twcYYN5Ld++SqN1/140ua+YWm/iRbO2M0rAcsZ7e6/4LTm6Pd1ldSwWihSuv2bSQmlnw== | ||
8174 | dependencies: | 8191 | dependencies: |
8175 | source-map "0.6.1" | 8192 | source-map "0.6.1" |
8176 | 8193 |