├── .gitignore ├── index.js ├── package.json ├── bin └── fanyi.js ├── README.md └── util.js /.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | node_modules 3 | .vscode 4 | .video 5 | .DS_Store -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | module.exports = require('./bin/fanyi.js'); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@wisestcoder/translate", 3 | "version": "0.0.2", 4 | "description": "翻译", 5 | "main": "index.js", 6 | "bin": { 7 | "fanyi": "index.js" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "axios": "^0.19.2", 16 | "chalk": "^4.1.0", 17 | "cheerio": "^1.0.0-rc.3", 18 | "cli-spinner": "^0.2.10", 19 | "is-chinese": "^1.2.9", 20 | "md5": "^2.2.1", 21 | "play-sound": "^1.1.3", 22 | "urlencode": "^1.1.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /bin/fanyi.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const process = require('process'); 3 | const cheerio = require('cheerio'); 4 | const isChinese = require('is-chinese'); 5 | const Spinner = require('cli-spinner').Spinner; 6 | const util = require('../util'); 7 | 8 | const word = process.argv[2]; 9 | const isCn = isChinese(word); 10 | const url = util.getUrl(word, isCn); 11 | 12 | const spinner = new Spinner('正在努力查询中.. %s'); 13 | spinner.setSpinnerString('|/-\\'); 14 | 15 | spinner.start(); 16 | axios({ url }) 17 | .then(({ data }) => { 18 | spinner.stop(true); 19 | const $ = cheerio.load(data, { 20 | ignoreWhitespace: true, 21 | xmlMode: true 22 | }); 23 | 24 | util.logger('关键词', util.getKeyword($, isCn), 'cyan'); 25 | util.logger('短语', util.getPhrase($, isCn), 'magenta'); 26 | util.logger('例句', util.getSentence($, isCn), 'green'); 27 | util.logger(); 28 | 29 | util.handleVideo(word, isCn); 30 | }).catch((err) => { 31 | spinner.stop(true); 32 | console.log(util.chalk.red(err)); 33 | }) 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 命令行翻译工具 2 | 3 | [![NPM version](https://img.shields.io/npm/v/@wisestcoder/translate.svg?style=flat)](https://npmjs.org/package/@wisestcoder/translate) 4 | [![npm](https://img.shields.io/npm/dt/@wisestcoder/translate.svg)](https://npmjs.org/package/@wisestcoder/translate) 5 | [![GitHub stars](https://img.shields.io/github/stars/@wisestcoderstCoder/translate.svg?style=social&label=Star)](https://github.com/WisestCoder/translate) 6 | [![GitHub forks](https://img.shields.io/github/forks/@wisestcoder/translate.svg?style=social&label=Fork)](https://github.com/WisestCoder/translate) 7 | 8 | 9 | ### Demo 10 | ![](https://github.com/wisestcoder/assert/blob/master/yumu_translate_1.jpg) 11 | 12 | ![](https://github.com/wisestcoder/assert/blob/master/yumu_translate_2.jpg) 13 | 14 | ### 安装 15 | 16 | ```bash 17 | npm install @wisestCoder/translate -g 18 | ``` 19 | 20 | ### 功能 21 | 22 | - [x] 支持命令行使用 23 | - [x] 中英互译(自动识别,无需指定源语言) 24 | - [x] 翻译单词 25 | - [x] 翻译短语 26 | - [x] 翻译例句 27 | - [x] 支持语音翻译(默认开启) 28 | 29 | ### 使用 30 | 31 | ```bash 32 | fanyi hello 33 | 34 | fanyi 你好 35 | ``` 36 | 37 | ### TODO 38 | - [ ] 支持多语种 39 | -------------------------------------------------------------------------------- /util.js: -------------------------------------------------------------------------------- 1 | const chalk = require("chalk"); 2 | const urlencode = require('urlencode'); 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const process = require('process'); 6 | const http = require('http'); 7 | const player = require('play-sound')(opts = {}); 8 | const os = require('os'); 9 | 10 | const HOMEDIR = os.homedir(); 11 | 12 | /** 13 | * 获取翻译后的关键词 14 | * @param {*} $ 15 | * @param {*} isCn 16 | */ 17 | const getKeyword = ($, isCn) => { 18 | const keywords = []; 19 | 20 | if(isCn) { 21 | $('div.trans-container > ul p.wordGroup').find('a.search-js').each(function() { 22 | keywords.push( $(this).text().replace(/\s+/g," ")); 23 | }); 24 | } else { 25 | result = $('div#phrsListTab > div.trans-container > ul').find('li').each(function() { 26 | keywords.push($(this).text().replace(/\s+/g," ")); 27 | }); 28 | } 29 | 30 | return keywords.slice(0, 4); 31 | } 32 | 33 | /** 34 | * 获取翻译后的短语 35 | * @param {*} $ 36 | * @param {*} isCn 37 | */ 38 | const getPhrase = ($, isCn) => { 39 | const phrase = []; 40 | 41 | if(isCn) { 42 | $('div#authTransToggle ul.wordGroup>li.wordGroup').find('span.def').each(function() { 43 | phrase.push($(this).text().trim().replace(/\s+/g," ")); 44 | }); 45 | } else { 46 | $('div#wordGroup2').find('p.wordGroup').each(function() { 47 | phrase.push($(this).text().trim().replace(/\s+/g," ")); 48 | }) 49 | } 50 | 51 | return phrase.slice(0, 4); 52 | } 53 | 54 | const getSentence = ($, isCn) => { 55 | const sentence = []; 56 | 57 | $('div#bilingual>ul').find('li').each(function() { 58 | sentence.push($(this).text().trim().replace(/\s+/g," ")); 59 | }); 60 | 61 | return sentence.slice(0, 4); 62 | } 63 | 64 | const logger = (title = '', list = [], color = 'blue') => { 65 | if (!(list && list.length)) { 66 | console.log(); 67 | return; 68 | } 69 | 70 | console.log(); 71 | console.log(chalk[color](`[${title}]`)); 72 | list.forEach((item) => { 73 | console.log(` ${chalk[color](item)}`); 74 | }); 75 | } 76 | 77 | const getUrl = (word, isCn) => { 78 | return isCn 79 | ? `http://dict.youdao.com/w/en/${urlencode(word)}` 80 | : `http://dict.youdao.com/w/${urlencode(word)}`; 81 | } 82 | 83 | const getVideoPath = () => { 84 | const dir = path.join(HOMEDIR, '.video'); 85 | if (!fs.existsSync(dir)) { 86 | fs.mkdirSync(dir, { recursive: true }); 87 | } 88 | return path.normalize(`${dir}/translate.mp3`) 89 | }; 90 | 91 | const deleteVideo = () => { 92 | fs.unlinkSync(getVideoPath()); 93 | } 94 | 95 | const playVideo = () => { 96 | player.play(getVideoPath(), function(err) { 97 | if (err) throw err; 98 | deleteVideo(); 99 | }) 100 | } 101 | 102 | const handleVideo = (word, isCn) => { 103 | const req = http.request({ 104 | "method": 'GET', 105 | "hostname": 'tts.baidu.com', 106 | "path": `/text2audio?lan=${isCn ? 'zh' : 'en'}&ie=UTF-8&spd=2&text=${urlencode(word)}` 107 | }, function(res) { 108 | const chunks = []; 109 | res.on('error', function(err) { 110 | console.log(err); 111 | }) 112 | res.on("data", function(chunk) { 113 | chunks.push(chunk);  // 获取到的音频文件数据暂存到chunks里面 114 | }); 115 | 116 | res.on("end", function() { 117 | const body = Buffer.concat(chunks); 118 | fs.writeFileSync(getVideoPath(), body); 119 | playVideo(); 120 | }); 121 | }); 122 | 123 | req.end(); 124 | } 125 | 126 | module.exports = { 127 | getKeyword, 128 | getPhrase, 129 | getSentence, 130 | logger, 131 | chalk, 132 | getUrl, 133 | handleVideo, 134 | playVideo, 135 | deleteVideo, 136 | getVideoPath 137 | }; --------------------------------------------------------------------------------