@agentscope-ai/i18n 0.1.9 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cli.js +59 -1
- package/lib/core/file-processor.js +11 -0
- package/lib/core/translator.js +181 -1
- package/lib/index.js +12 -1
- package/lib/utils/file-utils.js +21 -0
- package/lib/utils/mds-payload.js +86 -0
- package/package.json +1 -1
package/lib/cli.js
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const { program } = require('commander');
|
|
4
|
-
const {
|
|
4
|
+
const {
|
|
5
|
+
init,
|
|
6
|
+
initMT,
|
|
7
|
+
patch,
|
|
8
|
+
patchMT,
|
|
9
|
+
check,
|
|
10
|
+
reverse,
|
|
11
|
+
translate,
|
|
12
|
+
replaceOnly,
|
|
13
|
+
medusaRun,
|
|
14
|
+
} = require('./index');
|
|
5
15
|
const path = require('path');
|
|
6
16
|
const fs = require('fs-extra');
|
|
7
17
|
|
|
@@ -191,6 +201,54 @@ withCommonOptions(
|
|
|
191
201
|
}
|
|
192
202
|
});
|
|
193
203
|
|
|
204
|
+
withCommonOptions(
|
|
205
|
+
program
|
|
206
|
+
.command('translate')
|
|
207
|
+
.description(
|
|
208
|
+
'Extract Chinese text, translate, and generate locale files + mds-payload.json (Step 1/3, no code modification)',
|
|
209
|
+
)
|
|
210
|
+
.option(
|
|
211
|
+
'-f, --file <path>',
|
|
212
|
+
'Process a single file instead of the entire targetPath directory',
|
|
213
|
+
),
|
|
214
|
+
).action(async (options) => {
|
|
215
|
+
try {
|
|
216
|
+
const config = getConfig(options);
|
|
217
|
+
const targetPath = resolveCommonOptions(options, config);
|
|
218
|
+
if (options.file) {
|
|
219
|
+
config.file = options.file;
|
|
220
|
+
}
|
|
221
|
+
await translate(targetPath, config);
|
|
222
|
+
} catch (error) {
|
|
223
|
+
console.error('translate 失败:', error.message);
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
withCommonOptions(
|
|
229
|
+
program
|
|
230
|
+
.command('replace')
|
|
231
|
+
.description(
|
|
232
|
+
'Replace Chinese text in code with $i18n.get calls using saved translation state (Step 3/3)',
|
|
233
|
+
)
|
|
234
|
+
.option(
|
|
235
|
+
'-f, --file <path>',
|
|
236
|
+
'Process a single file instead of the entire targetPath directory',
|
|
237
|
+
),
|
|
238
|
+
).action(async (options) => {
|
|
239
|
+
try {
|
|
240
|
+
const config = getConfig(options);
|
|
241
|
+
const targetPath = resolveCommonOptions(options, config);
|
|
242
|
+
if (options.file) {
|
|
243
|
+
config.file = options.file;
|
|
244
|
+
}
|
|
245
|
+
await replaceOnly(targetPath, config);
|
|
246
|
+
} catch (error) {
|
|
247
|
+
console.error('replace 失败:', error.message);
|
|
248
|
+
process.exit(1);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
194
252
|
program
|
|
195
253
|
.command('init-config')
|
|
196
254
|
.description('Create i18n.config.js configuration file from template')
|
|
@@ -14,6 +14,7 @@ const {
|
|
|
14
14
|
const { extractStringValue } = require('../utils/ast-utils');
|
|
15
15
|
const { askQuestion } = require('../utils/cli-utils');
|
|
16
16
|
const { generateMedusaExcel } = require('../utils/excel-utils');
|
|
17
|
+
const { generateMdsPayload } = require('../utils/mds-payload');
|
|
17
18
|
const parseJSX = require('../parse-jsx');
|
|
18
19
|
|
|
19
20
|
// 提取共用的文案处理函数
|
|
@@ -285,6 +286,16 @@ const singleTranslateAndUpdateFiles = async (
|
|
|
285
286
|
console.log(' [取消] 已取消代码替换操作');
|
|
286
287
|
}
|
|
287
288
|
|
|
289
|
+
// 生成 mds-payload.json(供 sync-to-mds 脚本使用)
|
|
290
|
+
if (medusa && medusa.appName) {
|
|
291
|
+
const newKeys = Object.keys(zhCN);
|
|
292
|
+
generateMdsPayload({
|
|
293
|
+
localesFilePath,
|
|
294
|
+
appName: medusa.appName,
|
|
295
|
+
filterKeys: newKeys,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
288
299
|
// 询问是否生成medusa excel文件
|
|
289
300
|
if (medusa && medusa.appName && medusa.group && medusa.keyMap) {
|
|
290
301
|
let shouldGenExcel = skipConfirm;
|
package/lib/core/translator.js
CHANGED
|
@@ -4,12 +4,15 @@ const find = require('find');
|
|
|
4
4
|
const { parse } = require('@babel/parser');
|
|
5
5
|
const traverse = require('@babel/traverse').default;
|
|
6
6
|
const generate = require('@babel/generator').default;
|
|
7
|
-
const { scanFiles } = require('../utils/file-utils');
|
|
7
|
+
const { scanFiles, resolveFiles, injectImportStatement } = require('../utils/file-utils');
|
|
8
8
|
const {
|
|
9
9
|
processFiles,
|
|
10
10
|
translateAndUpdateFiles,
|
|
11
11
|
singleTranslateAndUpdateFiles,
|
|
12
|
+
replaceKeysInFiles,
|
|
12
13
|
} = require('./file-processor');
|
|
14
|
+
const { generateMdsPayload } = require('../utils/mds-payload');
|
|
15
|
+
const { singleKeyTranslate, singleTranslate } = require('../utils/translation-utils');
|
|
13
16
|
const {
|
|
14
17
|
extractI18nKey,
|
|
15
18
|
chnRegExp,
|
|
@@ -1129,6 +1132,181 @@ const reverse = async function (sourcePath, params) {
|
|
|
1129
1132
|
};
|
|
1130
1133
|
};
|
|
1131
1134
|
|
|
1135
|
+
/**
|
|
1136
|
+
* translate: 提取中文 → AI翻译 → 写locale文件 → 生成mds-payload.json
|
|
1137
|
+
* 不会修改源代码,是三步工作流的第一步。
|
|
1138
|
+
*/
|
|
1139
|
+
const translate = async function (sourcePath, params) {
|
|
1140
|
+
try {
|
|
1141
|
+
const {
|
|
1142
|
+
fileType = '.tsx,.js,.ts,.jsx',
|
|
1143
|
+
localesFilePath,
|
|
1144
|
+
keyPrefix,
|
|
1145
|
+
doNotTranslateFiles,
|
|
1146
|
+
dashScope,
|
|
1147
|
+
medusa,
|
|
1148
|
+
file: singleFile,
|
|
1149
|
+
} = params;
|
|
1150
|
+
|
|
1151
|
+
if (!keyPrefix) {
|
|
1152
|
+
throw new Error('keyPrefix is required in params');
|
|
1153
|
+
}
|
|
1154
|
+
if (!dashScope || !dashScope.apiKey) {
|
|
1155
|
+
throw new Error('dashScope.apiKey is required for translation');
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
console.log('');
|
|
1159
|
+
console.log('=== [Step 1/3] translate ===');
|
|
1160
|
+
console.log(' 提取中文 → AI翻译 → 生成locale文件和mds-payload');
|
|
1161
|
+
console.log('');
|
|
1162
|
+
|
|
1163
|
+
const files = resolveFiles(sourcePath, fileType, doNotTranslateFiles, singleFile);
|
|
1164
|
+
console.log(` # 扫描到 ${files.length} 个文件`);
|
|
1165
|
+
|
|
1166
|
+
const { i18n } = await processFiles(files, params);
|
|
1167
|
+
|
|
1168
|
+
if (Object.keys(i18n).length === 0) {
|
|
1169
|
+
console.log(' [完成] 没有找到需要提取的文案');
|
|
1170
|
+
return { newKeysCount: 0, newKeys: [], keyMap: {} };
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
console.log(` [进度] AI正在翻译key...共${Object.keys(i18n).length}条`);
|
|
1174
|
+
const { keyMap, zhCN } = await singleKeyTranslate(i18n, dashScope);
|
|
1175
|
+
|
|
1176
|
+
const zhCNPath = `${localesFilePath}/zh-cn.json`;
|
|
1177
|
+
let existingZhCN = {};
|
|
1178
|
+
try {
|
|
1179
|
+
existingZhCN = fs.readJsonSync(zhCNPath);
|
|
1180
|
+
} catch {
|
|
1181
|
+
// first run
|
|
1182
|
+
}
|
|
1183
|
+
const finalZhCN = { ...existingZhCN, ...zhCN };
|
|
1184
|
+
fs.ensureDirSync(localesFilePath);
|
|
1185
|
+
fs.writeJsonSync(zhCNPath, finalZhCN, { spaces: 2 });
|
|
1186
|
+
console.log(` [完成] 更新 zh-cn.json (新增${Object.keys(zhCN).length}条)`);
|
|
1187
|
+
|
|
1188
|
+
console.log(` [进度] AI正在翻译多语言...`);
|
|
1189
|
+
const translatedResults = await singleTranslate(zhCN, dashScope);
|
|
1190
|
+
|
|
1191
|
+
for (const [language, translatedI18n] of Object.entries(translatedResults)) {
|
|
1192
|
+
const languagePath = `${localesFilePath}/${language}.json`;
|
|
1193
|
+
let existing = {};
|
|
1194
|
+
try {
|
|
1195
|
+
existing = fs.readJsonSync(languagePath);
|
|
1196
|
+
} catch {
|
|
1197
|
+
// first run
|
|
1198
|
+
}
|
|
1199
|
+
fs.writeJsonSync(languagePath, { ...existing, ...translatedI18n }, { spaces: 2 });
|
|
1200
|
+
console.log(` [完成] 更新 ${language}.json`);
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
const newKeys = Object.keys(zhCN);
|
|
1204
|
+
|
|
1205
|
+
generateMdsPayload({
|
|
1206
|
+
localesFilePath,
|
|
1207
|
+
appName: medusa?.appName,
|
|
1208
|
+
filterKeys: newKeys,
|
|
1209
|
+
});
|
|
1210
|
+
|
|
1211
|
+
const statePath = path.resolve(localesFilePath, '.i18n-state.json');
|
|
1212
|
+
fs.writeJsonSync(
|
|
1213
|
+
statePath,
|
|
1214
|
+
{ keyMap, newKeys, sourcePath, timestamp: new Date().toISOString() },
|
|
1215
|
+
{ spaces: 2 },
|
|
1216
|
+
);
|
|
1217
|
+
console.log(` [完成] 翻译状态已保存到 ${statePath}`);
|
|
1218
|
+
|
|
1219
|
+
if (medusa && medusa.appName && medusa.group && medusa.keyMap) {
|
|
1220
|
+
try {
|
|
1221
|
+
await generateMedusaExcel({ localesFilePath, medusa, newKeys });
|
|
1222
|
+
} catch (error) {
|
|
1223
|
+
console.error(' [警告] 生成medusa excel文件失败:', error.message);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
console.log('');
|
|
1228
|
+
console.log(` ✅ translate 完成!新增 ${newKeys.length} 条翻译文案`);
|
|
1229
|
+
console.log(' 下一步: 运行 sync-to-mds 同步到美杜莎,然后运行 replace 替换代码');
|
|
1230
|
+
console.log('');
|
|
1231
|
+
|
|
1232
|
+
return { newKeysCount: newKeys.length, newKeys, keyMap };
|
|
1233
|
+
} catch (error) {
|
|
1234
|
+
console.error(' [错误] translate 失败:', error);
|
|
1235
|
+
throw error;
|
|
1236
|
+
}
|
|
1237
|
+
};
|
|
1238
|
+
|
|
1239
|
+
/**
|
|
1240
|
+
* replaceOnly: 将代码中的中文替换为 $i18n.get 调用。
|
|
1241
|
+
* 依赖 translate 步骤保存的 .i18n-state.json,是三步工作流的第三步。
|
|
1242
|
+
*/
|
|
1243
|
+
const replaceOnly = async function (sourcePath, params) {
|
|
1244
|
+
try {
|
|
1245
|
+
const {
|
|
1246
|
+
fileType = '.tsx,.js,.ts,.jsx',
|
|
1247
|
+
localesFilePath,
|
|
1248
|
+
keyPrefix,
|
|
1249
|
+
doNotTranslateFiles,
|
|
1250
|
+
functionImportPath,
|
|
1251
|
+
i18nFilePath,
|
|
1252
|
+
file: singleFile,
|
|
1253
|
+
} = params;
|
|
1254
|
+
|
|
1255
|
+
if (!keyPrefix) {
|
|
1256
|
+
throw new Error('keyPrefix is required in params');
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
console.log('');
|
|
1260
|
+
console.log('=== [Step 3/3] replace ===');
|
|
1261
|
+
console.log(' 将代码中的中文替换为 $i18n.get 调用');
|
|
1262
|
+
console.log('');
|
|
1263
|
+
|
|
1264
|
+
const statePath = path.resolve(localesFilePath, '.i18n-state.json');
|
|
1265
|
+
if (!fs.existsSync(statePath)) {
|
|
1266
|
+
throw new Error(`.i18n-state.json 不存在于 ${localesFilePath},请先运行 translate 命令`);
|
|
1267
|
+
}
|
|
1268
|
+
const state = fs.readJsonSync(statePath);
|
|
1269
|
+
const { keyMap } = state;
|
|
1270
|
+
console.log(` [进度] 已加载翻译状态 (${Object.keys(keyMap).length} 条keyMap)`);
|
|
1271
|
+
|
|
1272
|
+
const files = resolveFiles(sourcePath, fileType, doNotTranslateFiles, singleFile);
|
|
1273
|
+
console.log(` # 扫描到 ${files.length} 个文件`);
|
|
1274
|
+
|
|
1275
|
+
const { fileCodeMap, needImportFiles } = await processFiles(files, params);
|
|
1276
|
+
|
|
1277
|
+
console.log(` [进度] 开始替换文件中的key...`);
|
|
1278
|
+
await replaceKeysInFiles(sourcePath, keyMap, fileType, fileCodeMap);
|
|
1279
|
+
|
|
1280
|
+
if (needImportFiles.size > 0 && functionImportPath) {
|
|
1281
|
+
console.log(` [进度] 开始添加导入语句...`);
|
|
1282
|
+
needImportFiles.forEach((file) => {
|
|
1283
|
+
injectImportStatement(file, functionImportPath);
|
|
1284
|
+
});
|
|
1285
|
+
console.log(` [完成] 已为${needImportFiles.size}个文件添加导入语句`);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
if (i18nFilePath) {
|
|
1289
|
+
const sourceI18nPath = path.join(__dirname, '../i18n.ts');
|
|
1290
|
+
const targetI18nPath = path.join(i18nFilePath, 'index.ts');
|
|
1291
|
+
if (!fs.existsSync(targetI18nPath)) {
|
|
1292
|
+
fs.ensureDirSync(i18nFilePath);
|
|
1293
|
+
fs.copyFileSync(sourceI18nPath, targetI18nPath);
|
|
1294
|
+
console.log(` [完成] i18n.ts 已复制到 ${targetI18nPath}`);
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
fs.removeSync(statePath);
|
|
1299
|
+
console.log(` [完成] 已清理临时状态文件`);
|
|
1300
|
+
|
|
1301
|
+
console.log('');
|
|
1302
|
+
console.log(' ✅ replace 完成!代码中的中文已替换为 $i18n.get 调用');
|
|
1303
|
+
console.log('');
|
|
1304
|
+
} catch (error) {
|
|
1305
|
+
console.error(' [错误] replace 失败:', error);
|
|
1306
|
+
throw error;
|
|
1307
|
+
}
|
|
1308
|
+
};
|
|
1309
|
+
|
|
1132
1310
|
module.exports = {
|
|
1133
1311
|
init,
|
|
1134
1312
|
initMT,
|
|
@@ -1136,4 +1314,6 @@ module.exports = {
|
|
|
1136
1314
|
patchMT,
|
|
1137
1315
|
check,
|
|
1138
1316
|
reverse,
|
|
1317
|
+
translate,
|
|
1318
|
+
replaceOnly,
|
|
1139
1319
|
};
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const {
|
|
2
|
+
init,
|
|
3
|
+
initMT,
|
|
4
|
+
patch,
|
|
5
|
+
patchMT,
|
|
6
|
+
check,
|
|
7
|
+
reverse,
|
|
8
|
+
translate,
|
|
9
|
+
replaceOnly,
|
|
10
|
+
} = require('./core/translator');
|
|
2
11
|
const { run: medusaRun } = require('./utils/medusa');
|
|
3
12
|
|
|
4
13
|
module.exports = {
|
|
@@ -8,5 +17,7 @@ module.exports = {
|
|
|
8
17
|
patchMT,
|
|
9
18
|
check,
|
|
10
19
|
reverse,
|
|
20
|
+
translate,
|
|
21
|
+
replaceOnly,
|
|
11
22
|
medusaRun,
|
|
12
23
|
};
|
package/lib/utils/file-utils.js
CHANGED
|
@@ -271,11 +271,32 @@ const scanFiles = (path, fileType, doNotTranslateFiles = []) => {
|
|
|
271
271
|
}
|
|
272
272
|
};
|
|
273
273
|
|
|
274
|
+
const resolveFiles = (sourcePath, fileType, doNotTranslateFiles, singleFile) => {
|
|
275
|
+
if (singleFile) {
|
|
276
|
+
const nodePath = require('path');
|
|
277
|
+
const absFile = nodePath.isAbsolute(singleFile)
|
|
278
|
+
? singleFile
|
|
279
|
+
: nodePath.resolve(process.cwd(), singleFile);
|
|
280
|
+
|
|
281
|
+
if (!fs.existsSync(absFile)) {
|
|
282
|
+
throw new Error(`文件不存在: ${absFile}`);
|
|
283
|
+
}
|
|
284
|
+
if (shouldIgnoreByComment(absFile)) {
|
|
285
|
+
console.log(` # 文件 ${absFile} 包含 @i18n-ignore,已跳过`);
|
|
286
|
+
return [];
|
|
287
|
+
}
|
|
288
|
+
console.log(` # 单文件模式: ${absFile}`);
|
|
289
|
+
return [absFile];
|
|
290
|
+
}
|
|
291
|
+
return scanFiles(sourcePath, fileType, doNotTranslateFiles);
|
|
292
|
+
};
|
|
293
|
+
|
|
274
294
|
module.exports = {
|
|
275
295
|
shouldIgnoreFile,
|
|
276
296
|
shouldIgnoreByComment,
|
|
277
297
|
injectImportStatement,
|
|
278
298
|
scanFiles,
|
|
299
|
+
resolveFiles,
|
|
279
300
|
shouldIgnoreLineByComment,
|
|
280
301
|
shouldIgnoreBlockByComment,
|
|
281
302
|
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const LANG_CODE_TO_MCMS = {
|
|
5
|
+
'en-us': 'en_US',
|
|
6
|
+
'zh-cn': 'zh_CN',
|
|
7
|
+
'ja-jp': 'ja_JP',
|
|
8
|
+
'ko-kr': 'ko_KR',
|
|
9
|
+
'zh-tw': 'zh_TW',
|
|
10
|
+
'fr-fr': 'fr_FR',
|
|
11
|
+
'de-de': 'de_DE',
|
|
12
|
+
'es-es': 'es_ES',
|
|
13
|
+
'pt-br': 'pt_BR',
|
|
14
|
+
'ru-ru': 'ru_RU',
|
|
15
|
+
'ar-sa': 'ar_SA',
|
|
16
|
+
'th-th': 'th_TH',
|
|
17
|
+
'vi-vn': 'vi_VN',
|
|
18
|
+
'id-id': 'id_ID',
|
|
19
|
+
'ms-my': 'ms_MY',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 从 locale JSON 文件生成 MDS payload(McmsWriteDTO 数组)。
|
|
24
|
+
* 只包含 filterKeys 中的 key(即本次新增/变更的 key)。
|
|
25
|
+
*
|
|
26
|
+
* @param {object} options
|
|
27
|
+
* @param {string} options.localesFilePath - locale 文件目录
|
|
28
|
+
* @param {string} options.appName - 美杜莎应用名
|
|
29
|
+
* @param {string[]} options.filterKeys - 要包含的 key 列表
|
|
30
|
+
* @param {object} [options.langCodeMap] - 自定义语言 code 映射 { 'en-us': 'en_US' }
|
|
31
|
+
* @returns {object[]} McmsWriteDTO 数组
|
|
32
|
+
*/
|
|
33
|
+
const generateMdsPayload = ({ localesFilePath, appName, filterKeys, langCodeMap }) => {
|
|
34
|
+
if (!appName) {
|
|
35
|
+
console.log(' [跳过] 未配置 medusa.appName,跳过 mds-payload 生成');
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
if (!filterKeys || filterKeys.length === 0) {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const codeMap = { ...LANG_CODE_TO_MCMS, ...langCodeMap };
|
|
43
|
+
|
|
44
|
+
const localeDir = path.resolve(localesFilePath);
|
|
45
|
+
if (!fs.existsSync(localeDir)) {
|
|
46
|
+
console.log(` [跳过] locale 目录不存在: ${localeDir}`);
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const localeFiles = fs
|
|
51
|
+
.readdirSync(localeDir)
|
|
52
|
+
.filter((f) => f.endsWith('.json') && f !== 'key.json');
|
|
53
|
+
const locales = {};
|
|
54
|
+
for (const file of localeFiles) {
|
|
55
|
+
const langCode = file.replace('.json', '');
|
|
56
|
+
const mcmsLang = codeMap[langCode] || langCode;
|
|
57
|
+
try {
|
|
58
|
+
locales[mcmsLang] = fs.readJsonSync(path.join(localeDir, file));
|
|
59
|
+
} catch {
|
|
60
|
+
// skip unreadable files
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const keysSet = new Set(filterKeys);
|
|
65
|
+
const payload = [];
|
|
66
|
+
|
|
67
|
+
for (const key of keysSet) {
|
|
68
|
+
const i18n = {};
|
|
69
|
+
for (const [mcmsLang, data] of Object.entries(locales)) {
|
|
70
|
+
if (data[key] !== undefined) {
|
|
71
|
+
i18n[mcmsLang] = data[key];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (Object.keys(i18n).length > 0) {
|
|
75
|
+
payload.push({ key, appName, i18n });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const outputPath = path.resolve(process.cwd(), 'mds-payload.json');
|
|
80
|
+
fs.writeJsonSync(outputPath, payload, { spaces: 2 });
|
|
81
|
+
console.log(` [完成] 已生成 mds-payload.json (${payload.length} 条), 路径: ${outputPath}`);
|
|
82
|
+
|
|
83
|
+
return payload;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
module.exports = { generateMdsPayload };
|