Skip to content

Commit

Permalink
wip: support multilingual configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
anncwb committed Nov 19, 2020
1 parent 3a65176 commit 8882d4e
Show file tree
Hide file tree
Showing 25 changed files with 372 additions and 135 deletions.
96 changes: 0 additions & 96 deletions build/vite/plugin/context/transform.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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() });
Expand Down
201 changes: 201 additions & 0 deletions build/vite/plugin/transform/globby/index.ts
Original file line number Diff line number Diff line change
@@ -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;
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand All @@ -68,13 +69,15 @@
"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",
"eslint-plugin-prettier": "^3.1.4",
"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",
Expand Down
21 changes: 21 additions & 0 deletions src/hooks/web/useLocale.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 3 additions & 0 deletions src/locales/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import messages from 'globby?locale!/@/locales/lang/**/*.@(ts)';

export default messages;
3 changes: 3 additions & 0 deletions src/locales/lang/en/routes/menus/dashboard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
someentry: 'some text',
};
3 changes: 3 additions & 0 deletions src/locales/lang/en/system/basic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
some: 'Get Out',
};
3 changes: 3 additions & 0 deletions src/locales/lang/en/system/login.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
button: 'Login',
};
3 changes: 3 additions & 0 deletions src/locales/lang/ru/routes/menus/dashboard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
someentry: 'some text',
};
Loading

0 comments on commit 8882d4e

Please sign in to comment.