td-octopus 0.0.1
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 +66 -0
- package/bin/main.js +20 -0
- package/cmds/extract.js +23 -0
- package/cmds/import.js +9 -0
- package/cmds/init.js +22 -0
- package/cmds/translate.js +9 -0
- package/package.json +38 -0
- package/src/extract/extract.js +332 -0
- package/src/extract/file.js +64 -0
- package/src/extract/findChineseText.js +351 -0
- package/src/extract/getLangData.js +73 -0
- package/src/extract/replace.js +244 -0
- package/src/import/index.js +62 -0
- package/src/init/index.js +47 -0
- package/src/lib/update.js +19 -0
- package/src/translate/index.js +54 -0
- package/src/utils/colors.js +28 -0
- package/src/utils/const.js +45 -0
- package/src/utils/index.js +309 -0
- package/src/utils/syncLang.js +25 -0
- package/src/utils/translate.js +289 -0
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# octopus
|
|
2
|
+
====
|
|
3
|
+
|
|
4
|
+
## 安装
|
|
5
|
+
|
|
6
|
+
```
|
|
7
|
+
|
|
8
|
+
npm install td-octopus -g
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## 使用
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
otp <cmd> [-args]
|
|
16
|
+
|
|
17
|
+
命令:
|
|
18
|
+
otp extract [dirPath] [prefix] extract [dirPath] [prefix]
|
|
19
|
+
一键批量替换指定文件夹下的所有文案
|
|
20
|
+
otp import [langs] import [langs]
|
|
21
|
+
将excel中人工翻译的部分替换未翻译的key
|
|
22
|
+
otp init init 初始化配置文件
|
|
23
|
+
otp translate translate 通过对比zh-CN目录,获取各语言未翻译的
|
|
24
|
+
部分,并生成excel文件
|
|
25
|
+
|
|
26
|
+
选项:
|
|
27
|
+
-v, --version 显示版本号 [布尔]
|
|
28
|
+
-h, --help 显示帮助信息 [布尔]
|
|
29
|
+
|
|
30
|
+
copyright 2022 同盾
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## otp-config.json 说明
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
{
|
|
38
|
+
"otpDir": "./.octopus", // 语言包目录
|
|
39
|
+
"proType": "react", // 项目类型
|
|
40
|
+
"srcLang": "zh-CN", // 提取中文目录
|
|
41
|
+
"distLangs": [ "en-US", "zh-TW" ], // 需要转换的语言
|
|
42
|
+
"googleApiKey": "", // google翻译 这期没做
|
|
43
|
+
"baiduApiKey": { "appId": "", "appKey": "" }, // 百度翻译 这期没做
|
|
44
|
+
"baiduLangMap": { "en-US": "en" }, // 百度翻译 这期没做
|
|
45
|
+
"translateOptions": { "concurrentLimit": 10, "requestOptions": {} }, // google翻译 这期没做
|
|
46
|
+
"fileSuffix": [".ts", ".js", ".vue", ".jsx", ".tsx"], // 支持符合JS语法的后缀名
|
|
47
|
+
"defaultTranslateKeyApi": "Pinyin", // 默认生成的JSON key 使用拼音前5个
|
|
48
|
+
"importI18N": "import I18N from 'src/utils/I18N';",
|
|
49
|
+
"include": [], // 需要翻译的目录&也可以命令行输入参数
|
|
50
|
+
"exclude": [] // 过滤目录&文件
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+

|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
package/bin/main.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const checkUpdate = require('../src/lib/update');
|
|
4
|
+
|
|
5
|
+
const curTime = new Date();
|
|
6
|
+
const year = curTime.getFullYear();
|
|
7
|
+
|
|
8
|
+
checkUpdate()
|
|
9
|
+
|
|
10
|
+
const pkg = require('../package');
|
|
11
|
+
const argv = require('yargs')
|
|
12
|
+
.usage('$0 <cmd> [-args]')
|
|
13
|
+
.commandDir('../cmds')
|
|
14
|
+
.demand(1)
|
|
15
|
+
.version(pkg.version)
|
|
16
|
+
.alias('v', 'version')
|
|
17
|
+
.help()
|
|
18
|
+
.alias('h', 'help')
|
|
19
|
+
.epilog(`copyright ${year} 同盾`)
|
|
20
|
+
.argv;
|
package/cmds/extract.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const { isString } = require('lodash');
|
|
3
|
+
const { extractAll } = require('../src/extract/extract');
|
|
4
|
+
|
|
5
|
+
exports.command = 'extract [dirPath] [prefix]';
|
|
6
|
+
|
|
7
|
+
exports.describe = 'extract [dirPath] [prefix] 一键批量替换指定文件夹下的所有文案';
|
|
8
|
+
|
|
9
|
+
exports.handler = async (argv) => {
|
|
10
|
+
if (argv.prefix) {
|
|
11
|
+
console.log('请指定翻译后文案 key 值的前缀 extract src xxx');
|
|
12
|
+
}
|
|
13
|
+
if (isString(argv.prefix) && !new RegExp(/^([-_a-zA-Z1-9$]+)+$/).test(argv.prefix)) {
|
|
14
|
+
console.log('字母、下滑线、破折号、$ 字符组成的变量名');
|
|
15
|
+
} else {
|
|
16
|
+
const extractAllParams = {
|
|
17
|
+
prefix: isString(argv.prefix) && argv.prefix,
|
|
18
|
+
dirPath: isString(argv.dirPath) && argv.dirPath,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
extractAll(extractAllParams);
|
|
22
|
+
}
|
|
23
|
+
}
|
package/cmds/import.js
ADDED
package/cmds/init.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const { initProject } = require('../src/init');
|
|
2
|
+
const inquirer = require('inquirer');
|
|
3
|
+
const { spining } = require('../src/utils')
|
|
4
|
+
|
|
5
|
+
exports.command = 'init';
|
|
6
|
+
|
|
7
|
+
exports.describe = 'init 初始化配置文件';
|
|
8
|
+
|
|
9
|
+
exports.handler = async () => {
|
|
10
|
+
const answers = await inquirer.prompt([
|
|
11
|
+
{
|
|
12
|
+
type: 'list',
|
|
13
|
+
name: 'type',
|
|
14
|
+
message: '请选择项目类型',
|
|
15
|
+
choices: ['react', 'vue', 'typescript'],
|
|
16
|
+
default: 'react'
|
|
17
|
+
}
|
|
18
|
+
]);
|
|
19
|
+
spining('初始化项目', async () => {
|
|
20
|
+
initProject(answers);
|
|
21
|
+
});
|
|
22
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "td-octopus",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "I18N tool",
|
|
5
|
+
"author": "Anthony Li",
|
|
6
|
+
"bin": {
|
|
7
|
+
"otp": "bin/main.js"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@babel/core": "^7.18.2",
|
|
12
|
+
"baidu-translate": "^1.1.0",
|
|
13
|
+
"colors": "^1.4.0",
|
|
14
|
+
"glob": "^8.0.3",
|
|
15
|
+
"google-translate": "^3.0.0",
|
|
16
|
+
"inquirer": "^8.2.4",
|
|
17
|
+
"lodash": "^4.17.11",
|
|
18
|
+
"ora": "^5.4.1",
|
|
19
|
+
"pinyin-pro": "^3.3.1",
|
|
20
|
+
"prettier": "^2.7.1",
|
|
21
|
+
"shelljs": "^0.8.5",
|
|
22
|
+
"slash2": "^2.0.0",
|
|
23
|
+
"ts-node": "^10.8.1",
|
|
24
|
+
"typescript": "^3.2.2",
|
|
25
|
+
"update-notifier": "^4.1.0",
|
|
26
|
+
"vue-template-compiler": "^2.6.14",
|
|
27
|
+
"xlsx": "^0.18.5",
|
|
28
|
+
"yargs": "^15.3.1"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"I18N",
|
|
32
|
+
"tool"
|
|
33
|
+
],
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git@github.com:TDFE/octopus.git"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
const slash = require('slash2');
|
|
2
|
+
const _ = require('lodash');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
const { getSpecifiedFiles, readFile, writeFile, isFile, isDirectory } = require('./file');
|
|
6
|
+
const { translateText, findMatchKey, findMatchValue, translateKeyText, getProjectConfig } = require('../utils');
|
|
7
|
+
const { successInfo, failInfo, highlightText } = require('../utils/colors');
|
|
8
|
+
const { findChineseText } = require('./findChineseText');
|
|
9
|
+
const { getSuggestLangObj } = require('./getLangData');
|
|
10
|
+
|
|
11
|
+
const { replaceAndUpdate, hasImportI18N, createImportI18N } = require('./replace');
|
|
12
|
+
|
|
13
|
+
const CONFIG = getProjectConfig();
|
|
14
|
+
|
|
15
|
+
function formatExclude(exclude){
|
|
16
|
+
return (exclude || []).map(p => path.resolve(process.cwd(), p));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function removeLangsFiles(files) {
|
|
20
|
+
const langsDir = path.resolve(process.cwd(), CONFIG.otpDir);
|
|
21
|
+
return files.filter(file => {
|
|
22
|
+
const completeFile = path.resolve(process.cwd(), file);
|
|
23
|
+
return !completeFile.includes(langsDir);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 递归匹配项目中所有的代码的中文
|
|
29
|
+
*/
|
|
30
|
+
function findAllChineseText(dir) {
|
|
31
|
+
const first = dir.split(',')[0];
|
|
32
|
+
|
|
33
|
+
let files = [];
|
|
34
|
+
if (isDirectory(first)) {
|
|
35
|
+
const dirPath = path.resolve(process.cwd(), dir);
|
|
36
|
+
files = getSpecifiedFiles(dirPath, formatExclude(CONFIG.exclude));
|
|
37
|
+
} else {
|
|
38
|
+
files = removeLangsFiles(dir.split(','));
|
|
39
|
+
}
|
|
40
|
+
const filterFiles = files.filter(file => {
|
|
41
|
+
let flag = false;
|
|
42
|
+
for (let index = 0; index < CONFIG.fileSuffix.length; index++) {
|
|
43
|
+
const element = CONFIG.fileSuffix[index];
|
|
44
|
+
flag = file.endsWith(element);
|
|
45
|
+
if (flag) {
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return (isFile(file) && flag);
|
|
50
|
+
});
|
|
51
|
+
const allTexts = filterFiles.reduce((pre, file) => {
|
|
52
|
+
const code = readFile(file);
|
|
53
|
+
const texts = findChineseText(code, file);
|
|
54
|
+
// 调整文案顺序,保证从后面的文案往前替换,避免位置更新导致替换出错
|
|
55
|
+
const sortTexts = _.sortBy(texts, obj => -obj.range.start);
|
|
56
|
+
if (texts.length > 0) {
|
|
57
|
+
console.log(`${highlightText(file)} 发现 ${highlightText(texts.length)} 处中文文案`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return texts.length > 0 ? pre.concat({ file, texts: sortTexts }) : pre;
|
|
61
|
+
}, []);
|
|
62
|
+
|
|
63
|
+
return allTexts;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 处理作为key值的翻译原文
|
|
68
|
+
*/
|
|
69
|
+
function getTransOriginText(text) {
|
|
70
|
+
// 避免翻译的字符里包含数字或者特殊字符等情况,只过滤出汉字和字母
|
|
71
|
+
const reg = /[a-zA-Z\u4e00-\u9fa5]+/g;
|
|
72
|
+
const findText = text.match(reg) || [];
|
|
73
|
+
const transOriginText = findText ? findText.join('').slice(0, 5) : '中文符号';
|
|
74
|
+
|
|
75
|
+
return transOriginText;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @param currentFilename 文件路径
|
|
80
|
+
* @returns string[]
|
|
81
|
+
*/
|
|
82
|
+
function getSuggestion(currentFilename) {
|
|
83
|
+
let suggestion = [];
|
|
84
|
+
const suggestPageRegex = /\/pages\/\w+\/([^\/]+)\/([^\/\.]+)/;
|
|
85
|
+
|
|
86
|
+
if (currentFilename.includes('/pages/')) {
|
|
87
|
+
suggestion = currentFilename.match(suggestPageRegex);
|
|
88
|
+
}
|
|
89
|
+
if (suggestion) {
|
|
90
|
+
suggestion.shift();
|
|
91
|
+
}
|
|
92
|
+
/** 如果没有匹配到 Key */
|
|
93
|
+
if (!(suggestion && suggestion.length)) {
|
|
94
|
+
const names = slash(currentFilename).split('/');
|
|
95
|
+
const fileName = _.last(names);
|
|
96
|
+
let fileKey = fileName.split('.')[0].replace(new RegExp('-', 'g'), '_');
|
|
97
|
+
let dir = names[names.length - 2].replace(new RegExp('-', 'g'), '_');
|
|
98
|
+
if (dir === fileKey) {
|
|
99
|
+
suggestion = [dir];
|
|
100
|
+
} else {
|
|
101
|
+
suggestion = [dir, fileKey];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return suggestion;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* 统一处理key值,已提取过的文案直接替换,翻译后的key若相同,加上出现次数
|
|
110
|
+
* @param currentFilename 文件路径
|
|
111
|
+
* @param langsPrefix 替换后的前缀
|
|
112
|
+
* @param translateTexts 翻译后的key值
|
|
113
|
+
* @param targetStrs 当前文件提取后的文案
|
|
114
|
+
* @returns any[] 最终可用于替换的key值和文案
|
|
115
|
+
*/
|
|
116
|
+
function getReplaceableStrs(currentFilename, langsPrefix, translateTexts, targetStrs) {
|
|
117
|
+
const finalLangObj = getSuggestLangObj();
|
|
118
|
+
const virtualMemory = {};
|
|
119
|
+
const suggestion = getSuggestion(currentFilename);
|
|
120
|
+
|
|
121
|
+
const replaceableStrs = targetStrs.reduce((prev, curr, i) => {
|
|
122
|
+
const key = findMatchKey(finalLangObj, curr.text);
|
|
123
|
+
if (!virtualMemory[curr.text]) {
|
|
124
|
+
if (key) {
|
|
125
|
+
virtualMemory[curr.text] = key;
|
|
126
|
+
return prev.concat({
|
|
127
|
+
fileName:currentFilename,
|
|
128
|
+
target: curr,
|
|
129
|
+
key,
|
|
130
|
+
targetStrs,
|
|
131
|
+
needWrite: false
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
const transText = translateTexts[i] && _.camelCase(translateTexts[i]);
|
|
135
|
+
let suffix = suggestion.length ? suggestion.join('.') + '.' : '';
|
|
136
|
+
suffix = suffix.toLocaleLowerCase();
|
|
137
|
+
let transKey = `${suffix}${transText}`;
|
|
138
|
+
if (langsPrefix) {
|
|
139
|
+
transKey = `${langsPrefix}.${transText}`;
|
|
140
|
+
}
|
|
141
|
+
let occurTime = 1;
|
|
142
|
+
// 防止出现前四位相同但是整体文案不同的情况
|
|
143
|
+
while (
|
|
144
|
+
findMatchValue(finalLangObj, transKey) !== curr.text &&
|
|
145
|
+
_.keys(finalLangObj).includes(`${transKey}${occurTime >= 2 ? occurTime : ''}`)
|
|
146
|
+
) {
|
|
147
|
+
occurTime++;
|
|
148
|
+
}
|
|
149
|
+
if (occurTime >= 2) {
|
|
150
|
+
transKey = `${transKey}${occurTime}`;
|
|
151
|
+
}
|
|
152
|
+
virtualMemory[curr.text] = transKey;
|
|
153
|
+
finalLangObj[transKey] = curr.text;
|
|
154
|
+
return prev.concat({
|
|
155
|
+
fileName:currentFilename,
|
|
156
|
+
target: curr,
|
|
157
|
+
key: transKey,
|
|
158
|
+
targetStrs,
|
|
159
|
+
needWrite: true
|
|
160
|
+
});
|
|
161
|
+
} else {
|
|
162
|
+
return prev.concat({
|
|
163
|
+
fileName:currentFilename,
|
|
164
|
+
target: curr,
|
|
165
|
+
key: virtualMemory[curr.text],
|
|
166
|
+
targetStrs,
|
|
167
|
+
needWrite: true
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}, []);
|
|
171
|
+
|
|
172
|
+
return replaceableStrs;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* 递归匹配项目中所有的代码的中文
|
|
177
|
+
* @param {dirPath} 文件夹路径
|
|
178
|
+
*/
|
|
179
|
+
function extractAll({ dirPath, prefix }) {
|
|
180
|
+
const searchErrorMsg=[]; // 检索失败
|
|
181
|
+
const extractAction = []; // 执行翻译行为
|
|
182
|
+
const proType = CONFIG.proType;
|
|
183
|
+
const dirArr = dirPath ? [dirPath] : CONFIG.include && CONFIG.include.length > 0 ? CONFIG.include : ['./'];
|
|
184
|
+
// 去除I18N
|
|
185
|
+
const langsPrefix = prefix ? prefix : null;
|
|
186
|
+
// 翻译源配置错误,则终止
|
|
187
|
+
const origin = CONFIG.defaultTranslateKeyApi || 'Pinyin';
|
|
188
|
+
if (!['Pinyin', 'Google', 'Baidu'].includes(CONFIG.defaultTranslateKeyApi)) {
|
|
189
|
+
console.log(
|
|
190
|
+
`opt 仅支持 ${highlightText('Pinyin、Google、Baidu')},请修改 ${highlightText('defaultTranslateKeyApi')} 配置项`
|
|
191
|
+
);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const allTargetStrs = _.flatten(dirArr.map(findAllChineseText));
|
|
196
|
+
if (allTargetStrs.length === 0) {
|
|
197
|
+
console.log(highlightText('没有发现可替换的文案!'));
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// 对当前文件进行文案检索
|
|
202
|
+
const generateSearch = async (item, proType) => {
|
|
203
|
+
const currentFilename = item.file;
|
|
204
|
+
|
|
205
|
+
// 过滤掉模板字符串内的中文,避免替换时出现异常
|
|
206
|
+
const targetStrs = item.texts.reduce((pre, strObj, i) => {
|
|
207
|
+
// 因为文案已经根据位置倒排,所以比较时只需要比较剩下的文案即可
|
|
208
|
+
const afterStrs = item.texts.slice(i + 1);
|
|
209
|
+
if (afterStrs.some(obj => strObj.range.end <= obj.range.end)) {
|
|
210
|
+
return pre;
|
|
211
|
+
}
|
|
212
|
+
return pre.concat(strObj);
|
|
213
|
+
}, []);
|
|
214
|
+
|
|
215
|
+
const len = item.texts.length - targetStrs.length;
|
|
216
|
+
if (len > 0) {
|
|
217
|
+
searchErrorMsg.push(`${currentFilename}中存在 ${highlightText(len)} 处文案,请避免在模板字符串的变量中嵌套中文`);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
let translateTexts;
|
|
222
|
+
|
|
223
|
+
if (origin !== 'Google') {
|
|
224
|
+
// 翻译中文文案,百度和pinyin将文案进行拼接统一翻译
|
|
225
|
+
const delimiter = origin === 'Baidu' ? '\n' : '$';
|
|
226
|
+
const translateOriginTexts = targetStrs.reduce((prev, curr, i) => {
|
|
227
|
+
const transOriginText = getTransOriginText(curr.text);
|
|
228
|
+
if (i === 0) {
|
|
229
|
+
return transOriginText;
|
|
230
|
+
}
|
|
231
|
+
return `${prev}${delimiter}${transOriginText}`;
|
|
232
|
+
}, []);
|
|
233
|
+
|
|
234
|
+
translateTexts = await translateKeyText(translateOriginTexts, origin);
|
|
235
|
+
} else {
|
|
236
|
+
// google并发性较好,且未找到有效的分隔符,故仍然逐个文案进行翻译
|
|
237
|
+
const translatePromises = targetStrs.reduce((prev, curr) => {
|
|
238
|
+
const transOriginText = getTransOriginText(curr.text);
|
|
239
|
+
return prev.concat(translateText(transOriginText, 'en_US'));
|
|
240
|
+
}, []);
|
|
241
|
+
|
|
242
|
+
[...translateTexts] = await Promise.all(translatePromises);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (translateTexts.length === 0) {
|
|
246
|
+
failInfo(`${currentFilename}未得到翻译结果!`);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// 记录替换对象
|
|
251
|
+
const replaceableStrs = getReplaceableStrs(currentFilename, langsPrefix, translateTexts, targetStrs);
|
|
252
|
+
extractAction.push(replaceableStrs);
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
// 对文件进行替换
|
|
256
|
+
const generateReplace = async (item, proType) => {
|
|
257
|
+
let [currentFilename,targetStrs]=[,];
|
|
258
|
+
await item
|
|
259
|
+
.reduce((prev, obj) => {
|
|
260
|
+
return prev.then(() => {
|
|
261
|
+
currentFilename = obj.fileName;
|
|
262
|
+
targetStrs = obj.targetStrs;
|
|
263
|
+
console.log(`${currentFilename} 替换中...`);
|
|
264
|
+
return replaceAndUpdate(currentFilename, obj.target, `I18N.${obj.key}`, false, obj.needWrite, proType);
|
|
265
|
+
});
|
|
266
|
+
}, Promise.resolve())
|
|
267
|
+
.then(() => {
|
|
268
|
+
// 添加 import I18N
|
|
269
|
+
if (!hasImportI18N(currentFilename)) {
|
|
270
|
+
const code = createImportI18N(currentFilename);
|
|
271
|
+
writeFile(currentFilename, code);
|
|
272
|
+
}
|
|
273
|
+
successInfo(`${currentFilename} 替换完成,共替换 ${targetStrs.length} 处文案!`);
|
|
274
|
+
})
|
|
275
|
+
.catch(e => {
|
|
276
|
+
failInfo(e.message);
|
|
277
|
+
});
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
new Promise((resolve)=>{
|
|
281
|
+
allTargetStrs
|
|
282
|
+
.reduce((prev, current) => {
|
|
283
|
+
return prev.then(() => {
|
|
284
|
+
return generateSearch(current, proType);
|
|
285
|
+
});
|
|
286
|
+
}, Promise.resolve())
|
|
287
|
+
.then(() => {
|
|
288
|
+
successInfo('📢 📢 📢 📢 检索完成!');
|
|
289
|
+
// 如果全部检索成功则进行翻译
|
|
290
|
+
if(!searchErrorMsg?.length){
|
|
291
|
+
resolve();
|
|
292
|
+
}else{
|
|
293
|
+
failInfo("--------------------------------");
|
|
294
|
+
failInfo("但存在以下文件检索失败:");
|
|
295
|
+
failInfo("--------------------------------");
|
|
296
|
+
searchErrorMsg?.forEach(msg=>{
|
|
297
|
+
failInfo(msg||"替换失败");
|
|
298
|
+
})
|
|
299
|
+
}
|
|
300
|
+
}).catch(e=>{
|
|
301
|
+
failInfo(e||"替换失败");
|
|
302
|
+
});
|
|
303
|
+
}).then(()=>{
|
|
304
|
+
// 开始替换
|
|
305
|
+
// 提示翻译源
|
|
306
|
+
if (CONFIG.defaultTranslateKeyApi === 'Pinyin') {
|
|
307
|
+
console.log(
|
|
308
|
+
`当前使用 ${highlightText('Pinyin')} 作为key值的翻译源,若想得到更好的体验,可配置 ${highlightText(
|
|
309
|
+
'googleApiKey'
|
|
310
|
+
)} 或 ${highlightText('baiduApiKey')},并切换 ${highlightText('defaultTranslateKeyApi')}`
|
|
311
|
+
);
|
|
312
|
+
} else {
|
|
313
|
+
console.log(`当前使用 ${highlightText(CONFIG.defaultTranslateKeyApi)} 作为key值的翻译源`);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
console.log('即将截取每个中文文案的前5位翻译生成key值,并替换中...');
|
|
317
|
+
|
|
318
|
+
extractAction.reduce((prev,current)=>{
|
|
319
|
+
return prev.then(() => {
|
|
320
|
+
return generateReplace(current, proType);
|
|
321
|
+
});
|
|
322
|
+
}, Promise.resolve())
|
|
323
|
+
.then(() => {
|
|
324
|
+
successInfo('替换完成!');
|
|
325
|
+
}).catch(e=>{
|
|
326
|
+
failInfo(e||"替换成功");
|
|
327
|
+
});
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
module.exports = { extractAll };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 获取文件夹下符合要求的所有文件
|
|
7
|
+
* @function getSpecifiedFiles
|
|
8
|
+
* @param {string} dir 路径
|
|
9
|
+
* @param {exclude} 忽略文件夹或文件
|
|
10
|
+
*/
|
|
11
|
+
function getSpecifiedFiles(dir, exclude = []) {
|
|
12
|
+
return fs.readdirSync(dir).reduce((files, file) => {
|
|
13
|
+
const name = path.join(dir, file);
|
|
14
|
+
const isDirectory = fs.statSync(name).isDirectory();
|
|
15
|
+
const isFile = fs.statSync(name).isFile();
|
|
16
|
+
|
|
17
|
+
if (isDirectory) {
|
|
18
|
+
return files.concat(getSpecifiedFiles(name, exclude));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (isFile && !_.find(exclude, p=>name.includes(p))) {
|
|
22
|
+
return files.concat(name);
|
|
23
|
+
}
|
|
24
|
+
return files;
|
|
25
|
+
}, []);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 读取文件
|
|
30
|
+
* @param fileName
|
|
31
|
+
*/
|
|
32
|
+
function readFile(fileName) {
|
|
33
|
+
if (fs.existsSync(fileName)) {
|
|
34
|
+
return fs.readFileSync(fileName, 'utf-8');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 读取文件
|
|
40
|
+
* @param fileName
|
|
41
|
+
*/
|
|
42
|
+
function writeFile(filePath, file) {
|
|
43
|
+
if (fs.existsSync(filePath)) {
|
|
44
|
+
fs.writeFileSync(filePath, file);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 判断是文件
|
|
50
|
+
* @param path
|
|
51
|
+
*/
|
|
52
|
+
function isFile(path) {
|
|
53
|
+
return fs.statSync(path).isFile();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 判断是文件夹
|
|
58
|
+
* @param path
|
|
59
|
+
*/
|
|
60
|
+
function isDirectory(path) {
|
|
61
|
+
return fs.statSync(path).isDirectory();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = { getSpecifiedFiles, readFile, writeFile, isFile, isDirectory };
|