25 changed files with 372 additions and 135 deletions
@ -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; |
|
||||
@ -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; |
||||
@ -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 }; |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
import messages from 'globby?locale!/@/locales/lang/**/*.@(ts)'; |
||||
|
|
||||
|
export default messages; |
||||
@ -0,0 +1,3 @@ |
|||||
|
export default { |
||||
|
someentry: 'some text', |
||||
|
}; |
||||
@ -0,0 +1,3 @@ |
|||||
|
export default { |
||||
|
some: 'Get Out', |
||||
|
}; |
||||
@ -0,0 +1,3 @@ |
|||||
|
export default { |
||||
|
button: 'Login', |
||||
|
}; |
||||
@ -0,0 +1,3 @@ |
|||||
|
export default { |
||||
|
someentry: 'some text', |
||||
|
}; |
||||
@ -0,0 +1,3 @@ |
|||||
|
export default { |
||||
|
some: 'Get Out', |
||||
|
}; |
||||
@ -0,0 +1,7 @@ |
|||||
|
export default { |
||||
|
button: 'Login', |
||||
|
validation: { |
||||
|
account: 'Required Field account', |
||||
|
password: 'Required Field password', |
||||
|
}, |
||||
|
}; |
||||
@ -0,0 +1,3 @@ |
|||||
|
export default { |
||||
|
someentry: '一些文本', |
||||
|
}; |
||||
@ -0,0 +1,3 @@ |
|||||
|
export default { |
||||
|
some: '出去', |
||||
|
}; |
||||
@ -0,0 +1,3 @@ |
|||||
|
export default { |
||||
|
button: '登录', |
||||
|
}; |
||||
@ -0,0 +1 @@ |
|||||
|
export type LocaleType = 'zhCN' | 'en' | 'ru' | 'ja'; |
||||
@ -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);
|
||||
|
} |
||||
Loading…
Reference in new issue