node实现国际化翻译以及文件读写

前几天有项任务是让我在几千个文件中,对指定内容进行繁体翻译。大概估算了一下有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:[{},{},{},...]//关键属性,
}
某个英文单词

思路

  1. 从每个文件中提取出要翻译的简体中文,并将其作为key存入Map(减轻网络资源消耗,从拒绝重复做起)。遍历完文件后,我再统一调用google翻译接口进行翻译,最后将繁体作为value存入Map中。
  2. 再遍历一遍文件,将文本转化为对象,给对象中对应属性添加繁体内容(从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版权协议,转载请附上原文出处链接和本声明。