td-octopus 0.1.7 → 0.1.9

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/README.md CHANGED
@@ -23,6 +23,10 @@ otp <cmd> [-args]
23
23
  otp init init 初始化配置文件
24
24
  otp translate translate 通过对比zh-CN目录,获取各语言未翻译的
25
25
  部分,并生成excel文件
26
+ otp back 回滚国际化翻译
27
+ otp download 下载思南翻译到本地-需要配置downloadUrl
28
+ otp reduce 校验数据,删除多余的翻译
29
+ otp knowledge knowledge 导出所有文件,并生成知识库的excel文件
26
30
 
27
31
  选项:
28
32
  -v, --version 显示版本号 [布尔]
@@ -43,6 +47,7 @@ copyright 2022 同盾
43
47
  "googleApiKey": "", // google翻译 这期没做
44
48
  "baiduApiKey": { "appId": "", "appKey": "" }, // 百度翻译 这期没做
45
49
  "baiduLangMap": { "en-US": "en" }, // 百度翻译 这期没做
50
+ "difyApiKey": { "appUrl": "", "appKey": "" }, // dify翻译
46
51
  "translateOptions": { "concurrentLimit": 10, "requestOptions": {} }, // google翻译 这期没做
47
52
  "fileSuffix": [".ts", ".js", ".vue", ".jsx", ".tsx"], // 支持符合JS语法的后缀名
48
53
  "defaultTranslateKeyApi": "Pinyin", // 默认生成的JSON key 使用拼音前5个
@@ -0,0 +1,9 @@
1
+ const { doExportKnowledge } = require('../src/export');
2
+
3
+ exports.command = 'knowledge';
4
+
5
+ exports.describe = 'knowledge 导出所有文件,并生成知识库的excel文件';
6
+
7
+ exports.handler = () => {
8
+ doExportKnowledge()
9
+ }
package/cmds/reduce.js ADDED
@@ -0,0 +1,9 @@
1
+ const { main } = require('../src/reduce');
2
+
3
+ exports.command = 'reduce';
4
+
5
+ exports.describe = 'reduce 将项目中多余的key删除';
6
+
7
+ exports.handler = () => {
8
+ main()
9
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "td-octopus",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "I18N tool",
5
5
  "author": "Anthony Li",
6
6
  "bin": {
@@ -5,6 +5,7 @@
5
5
  * @LastEditors: 郑泳健
6
6
  * @LastEditTime: 2023-06-12 11:33:34
7
7
  */
8
+ const _ = require('lodash');
8
9
  const {
9
10
  syncFiles,
10
11
  otpPath,
@@ -25,7 +26,6 @@ function doExport() {
25
26
  (async () => {
26
27
  const otpConfig = getProjectConfig()
27
28
  const distLang = otpConfig && otpConfig.distLangs
28
- const baiduApiKey = otpConfig && otpConfig.baiduApiKey
29
29
 
30
30
  if (!Array.isArray(distLang)) {
31
31
  console.log(`请配置${OCTOPUS_CONFIG_FILE}里面的distLangs`)
@@ -43,20 +43,57 @@ function doExport() {
43
43
  const langFlat = flatObject(currentLangMap);
44
44
  spinner.start('正在同步文件的key')
45
45
  // 删除掉多余的key,增加新的key,同时提取没有翻译过的key的列表
46
- const { fileKeyValueList, addList, allList } = await getAdjustLangObjAndAddList(lang, langFlat, zhCNflat, baiduApiKey, spinner);
46
+ const { fileKeyValueList, allList } = await getAdjustLangObjAndAddList({ lang, langObj: langFlat, zhCNObj: zhCNflat, spinner });
47
47
  spinner.succeed('同步文件的key成功')
48
48
  // 重写文件
49
49
  rewriteFiles(fileKeyValueList, lang);
50
50
  spinner.start(`正在生成${lang} excel`)
51
51
  // 生成excel
52
- generateExcel(allList, otpPath + '/' + lang, lang);
52
+ generateExcel(_.sortBy(allList, it=>it[1]), otpPath + '/' + lang, lang);
53
53
  spinner.succeed(`生成${lang} excel成功`)
54
54
  }
55
55
 
56
- spinner.stop('translate成功')
56
+ spinner.stop('export成功')
57
+ })()
58
+ }
59
+
60
+ function doExportKnowledge() {
61
+ (async () => {
62
+ const otpConfig = getProjectConfig()
63
+ const distLang = otpConfig && otpConfig.distLangs
64
+
65
+ if (!Array.isArray(distLang)) {
66
+ console.log(`请配置${OCTOPUS_CONFIG_FILE}里面的distLangs`)
67
+ return;
68
+ }
69
+ spinner.start('正在同步文件')
70
+ await syncFiles(distLang);
71
+ spinner.succeed('同步文件成功')
72
+
73
+ const zhCN = syncLang('zh-CN');
74
+ const zhCNflat = flatObject(zhCN);
75
+
76
+ for (const lang of distLang) {
77
+ const currentLangMap = syncLang(lang);
78
+ const langFlat = flatObject(currentLangMap);
79
+ spinner.start('正在同步文件的key')
80
+ // 删除掉多余的key,增加新的key,同时提取没有翻译过的key的列表
81
+ const { fileKeyValueList, allList } = await getAdjustLangObjAndAddList({ lang, langObj: langFlat, zhCNObj: zhCNflat, spinner });
82
+ spinner.succeed('同步文件的key成功')
83
+ // 重写文件
84
+ rewriteFiles(fileKeyValueList, lang);
85
+ spinner.start(`正在生成${lang} excel`)
86
+ // 生成excel
87
+ const formatData = _.uniqBy(allList.filter(arr => !!arr[3]).map(arr => ([arr[1], arr[3]])), arr => JSON.stringify(arr));
88
+ generateExcel(_.sortBy(formatData, it => it[0]), otpPath + '/' + lang, lang, true);
89
+ spinner.succeed(`生成${lang} excel成功`)
90
+ }
91
+
92
+ spinner.stop('Export Knowledge 成功')
57
93
  })()
58
94
  }
59
95
 
60
96
  module.exports = {
61
- doExport
97
+ doExport,
98
+ doExportKnowledge
62
99
  }
@@ -9,7 +9,6 @@ const {
9
9
  findMatchKey,
10
10
  findMatchValue,
11
11
  getLangDir,
12
- translateKeyText,
13
12
  getProjectConfig,
14
13
  prettierFile
15
14
  } = require('../utils');
@@ -0,0 +1,79 @@
1
+ /*
2
+ * @Descripttion: 删除多余的key
3
+ * @Author: 郑泳健
4
+ * @Date: 2024-12-12 15:00:24
5
+ * @LastEditors: 郑泳健
6
+ * @LastEditTime: 2024-12-12 15:00:33
7
+ */
8
+ const path = require('path')
9
+ const fs = require('fs')
10
+ const shell = require('shelljs');
11
+ const { getSpecifiedFiles, isFile } = require('../utils/file')
12
+ const syncLang = require('../utils/syncLang')
13
+ const { flatObject, rewriteFiles, getFileKeyValueList, getAdjustLangObjAndAddList } = require('../utils/translate');
14
+ const { failInfo, highlightText } = require('../utils/colors');
15
+ const { getProjectConfig } = require('../utils/index')
16
+ const ora = require('ora');
17
+
18
+ const CONFIG = getProjectConfig();
19
+
20
+ const spinner = ora('开始reduce');
21
+
22
+ // 递归读取文件夹中所有 .js 文件内容
23
+ function readJsFiles(folderPath) {
24
+ let jsContent = '';
25
+
26
+ const files = fs.readdirSync(folderPath); // 读取文件夹内容
27
+ for (const file of files) {
28
+ const fullPath = path.join(folderPath, file);
29
+ const stats = fs.statSync(fullPath); // 获取文件或文件夹状态
30
+
31
+ if (stats.isDirectory()) {
32
+ // 如果是文件夹,递归处理
33
+ jsContent += readJsFiles(fullPath);
34
+ } else if (path.extname(fullPath) === '.js') {
35
+ // 如果是 .js 文件,读取文件内容
36
+ const content = fs.readFileSync(fullPath, 'utf-8');
37
+ jsContent += `\n/* File: ${fullPath} */\n${content}\n`;
38
+ }
39
+ }
40
+
41
+ return jsContent;
42
+ }
43
+
44
+ // 同步不同的语言包
45
+ function main() {
46
+ (async () => {
47
+ const distLang = Array.isArray(CONFIG.distLangs) ? CONFIG.distLangs : []
48
+
49
+ // 删除中文下躲雨的key
50
+ const zhCN = syncLang('zh-CN');
51
+ const zhCNFlat = flatObject(zhCN);
52
+ // 当前项目所有的key
53
+ const keys = Object.keys(zhCNFlat)
54
+ const totalText = readJsFiles(path.resolve(process.cwd(), 'src'))
55
+ let result = {}
56
+ for(let i in zhCNFlat){
57
+ if(totalText.includes(i)){
58
+ result[i] = zhCNFlat[i]
59
+ }
60
+ }
61
+
62
+ rewriteFiles(getFileKeyValueList(result), 'zh-CN')
63
+
64
+ for (const lang of distLang) {
65
+ const currentLangMap = syncLang(lang);
66
+ const langFlat = flatObject(currentLangMap);
67
+ spinner.start(`正在清理${lang}下多余的key`)
68
+ // 删除掉多余的key,增加新的key,同时提取没有翻译过的key的列表
69
+ const { fileKeyValueList } = await getAdjustLangObjAndAddList({ lang, langObj: langFlat, zhCNObj: result, spinner });
70
+ spinner.succeed(`已完成清理${lang}下多余的key`)
71
+ // 重写文件
72
+ rewriteFiles(fileKeyValueList, lang);
73
+ }
74
+ })()
75
+ }
76
+
77
+ module.exports = {
78
+ main
79
+ }
@@ -4,7 +4,7 @@ const path = require('path');
4
4
  const Parser = require('properties');
5
5
 
6
6
  const { getSpecifiedFiles, readFile, writeFile, isFile, isDirectory } = require('../utils/file');
7
- const { translateText, findMatchKey, findMatchValue, translateKeyText, getProjectConfig } = require('../utils');
7
+ const { translateText, findMatchKey, findMatchValue, getProjectConfig } = require('../utils');
8
8
  const { successInfo, failInfo, highlightText } = require('../utils/colors');
9
9
 
10
10
  const CONFIG = getProjectConfig();
@@ -5,6 +5,7 @@
5
5
  * @LastEditors: 郑泳健
6
6
  * @LastEditTime: 2023-06-12 11:33:51
7
7
  */
8
+ const _ = require('lodash');
8
9
  const {
9
10
  syncFiles,
10
11
  otpPath,
@@ -26,6 +27,7 @@ function translate() {
26
27
  const otpConfig = getProjectConfig()
27
28
  const distLang = otpConfig && otpConfig.distLangs
28
29
  const baiduApiKey = otpConfig && otpConfig.baiduApiKey
30
+ const difyApiKey = otpConfig && otpConfig.difyApiKey
29
31
 
30
32
  if (!Array.isArray(distLang)) {
31
33
  console.log(`请配置${OCTOPUS_CONFIG_FILE}里面的distLangs`)
@@ -41,16 +43,16 @@ function translate() {
41
43
  for (const lang of distLang) {
42
44
  const currentLangMap = syncLang(lang);
43
45
  const langFlat = flatObject(currentLangMap);
44
- spinner.start('正在同步文件的key')
46
+ spinner.start('正在同步文件的key');
45
47
  // 删除掉多余的key,增加新的key,同时提取没有翻译过的key的列表
46
- const { fileKeyValueList, addList } = await getAdjustLangObjAndAddList(lang, langFlat, zhCNflat, baiduApiKey, spinner);
47
- spinner.succeed('同步文件的key成功')
48
+ const { fileKeyValueList, addList } = await getAdjustLangObjAndAddList({ lang, langObj: langFlat, zhCNObj: zhCNflat, baiduApiKey, difyApiKey, spinner });
49
+ spinner.succeed('同步文件的key成功');
48
50
  // 重写文件
49
51
  rewriteFiles(fileKeyValueList, lang);
50
- spinner.start(`正在生成${lang} excel`)
52
+ spinner.start(`正在生成${lang} excel`);
51
53
  // 生成excel
52
- generateExcel(addList, otpPath + '/' + lang, lang);
53
- spinner.succeed(`生成${lang} excel成功`)
54
+ generateExcel(_.sortBy(addList, it=>it[1]), otpPath + '/' + lang, lang);
55
+ spinner.succeed(`生成${lang} excel成功`);
54
56
  }
55
57
 
56
58
  spinner.stop('translate成功')
@@ -12,6 +12,10 @@ const PROJECT_CONFIG = {
12
12
  appId: '',
13
13
  appKey: ''
14
14
  },
15
+ difyApiKey: {
16
+ apiUrl: '',
17
+ appKey: ''
18
+ },
15
19
  baiduLangMap: {
16
20
  ['en-US']: 'en'
17
21
  },
@@ -236,44 +236,6 @@ function flatten(obj, prefix = '') {
236
236
  return ret;
237
237
  }
238
238
 
239
- /**
240
- * 获取翻译源类型
241
- */
242
- async function getTranslateOriginType() {
243
- const { googleApiKey, baiduApiKey } = getProjectConfig();
244
- let translateType = ['Google', 'Baidu'];
245
- if (!googleApiKey) {
246
- translateType = translateType.filter(item => item !== 'Google');
247
- }
248
- if (!baiduApiKey || !baiduApiKey.appId || !baiduApiKey.appKey) {
249
- translateType = translateType.filter(item => item !== 'Baidu');
250
- }
251
- if (translateType.length === 0) {
252
- console.log('请配置 googleApiKey 或 baiduApiKey ');
253
- return {
254
- pass: false,
255
- origin: ''
256
- };
257
- }
258
- if (translateType.length == 1) {
259
- return {
260
- pass: true,
261
- origin: translateType[0]
262
- };
263
- }
264
- const { origin } = await inquirer.prompt({
265
- type: 'list',
266
- name: 'origin',
267
- message: '请选择使用的翻译源',
268
- default: 'Google',
269
- choices: ['Google', 'Baidu']
270
- });
271
- return {
272
- pass: true,
273
- origin: origin
274
- };
275
- }
276
-
277
239
  /**
278
240
  * 进度条加载
279
241
  * @param text
@@ -321,7 +283,6 @@ module.exports = {
321
283
  findMatchValue,
322
284
  flatten,
323
285
  lookForFiles,
324
- getTranslateOriginType,
325
286
  translateKeyText,
326
287
  spining,
327
288
  prettierFile
@@ -130,7 +130,9 @@ function getFileKeyValueList(adjustLangObj) {
130
130
  * @param {*} zhCNObj zh-CN 的key/value
131
131
  * @returns { fileKeyValueList: [{fileName: a, value: {"b.c": xx}}], addList: [["a.b.c": "dd"]] }
132
132
  */
133
- async function getAdjustLangObjAndAddList(lang, langObj = {}, zhCNObj = {}, baiduApiKey, spinner) {
133
+ async function getAdjustLangObjAndAddList({
134
+ lang, langObj = {}, zhCNObj = {}, baiduApiKey, difyApiKey, spinner
135
+ }) {
134
136
  const langObjKeys = Object.keys(langObj);
135
137
  // 调整后的语言包key/value
136
138
  const adjustLangObj = {};
@@ -154,10 +156,8 @@ async function getAdjustLangObjAndAddList(lang, langObj = {}, zhCNObj = {}, baid
154
156
 
155
157
  adjustLangObj[key] = langObj[key] || zhCNObj[key];
156
158
  }
157
- if (baiduApiKey) {
158
- spinner.text = `当前配置了百度翻译,预计翻译时间需要${(needAddList.length / 60)?.toFixed(2)}分钟,如果等不及,请先去掉百度翻译配置`
159
- }
160
- const addList = await combinText(needAddList, lang, spinner);
159
+
160
+ const addList = await combineText({ needAddList, lang, spinner, baiduApiKey, difyApiKey });
161
161
 
162
162
  return {
163
163
  fileKeyValueList: getFileKeyValueList(adjustLangObj),
@@ -171,20 +171,27 @@ async function getAdjustLangObjAndAddList(lang, langObj = {}, zhCNObj = {}, baid
171
171
  * 因为百度免费翻译有时候会抽风,会导致翻译结果出错,为了减少没被翻译的,所以现在设置一次性翻译200字
172
172
  * @param {*} needAddList
173
173
  */
174
- async function combinText(needAddList, lang, spinner) {
175
- const otpConfig = getProjectConfig()
176
- const { baiduApiKey, baiduLangMap } = otpConfig || {}
177
- const { appId, appKey } = baiduApiKey || {}
178
- const toLang = baiduLangMap?.[lang] || '';
179
-
174
+ async function combineText({ needAddList, lang, spinner, baiduApiKey, difyApiKey }) {
180
175
  if (!Array.isArray(needAddList)) {
181
176
  return []
182
177
  }
178
+ const otpConfig = getProjectConfig();
179
+ const { baiduLangMap } = otpConfig || {};
180
+ const _difyApiKey = difyApiKey || {};
181
+ if (_difyApiKey.appUrl && _difyApiKey.appKey) {
182
+ spinner.text = `当前配置了dify翻译,预计翻译时间需要${(needAddList.length / 9)?.toFixed(2)}分钟,如果等不及,请先去掉dify翻译配置`;
183
+ const backList = await difyTranslate({ needAddList, spinner, appUrl: _difyApiKey.appUrl, appKey: _difyApiKey.appKey });
184
+ return backList;
185
+ }
186
+
187
+ const { appId, appKey } = baiduApiKey || {}
188
+ const toLang = baiduLangMap?.[lang] || '';
183
189
 
184
190
  // 如果不配置百度翻译就直接返回
185
191
  if (!appId || !appKey || !toLang) {
186
192
  return needAddList;
187
193
  }
194
+ spinner.text = `当前配置了百度翻译,预计翻译时间需要${(needAddList.length / 60)?.toFixed(2)}分钟,如果等不及,请先去掉百度翻译配置`
188
195
  // 分组
189
196
  const groupList = groupByLength(needAddList, 200)
190
197
  // 分组翻译结果
@@ -196,6 +203,76 @@ async function combinText(needAddList, lang, spinner) {
196
203
  })
197
204
  }
198
205
 
206
+ /**
207
+ * 使用Dify API进行批量翻译
208
+ * @param {Object} params - 参数对象
209
+ * @param {Array} params.needAddList - 需要翻译的文本列表,格式为二维数组
210
+ * @param {Object} params.spinner - 进度显示对象
211
+ * @param {string} params.appUrl - Dify API地址
212
+ * @param {string} params.appKey - Dify API密钥
213
+ * @param {number} [params.maxLength=10] - 最大并发请求数
214
+ * @returns {Promise} 返回Promise,resolve时返回翻译结果数组
215
+ * @description 该函数通过并发请求Dify翻译API,并缓存翻译结果,同时更新进度显示
216
+ */
217
+ function difyTranslate({ needAddList, spinner, appUrl, appKey, maxLength = 10 }) {
218
+ return new Promise((resolve, reject) => {
219
+ const cacheMap = {};
220
+ const textList = needAddList.map(i => i[1]);
221
+ // 当前任务的索引
222
+ let index = -1;
223
+ // 当前正在执行的任务数
224
+ let runningTasks = 0;
225
+ // 已完成的任务数
226
+ let completedTasks = 0;
227
+ const processTask = () => {
228
+ index++;
229
+ const text = textList[index];
230
+ if (!text) {
231
+ if (runningTasks === 0) {
232
+ resolve(needAddList.map(arr => {
233
+ arr[3] = cacheMap[arr[1]] || '';
234
+ return arr;
235
+ }))
236
+ }
237
+ return
238
+ }
239
+ runningTasks++;
240
+ fetch(`${appUrl}/workflows/run`, {
241
+ method: 'POST',
242
+ headers: {
243
+ 'Content-Type': 'application/json',
244
+ 'Authorization': `Bearer ${appKey}`
245
+ },
246
+ body: JSON.stringify({
247
+ inputs: {
248
+ query: text,
249
+ },
250
+ user: 'admin',
251
+ response_mode: 'blocking'
252
+ })
253
+ }).then(res => res.json())
254
+ .then(res => {
255
+ const back = res?.data?.outputs?.result;
256
+ if (back && !/[\u4e00-\u9fa5]/.test(back)) {
257
+ cacheMap[text] = back;
258
+ }
259
+ completedTasks++;
260
+ spinner.text = `Dify翻译进度: ${completedTasks}/${textList.length}, 当前并发数: ${maxLength}`;
261
+ })
262
+ .catch((error)=>{
263
+ console.log('Dify翻译失败: ', error);
264
+ })
265
+ .finally(() => {
266
+ runningTasks--;
267
+ processTask();
268
+ });
269
+ };
270
+ for (let i = 0; i < maxLength; i++) {
271
+ processTask();
272
+ }
273
+ })
274
+ }
275
+
199
276
  /**
200
277
  * 对分组的结果进行翻译
201
278
  * @param {*} groupList
@@ -235,7 +312,7 @@ async function getTransformResultList(groupList, appId, appKey, fromLang, toLang
235
312
  * @returns
236
313
  */
237
314
  function groupByLength(groupList, max) {
238
- const list = [[]]
315
+ const list = []
239
316
  let str = ''
240
317
  // 变成${max}个字符一组的数组,用于一次百度翻译
241
318
  groupList.forEach((it) => {
@@ -295,14 +372,14 @@ function getDistRst(adjustLangObj) {
295
372
  * @param {*} addList 需要翻译的列表
296
373
  * @param {*} path 生成的路径
297
374
  */
298
- function generateExcel(addList, path, lang) {
299
- const excleData = [['需要翻译的字段', '中文', '百度翻译', '人工翻译'], ...addList];
375
+ function generateExcel(addList, path, lang, isKnowledge) {
376
+ const excelData = [isKnowledge ? ['中文', '人工翻译'] : ['需要翻译的字段', '中文', '百度翻译', '人工翻译'], ...addList];
300
377
 
301
378
  const options = {
302
- '!cols': [{ wpx: 100 }, { wpx: 100 }, { wpx: 100 }, { wpx: 100 }]
379
+ '!cols': isKnowledge ? [{ wpx: 100 }, { wpx: 100 }] : [{ wpx: 100 }, { wpx: 100 }, { wpx: 100 }, { wpx: 100 }]
303
380
  };
304
381
 
305
- const worksheet = XLSX.utils.aoa_to_sheet(excleData);
382
+ const worksheet = XLSX.utils.aoa_to_sheet(excelData);
306
383
  worksheet['!cols'] = options['!cols'];
307
384
 
308
385
  const workbook = XLSX.utils.book_new();