scancscode 1.0.43 → 1.0.46
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/dist/src/CmdExecutor.js +2 -2
- package/dist/src/CsvAutoTranslator.js +117 -50
- package/dist/test/CsvAutoTranslator.test.js +15 -5
- package/package.json +1 -1
- package/src/CmdExecutor.ts +3 -3
- package/src/CsvAutoTranslator.ts +127 -55
- package/test/Auto.csv +3 -12484
- package/test/CsvAutoTranslator.test.ts +19 -8
package/dist/src/CmdExecutor.js
CHANGED
|
@@ -123,7 +123,7 @@ class CmdExecutor {
|
|
|
123
123
|
let toLangs = options.toLangs ?? undefined;
|
|
124
124
|
let apiKey = options.apiKey;
|
|
125
125
|
let appId = options.appId;
|
|
126
|
-
console.log(`
|
|
126
|
+
console.log(`translate csv cmd options: `, incsv, outcsv, fromLang, toLangs, apiKey, appId);
|
|
127
127
|
let argv = process.argv;
|
|
128
128
|
if (incsv == null) {
|
|
129
129
|
console.error(`incsv missing:`, argv);
|
|
@@ -149,7 +149,7 @@ class CmdExecutor {
|
|
|
149
149
|
console.error(`appId missing:`, argv);
|
|
150
150
|
return;
|
|
151
151
|
}
|
|
152
|
-
await CsvAutoTranslator_1.CsvAutoTranslator.translateCsvWithLangs(incsv, outcsv, fromLang, toLangs
|
|
152
|
+
await CsvAutoTranslator_1.CsvAutoTranslator.translateCsvWithLangs(appId, apiKey, incsv, outcsv, fromLang, toLangs);
|
|
153
153
|
console.log("translate csv with cmd options done.");
|
|
154
154
|
}
|
|
155
155
|
}
|
|
@@ -37,6 +37,31 @@ exports.CsvAutoTranslator = void 0;
|
|
|
37
37
|
const crypto = __importStar(require("crypto"));
|
|
38
38
|
const https = __importStar(require("https"));
|
|
39
39
|
const CSVUtils_1 = require("./CSVUtils");
|
|
40
|
+
class OnlineTranslateResult {
|
|
41
|
+
errorCode = "";
|
|
42
|
+
errorMsg = "";
|
|
43
|
+
isOk = false;
|
|
44
|
+
translatedTexts;
|
|
45
|
+
constructor(errorCode, errorMsg, isOk, translatedTexts) {
|
|
46
|
+
this.errorCode = errorCode;
|
|
47
|
+
this.errorMsg = errorMsg;
|
|
48
|
+
this.isOk = isOk;
|
|
49
|
+
this.translatedTexts = translatedTexts;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
class TranslateCSVResult {
|
|
53
|
+
translateCount = 0;
|
|
54
|
+
isOk = false;
|
|
55
|
+
firstErrorCode = "";
|
|
56
|
+
firstErrorMsg = "";
|
|
57
|
+
constructor(translateCount, isOk, firstErrorCode, firstErrorMsg) {
|
|
58
|
+
this.translateCount = translateCount;
|
|
59
|
+
this.isOk = isOk;
|
|
60
|
+
this.firstErrorCode = firstErrorCode;
|
|
61
|
+
this.firstErrorMsg = firstErrorMsg;
|
|
62
|
+
}
|
|
63
|
+
static Empty = new TranslateCSVResult(0, false, "", "");
|
|
64
|
+
}
|
|
40
65
|
class CsvAutoTranslator {
|
|
41
66
|
apiKey;
|
|
42
67
|
appId;
|
|
@@ -44,7 +69,7 @@ class CsvAutoTranslator {
|
|
|
44
69
|
maxBatchSize = 5;
|
|
45
70
|
MAX_BATCH_TEXT_COUNT = 50;
|
|
46
71
|
MAX_BATCH_JSON_SIZE = 4900;
|
|
47
|
-
constructor(
|
|
72
|
+
constructor(appId, apiKey) {
|
|
48
73
|
this.apiKey = apiKey;
|
|
49
74
|
this.appId = appId;
|
|
50
75
|
}
|
|
@@ -107,9 +132,14 @@ class CsvAutoTranslator {
|
|
|
107
132
|
async translateBatch(texts, from, to) {
|
|
108
133
|
const response = await this.requestTranslate(texts, from, to);
|
|
109
134
|
if (response.resultCode !== "200") {
|
|
110
|
-
|
|
135
|
+
let errTip = `翻译失败: ${response.errorCode}, ${response.errorMsg}, ${response.resultCode}, ${response.resultMsg}`;
|
|
136
|
+
console.error(errTip);
|
|
137
|
+
console.error("翻译失败内容:", texts);
|
|
138
|
+
let result = new OnlineTranslateResult(response.errorCode, response.errorMsg, false, new Array(texts.length).fill(""));
|
|
139
|
+
return result;
|
|
111
140
|
}
|
|
112
|
-
|
|
141
|
+
let result = new OnlineTranslateResult(response.resultCode, response.resultMsg, true, response.tgtList.map(item => item.tgtText || ""));
|
|
142
|
+
return result;
|
|
113
143
|
}
|
|
114
144
|
estimateSingleTextJsonSize(text, isSingle) {
|
|
115
145
|
return Buffer.byteLength(JSON.stringify(text)) + (isSingle ? 0 : 1);
|
|
@@ -159,18 +189,28 @@ class CsvAutoTranslator {
|
|
|
159
189
|
const results = new Array(batches.length);
|
|
160
190
|
const concurrencyLimit = this.maxBatchSize;
|
|
161
191
|
let index = 0;
|
|
192
|
+
let isOk = true;
|
|
162
193
|
let batchCount = 0;
|
|
194
|
+
let firstErrorCode = "";
|
|
195
|
+
let firstErrorMsg = "";
|
|
163
196
|
const processBatch = async () => {
|
|
164
197
|
while (index < batches.length) {
|
|
165
198
|
const batchIndex = index++;
|
|
166
199
|
const batch = batches[batchIndex];
|
|
167
200
|
batchCount++;
|
|
168
201
|
console.log(`batchCount++: ${batchCount}`);
|
|
169
|
-
const
|
|
202
|
+
const batchResult = await this.translateBatch(batch, from, to);
|
|
203
|
+
if (!batchResult.isOk) {
|
|
204
|
+
isOk = false;
|
|
205
|
+
if (firstErrorCode === "") {
|
|
206
|
+
firstErrorCode = batchResult.errorCode;
|
|
207
|
+
firstErrorMsg = batchResult.errorMsg;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
170
210
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
171
211
|
batchCount--;
|
|
172
212
|
console.log(`batchCount--: ${batchCount}`);
|
|
173
|
-
results[batchIndex] =
|
|
213
|
+
results[batchIndex] = batchResult.translatedTexts;
|
|
174
214
|
}
|
|
175
215
|
};
|
|
176
216
|
const workers = [];
|
|
@@ -179,7 +219,9 @@ class CsvAutoTranslator {
|
|
|
179
219
|
}
|
|
180
220
|
await Promise.all(workers);
|
|
181
221
|
// await processBatch()
|
|
182
|
-
|
|
222
|
+
let translatedTexts = results.flat();
|
|
223
|
+
let result = new OnlineTranslateResult(firstErrorCode, firstErrorMsg, isOk, translatedTexts);
|
|
224
|
+
return result;
|
|
183
225
|
}
|
|
184
226
|
langMap = new Map([
|
|
185
227
|
["zh_cn", "zh"],
|
|
@@ -189,45 +231,27 @@ class CsvAutoTranslator {
|
|
|
189
231
|
async translateCsvRows(rows, fromLang, toLangs) {
|
|
190
232
|
if (rows.length === 0) {
|
|
191
233
|
console.log("CSV文件为空");
|
|
192
|
-
return
|
|
234
|
+
return TranslateCSVResult.Empty;
|
|
193
235
|
}
|
|
194
236
|
let header = rows[0];
|
|
195
237
|
let fromLangIndex = header.indexOf(fromLang);
|
|
196
238
|
if (fromLangIndex === -1) {
|
|
197
239
|
console.error(`未找到 ${fromLang} 列`);
|
|
198
|
-
return
|
|
240
|
+
return TranslateCSVResult.Empty;
|
|
199
241
|
}
|
|
200
242
|
if (!this.langMap.has(fromLang)) {
|
|
201
243
|
console.error(`未找到 ${fromLang} 的目标语言`);
|
|
202
|
-
return
|
|
244
|
+
return TranslateCSVResult.Empty;
|
|
203
245
|
}
|
|
204
246
|
let fromLang2 = this.langMap.get(fromLang);
|
|
205
247
|
if (fromLang2 == null) {
|
|
206
248
|
console.error(`未找到 ${fromLang} 的目标语言`);
|
|
207
|
-
return
|
|
249
|
+
return TranslateCSVResult.Empty;
|
|
208
250
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if (!row[0] || row[0].trim() === "") {
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
if (row[fromLangIndex] && row[fromLangIndex].trim() !== "") {
|
|
217
|
-
continue;
|
|
218
|
-
}
|
|
219
|
-
needTranslateIndices.push(i);
|
|
220
|
-
let text = row[fromLangIndex];
|
|
221
|
-
if (text == null || text == "") {
|
|
222
|
-
text = row[0];
|
|
223
|
-
}
|
|
224
|
-
needTranslateTexts.push(text);
|
|
225
|
-
}
|
|
226
|
-
if (needTranslateTexts.length === 0) {
|
|
227
|
-
console.log("没有需要翻译的内容");
|
|
228
|
-
return 0;
|
|
229
|
-
}
|
|
230
|
-
console.log(`开始翻译 ${needTranslateTexts.length} 条内容...`);
|
|
251
|
+
let isOk = true;
|
|
252
|
+
let translateCount = 0;
|
|
253
|
+
let firstErrorCode = "";
|
|
254
|
+
let firstErrorMsg = "";
|
|
231
255
|
for (let curLangIndex = 0; curLangIndex < header.length; curLangIndex++) {
|
|
232
256
|
let lang = header[curLangIndex];
|
|
233
257
|
lang = lang.trim().toLowerCase();
|
|
@@ -237,36 +261,79 @@ class CsvAutoTranslator {
|
|
|
237
261
|
if (toLangs != undefined && !toLangs.includes(lang)) {
|
|
238
262
|
continue;
|
|
239
263
|
}
|
|
240
|
-
if (lang != fromLang && this.langMap.has(lang)) {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
264
|
+
if (!(lang != fromLang && this.langMap.has(lang))) {
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
const needTranslateIndices = [];
|
|
268
|
+
const needTranslateTexts = [];
|
|
269
|
+
for (let i = 1; i < rows.length; i++) {
|
|
270
|
+
const row = rows[i];
|
|
271
|
+
if (!row[0] || row[0].trim() === "") {
|
|
244
272
|
continue;
|
|
245
273
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
274
|
+
if (row[curLangIndex] && row[curLangIndex].trim() !== "") {
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
let text = row[fromLangIndex];
|
|
278
|
+
if (text == null || text == "") {
|
|
279
|
+
text = row[0];
|
|
280
|
+
}
|
|
281
|
+
if (text == null || text === "") {
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
needTranslateIndices.push(i);
|
|
285
|
+
needTranslateTexts.push(text);
|
|
286
|
+
}
|
|
287
|
+
if (needTranslateTexts.length === 0) {
|
|
288
|
+
console.log("没有需要翻译的内容");
|
|
289
|
+
return TranslateCSVResult.Empty;
|
|
290
|
+
}
|
|
291
|
+
console.log(`开始翻译 ${needTranslateTexts.length} 条内容...`);
|
|
292
|
+
const toLang = this.langMap.get(lang);
|
|
293
|
+
if (toLang == null) {
|
|
294
|
+
console.error(`未找到 ${lang} 的目标语言`);
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
const translateResult = await this.translateAll(needTranslateTexts, fromLang2, toLang);
|
|
298
|
+
isOk = isOk && translateResult.isOk;
|
|
299
|
+
if (!translateResult.isOk) {
|
|
300
|
+
if (firstErrorCode === "") {
|
|
301
|
+
firstErrorCode = translateResult.errorCode;
|
|
302
|
+
firstErrorMsg = translateResult.errorMsg;
|
|
303
|
+
}
|
|
304
|
+
console.error(`翻译 ${lang} 失败: ${translateResult.errorCode} ${translateResult.errorMsg}`);
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
let translations = translateResult.translatedTexts;
|
|
308
|
+
for (let j = 0; j < needTranslateIndices.length; j++) {
|
|
309
|
+
const rowIndex = needTranslateIndices[j];
|
|
310
|
+
while (rows[rowIndex].length < header.length) {
|
|
311
|
+
rows[rowIndex].push("");
|
|
253
312
|
}
|
|
313
|
+
rows[rowIndex][curLangIndex] = translations[j];
|
|
254
314
|
}
|
|
315
|
+
translateCount += needTranslateTexts.length;
|
|
255
316
|
}
|
|
256
|
-
|
|
317
|
+
let result = new TranslateCSVResult(translateCount, isOk, firstErrorCode, firstErrorMsg);
|
|
318
|
+
return result;
|
|
257
319
|
}
|
|
258
320
|
async translateCsv(filePath, outFilePath, fromLang = "auto", toLangs) {
|
|
259
321
|
const csvUtils = new CSVUtils_1.CSVUtils(filePath);
|
|
260
322
|
const rows = await csvUtils.parseCsv();
|
|
261
|
-
const
|
|
262
|
-
if (
|
|
323
|
+
const translatedResult = await this.translateCsvRows(rows, fromLang, toLangs);
|
|
324
|
+
if (translatedResult.isOk && translatedResult.translateCount > 0) {
|
|
263
325
|
await CSVUtils_1.CSVUtils.writeCsv(outFilePath, rows);
|
|
264
|
-
console.log(`翻译完成,已更新 ${
|
|
326
|
+
console.log(`翻译完成,已更新 ${translatedResult.translateCount} 条内容到 ${outFilePath}`);
|
|
265
327
|
}
|
|
328
|
+
else {
|
|
329
|
+
console.log(`翻译完成,未更新任何内容到 ${outFilePath}`);
|
|
330
|
+
}
|
|
331
|
+
return translatedResult;
|
|
266
332
|
}
|
|
267
|
-
static async translateCsvWithLangs(filePath, outFilePath, fromLang, langs
|
|
268
|
-
const translator = new CsvAutoTranslator(
|
|
269
|
-
await translator.translateCsv(filePath, outFilePath, fromLang, langs);
|
|
333
|
+
static async translateCsvWithLangs(appId, apiKey, filePath, outFilePath, fromLang, langs) {
|
|
334
|
+
const translator = new CsvAutoTranslator(appId, apiKey);
|
|
335
|
+
let result = await translator.translateCsv(filePath, outFilePath, fromLang, langs);
|
|
336
|
+
return result;
|
|
270
337
|
}
|
|
271
338
|
}
|
|
272
339
|
exports.CsvAutoTranslator = CsvAutoTranslator;
|
|
@@ -27,7 +27,7 @@ describe("CsvAutoTranslator", () => {
|
|
|
27
27
|
expect(true).toBe(true);
|
|
28
28
|
return;
|
|
29
29
|
}
|
|
30
|
-
const translator = new CsvAutoTranslator_1.CsvAutoTranslator(
|
|
30
|
+
const translator = new CsvAutoTranslator_1.CsvAutoTranslator(appId, apiKey);
|
|
31
31
|
const rows = JSON.parse(JSON.stringify(testRows));
|
|
32
32
|
const translatedCount = await translator.translateCsvRows(rows, "zh_cn");
|
|
33
33
|
expect(translatedCount).toBe(10);
|
|
@@ -45,7 +45,7 @@ describe("CsvAutoTranslator", () => {
|
|
|
45
45
|
expect(true).toBe(true);
|
|
46
46
|
return;
|
|
47
47
|
}
|
|
48
|
-
const translator = new CsvAutoTranslator_1.CsvAutoTranslator(
|
|
48
|
+
const translator = new CsvAutoTranslator_1.CsvAutoTranslator(appId, apiKey);
|
|
49
49
|
const rows = JSON.parse(JSON.stringify(testRows));
|
|
50
50
|
rows[1][2] = "已有译文";
|
|
51
51
|
const translatedCount = await translator.translateCsvRows(rows, "zh_cn");
|
|
@@ -53,12 +53,12 @@ describe("CsvAutoTranslator", () => {
|
|
|
53
53
|
expect(rows[10][2]).toBe("歡迎來到中國");
|
|
54
54
|
}, 120000);
|
|
55
55
|
test("translateCsvRows - 空CSV测试", async () => {
|
|
56
|
-
const translator = new CsvAutoTranslator_1.CsvAutoTranslator(
|
|
56
|
+
const translator = new CsvAutoTranslator_1.CsvAutoTranslator(NIUTRANS_APP_ID, NIUTRANS_API_KEY);
|
|
57
57
|
const translatedCount = await translator.translateCsvRows([], "zh_cn");
|
|
58
58
|
expect(translatedCount).toBe(0);
|
|
59
59
|
});
|
|
60
60
|
test("translateCsvRows - 没有需要翻译的内容", async () => {
|
|
61
|
-
const translator = new CsvAutoTranslator_1.CsvAutoTranslator(
|
|
61
|
+
const translator = new CsvAutoTranslator_1.CsvAutoTranslator(NIUTRANS_APP_ID, NIUTRANS_API_KEY);
|
|
62
62
|
const rows = [
|
|
63
63
|
["key", "en_us", "zh_hk"],
|
|
64
64
|
["Hello", "", "已有译文1"],
|
|
@@ -68,7 +68,7 @@ describe("CsvAutoTranslator", () => {
|
|
|
68
68
|
expect(translatedCount).toBe(0);
|
|
69
69
|
});
|
|
70
70
|
describe("smartBatch - 边界回归测试", () => {
|
|
71
|
-
const translator = new CsvAutoTranslator_1.CsvAutoTranslator(
|
|
71
|
+
const translator = new CsvAutoTranslator_1.CsvAutoTranslator(NIUTRANS_APP_ID, NIUTRANS_API_KEY);
|
|
72
72
|
test("空文本数组", () => {
|
|
73
73
|
const batches = translator.smartBatch([]);
|
|
74
74
|
expect(batches.length).toBe(0);
|
|
@@ -157,4 +157,14 @@ describe("CsvAutoTranslator", () => {
|
|
|
157
157
|
}
|
|
158
158
|
}, 300000);
|
|
159
159
|
});
|
|
160
|
+
describe("translateCSV", () => {
|
|
161
|
+
test("翻译 Auto.csv 并验证输出", async () => {
|
|
162
|
+
const incsv = "test/Auto.csv";
|
|
163
|
+
const outcsv = "temp/Auto-Out.csv";
|
|
164
|
+
const fromLang = "zh_cn";
|
|
165
|
+
let result = await CsvAutoTranslator_1.CsvAutoTranslator.translateCsvWithLangs("kDr1772519780125", "4cfb5525d3be1e45003910059cd7ea9b", incsv, outcsv, fromLang);
|
|
166
|
+
expect(result.isOk).toBe(true);
|
|
167
|
+
expect(result.translateCount).toBeGreaterThan(0);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
160
170
|
});
|
package/package.json
CHANGED
package/src/CmdExecutor.ts
CHANGED
|
@@ -124,7 +124,7 @@ export class CmdExecutor {
|
|
|
124
124
|
let apiKey: string = options.apiKey;
|
|
125
125
|
let appId: string = options.appId;
|
|
126
126
|
|
|
127
|
-
console.log(`
|
|
127
|
+
console.log(`translate csv cmd options: `, incsv, outcsv, fromLang, toLangs, apiKey, appId);
|
|
128
128
|
let argv = process.argv;
|
|
129
129
|
if (incsv == null) {
|
|
130
130
|
console.error(`incsv missing:`, argv);
|
|
@@ -149,8 +149,8 @@ export class CmdExecutor {
|
|
|
149
149
|
if (appId == null) {
|
|
150
150
|
console.error(`appId missing:`, argv);
|
|
151
151
|
return;
|
|
152
|
-
|
|
153
|
-
|
|
152
|
+
}
|
|
153
|
+
await CsvAutoTranslator.translateCsvWithLangs(appId, apiKey, incsv, outcsv, fromLang, toLangs);
|
|
154
154
|
console.log("translate csv with cmd options done.");
|
|
155
155
|
}
|
|
156
156
|
}
|
package/src/CsvAutoTranslator.ts
CHANGED
|
@@ -13,6 +13,38 @@ interface NiuTransResponse {
|
|
|
13
13
|
}>;
|
|
14
14
|
resultCode: string;
|
|
15
15
|
resultMsg: string;
|
|
16
|
+
errorCode: string;
|
|
17
|
+
errorMsg: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
class OnlineTranslateResult {
|
|
21
|
+
public errorCode: string = "";
|
|
22
|
+
public errorMsg: string = "";
|
|
23
|
+
public isOk: boolean = false;
|
|
24
|
+
public translatedTexts: string[];
|
|
25
|
+
|
|
26
|
+
public constructor(errorCode: string, errorMsg: string, isOk: boolean, translatedTexts: string[]) {
|
|
27
|
+
this.errorCode = errorCode;
|
|
28
|
+
this.errorMsg = errorMsg;
|
|
29
|
+
this.isOk = isOk;
|
|
30
|
+
this.translatedTexts = translatedTexts;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
class TranslateCSVResult {
|
|
35
|
+
public translateCount: number = 0;
|
|
36
|
+
public isOk: boolean = false;
|
|
37
|
+
public firstErrorCode: string = "";
|
|
38
|
+
public firstErrorMsg: string = "";
|
|
39
|
+
|
|
40
|
+
public constructor(translateCount: number, isOk: boolean, firstErrorCode: string, firstErrorMsg: string) {
|
|
41
|
+
this.translateCount = translateCount;
|
|
42
|
+
this.isOk = isOk;
|
|
43
|
+
this.firstErrorCode = firstErrorCode;
|
|
44
|
+
this.firstErrorMsg = firstErrorMsg;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public static Empty: TranslateCSVResult = new TranslateCSVResult(0, false, "", "");
|
|
16
48
|
}
|
|
17
49
|
|
|
18
50
|
export class CsvAutoTranslator {
|
|
@@ -23,7 +55,7 @@ export class CsvAutoTranslator {
|
|
|
23
55
|
private readonly MAX_BATCH_TEXT_COUNT: number = 50;
|
|
24
56
|
private readonly MAX_BATCH_JSON_SIZE: number = 4900;
|
|
25
57
|
|
|
26
|
-
constructor(
|
|
58
|
+
constructor(appId: string, apiKey: string) {
|
|
27
59
|
this.apiKey = apiKey;
|
|
28
60
|
this.appId = appId;
|
|
29
61
|
}
|
|
@@ -85,12 +117,17 @@ export class CsvAutoTranslator {
|
|
|
85
117
|
});
|
|
86
118
|
}
|
|
87
119
|
|
|
88
|
-
private async translateBatch(texts: string[], from: string, to: string): Promise<
|
|
120
|
+
private async translateBatch(texts: string[], from: string, to: string): Promise<OnlineTranslateResult> {
|
|
89
121
|
const response = await this.requestTranslate(texts, from, to);
|
|
90
122
|
if (response.resultCode !== "200") {
|
|
91
|
-
|
|
123
|
+
let errTip = `翻译失败: ${response.errorCode}, ${response.errorMsg}, ${response.resultCode}, ${response.resultMsg}`;
|
|
124
|
+
console.error(errTip);
|
|
125
|
+
console.error("翻译失败内容:", texts);
|
|
126
|
+
let result = new OnlineTranslateResult(response.errorCode, response.errorMsg, false, new Array(texts.length).fill(""));
|
|
127
|
+
return result;
|
|
92
128
|
}
|
|
93
|
-
|
|
129
|
+
let result = new OnlineTranslateResult(response.resultCode, response.resultMsg, true, response.tgtList.map(item => item.tgtText || ""));
|
|
130
|
+
return result;
|
|
94
131
|
}
|
|
95
132
|
|
|
96
133
|
private estimateSingleTextJsonSize(text: string, isSingle: boolean): number {
|
|
@@ -139,24 +176,33 @@ export class CsvAutoTranslator {
|
|
|
139
176
|
return resultBatches;
|
|
140
177
|
}
|
|
141
178
|
|
|
142
|
-
private async translateAll(texts: string[], from: string, to: string): Promise<
|
|
179
|
+
private async translateAll(texts: string[], from: string, to: string): Promise<OnlineTranslateResult> {
|
|
143
180
|
const batches = this.smartBatch(texts);
|
|
144
181
|
const results: string[][] = new Array(batches.length);
|
|
145
182
|
const concurrencyLimit = this.maxBatchSize;
|
|
146
183
|
let index = 0;
|
|
147
|
-
|
|
184
|
+
let isOk = true;
|
|
148
185
|
let batchCount = 0;
|
|
186
|
+
let firstErrorCode = "";
|
|
187
|
+
let firstErrorMsg = "";
|
|
149
188
|
const processBatch = async (): Promise<void> => {
|
|
150
189
|
while (index < batches.length) {
|
|
151
190
|
const batchIndex = index++;
|
|
152
191
|
const batch = batches[batchIndex];
|
|
153
192
|
batchCount++;
|
|
154
193
|
console.log(`batchCount++: ${batchCount}`)
|
|
155
|
-
const
|
|
194
|
+
const batchResult = await this.translateBatch(batch, from, to);
|
|
195
|
+
if (!batchResult.isOk) {
|
|
196
|
+
isOk = false;
|
|
197
|
+
if (firstErrorCode === "") {
|
|
198
|
+
firstErrorCode = batchResult.errorCode;
|
|
199
|
+
firstErrorMsg = batchResult.errorMsg;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
156
202
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
157
203
|
batchCount--;
|
|
158
204
|
console.log(`batchCount--: ${batchCount}`)
|
|
159
|
-
results[batchIndex] =
|
|
205
|
+
results[batchIndex] = batchResult.translatedTexts;
|
|
160
206
|
}
|
|
161
207
|
};
|
|
162
208
|
|
|
@@ -167,7 +213,9 @@ export class CsvAutoTranslator {
|
|
|
167
213
|
await Promise.all(workers);
|
|
168
214
|
// await processBatch()
|
|
169
215
|
|
|
170
|
-
|
|
216
|
+
let translatedTexts = results.flat();
|
|
217
|
+
let result = new OnlineTranslateResult(firstErrorCode, firstErrorMsg, isOk, translatedTexts);
|
|
218
|
+
return result;
|
|
171
219
|
}
|
|
172
220
|
|
|
173
221
|
langMap: Map<string, string> = new Map([
|
|
@@ -175,48 +223,30 @@ export class CsvAutoTranslator {
|
|
|
175
223
|
["zh_hk", "cht"],
|
|
176
224
|
["en_us", "en"],
|
|
177
225
|
])
|
|
178
|
-
async translateCsvRows(rows: string[][], fromLang: string, toLangs?: string[]): Promise<
|
|
226
|
+
async translateCsvRows(rows: string[][], fromLang: string, toLangs?: string[]): Promise<TranslateCSVResult> {
|
|
179
227
|
if (rows.length === 0) {
|
|
180
228
|
console.log("CSV文件为空");
|
|
181
|
-
return
|
|
229
|
+
return TranslateCSVResult.Empty;
|
|
182
230
|
}
|
|
183
231
|
let header = rows[0];
|
|
184
232
|
let fromLangIndex = header.indexOf(fromLang);
|
|
185
233
|
if (fromLangIndex === -1) {
|
|
186
234
|
console.error(`未找到 ${fromLang} 列`);
|
|
187
|
-
return
|
|
235
|
+
return TranslateCSVResult.Empty;
|
|
188
236
|
}
|
|
189
237
|
if (!this.langMap.has(fromLang)) {
|
|
190
238
|
console.error(`未找到 ${fromLang} 的目标语言`);
|
|
191
|
-
return
|
|
239
|
+
return TranslateCSVResult.Empty;
|
|
192
240
|
}
|
|
193
241
|
let fromLang2 = this.langMap.get(fromLang);
|
|
194
242
|
if (fromLang2 == null) {
|
|
195
243
|
console.error(`未找到 ${fromLang} 的目标语言`);
|
|
196
|
-
return
|
|
197
|
-
}
|
|
198
|
-
const needTranslateIndices: number[] = [];
|
|
199
|
-
const needTranslateTexts: string[] = [];
|
|
200
|
-
for (let i = 1; i < rows.length; i++) {
|
|
201
|
-
const row = rows[i];
|
|
202
|
-
if (!row[0] || row[0].trim() === "") {
|
|
203
|
-
continue;
|
|
204
|
-
}
|
|
205
|
-
if (row[fromLangIndex] && row[fromLangIndex].trim() !== "") {
|
|
206
|
-
continue;
|
|
207
|
-
}
|
|
208
|
-
needTranslateIndices.push(i);
|
|
209
|
-
let text = row[fromLangIndex]
|
|
210
|
-
if (text == null || text == "") {
|
|
211
|
-
text = row[0];
|
|
212
|
-
}
|
|
213
|
-
needTranslateTexts.push(text);
|
|
214
|
-
}
|
|
215
|
-
if (needTranslateTexts.length === 0) {
|
|
216
|
-
console.log("没有需要翻译的内容");
|
|
217
|
-
return 0;
|
|
244
|
+
return TranslateCSVResult.Empty;
|
|
218
245
|
}
|
|
219
|
-
|
|
246
|
+
let isOk = true;
|
|
247
|
+
let translateCount = 0;
|
|
248
|
+
let firstErrorCode = "";
|
|
249
|
+
let firstErrorMsg = "";
|
|
220
250
|
for (let curLangIndex = 0; curLangIndex < header.length; curLangIndex++) {
|
|
221
251
|
let lang = header[curLangIndex];
|
|
222
252
|
lang = lang.trim().toLowerCase();
|
|
@@ -226,36 +256,78 @@ export class CsvAutoTranslator {
|
|
|
226
256
|
if (toLangs != undefined && !toLangs.includes(lang)) {
|
|
227
257
|
continue;
|
|
228
258
|
}
|
|
229
|
-
if (lang != fromLang && this.langMap.has(lang)) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
259
|
+
if (!(lang != fromLang && this.langMap.has(lang))) {
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
const needTranslateIndices: number[] = [];
|
|
263
|
+
const needTranslateTexts: string[] = [];
|
|
264
|
+
for (let i = 1; i < rows.length; i++) {
|
|
265
|
+
const row = rows[i];
|
|
266
|
+
if (!row[0] || row[0].trim() === "") {
|
|
233
267
|
continue;
|
|
234
268
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
269
|
+
if (row[curLangIndex] && row[curLangIndex].trim() !== "") {
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
let text = row[fromLangIndex]
|
|
273
|
+
if (text == null || text == "") {
|
|
274
|
+
text = row[0];
|
|
275
|
+
}
|
|
276
|
+
if (text == null || text === "") {
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
needTranslateIndices.push(i);
|
|
280
|
+
needTranslateTexts.push(text);
|
|
281
|
+
}
|
|
282
|
+
if (needTranslateTexts.length === 0) {
|
|
283
|
+
console.log("没有需要翻译的内容");
|
|
284
|
+
return TranslateCSVResult.Empty;
|
|
285
|
+
}
|
|
286
|
+
console.log(`开始翻译 ${needTranslateTexts.length} 条内容...`);
|
|
287
|
+
const toLang = this.langMap.get(lang);
|
|
288
|
+
if (toLang == null) {
|
|
289
|
+
console.error(`未找到 ${lang} 的目标语言`);
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
const translateResult = await this.translateAll(needTranslateTexts, fromLang2, toLang);
|
|
293
|
+
isOk = isOk && translateResult.isOk;
|
|
294
|
+
if (!translateResult.isOk) {
|
|
295
|
+
if (firstErrorCode === "") {
|
|
296
|
+
firstErrorCode = translateResult.errorCode;
|
|
297
|
+
firstErrorMsg = translateResult.errorMsg;
|
|
298
|
+
}
|
|
299
|
+
console.error(`翻译 ${lang} 失败: ${translateResult.errorCode} ${translateResult.errorMsg}`);
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
let translations = translateResult.translatedTexts;
|
|
303
|
+
for (let j = 0; j < needTranslateIndices.length; j++) {
|
|
304
|
+
const rowIndex = needTranslateIndices[j];
|
|
305
|
+
while (rows[rowIndex].length < header.length) {
|
|
306
|
+
rows[rowIndex].push("");
|
|
242
307
|
}
|
|
308
|
+
rows[rowIndex][curLangIndex] = translations[j];
|
|
243
309
|
}
|
|
310
|
+
translateCount += needTranslateTexts.length;
|
|
244
311
|
}
|
|
245
|
-
|
|
312
|
+
let result = new TranslateCSVResult(translateCount, isOk, firstErrorCode, firstErrorMsg);
|
|
313
|
+
return result;
|
|
246
314
|
}
|
|
247
|
-
async translateCsv(filePath: string, outFilePath: string, fromLang: string = "auto", toLangs?: string[]): Promise<
|
|
315
|
+
async translateCsv(filePath: string, outFilePath: string, fromLang: string = "auto", toLangs?: string[]): Promise<TranslateCSVResult> {
|
|
248
316
|
const csvUtils = new CSVUtils(filePath);
|
|
249
317
|
const rows = await csvUtils.parseCsv();
|
|
250
|
-
const
|
|
251
|
-
if (
|
|
318
|
+
const translatedResult = await this.translateCsvRows(rows, fromLang, toLangs);
|
|
319
|
+
if (translatedResult.isOk && translatedResult.translateCount > 0) {
|
|
252
320
|
await CSVUtils.writeCsv(outFilePath, rows);
|
|
253
|
-
console.log(`翻译完成,已更新 ${
|
|
321
|
+
console.log(`翻译完成,已更新 ${translatedResult.translateCount} 条内容到 ${outFilePath}`);
|
|
322
|
+
} else {
|
|
323
|
+
console.log(`翻译完成,未更新任何内容到 ${outFilePath}`);
|
|
254
324
|
}
|
|
325
|
+
return translatedResult;
|
|
255
326
|
}
|
|
256
327
|
|
|
257
|
-
public static async translateCsvWithLangs(
|
|
258
|
-
const translator = new CsvAutoTranslator(
|
|
259
|
-
await translator.translateCsv(filePath, outFilePath, fromLang, langs);
|
|
328
|
+
public static async translateCsvWithLangs(appId: string, apiKey: string, filePath: string, outFilePath: string, fromLang: string, langs?: string[]) {
|
|
329
|
+
const translator = new CsvAutoTranslator(appId, apiKey);
|
|
330
|
+
let result = await translator.translateCsv(filePath, outFilePath, fromLang, langs);
|
|
331
|
+
return result;
|
|
260
332
|
}
|
|
261
333
|
}
|