mtong-tools
Advanced tools
+14
-1
@@ -34,3 +34,5 @@ #!/usr/bin/env node | ||
| .description('输出多语言配置') | ||
| .option('-file, --path <url>', '自定义多语言路径') | ||
| .option('-x, --xlsx', '反向输出xlsx文件') | ||
| .option('-n, --noKey', '关闭多语言key-map功能') | ||
| .option('-f, --file <url>', '自定义多语言路径') | ||
| .action((options) => { | ||
@@ -40,3 +42,14 @@ require('./lang')(options) | ||
| program.command('help') | ||
| .description('查看帮助') | ||
| .action(() => { | ||
| console.log(` | ||
| mt create <app-name> 创建一个新的项目 | ||
| mt download 更新公用代码到业务仓库 | ||
| mt lang 输出多语言配置 | ||
| `) | ||
| }) | ||
| program.version(require('../package.json').version, '-v, --version') | ||
| program.parse(process.argv) |
+188
-66
@@ -1,75 +0,197 @@ | ||
| const {error} = require('../utils') | ||
| const fs = require('fs-extra'); | ||
| const path = require('path') | ||
| const jsonFile = require('jsonfile') | ||
| const xlsx = require('node-xlsx') | ||
| const Path = require('path') | ||
| const chalk = require('chalk') | ||
| const { error } = require("../utils"); | ||
| const fs = require("fs-extra"); | ||
| const path = require("path"); | ||
| const jsonFile = require("jsonfile"); | ||
| const xlsx = require("node-xlsx"); | ||
| const Path = require("path"); | ||
| const chalk = require("chalk"); | ||
| const _ = require("lodash"); | ||
| async function createLang(options) { | ||
| try { | ||
| const cwd = options.cwd || process.cwd() | ||
| let file = options.path | ||
| const json = path.join(cwd, './package.json'); | ||
| const jsonData = jsonFile.readFileSync(json); | ||
| if (!file) { | ||
| file = path.join(cwd, jsonData.lang.path) | ||
| // 🔧 工具:展开嵌套 key(用于 isMap=true) | ||
| function flattenKeys(obj, prefix = "") { | ||
| let keys = []; | ||
| for (const k in obj) { | ||
| const path = prefix ? `${prefix}.${k}` : k; | ||
| if (_.isObject(obj[k]) && !Array.isArray(obj[k])) { | ||
| keys = keys.concat(flattenKeys(obj[k], path)); | ||
| } else { | ||
| keys.push(path); | ||
| } | ||
| const outPath = Path.join(cwd, jsonData.lang.outPath || 'lang') | ||
| const suffix = jsonData.lang.suffix || 'js' | ||
| } | ||
| return keys; | ||
| } | ||
| if (file) { | ||
| const sheets = xlsx.parse(file) | ||
| const sheet = sheets[0].data | ||
| const data = {}, keys = sheet[0].map(item => item.toLocaleLowerCase()); | ||
| const enumMap = {} | ||
| for (let i = 1; i < keys.length; i++) { | ||
| const key = keys[i] | ||
| if (key) data[key] = { | ||
| message: {} | ||
| } | ||
| const xlsxToJson = async (params) => { | ||
| const { filePath, isMap = true, outPath, suffix } = params; | ||
| const enumKey = key.toLocaleUpperCase() | ||
| enumMap[enumKey] = key | ||
| } | ||
| for (let i = 1; i < sheet.length; i++) { | ||
| const lang = sheet[i] | ||
| const key = lang[0] | ||
| for (let j = 1; j < lang.length; j++) { | ||
| if (keys[j] && key) { | ||
| data[keys[j]].message[key] = lang[j] || '' | ||
| if (filePath) { | ||
| const sheets = xlsx.parse(filePath); | ||
| const sheet = sheets[0].data; | ||
| const data = {}, | ||
| keys = sheet[0].map((item) => item.toLocaleLowerCase()); | ||
| const enumMap = {}; | ||
| for (let i = 1; i < keys.length; i++) { | ||
| const key = keys[i]; | ||
| if (!key) continue; | ||
| data[key] = { | ||
| message: {}, | ||
| }; | ||
| const enumKey = key.toLocaleUpperCase(); | ||
| enumMap[enumKey] = key; | ||
| } | ||
| for (let i = 1; i < sheet.length; i++) { | ||
| const lang = sheet[i]; | ||
| const key = lang[0]; | ||
| for (let j = 1; j < lang.length; j++) { | ||
| if (keys[j] && key && lang[j]) { | ||
| if (isMap) { | ||
| _.set(data[keys[j]].message, key, lang[j]); | ||
| } else { | ||
| data[keys[j]].message[key] = lang[j]; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| Object.entries(data).forEach(([key, data]) => { | ||
| let str = `const ${key} = ` | ||
| str += JSON.stringify(data, "", "\t") | ||
| str += '\n\n' | ||
| str += `export default ${key}` | ||
| if (!fs.existsSync(outPath)) { | ||
| fs.mkdirSync(outPath) | ||
| } | ||
| const file = Path.join(outPath, `${key}.${suffix}`) | ||
| fs.outputFileSync(file, str) | ||
| }) | ||
| Object.entries(data).forEach(([key, data]) => { | ||
| let str = `const ${key} = `; | ||
| str += JSON.stringify(data, "", "\t"); | ||
| str += "\n\n"; | ||
| str += `export default ${key}`; | ||
| if (!fs.existsSync(outPath)) { | ||
| fs.mkdirSync(outPath); | ||
| } | ||
| const file = Path.join(outPath, `${key}.${suffix}`); | ||
| fs.outputFileSync(file, str); | ||
| }); | ||
| let str = suffix === 'ts' ? 'enum I18nMessageType {\n' : 'export default {\n' | ||
| Object.entries(enumMap).forEach(([key, value]) => { | ||
| str += ` ${key} = '${value}',\n` | ||
| }) | ||
| str += `}` | ||
| const isTs = suffix === "ts"; | ||
| if(suffix === 'ts') { | ||
| str += '\nexport default I18nMessageType' | ||
| } | ||
| let str = isTs ? "enum I18nMessageType {\n" : "export default {\n"; | ||
| const connector = isTs ? " = " : ": "; | ||
| Object.entries(enumMap).forEach(([key, value]) => { | ||
| str += ` ${key} ${connector} '${value}',\n`; | ||
| }); | ||
| str += `}`; | ||
| const fileEnmu = Path.join(outPath, `index.${suffix}`) | ||
| fs.outputFileSync(fileEnmu, str) | ||
| if (isTs) { | ||
| str += "\nexport default I18nMessageType"; | ||
| } | ||
| console.log(`多语言生成成功:${chalk.blue(file)}-${chalk.yellow(outPath)}`) | ||
| process.exit(0) | ||
| const fileEnmu = Path.join(outPath, `index.${suffix}`); | ||
| fs.outputFileSync(fileEnmu, str); | ||
| } | ||
| console.log( | ||
| `多语言生成成功:${chalk.blue(filePath)}-${chalk.yellow(outPath)}` | ||
| ); | ||
| }; | ||
| const jsonToXlsx = async (params) => { | ||
| const { outPath, suffix } = params; | ||
| // 1️⃣ 读取所有语言文件 | ||
| const files = fs | ||
| .readdirSync(outPath) | ||
| .filter( | ||
| (file) => file.endsWith(`.${suffix}`) && file !== `index.${suffix}` | ||
| ); | ||
| if (files.length === 0) { | ||
| console.log(chalk.yellow(`No ${suffix} files found in the directory`)); | ||
| return; | ||
| } | ||
| const langs = files.map((file) => Path.basename(file, `.${suffix}`)); | ||
| const messagesMap = {}; | ||
| const prefix = ["en", "EN", "cn", "CN", "zh", "ZH"]; | ||
| prefix.forEach((lang) => { | ||
| const index = langs.indexOf(lang); | ||
| if (index !== -1) { | ||
| langs.unshift(...langs.splice(index, 1)); // 将匹配的语言移到第一个位置 | ||
| } | ||
| }); | ||
| langs.forEach((lang) => { | ||
| const file = Path.join(outPath, `${lang}.${suffix}`); | ||
| const content = fs.readFileSync(file, "utf-8").trim(); | ||
| const formula = content | ||
| .replace(/export\s+default\s+/, "return ") // 去掉 export default | ||
| .replace(/;$/, "") // 去掉末尾 ; | ||
| .trim(); | ||
| const contentMap = new Function(formula)(); | ||
| messagesMap[lang] = contentMap.message; | ||
| }); | ||
| // 2️⃣ 生成完整 key 列表 | ||
| const keysSet = new Set(); | ||
| langs.forEach((lang) => { | ||
| const messages = messagesMap[lang]; | ||
| const flatKeys = flattenKeys(messages); | ||
| flatKeys.forEach((key) => keysSet.add(key)); | ||
| }); | ||
| const keys = Array.from(keysSet); | ||
| // 3️⃣ 生成 Excel 二维数组 | ||
| const sheet = []; | ||
| const header = ["STRID", ...langs].map((lang) => lang.toLocaleUpperCase()); | ||
| sheet.push(header); | ||
| keys.forEach((key) => { | ||
| const row = [key]; | ||
| langs.forEach((lang) => { | ||
| const messages = messagesMap[lang]; | ||
| const value = _.get(messages, key, ""); | ||
| row.push(value); | ||
| }); | ||
| sheet.push(row); | ||
| }); | ||
| // 4️⃣ 写入 Excel 文件 | ||
| const buffer = xlsx.build([{ name: "Sheet1", data: sheet }]); | ||
| const outputFile = Path.join(outPath, "lang_new.xlsx"); | ||
| fs.writeFileSync(outputFile, buffer); | ||
| console.log(`多语言 xlsx 生成成功:${chalk.blue(outputFile)}`); | ||
| }; | ||
| async function createLang(options) { | ||
| const { xlsx, noKey, file } = options; | ||
| try { | ||
| const cwd = options.cwd || process.cwd(); | ||
| let filePath = file; | ||
| const json = path.join(cwd, "./package.json"); | ||
| const jsonData = jsonFile.readFileSync(json); | ||
| if (!filePath) { | ||
| filePath = path.join(cwd, jsonData.lang.path); | ||
| } | ||
| const outPath = Path.join(cwd, jsonData.lang.outPath || "lang"); | ||
| const outXlsxPath = Path.join(cwd, jsonData.lang.outXlsxPath || "lang"); | ||
| const suffix = jsonData.lang.suffix || "js"; | ||
| if (xlsx) { | ||
| await jsonToXlsx({ | ||
| filePath: filePath, | ||
| outPath: outXlsxPath, | ||
| suffix, | ||
| }); | ||
| } else { | ||
| await xlsxToJson({ | ||
| filePath: filePath, | ||
| isMap: !noKey, | ||
| outPath: outPath, | ||
| suffix, | ||
| }); | ||
| } | ||
| process.exit(0); | ||
| } catch (e) { | ||
| error(e) | ||
| process.exit(1) | ||
| error(e); | ||
| process.exit(1); | ||
| } | ||
@@ -79,6 +201,6 @@ } | ||
| module.exports = (...args) => { | ||
| return createLang(...args).catch(err => { | ||
| error(err) | ||
| process.exit(1) | ||
| }) | ||
| } | ||
| return createLang(...args).catch((err) => { | ||
| error(err); | ||
| process.exit(1); | ||
| }); | ||
| }; |
+4
-2
| { | ||
| "name": "mtong-tools", | ||
| "version": "2.0.4", | ||
| "version": "3.0.6", | ||
| "description": "mt cli", | ||
@@ -26,2 +26,3 @@ "bin": { | ||
| "jsonfile": "^6.1.0", | ||
| "loadsh": "^0.0.4", | ||
| "node-xlsx": "^0.17.1", | ||
@@ -34,3 +35,4 @@ "semver": "^7.3.5", | ||
| "outPath": "test/lang", | ||
| "suffix": "ts" | ||
| "outXlsxPath": "test/lang", | ||
| "suffix": "js" | ||
| }, | ||
@@ -37,0 +39,0 @@ "framework": { |
+17
-2
@@ -14,7 +14,14 @@ # MOONTON 常用工具 | ||
| ```shell | ||
| # 编译多语言 | ||
| # 编译多语言 配置在 package.json 中的lang 具体参考底部 | ||
| mt lang | ||
| # 忽略多级对象配置输出 | ||
| mt lang -n | ||
| # 反向多语言配置 转 xlsx | ||
| mt lang -x | ||
| ``` | ||
| 项目根目录下执行,同步公用代码到业务项目,同步公用依赖到业务依赖 | ||
| ```shell | ||
@@ -24,5 +31,7 @@ # 同步指定公用代码 | ||
| ``` | ||
| 业务项目中公用代码指定方式 | ||
| package.json | ||
| ```text | ||
@@ -33,4 +42,10 @@ ... | ||
| "branch": "master" | ||
| }, | ||
| "lang": { | ||
| "path": "test/lang.xlsx", // 多语言文件路径 | ||
| "outPath": "test/lang", // 输出路径 | ||
| "outXlsxPath": "test/lang", // 输出xlsx路径 | ||
| "suffix": "ts" // 输出文件后缀 | ||
| } | ||
| ... | ||
| ``` | ||
| ``` |
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
20245
27.24%530
27.4%49
48.48%10
11.11%3
50%+ Added
+ Added