scancscode 1.0.42 → 1.0.43

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.
@@ -1,261 +1,261 @@
1
- import * as crypto from "crypto";
2
- import * as https from "https";
3
- import { CSVUtils } from "./CSVUtils";
4
-
5
- interface NiuTransResponse {
6
- tgtList: Array<{
7
- from: string;
8
- to: string;
9
- tgtText: string;
10
- srcText?: string;
11
- errorCode?: string;
12
- errorMsg?: string;
13
- }>;
14
- resultCode: string;
15
- resultMsg: string;
16
- }
17
-
18
- export class CsvAutoTranslator {
19
- private apiKey: string;
20
- private appId: string;
21
- private apiUrl: string = "https://api.niutrans.com/v2/text/translate/array";
22
- private maxBatchSize: number = 5;
23
- private readonly MAX_BATCH_TEXT_COUNT: number = 50;
24
- private readonly MAX_BATCH_JSON_SIZE: number = 4900;
25
-
26
- constructor(apiKey: string, appId: string) {
27
- this.apiKey = apiKey;
28
- this.appId = appId;
29
- }
30
-
31
- private generateAuthStr(params: Record<string, string>): string {
32
- const sortedKeys = Object.keys(params).sort();
33
- const paramStr = sortedKeys
34
- .filter(key => params[key] !== "" && params[key] !== undefined && params[key] !== null)
35
- .map(key => `${key}=${params[key]}`)
36
- .join("&");
37
- const fullStr = `apikey=${this.apiKey}&${paramStr}`;
38
- return crypto.createHash("md5").update(fullStr).digest("hex");
39
- }
40
-
41
- private async requestTranslate(texts: string[], from: string, to: string): Promise<NiuTransResponse> {
42
- const timestamp = Date.now().toString();
43
- const params: Record<string, string> = {
44
- from: from,
45
- to: to,
46
- appId: this.appId,
47
- timestamp: timestamp
48
- };
49
- const authStr = this.generateAuthStr(params);
50
- const postData = JSON.stringify({
51
- ...params,
52
- srcText: texts,
53
- authStr: authStr
54
- });
55
- const url = new URL(this.apiUrl);
56
- const options: https.RequestOptions = {
57
- hostname: url.hostname,
58
- port: 443,
59
- path: url.pathname,
60
- method: "POST",
61
- headers: {
62
- "Content-Type": "application/json",
63
- "Content-Length": Buffer.byteLength(postData)
64
- }
65
- };
66
- return new Promise((resolve, reject) => {
67
- const req = https.request(options, (res) => {
68
- let data = "";
69
- res.on("data", (chunk) => {
70
- data += chunk;
71
- });
72
- res.on("end", async () => {
73
- try {
74
- let ret = JSON.parse(data);
75
- await res.destroy();
76
- resolve(ret);
77
- } catch (e) {
78
- reject(e);
79
- }
80
- });
81
- });
82
- req.on("error", reject);
83
- req.write(postData);
84
- req.end();
85
- });
86
- }
87
-
88
- private async translateBatch(texts: string[], from: string, to: string): Promise<string[]> {
89
- const response = await this.requestTranslate(texts, from, to);
90
- if (response.resultCode !== "200") {
91
- throw new Error(`翻译失败: ${response.resultMsg}`);
92
- }
93
- return response.tgtList.map(item => item.tgtText || "");
94
- }
95
-
96
- private estimateSingleTextJsonSize(text: string, isSingle: boolean): number {
97
- return Buffer.byteLength(JSON.stringify(text)) + (isSingle ? 0 : 1);
98
- }
99
-
100
- estimateJsonSize(texts: string[]): number {
101
- return Buffer.byteLength(JSON.stringify(texts));
102
- }
103
-
104
- smartBatch(texts: string[], batches?: string[][]): string[][] {
105
- const resultBatches = batches || [];
106
- let startIndex = 0;
107
- while (startIndex < texts.length) {
108
- let endIndexMax = Math.min(startIndex + this.MAX_BATCH_TEXT_COUNT, texts.length);
109
- let batchByteLength = 2;
110
- let currentBatchCount = 0;
111
- // let currentBatch = texts.slice(startIndex, endIndexMax);
112
- for (let i = startIndex; i < endIndexMax; i++) {
113
- const text = texts[i];
114
- const textSize = Buffer.byteLength(text) + (i >= 1 ? 1 : 0);
115
- currentBatchCount++;
116
- batchByteLength += textSize;
117
- if (batchByteLength + textSize > this.MAX_BATCH_JSON_SIZE) {
118
- break;
119
- }
120
- }
121
- let endIndex = startIndex + currentBatchCount;
122
- let currentBatch: string[] = texts.slice(startIndex, endIndex);
123
- let currentSize = this.estimateJsonSize(currentBatch);
124
- while (currentSize > this.MAX_BATCH_JSON_SIZE && currentBatch.length > 0) {
125
- endIndex--;
126
- let endText = texts[endIndex];
127
- let endTextSize = this.estimateSingleTextJsonSize(endText, endIndex > startIndex + 1);
128
- currentSize -= endTextSize;
129
- currentBatch.pop();
130
- }
131
- if (currentBatch.length == 0 && startIndex < endIndexMax) {
132
- console.error(`无法将文本分成合适的批次, 存在过长的文本, 起始索引: ${startIndex}, 结束索引: ${endIndex}`);
133
- currentBatch = ['']
134
- endIndex = startIndex + 1;
135
- }
136
- resultBatches.push(currentBatch);
137
- startIndex = endIndex;
138
- }
139
- return resultBatches;
140
- }
141
-
142
- private async translateAll(texts: string[], from: string, to: string): Promise<string[]> {
143
- const batches = this.smartBatch(texts);
144
- const results: string[][] = new Array(batches.length);
145
- const concurrencyLimit = this.maxBatchSize;
146
- let index = 0;
147
-
148
- let batchCount = 0;
149
- const processBatch = async (): Promise<void> => {
150
- while (index < batches.length) {
151
- const batchIndex = index++;
152
- const batch = batches[batchIndex];
153
- batchCount++;
154
- console.log(`batchCount++: ${batchCount}`)
155
- const batchResults = await this.translateBatch(batch, from, to);
156
- await new Promise(resolve => setTimeout(resolve, 1000));
157
- batchCount--;
158
- console.log(`batchCount--: ${batchCount}`)
159
- results[batchIndex] = batchResults;
160
- }
161
- };
162
-
163
- const workers: Promise<void>[] = [];
164
- for (let i = 0; i < concurrencyLimit && i < batches.length; i++) {
165
- workers.push(processBatch());
166
- }
167
- await Promise.all(workers);
168
- // await processBatch()
169
-
170
- return results.flat();
171
- }
172
-
173
- langMap: Map<string, string> = new Map([
174
- ["zh_cn", "zh"],
175
- ["zh_hk", "cht"],
176
- ["en_us", "en"],
177
- ])
178
- async translateCsvRows(rows: string[][], fromLang: string, toLangs?: string[]): Promise<number> {
179
- if (rows.length === 0) {
180
- console.log("CSV文件为空");
181
- return 0;
182
- }
183
- let header = rows[0];
184
- let fromLangIndex = header.indexOf(fromLang);
185
- if (fromLangIndex === -1) {
186
- console.error(`未找到 ${fromLang} 列`);
187
- return 0;
188
- }
189
- if (!this.langMap.has(fromLang)) {
190
- console.error(`未找到 ${fromLang} 的目标语言`);
191
- return 0;
192
- }
193
- let fromLang2 = this.langMap.get(fromLang);
194
- if (fromLang2 == null) {
195
- console.error(`未找到 ${fromLang} 的目标语言`);
196
- return 0;
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;
218
- }
219
- console.log(`开始翻译 ${needTranslateTexts.length} 条内容...`);
220
- for (let curLangIndex = 0; curLangIndex < header.length; curLangIndex++) {
221
- let lang = header[curLangIndex];
222
- lang = lang.trim().toLowerCase();
223
- if (lang == "key") {
224
- continue;
225
- }
226
- if (toLangs != undefined && !toLangs.includes(lang)) {
227
- continue;
228
- }
229
- if (lang != fromLang && this.langMap.has(lang)) {
230
- const toLang = this.langMap.get(lang);
231
- if (toLang == null) {
232
- console.error(`未找到 ${lang} 的目标语言`);
233
- continue;
234
- }
235
- const translations = await this.translateAll(needTranslateTexts, fromLang2, toLang);
236
- for (let j = 0; j < needTranslateIndices.length; j++) {
237
- const rowIndex = needTranslateIndices[j];
238
- while (rows[rowIndex].length < header.length) {
239
- rows[rowIndex].push("");
240
- }
241
- rows[rowIndex][curLangIndex] = translations[j];
242
- }
243
- }
244
- }
245
- return needTranslateTexts.length;
246
- }
247
- async translateCsv(filePath: string, outFilePath: string, fromLang: string = "auto", toLangs?: string[]): Promise<void> {
248
- const csvUtils = new CSVUtils(filePath);
249
- const rows = await csvUtils.parseCsv();
250
- const translatedCount = await this.translateCsvRows(rows, fromLang, toLangs);
251
- if (translatedCount > 0) {
252
- await CSVUtils.writeCsv(outFilePath, rows);
253
- console.log(`翻译完成,已更新 ${translatedCount} 条内容到 ${outFilePath}`);
254
- }
255
- }
256
-
257
- public static async translateCsvWithLangs(filePath: string, outFilePath: string, fromLang: string, langs: string[], apiKey: string, appId: string) {
258
- const translator = new CsvAutoTranslator(apiKey, appId);
259
- await translator.translateCsv(filePath, outFilePath, fromLang, langs);
260
- }
261
- }
1
+ import * as crypto from "crypto";
2
+ import * as https from "https";
3
+ import { CSVUtils } from "./CSVUtils";
4
+
5
+ interface NiuTransResponse {
6
+ tgtList: Array<{
7
+ from: string;
8
+ to: string;
9
+ tgtText: string;
10
+ srcText?: string;
11
+ errorCode?: string;
12
+ errorMsg?: string;
13
+ }>;
14
+ resultCode: string;
15
+ resultMsg: string;
16
+ }
17
+
18
+ export class CsvAutoTranslator {
19
+ private apiKey: string;
20
+ private appId: string;
21
+ private apiUrl: string = "https://api.niutrans.com/v2/text/translate/array";
22
+ private maxBatchSize: number = 5;
23
+ private readonly MAX_BATCH_TEXT_COUNT: number = 50;
24
+ private readonly MAX_BATCH_JSON_SIZE: number = 4900;
25
+
26
+ constructor(apiKey: string, appId: string) {
27
+ this.apiKey = apiKey;
28
+ this.appId = appId;
29
+ }
30
+
31
+ private generateAuthStr(params: Record<string, string>): string {
32
+ const sortedKeys = Object.keys(params).sort();
33
+ const paramStr = sortedKeys
34
+ .filter(key => params[key] !== "" && params[key] !== undefined && params[key] !== null)
35
+ .map(key => `${key}=${params[key]}`)
36
+ .join("&");
37
+ const fullStr = `apikey=${this.apiKey}&${paramStr}`;
38
+ return crypto.createHash("md5").update(fullStr).digest("hex");
39
+ }
40
+
41
+ private async requestTranslate(texts: string[], from: string, to: string): Promise<NiuTransResponse> {
42
+ const timestamp = Date.now().toString();
43
+ const params: Record<string, string> = {
44
+ from: from,
45
+ to: to,
46
+ appId: this.appId,
47
+ timestamp: timestamp
48
+ };
49
+ const authStr = this.generateAuthStr(params);
50
+ const postData = JSON.stringify({
51
+ ...params,
52
+ srcText: texts,
53
+ authStr: authStr
54
+ });
55
+ const url = new URL(this.apiUrl);
56
+ const options: https.RequestOptions = {
57
+ hostname: url.hostname,
58
+ port: 443,
59
+ path: url.pathname,
60
+ method: "POST",
61
+ headers: {
62
+ "Content-Type": "application/json",
63
+ "Content-Length": Buffer.byteLength(postData)
64
+ }
65
+ };
66
+ return new Promise((resolve, reject) => {
67
+ const req = https.request(options, (res) => {
68
+ let data = "";
69
+ res.on("data", (chunk) => {
70
+ data += chunk;
71
+ });
72
+ res.on("end", async () => {
73
+ try {
74
+ let ret = JSON.parse(data);
75
+ await res.destroy();
76
+ resolve(ret);
77
+ } catch (e) {
78
+ reject(e);
79
+ }
80
+ });
81
+ });
82
+ req.on("error", reject);
83
+ req.write(postData);
84
+ req.end();
85
+ });
86
+ }
87
+
88
+ private async translateBatch(texts: string[], from: string, to: string): Promise<string[]> {
89
+ const response = await this.requestTranslate(texts, from, to);
90
+ if (response.resultCode !== "200") {
91
+ throw new Error(`翻译失败: ${response.resultMsg}`);
92
+ }
93
+ return response.tgtList.map(item => item.tgtText || "");
94
+ }
95
+
96
+ private estimateSingleTextJsonSize(text: string, isSingle: boolean): number {
97
+ return Buffer.byteLength(JSON.stringify(text)) + (isSingle ? 0 : 1);
98
+ }
99
+
100
+ estimateJsonSize(texts: string[]): number {
101
+ return Buffer.byteLength(JSON.stringify(texts));
102
+ }
103
+
104
+ smartBatch(texts: string[], batches?: string[][]): string[][] {
105
+ const resultBatches = batches || [];
106
+ let startIndex = 0;
107
+ while (startIndex < texts.length) {
108
+ let endIndexMax = Math.min(startIndex + this.MAX_BATCH_TEXT_COUNT, texts.length);
109
+ let batchByteLength = 2;
110
+ let currentBatchCount = 0;
111
+ // let currentBatch = texts.slice(startIndex, endIndexMax);
112
+ for (let i = startIndex; i < endIndexMax; i++) {
113
+ const text = texts[i];
114
+ const textSize = Buffer.byteLength(text) + (i >= 1 ? 1 : 0);
115
+ currentBatchCount++;
116
+ batchByteLength += textSize;
117
+ if (batchByteLength + textSize > this.MAX_BATCH_JSON_SIZE) {
118
+ break;
119
+ }
120
+ }
121
+ let endIndex = startIndex + currentBatchCount;
122
+ let currentBatch: string[] = texts.slice(startIndex, endIndex);
123
+ let currentSize = this.estimateJsonSize(currentBatch);
124
+ while (currentSize > this.MAX_BATCH_JSON_SIZE && currentBatch.length > 0) {
125
+ endIndex--;
126
+ let endText = texts[endIndex];
127
+ let endTextSize = this.estimateSingleTextJsonSize(endText, endIndex > startIndex + 1);
128
+ currentSize -= endTextSize;
129
+ currentBatch.pop();
130
+ }
131
+ if (currentBatch.length == 0 && startIndex < endIndexMax) {
132
+ console.error(`无法将文本分成合适的批次, 存在过长的文本, 起始索引: ${startIndex}, 结束索引: ${endIndex}`);
133
+ currentBatch = ['']
134
+ endIndex = startIndex + 1;
135
+ }
136
+ resultBatches.push(currentBatch);
137
+ startIndex = endIndex;
138
+ }
139
+ return resultBatches;
140
+ }
141
+
142
+ private async translateAll(texts: string[], from: string, to: string): Promise<string[]> {
143
+ const batches = this.smartBatch(texts);
144
+ const results: string[][] = new Array(batches.length);
145
+ const concurrencyLimit = this.maxBatchSize;
146
+ let index = 0;
147
+
148
+ let batchCount = 0;
149
+ const processBatch = async (): Promise<void> => {
150
+ while (index < batches.length) {
151
+ const batchIndex = index++;
152
+ const batch = batches[batchIndex];
153
+ batchCount++;
154
+ console.log(`batchCount++: ${batchCount}`)
155
+ const batchResults = await this.translateBatch(batch, from, to);
156
+ await new Promise(resolve => setTimeout(resolve, 1000));
157
+ batchCount--;
158
+ console.log(`batchCount--: ${batchCount}`)
159
+ results[batchIndex] = batchResults;
160
+ }
161
+ };
162
+
163
+ const workers: Promise<void>[] = [];
164
+ for (let i = 0; i < concurrencyLimit && i < batches.length; i++) {
165
+ workers.push(processBatch());
166
+ }
167
+ await Promise.all(workers);
168
+ // await processBatch()
169
+
170
+ return results.flat();
171
+ }
172
+
173
+ langMap: Map<string, string> = new Map([
174
+ ["zh_cn", "zh"],
175
+ ["zh_hk", "cht"],
176
+ ["en_us", "en"],
177
+ ])
178
+ async translateCsvRows(rows: string[][], fromLang: string, toLangs?: string[]): Promise<number> {
179
+ if (rows.length === 0) {
180
+ console.log("CSV文件为空");
181
+ return 0;
182
+ }
183
+ let header = rows[0];
184
+ let fromLangIndex = header.indexOf(fromLang);
185
+ if (fromLangIndex === -1) {
186
+ console.error(`未找到 ${fromLang} 列`);
187
+ return 0;
188
+ }
189
+ if (!this.langMap.has(fromLang)) {
190
+ console.error(`未找到 ${fromLang} 的目标语言`);
191
+ return 0;
192
+ }
193
+ let fromLang2 = this.langMap.get(fromLang);
194
+ if (fromLang2 == null) {
195
+ console.error(`未找到 ${fromLang} 的目标语言`);
196
+ return 0;
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;
218
+ }
219
+ console.log(`开始翻译 ${needTranslateTexts.length} 条内容...`);
220
+ for (let curLangIndex = 0; curLangIndex < header.length; curLangIndex++) {
221
+ let lang = header[curLangIndex];
222
+ lang = lang.trim().toLowerCase();
223
+ if (lang == "key") {
224
+ continue;
225
+ }
226
+ if (toLangs != undefined && !toLangs.includes(lang)) {
227
+ continue;
228
+ }
229
+ if (lang != fromLang && this.langMap.has(lang)) {
230
+ const toLang = this.langMap.get(lang);
231
+ if (toLang == null) {
232
+ console.error(`未找到 ${lang} 的目标语言`);
233
+ continue;
234
+ }
235
+ const translations = await this.translateAll(needTranslateTexts, fromLang2, toLang);
236
+ for (let j = 0; j < needTranslateIndices.length; j++) {
237
+ const rowIndex = needTranslateIndices[j];
238
+ while (rows[rowIndex].length < header.length) {
239
+ rows[rowIndex].push("");
240
+ }
241
+ rows[rowIndex][curLangIndex] = translations[j];
242
+ }
243
+ }
244
+ }
245
+ return needTranslateTexts.length;
246
+ }
247
+ async translateCsv(filePath: string, outFilePath: string, fromLang: string = "auto", toLangs?: string[]): Promise<void> {
248
+ const csvUtils = new CSVUtils(filePath);
249
+ const rows = await csvUtils.parseCsv();
250
+ const translatedCount = await this.translateCsvRows(rows, fromLang, toLangs);
251
+ if (translatedCount > 0) {
252
+ await CSVUtils.writeCsv(outFilePath, rows);
253
+ console.log(`翻译完成,已更新 ${translatedCount} 条内容到 ${outFilePath}`);
254
+ }
255
+ }
256
+
257
+ public static async translateCsvWithLangs(filePath: string, outFilePath: string, fromLang: string, langs: string[], apiKey: string, appId: string) {
258
+ const translator = new CsvAutoTranslator(apiKey, appId);
259
+ await translator.translateCsv(filePath, outFilePath, fromLang, langs);
260
+ }
261
+ }
package/src/RunConvert.ts CHANGED
@@ -1,3 +1,3 @@
1
- import { CmdExecutor } from "./CmdExecutor";
2
-
3
- CmdExecutor.runConvertWithCmdOptions()
1
+ import { CmdExecutor } from "./CmdExecutor";
2
+
3
+ CmdExecutor.runConvertWithCmdOptions()
@@ -1,3 +1,3 @@
1
- import { CmdExecutor } from "./CmdExecutor";
2
-
3
- CmdExecutor.runSlimCsvWithLangs()
1
+ import { CmdExecutor } from "./CmdExecutor";
2
+
3
+ CmdExecutor.runSlimCsvWithLangs()
@@ -1,3 +1,3 @@
1
- import { CmdExecutor } from "./CmdExecutor";
2
-
3
- CmdExecutor.runTranslateCsvWithCmdOptions()
1
+ import { CmdExecutor } from "./CmdExecutor";
2
+
3
+ CmdExecutor.runTranslateCsvWithCmdOptions()