前几天有项任务是让我在几千个文件中,对指定内容进行繁体翻译。大概估算了一下有4700多处需要进行繁体翻译,如果只靠去google翻译复制粘贴也需要2天多的时间,而且会非常无聊!!
所以,在这里利用了nodejs的文件读写以及google翻译的api帮助我来做这繁琐的事情
其中,编程用到的工具有:
1.typescript (npm安装即可)
2.lodash(npm安装即可)
3.fsPromise(用异步读取文件的目的只是想用用async)
4.google-translate-api(npm 安装即可)
注意:
安装完google-translate-api后需要将index.js中网址google.com改成google.cn(否则不翻墙连不上),client:t改成client:gtx。
不要频繁请求这个接口(我的ip被封了一次。泪目)
要处理的文本内容的结构是:
{
......//一些无关紧要的属性,
Data:[{},{},{},...]//关键属性,其中{}中的属性name要是以_chs为后缀的话,进行繁体,并将内容写入对应以_cht结尾的{}对象中(不存在的话,则创建)
}
某个英文单词
......
{
......//一些无关紧要的属性,
Data:[{},{},{},...]//关键属性,
}
某个英文单词
思路
- 从每个文件中提取出要翻译的简体中文,并将其作为key存入Map(减轻网络资源消耗,从拒绝重复做起)。遍历完文件后,我再统一调用google翻译接口进行翻译,最后将繁体作为value存入Map中。
- 再遍历一遍文件,将文本转化为对象,给对象中对应属性添加繁体内容(从Map中获取繁体资源),最后再将对象转化后文本,创建并写入新的文件。
代码
main.ts
//main.ts
import { findAndReadFiles } from './read';
import * as path from 'path';
import translate = require('google-translate-api');
import * as _ from 'lodash';
import { findFileAndWrite } from './write';
const cache: Map<string, string> = new Map();
const sourcePath: string = path.resolve('./asset');//来源目录
const targetPath: string = path.resolve('./target');//新建一个目标目录,将新文件存入这里
findAndReadFiles(sourcePath, cache).then(() => {
//走完findAndReadFiles后,会进入这里,并且需要的翻译内容都在cache的key中了
const keys = [ ...cache.keys() ];//所有需要翻译的中文简体
const keyChunk = _.chunk(keys, 200);//分成多个组后,每个组一次调用翻译api(因为一次翻译量有限)
(async function() {
for (let item of keyChunk) {
try {
const value = await translate(item.join('&'), { from: 'zh-cn', to: 'zh-tw' });
const result = value.text;
const resArr = result.split('&');//用&单词或短语啥的
item.forEach((key, index) => {
cache.set(key, resArr[index]);
});
} catch (e) {
console.error(e);
}
await sleep(4000);//调用完一次翻译后,休息4s,再进行一次调用,防止ip被禁
}
})().then(() => {
findFileAndWrite(sourcePath, targetPath, cache);
});
});
//睡眠函数
function sleep(interval: number) {
return new Promise((resolve) => {
setTimeout(resolve, interval);
});
}
read.ts
//read.ts
import { promises as fsPromise } from 'fs';
import * as path from 'path';
import * as _ from 'lodash';
import { FileContentObj } from './type/type';
//将文本转化为对象
function content2Obj(content: string): FileContentObj[] {
const reg = new RegExp(/*根据需求写*/);
const fileContentObjs: FileContentObj[] = [];
let regInfo;
let lastIndex = 0;
while ((regInfo = reg.exec(content))) {
if (lastIndex < regInfo.index) {
fileContentObjs.push(JSON.parse(content.substring(lastIndex, regInfo.index).trim()));
}
lastIndex = reg.lastIndex;
}
return fileContentObjs;
}
//收集中文
function collectWords(fileContentObjs: FileContentObj[]) {
const simp_suffix = /_chs$/i;
const words = _.flatten(fileContentObjs.map(/*自定义找出对应的结构*/)
.filter(/*过滤不要的数据*/)
.map(/*拿到最终数据*/);
return words;
}
//过滤掉不需要遍历的文件
function filterFile(files: string[]) {
return files.filter((file) => /*自定义*/);
}
/**
* 深度遍历
* @param pathName 文件路径
*/
async function findAndReadFiles(pathName: string, cache = new Map()) {
const fileStat = await fsPromise.stat(pathName);
if (fileStat.isDirectory()) {//文件夹
const files = await fsPromise.readdir(pathName);
for (let file of filterFile(files)) {
const currentFileName = path.join(pathName, file);
await findAndReadFiles(currentFileName, cache);
}
} else {
try {
const content = await fsPromise.readFile(pathName, { encoding: 'utf8' });
const fileContentObjs = content2Obj(content);
collectWords(fileContentObjs).forEach((word) => cache.set(word, null));
} catch (e) {
console.error(pathName, e);
}
}
}
export { findAndReadFiles, content2Obj, filterFile,collectWords };
write.ts
//write.ts
import { promises as fsPromise } from 'fs';
import * as path from 'path';
import * as _ from 'lodash';
import { filterFile, content2Obj, collectWords } from './read';
import { FileContentObj } from './type/type';
async function findFileAndWrite(sourcePath: string, targetPath: string, cache: Map<string, string>) {
try {
await fsPromise.mkdir(targetPath);//创建文件夹
} catch (e) {
console.error(e);
}
const files = await fsPromise.readdir(sourcePath);
for (let file of filterFile(files)) {
const sourceFilePath = path.join(sourcePath, file);
const targetFilePath = path.join(targetPath, file);
try {
const stats = await fsPromise.stat(sourceFilePath);
if (stats.isFile()) {
const content = await fsPromise.readFile(sourceFilePath, { encoding: 'utf8' });
const fileContentObjs = content2Obj(content);
const newFileContentObj = createNewFileObj(fileContentObjs, cache);
if (newFileContentObj) {
writeFile(<FileContentObj[]>newFileContentObj, targetFilePath);
}
} else if (stats.isDirectory()) {
await findFileAndWrite(sourceFilePath, targetFilePath, cache);
}
} catch (e) {
console.log(sourceFilePath);
console.error(e);
}
}
}
function createNewFileObj(fileContentObjs: FileContentObj[], cache: Map<string, string>) {
const words = collectWords(fileContentObjs);//获取当前文件中需要翻译的中文简体
if (!words.length) {
return null;
}
...
...
...//处理各种情况啥的(筛选,去重等等很多操作)
return //新的对象
}
async function writeFile(fileContentObjs: FileContentObj[], targetFilePath: string) {
let targetContent = '';
//将对象转化成文本
fileContentObjs.forEach((value) => {
targetContent += JSON.stringify(value, null, 4) + '\r\n某个英文单词\r\n';
});
if (targetContent) {
try {
await fsPromise.writeFile(//写入新文件
targetFilePath,
targetContent.trim()
);
} catch (e) {
console.log(targetFilePath);
console.error(e);
}
}
}
export { findFileAndWrite };
思考
代码中对于中文词语先是用&分隔,再去翻译,然后对翻译结果通过split(’&’)转化成数组。但后来想了想,其实繁体汉字和简体汉字是一一对应的,直接可以把cache中的key定为单个汉字,这样估计网络资源消耗更少。
另外,我在写文件时,需要去重,利用对象属性来实现去重很方效。
还有,利用sleep函数让请求不要那么频繁也蛮有效的。
效果
执行完脚本大概花了半分钟时间。通过git校验,最终所有文件中大概有1000个文件有修改,只有1个文件修改的问题(该文件内容本身有结构缺失导致的)。所以,以后需要先对文件进行校验,输出有问题的文件。
版权声明:本文为XIAOLONGJUANFENG原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。