@zzclub/z-cli 0.7.1 → 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/LICENSE +20 -20
- package/dist/commands/config.d.ts +9 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +33 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/set.d.ts +11 -0
- package/dist/commands/set.d.ts.map +1 -0
- package/dist/commands/set.js +43 -0
- package/dist/commands/set.js.map +1 -0
- package/dist/commands/tiny/compressor.d.ts +11 -0
- package/dist/commands/tiny/compressor.d.ts.map +1 -0
- package/dist/commands/tiny/compressor.js +46 -0
- package/dist/commands/tiny/compressor.js.map +1 -0
- package/dist/commands/tiny/file-processor.d.ts +40 -0
- package/dist/commands/tiny/file-processor.d.ts.map +1 -0
- package/dist/commands/tiny/file-processor.js +137 -0
- package/dist/commands/tiny/file-processor.js.map +1 -0
- package/dist/commands/tiny/index.d.ts +15 -0
- package/dist/commands/tiny/index.d.ts.map +1 -0
- package/dist/commands/tiny/index.js +55 -0
- package/dist/commands/tiny/index.js.map +1 -0
- package/dist/commands/tiny/types.d.ts +55 -0
- package/dist/commands/tiny/types.d.ts.map +1 -0
- package/dist/commands/tiny/types.js +5 -0
- package/dist/commands/tiny/types.js.map +1 -0
- package/dist/core/config-manager.d.ts +38 -0
- package/dist/core/config-manager.d.ts.map +1 -0
- package/dist/core/config-manager.js +87 -0
- package/dist/core/config-manager.js.map +1 -0
- package/dist/core/logger.d.ts +17 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +30 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +177 -0
- package/dist/index.js.map +1 -0
- package/dist/types/config.d.ts +32 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +18 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +34 -19
- package/readme.md +457 -0
- package/CHANGELOG.md +0 -211
- package/README.md +0 -329
- package/src/command/config.js +0 -97
- package/src/command/i18n.js +0 -318
- package/src/command/index.js +0 -30
- package/src/command/picgo.js +0 -122
- package/src/command/replace.js +0 -81
- package/src/command/set.js +0 -47
- package/src/command/tiny.js +0 -328
- package/src/command/translate.js +0 -337
- package/src/config.json +0 -16
- package/src/index.js +0 -31
- package/src/translate-api/index.js +0 -77
- package/src/translate-api/md5.js +0 -231
- package/src/utils/common.js +0 -218
- package/src/utils/file.js +0 -61
- package/src/utils/picgo.js +0 -149
package/src/command/tiny.js
DELETED
|
@@ -1,328 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
writeFileContent,
|
|
3
|
-
getLocalConfig,
|
|
4
|
-
setHighLightStr,
|
|
5
|
-
} from "../utils/common.js";
|
|
6
|
-
|
|
7
|
-
import { getFormatedFileSize, replaceFileContent } from "../utils/file.js";
|
|
8
|
-
import chalk from "chalk";
|
|
9
|
-
import ora from "ora";
|
|
10
|
-
import path from "node:path";
|
|
11
|
-
import { dirname } from "node:path";
|
|
12
|
-
import { fileURLToPath } from "node:url";
|
|
13
|
-
import fs from "node:fs";
|
|
14
|
-
import sharp from "sharp";
|
|
15
|
-
import { picgoCmd } from "./picgo.js";
|
|
16
|
-
|
|
17
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
18
|
-
const __dirname = dirname(__filename);
|
|
19
|
-
|
|
20
|
-
const supportFileTypes = ["png", "jpg", "jpeg", "gif", "webp"];
|
|
21
|
-
|
|
22
|
-
export const tinyCmd = {
|
|
23
|
-
name: "tiny",
|
|
24
|
-
description: "压缩图片",
|
|
25
|
-
options: [
|
|
26
|
-
{
|
|
27
|
-
flags: "-t, --type <fileType>",
|
|
28
|
-
description: "转换后的图片类型",
|
|
29
|
-
defaultValue: null,
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
flags: "-f, --file <file>",
|
|
33
|
-
description: "要压缩的图片文件",
|
|
34
|
-
defaultValue: null,
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
flags: "-d, --dir <dir>",
|
|
38
|
-
description: "压缩文件夹内所有文件",
|
|
39
|
-
defaultValue: null,
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
flags: "-co, --condition <condition>",
|
|
43
|
-
description: "压缩文件夹内所有名称包含[--condition]的图片文件",
|
|
44
|
-
defaultValue: null,
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
flags: "-q, --quality <quality>",
|
|
48
|
-
description: "压缩质量(1-100)",
|
|
49
|
-
defaultValue: 75,
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
flags: "-c, --colours <colours>",
|
|
53
|
-
description: "GIF色彩保留(2-256)",
|
|
54
|
-
defaultValue: 128,
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
flags: "-n, --name <name>",
|
|
58
|
-
description: "指定文件名输出",
|
|
59
|
-
defaultValue: "",
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
flags: "-m, --max <max>",
|
|
63
|
-
description: "限制要上传的文件大小(仅当开启 --picgo 时会用到)",
|
|
64
|
-
defaultValue: 60,
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
flags: "--picgo [type]",
|
|
68
|
-
description: "是否调用picgo",
|
|
69
|
-
defaultValue: null,
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
flags: "--no-picgo [type]",
|
|
73
|
-
description: "是否调用picgo",
|
|
74
|
-
defaultValue: null,
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
flags: "-ref, --replace-file <replaceFile>",
|
|
78
|
-
description: "要替换内容的文件",
|
|
79
|
-
defaultValue: "",
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
flags: "--no-replace [type]",
|
|
83
|
-
description:
|
|
84
|
-
"是否替换指定文件的内容, 替换规则斤仅针对Obsidian, 请看readme.md",
|
|
85
|
-
defaultValue: null,
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
flags: "--replace [type]",
|
|
89
|
-
description:
|
|
90
|
-
"是否替换指定文件的内容, 替换规则斤仅针对Obsidian, 请看readme.md",
|
|
91
|
-
defaultValue: null,
|
|
92
|
-
},
|
|
93
|
-
],
|
|
94
|
-
action: async (option) => {
|
|
95
|
-
let spinner = ora();
|
|
96
|
-
let config = await getLocalConfig();
|
|
97
|
-
// console.log(`OPTION`, option);
|
|
98
|
-
let { type, file, dir, condition, quality, colours } = option;
|
|
99
|
-
|
|
100
|
-
if (!file && !dir && !condition) {
|
|
101
|
-
spinner.fail("请指定要压缩的文件(--file=xxx)或文件夹(--dir=/a/b)");
|
|
102
|
-
process.exit(1);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// 是否需要替换内容, true时 需要收集list<Map>
|
|
106
|
-
let isNeedUploadAndReplace = !!(
|
|
107
|
-
// option.picgo &&
|
|
108
|
-
(option.replace && option.replaceFile)
|
|
109
|
-
);
|
|
110
|
-
let replaceMaps = [];
|
|
111
|
-
|
|
112
|
-
function collectReplaceMaps(replaceMaps, { text, newText }) {
|
|
113
|
-
if (isNeedUploadAndReplace) {
|
|
114
|
-
let map = {
|
|
115
|
-
text, // obsidian md 文件中 粘贴后的 值
|
|
116
|
-
newText, // 此时是压缩后的文件名, 在picgo里再替换成上传后的
|
|
117
|
-
};
|
|
118
|
-
replaceMaps.push(map);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
async function tinyFile(file, statisticsCount, options = {}) {
|
|
122
|
-
try {
|
|
123
|
-
let stats = fs.statSync(file);
|
|
124
|
-
let isFile = stats.isFile();
|
|
125
|
-
if (isFile) {
|
|
126
|
-
// 文件类型
|
|
127
|
-
let extname = path.extname(file);
|
|
128
|
-
// 带后缀的文件名
|
|
129
|
-
let fileName = path.basename(file)?.split(".")[0];
|
|
130
|
-
// 文件路径
|
|
131
|
-
let dirPath = path.dirname(file);
|
|
132
|
-
// 文件类型
|
|
133
|
-
let filetype = extname.slice(1);
|
|
134
|
-
let fileSize = stats.size;
|
|
135
|
-
let beforeSize = getFormatedFileSize(fileSize);
|
|
136
|
-
// 是否在支持的格式里
|
|
137
|
-
// 如果正在批量压缩, 并且要求指定名称输出, 则加一个index后缀, 避免重名
|
|
138
|
-
let customFileName;
|
|
139
|
-
if (options.count && options.count > 1 && option.name) {
|
|
140
|
-
customFileName = `${option.name}${options.order + 1}`;
|
|
141
|
-
} else {
|
|
142
|
-
customFileName = option.name || `${fileName}-tiny`;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
let outputPath =
|
|
146
|
-
path.resolve(process.cwd(), dirPath) +
|
|
147
|
-
"/" +
|
|
148
|
-
customFileName +
|
|
149
|
-
extname;
|
|
150
|
-
let inputPath = path.resolve(process.cwd(), file);
|
|
151
|
-
if (extname.slice(1) === "gif") {
|
|
152
|
-
// console.log(`option.colours`, option.colours);
|
|
153
|
-
await sharp(inputPath, {
|
|
154
|
-
animated: true,
|
|
155
|
-
limitInputPixels: false,
|
|
156
|
-
})
|
|
157
|
-
.gif({
|
|
158
|
-
colours: +option.colours,
|
|
159
|
-
})
|
|
160
|
-
.toFile(outputPath);
|
|
161
|
-
let afterStats = fs.statSync(outputPath);
|
|
162
|
-
let afterSize = getFormatedFileSize(afterStats.size);
|
|
163
|
-
let offPercent = ((1 - afterStats.size / fileSize) * 100).toFixed(
|
|
164
|
-
2
|
|
165
|
-
);
|
|
166
|
-
spinner.succeed(
|
|
167
|
-
`${chalk.red(beforeSize)} ${chalk.yellowBright(
|
|
168
|
-
"=>"
|
|
169
|
-
)} ${chalk.green(afterSize)} (${
|
|
170
|
-
offPercent >= 0 ? chalk.green("↓") : chalk.red("↑")
|
|
171
|
-
}${Math.abs(offPercent)}%)【 ${customFileName}${extname} 】`
|
|
172
|
-
);
|
|
173
|
-
// 收集压缩前后的文件名映射关系
|
|
174
|
-
collectReplaceMaps(replaceMaps, {
|
|
175
|
-
text: `${path.basename(file)}`,
|
|
176
|
-
newText: `${customFileName}${extname}`,
|
|
177
|
-
});
|
|
178
|
-
statisticsCount.ok++;
|
|
179
|
-
statisticsCount.okFiles.push(outputPath);
|
|
180
|
-
} else {
|
|
181
|
-
if (sharp(inputPath)[filetype]) {
|
|
182
|
-
await sharp(path.resolve(process.cwd(), file))
|
|
183
|
-
[filetype]({ quality: +quality })
|
|
184
|
-
.toFile(outputPath);
|
|
185
|
-
let afterStats = fs.statSync(outputPath);
|
|
186
|
-
let afterSize = getFormatedFileSize(afterStats.size);
|
|
187
|
-
let offPercent = ((1 - afterStats.size / fileSize) * 100).toFixed(
|
|
188
|
-
2
|
|
189
|
-
);
|
|
190
|
-
spinner.succeed(
|
|
191
|
-
`${chalk.red(beforeSize)} ${chalk.yellowBright(
|
|
192
|
-
"=>"
|
|
193
|
-
)} ${chalk.green(afterSize)} (${
|
|
194
|
-
offPercent >= 0 ? chalk.green("↓") : chalk.red("↑")
|
|
195
|
-
}${Math.abs(offPercent)}%)【 ${customFileName}${extname} 】`
|
|
196
|
-
);
|
|
197
|
-
// 收集压缩前后的文件名映射关系
|
|
198
|
-
collectReplaceMaps(replaceMaps, {
|
|
199
|
-
text: `${path.basename(file)}`,
|
|
200
|
-
newText: `${customFileName}${extname}`,
|
|
201
|
-
});
|
|
202
|
-
statisticsCount.ok++;
|
|
203
|
-
statisticsCount.okFiles.push(outputPath);
|
|
204
|
-
} else {
|
|
205
|
-
spinner.fail(
|
|
206
|
-
`不支持此文件类型[${filetype || fileName || file}]!`
|
|
207
|
-
);
|
|
208
|
-
statisticsCount.error++;
|
|
209
|
-
statisticsCount.okFiles.push(outputPath);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
} catch (err) {
|
|
214
|
-
// console.log(`err`, err);
|
|
215
|
-
spinner.fail("出错啦, 文件不存在或不支持此类型 \n" + err);
|
|
216
|
-
statisticsCount.error++;
|
|
217
|
-
statisticsCount.errFiles.push(outputPath);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
let statisticsCount = {
|
|
222
|
-
ok: 0,
|
|
223
|
-
okFiles: [],
|
|
224
|
-
err: 0,
|
|
225
|
-
errFiles: [],
|
|
226
|
-
};
|
|
227
|
-
// 指定了文件, 压缩
|
|
228
|
-
if (file) {
|
|
229
|
-
await tinyFile(file, statisticsCount);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// 指定文件夹, 翻译文件夹内所有文件
|
|
233
|
-
if (dir || condition) {
|
|
234
|
-
let inputDirPath = dir || "./";
|
|
235
|
-
let files;
|
|
236
|
-
try {
|
|
237
|
-
// 读取所有文件
|
|
238
|
-
files = fs.readdirSync(inputDirPath);
|
|
239
|
-
// spinner.succeed(`共有${files.length}个文件`);
|
|
240
|
-
} catch (err) {
|
|
241
|
-
spinner.fail("出错啦, 文件夹似乎不存在");
|
|
242
|
-
process.exit(1);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
for (let i = 0; i < files.length; i++) {
|
|
246
|
-
let file = files[i];
|
|
247
|
-
let filePath = path.join(inputDirPath, file);
|
|
248
|
-
let stats = fs.statSync(filePath);
|
|
249
|
-
if (stats.isFile()) {
|
|
250
|
-
if (condition) {
|
|
251
|
-
let fileName = path.basename(file);
|
|
252
|
-
let index = fileName.indexOf(condition);
|
|
253
|
-
if (index > -1) {
|
|
254
|
-
let tip = setHighLightStr(fileName, condition);
|
|
255
|
-
spinner.succeed(`${tip}正在压缩`);
|
|
256
|
-
await tinyFile(filePath, statisticsCount, {
|
|
257
|
-
count: files.length,
|
|
258
|
-
order: i,
|
|
259
|
-
});
|
|
260
|
-
}
|
|
261
|
-
} else {
|
|
262
|
-
spinner.succeed(`正在压缩:${file}`);
|
|
263
|
-
await tinyFile(filePath, statisticsCount, {
|
|
264
|
-
count: files.length,
|
|
265
|
-
order: i,
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
} else {
|
|
269
|
-
spinner.fail(`${filePath}不是文件`);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
spinner.succeed(`压缩成功 ${chalk.green(statisticsCount.ok)} 个`);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// 自动替换wiki链接
|
|
276
|
-
// 因为obsidian里已经有了一个插件, 可以自动上传到picgo并替换链接, 功能重复度很高
|
|
277
|
-
// 后续考虑给作者提个pr, 加入压缩功能
|
|
278
|
-
// console.log(`fileMaps`, replaceMaps);
|
|
279
|
-
if (option.replace) {
|
|
280
|
-
// 上传成功改的图片文件
|
|
281
|
-
let tinyFiles = statisticsCount.okFiles;
|
|
282
|
-
if (tinyFiles && tinyFiles.length) {
|
|
283
|
-
// await batchUploadByPicGo(tinyFiles, option);
|
|
284
|
-
if (!option.replace) process.exit(1);
|
|
285
|
-
spinner.start("开始替换文件内容, 替换后请仔细检查!");
|
|
286
|
-
console.log(`replaceMaps`, replaceMaps);
|
|
287
|
-
const fileContent = await replaceFileContent(
|
|
288
|
-
option.replaceFile,
|
|
289
|
-
replaceMaps
|
|
290
|
-
);
|
|
291
|
-
console.log(`fileContent`, fileContent);
|
|
292
|
-
if (!fileContent) {
|
|
293
|
-
spinner.fail("替换文件内容失败! 请检查要替换的文件是否存在!");
|
|
294
|
-
process.exit(1);
|
|
295
|
-
}
|
|
296
|
-
try {
|
|
297
|
-
fs.writeFileSync(option.replaceFile, fileContent, "utf-8");
|
|
298
|
-
spinner.succeed(
|
|
299
|
-
`[${chalk.red(
|
|
300
|
-
path.basename(option.replaceFile)
|
|
301
|
-
)}]图片链接替换完成!请前往检查!`
|
|
302
|
-
);
|
|
303
|
-
} catch (err) {
|
|
304
|
-
spinner.fail("图片链接替换失败!");
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
// 处理完后不再上传picgo
|
|
308
|
-
process.exit(1);
|
|
309
|
-
}
|
|
310
|
-
// 自动上传至picgo
|
|
311
|
-
if (option.picgo) {
|
|
312
|
-
spinner.start(`正在连接picgo`);
|
|
313
|
-
await picgoCmd.action({
|
|
314
|
-
tinyFiles: statisticsCount.okFiles,
|
|
315
|
-
max: option.max,
|
|
316
|
-
replace: isNeedUploadAndReplace,
|
|
317
|
-
replaceFile: option.replaceFile,
|
|
318
|
-
replaceMaps,
|
|
319
|
-
});
|
|
320
|
-
spinner.stop();
|
|
321
|
-
|
|
322
|
-
// 只有开启了picgo后时 才会使用替换功能 后续替换功能会独立出去 目前仅为满足自己所需
|
|
323
|
-
// if (option.replace && option.replaceFile) {
|
|
324
|
-
// spinner.start(`开始替换内容, 替换后请仔细检查!`);
|
|
325
|
-
// }
|
|
326
|
-
}
|
|
327
|
-
},
|
|
328
|
-
};
|
package/src/command/translate.js
DELETED
|
@@ -1,337 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import chalk from "chalk";
|
|
4
|
-
import { translate } from "../translate-api/index.js";
|
|
5
|
-
import { readJsonFile } from "../utils/file.js";
|
|
6
|
-
import { writeFileContent, getLocalConfig } from "../utils/common.js";
|
|
7
|
-
import ora from "ora";
|
|
8
|
-
|
|
9
|
-
const translateCmd = {
|
|
10
|
-
name: "translate",
|
|
11
|
-
alias: "trans",
|
|
12
|
-
description: "中译英功能,支持批量和单个文件翻译",
|
|
13
|
-
// options: ['-l, --language <language>', '转换为什么语言, 支持[zh]和[en]', 'en'],
|
|
14
|
-
options: [
|
|
15
|
-
{
|
|
16
|
-
flags: "-l, --language <language>",
|
|
17
|
-
description: "目前只支持从中文到英文",
|
|
18
|
-
defaultValue: "en",
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
flags: "-f, --file <file>",
|
|
22
|
-
description: "转换文件的路径",
|
|
23
|
-
defaultValue: null,
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
flags: "-d, --dir <dirpath>",
|
|
27
|
-
description: "转换文件夹的路径",
|
|
28
|
-
defaultValue: null,
|
|
29
|
-
},
|
|
30
|
-
],
|
|
31
|
-
action: async (option) => {
|
|
32
|
-
let filePath = option.file;
|
|
33
|
-
let dirPath = option.dir;
|
|
34
|
-
let file_spinner = ora();
|
|
35
|
-
if (!filePath && !dirPath) {
|
|
36
|
-
file_spinner.fail('请指定文件或目录')
|
|
37
|
-
process.exit(1);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
let config = await getLocalConfig();
|
|
41
|
-
const translateConfig = config.translate;
|
|
42
|
-
if (!translateConfig.account.appId || !translateConfig.account.key) {
|
|
43
|
-
file_spinner.fail("请先设置appId和key后再使用翻译功能");
|
|
44
|
-
process.exit(1);
|
|
45
|
-
}
|
|
46
|
-
// 有文件夹路径时忽略文件
|
|
47
|
-
if (dirPath) {
|
|
48
|
-
dirPath = path.resolve(process.cwd(), dirPath);
|
|
49
|
-
let stat;
|
|
50
|
-
try {
|
|
51
|
-
stat = fs.statSync(dirPath);
|
|
52
|
-
} catch (err) {
|
|
53
|
-
file_spinner.fail(`${chalk.red(dirPath)}不存在!`);
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (!stat.isDirectory()) {
|
|
58
|
-
file_spinner.fail(`${chalk.red(dirPath)}不是一个文件夹!`);
|
|
59
|
-
return;
|
|
60
|
-
} else {
|
|
61
|
-
let filePaths = [];
|
|
62
|
-
file_spinner.succeed(`开始检索${chalk.red(dirPath)}`);
|
|
63
|
-
|
|
64
|
-
getAllFilePaths(translateConfig, dirPath, filePaths);
|
|
65
|
-
|
|
66
|
-
// log.success(`共找到${chalk.red(filePaths.length)}个要翻译的文件`);
|
|
67
|
-
if (filePaths.length) {
|
|
68
|
-
file_spinner.succeed(
|
|
69
|
-
`共找到${chalk.red(filePaths.length)}个要翻译的文件`
|
|
70
|
-
);
|
|
71
|
-
file_spinner.start();
|
|
72
|
-
await execWorkerSync(filePaths, 0);
|
|
73
|
-
file_spinner.stop();
|
|
74
|
-
} else {
|
|
75
|
-
// log.success(`Exit`);
|
|
76
|
-
file_spinner.warn(
|
|
77
|
-
`共找到${chalk.red(filePaths.length)}个要翻译的文件`
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
} else {
|
|
82
|
-
file_spinner.succeed(`正在翻译${chalk.yellowBright(filePath)}`);
|
|
83
|
-
file_spinner.start();
|
|
84
|
-
let file_content = await readAndTranslateFileContent(filePath);
|
|
85
|
-
let fileName = path.basename(filePath);
|
|
86
|
-
let dirPath = path.dirname(filePath);
|
|
87
|
-
let newFileName =
|
|
88
|
-
fileName.split(".")[0] +
|
|
89
|
-
`-${option.language}.` +
|
|
90
|
-
fileName.split(".")[1];
|
|
91
|
-
let newFilePath = dirPath + "/" + newFileName;
|
|
92
|
-
writeFileContent(newFilePath, file_content, (spinner, isOk) => {
|
|
93
|
-
if (isOk) {
|
|
94
|
-
spinner.succeed("翻译结束");
|
|
95
|
-
} else {
|
|
96
|
-
spinner.fail("翻译失败!");
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
file_spinner.stop();
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
},
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* 递归处理i18n配置对象
|
|
107
|
-
* @param config i18n配置js 一般为langs文件下的js文件
|
|
108
|
-
* @description 把js对象处理成 [ { keys: ['common', 'title'], value: '要翻译的值'} ] 每个要翻译的中文为一个item keys表示他在对象里的位置
|
|
109
|
-
*/
|
|
110
|
-
function parseConfigs(config) {
|
|
111
|
-
let words = [];
|
|
112
|
-
|
|
113
|
-
parseConfig(config, null);
|
|
114
|
-
function parseConfig(config, curItem) {
|
|
115
|
-
let keys = Object.keys(config);
|
|
116
|
-
keys.forEach((key) => {
|
|
117
|
-
let item = {
|
|
118
|
-
keys: curItem ? curItem.keys.concat([key]) : [key],
|
|
119
|
-
value: config[key],
|
|
120
|
-
};
|
|
121
|
-
// 对象的value为string时则为要翻译的值
|
|
122
|
-
if (typeof item.value === "string") {
|
|
123
|
-
words.push(item);
|
|
124
|
-
} else {
|
|
125
|
-
parseConfig(item.value, item);
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
return words;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* 把所有要翻译的词分组 每秒有查询次数限制
|
|
134
|
-
* @param words 处理好的数据
|
|
135
|
-
* @param limitLength 每秒查几个词
|
|
136
|
-
* @returns {*[]} 处理后的二维数组
|
|
137
|
-
*/
|
|
138
|
-
function limitWords(words, limitLength = 7) {
|
|
139
|
-
let wordsLimit = [];
|
|
140
|
-
if (words.length < limitLength) {
|
|
141
|
-
return [words];
|
|
142
|
-
} else {
|
|
143
|
-
for (let i = 0; i < words.length; i += limitLength) {
|
|
144
|
-
wordsLimit.push(words.slice(i, i + limitLength));
|
|
145
|
-
}
|
|
146
|
-
return wordsLimit;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* 调用翻译功能
|
|
152
|
-
* @param limitedWords 分组后的word数据
|
|
153
|
-
* @param cb 全部翻译结束后的回调函数
|
|
154
|
-
*/
|
|
155
|
-
function startTranslate(limitedWords, cb) {
|
|
156
|
-
let curIndex = 0;
|
|
157
|
-
let timer = null;
|
|
158
|
-
timer = setInterval(() => {
|
|
159
|
-
if (curIndex >= limitedWords.length) {
|
|
160
|
-
clearInterval(timer);
|
|
161
|
-
cb && cb();
|
|
162
|
-
} else {
|
|
163
|
-
limitedWords[curIndex].forEach(async (word) => {
|
|
164
|
-
let res = await translate({
|
|
165
|
-
query: word.value,
|
|
166
|
-
from: "zh",
|
|
167
|
-
to: "en",
|
|
168
|
-
}).catch((err) => {
|
|
169
|
-
console.log(err);
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
let translate_result = res?.trans_result
|
|
173
|
-
? res?.trans_result[0].dst
|
|
174
|
-
: word.value;
|
|
175
|
-
if (res?.error_code || !res) {
|
|
176
|
-
const spinner = ora();
|
|
177
|
-
spinner.warn(`翻译[${word.value}]时出错:` + JSON.stringify(res))
|
|
178
|
-
}
|
|
179
|
-
word.value = translate_result;
|
|
180
|
-
});
|
|
181
|
-
curIndex++;
|
|
182
|
-
}
|
|
183
|
-
}, 1000);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* 组装翻译后的数据结构
|
|
188
|
-
* @param words
|
|
189
|
-
* @param obj
|
|
190
|
-
*/
|
|
191
|
-
function setTranslatedObj(words, obj) {
|
|
192
|
-
words.forEach((item) => {
|
|
193
|
-
item.keys.forEach((key, index) => {
|
|
194
|
-
if (index === 0 && item.keys.length > 1) {
|
|
195
|
-
if (!obj[key]) obj[key] = {};
|
|
196
|
-
} else if (index < item.keys.length - 1) {
|
|
197
|
-
// a.b.c
|
|
198
|
-
let _key = item.keys.slice(0, index + 1).join(".");
|
|
199
|
-
let flag = false;
|
|
200
|
-
eval(`flag = !!!obj.${_key}`);
|
|
201
|
-
if (flag) eval(`obj.${_key} = {}`);
|
|
202
|
-
} else {
|
|
203
|
-
let _key = item.keys.slice(0, index + 1).join(".");
|
|
204
|
-
eval(`obj.${_key} = "${item.value}"`);
|
|
205
|
-
}
|
|
206
|
-
});
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
function unquoteKeys(json) {
|
|
210
|
-
return json.replace(/"(\\[^]|[^\\"])*"\s*:?/g, function (match) {
|
|
211
|
-
if (/:$/.test(match)) {
|
|
212
|
-
return match.replace(/^"|"(?=\s*:$)/g, "");
|
|
213
|
-
} else {
|
|
214
|
-
return match;
|
|
215
|
-
}
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
/**
|
|
219
|
-
* 读取并翻译文本内容
|
|
220
|
-
* @param filePath 文件地址
|
|
221
|
-
* @param cb 翻译后的回调
|
|
222
|
-
* @return 翻译后的文本
|
|
223
|
-
*/
|
|
224
|
-
function readAndTranslateFileContent(filePath, cb = () => {}) {
|
|
225
|
-
return new Promise((resolve, reject) => {
|
|
226
|
-
fs.readFile(filePath, { encoding: "utf8" }, (err, data) => {
|
|
227
|
-
if (err) {
|
|
228
|
-
// log.error("读取文件失败");
|
|
229
|
-
reject();
|
|
230
|
-
} else {
|
|
231
|
-
let jsonObj;
|
|
232
|
-
let fileData = data.toString();
|
|
233
|
-
let startIndex = fileData.indexOf("{");
|
|
234
|
-
let endIndex = fileData.lastIndexOf("}");
|
|
235
|
-
let jsonStr = fileData.slice(
|
|
236
|
-
startIndex,
|
|
237
|
-
endIndex === fileData.length ? endIndex : endIndex + 1
|
|
238
|
-
);
|
|
239
|
-
try {
|
|
240
|
-
// 当成js执行
|
|
241
|
-
eval("jsonObj = " + jsonStr);
|
|
242
|
-
} catch (err) {
|
|
243
|
-
jsonObj = null;
|
|
244
|
-
// log.error("文件解析失败");
|
|
245
|
-
reject();
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
if (jsonObj) {
|
|
249
|
-
let obj = {};
|
|
250
|
-
let words = parseConfigs(jsonObj);
|
|
251
|
-
let limitedWords = limitWords(words, 7);
|
|
252
|
-
// log.on(`正在翻译${chalk.yellow(filePath)}`);
|
|
253
|
-
startTranslate(limitedWords, () => {
|
|
254
|
-
let words_result = limitedWords.flat(1);
|
|
255
|
-
setTranslatedObj(words_result, obj);
|
|
256
|
-
let file_result =
|
|
257
|
-
`export default ` + unquoteKeys(JSON.stringify(obj, null, 2));
|
|
258
|
-
resolve(file_result);
|
|
259
|
-
});
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
});
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* 获取所有需要处理的文件路径+目标路径
|
|
268
|
-
* @param dirPath 从指定的目录地址开始查找
|
|
269
|
-
* @param filePaths 一个空数组,用来接收结果
|
|
270
|
-
*/
|
|
271
|
-
function getAllFilePaths(translateConfig, dirPath, filePaths) {
|
|
272
|
-
let files = fs.readdirSync(dirPath);
|
|
273
|
-
files.forEach((file) => {
|
|
274
|
-
let filePath = path.join(dirPath, file);
|
|
275
|
-
let stats = fs.statSync(filePath);
|
|
276
|
-
// 是否是文件夹
|
|
277
|
-
let isDir = stats.isDirectory();
|
|
278
|
-
if (isDir) {
|
|
279
|
-
if (file === translateConfig.sourceDirName) {
|
|
280
|
-
// 找到目标文件夹, 获取所有文件
|
|
281
|
-
let files = fs.readdirSync(filePath);
|
|
282
|
-
files.forEach((file) => {
|
|
283
|
-
let jsPath = path.join(filePath, file);
|
|
284
|
-
let targetPath = path.join(dirPath, translateConfig.targetDirName);
|
|
285
|
-
filePaths.push({
|
|
286
|
-
sourcePath: jsPath,
|
|
287
|
-
targetPath,
|
|
288
|
-
});
|
|
289
|
-
});
|
|
290
|
-
} else if (!translateConfig.ignoreFiles.includes(file)) {
|
|
291
|
-
getAllFilePaths(translateConfig, filePath, filePaths);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* 同步执行所有翻译操作
|
|
299
|
-
* 因为每秒请求数有限制, 异步请求会超过最大并发数
|
|
300
|
-
* @param files 所有要翻译的文件
|
|
301
|
-
* @param index 当前进行到的index
|
|
302
|
-
*/
|
|
303
|
-
async function execWorkerSync(files, index = 0) {
|
|
304
|
-
let fileItem = files[index];
|
|
305
|
-
let file_content = await readAndTranslateFileContent(fileItem.sourcePath);
|
|
306
|
-
let fileName = path.basename(fileItem.sourcePath);
|
|
307
|
-
let newFilePath = fileItem.targetPath + "/" + fileName;
|
|
308
|
-
let exist = fs.existsSync(fileItem.targetPath);
|
|
309
|
-
// 自动创建不存在的目录
|
|
310
|
-
if (!exist) {
|
|
311
|
-
try {
|
|
312
|
-
// log.on(`创建文件夹${chalk.yellow(fileItem.targetPath)}`);
|
|
313
|
-
fs.mkdirSync(fileItem.targetPath);
|
|
314
|
-
} catch (error) {
|
|
315
|
-
// log.error(`创建文件夹${chalk.red(fileItem.targetPath)}失败`);
|
|
316
|
-
process.exit(1);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
writeFileContent(newFilePath, file_content, async (spinner, isOk) => {
|
|
320
|
-
if (isOk) {
|
|
321
|
-
spinner.succeed(`${newFilePath}已翻译`);
|
|
322
|
-
} else {
|
|
323
|
-
spinner.fail(`${newFilePath}翻译失败`);
|
|
324
|
-
}
|
|
325
|
-
index++;
|
|
326
|
-
if (index < files.length) {
|
|
327
|
-
spinner.start();
|
|
328
|
-
await execWorkerSync(files, index);
|
|
329
|
-
spinner.stop();
|
|
330
|
-
} else {
|
|
331
|
-
spinner.stop();
|
|
332
|
-
spinner.succeed("翻译完毕");
|
|
333
|
-
}
|
|
334
|
-
});
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
export { translateCmd };
|
package/src/config.json
DELETED
package/src/index.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { Command } from "commander";
|
|
3
|
-
import { registerCommand, initProgram } from "./command/index.js";
|
|
4
|
-
import { translateCmd } from "./command/translate.js";
|
|
5
|
-
import { configCmd } from "./command/config.js";
|
|
6
|
-
import { setCmd } from "./command/set.js";
|
|
7
|
-
import { tinyCmd } from "./command/tiny.js";
|
|
8
|
-
import { picgoCmd } from "./command/picgo.js";
|
|
9
|
-
import { checkUpdate, checkNodeVersion } from './utils/common.js'
|
|
10
|
-
import { i18nCmd } from "./command/i18n.js";
|
|
11
|
-
|
|
12
|
-
const program = new Command();
|
|
13
|
-
|
|
14
|
-
initProgram(program, async () => {
|
|
15
|
-
|
|
16
|
-
if (process.env.RUNTIME_ENV !== 'electron') {
|
|
17
|
-
checkNodeVersion()
|
|
18
|
-
await checkUpdate()
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
registerCommand(program, translateCmd);
|
|
22
|
-
|
|
23
|
-
registerCommand(program, setCmd);
|
|
24
|
-
|
|
25
|
-
registerCommand(program, configCmd);
|
|
26
|
-
|
|
27
|
-
registerCommand(program, tinyCmd);
|
|
28
|
-
registerCommand(program, picgoCmd);
|
|
29
|
-
registerCommand(program, i18nCmd)
|
|
30
|
-
program.parse(process.argv);
|
|
31
|
-
});
|