命令行翻译工具
(温馨提示:注意保持package.json中版本一致,复制packeage.json,yarn install)
使用百度翻译工具API
使用Commander.js自带的功能
创建一个空目录,叫teanslate-reagen
(webstorm快捷键:ALT+1打开关闭项目目录;Alt + 2打开或关闭命令行)
yarn init -y
把它初始化为一个npm包,package.json中版本号改为0.0.1
tsc --init
生成tsconfig.json
配置文件,否则会报错
创建src目录放源代码,并在下面创建main.ts(console.log('hi'))
ts-node-dev src/main.ts
控制台输出hi,说明TS代码是可以执行的
如果不想每次都写ts-node-dev
,可以在package.json中配置
{
"name": "translate-reagen",
"version": "0.0.1",
"main": "index.js",
"scripts": {
"start": "ts-node-dev src/main.ts"
},
"license": "MIT"
}
直接运行yarn start
即可
既然要做命令行就要使用commander.js
,yarn add commander
既然做node的开发,就要安装node的类型声明文件yarn add --dev @types/node
这样在webstorm中使用node写代码的时候就会有条提示,且不会划线报错了
当看到typings/index.ts 说明Commander自带了TS的声明文件,因此就不用安装额外的东西了
创建 src/cli.ts
用来控制命令行(ts其实就是js,只不过多了类型等其他API)
接下来就是CRM学习法,先拷贝Commander.js官方文档中的代码 const program = new commander.Command();
Webstorm 会自动引入响应模块
声明一下版本
import commander from "commander";
const program = new commander.Command();
program.version('0.1.0')
// parse就是对参数进行解析
program.parse(process.argv);
运行ts-node-dev src/cli.ts -h
尝试修改下显示的cli.ts
program.version('0.1.0')
.name('fy-bd')
尝试修改下 [options],添加『用法』,"<>"表示必传的参数,“[]”表示可传的参数
program.version('0.1.0')
.name('fy-bd')
.usage('<a word>')
我们可以获取到当前命令行工具的版本号
因此,Commander.js是一个声明式写法,这样我们就写好了命令行工具的版本、名称及参数
添加我们自己的功能
获取用户传的参数(单词),然后去查这个单词的意思
// cli.ts
import commander from "commander";
import {translate} from "./main";
const program = new commander.Command();
program.version('0.1.0')
.name('fy-bd')
.usage('<a Word>')
.arguments('<Word>')
.action(function (word) {
translate(word)
});
// parse就是对参数进行解析
program.parse(process.argv);
translate函数哪里来的?在main.ts(主要功能实现)中写的
// main.ts
export const translate = (word:any) => {
console.log('word')
console.log(word)
}
这样我们就拿到了用户输入的单词
但是我们怎么把单词翻译出来?
接下来我们就要找一个免费翻译接口(推荐使用百度和有道的,国外的容易被墙)
如何使用呢?
拼接完整请求:
<======输出举例 =====>
正确情况:
{
"from": "en",
"to": "zh",
"trans_result": [
{
"src": "apple",
"dst": "苹果"
}
]
}
异常情况:
{
"error_code": "54001",
"error_msg": "Invalid Sign"
}
备注
- 修改appid为自己的百度翻译开放平台appid
- sign就是要传自己的秘钥,当然不能直接传,需要appid+q+salt+密钥的MD5值,即要这四个值的MD5值
- 搞一个MD5生成器,Google:
md5 Hash Generator
将appid、q(这里为apple)、salt就是个随机数这个随便(1435660288)、密钥放到md5自动生成器中,将得到的那一串东西,放到sign中
这样就在浏览器中实现了翻译效果了!!
但是我们怎么通过Node.js实现翻译效果呢?
我们需要借助Node.js中的https中的https.request()
来实现在Node.js发请求,之前是用Node.js响应
使用Node.js调用百度翻译API
Node.js中构造查询参数
复制可运行的代码
import * as https from "https";
export const translate = (word:any) => {
const options = {
hostname: 'www.baidu,com',
port: 443,
path: '/',
method: 'GET'
};
const req = https.request(options, (res) => {
console.log('statusCode:', res.statusCode);
console.log('headers:', res.headers);
res.on('data', (d) => {
process.stdout.write(d);
});
});
req.on('error', (e) => {
console.error(e);
});
req.end();
}
运行一下,我们就得到了百度首页的源代码
那么接下来我们把访问地址改为百度翻译的地址(不要http),路径为/api/trans/vip/translate
那么查询参数(?...
)我们怎么传?
我们需要借助Node.js中的querystring - 查询字符串
它有一个querystring.stringify
方法
例子:
querystring.stringify({ foo: 'bar', baz: ['qux', 'quux'], corge: '' });
// 返回 'foo=bar&baz=qux&baz=quux&corge='
querystring.stringify({ foo: 'bar', baz: 'qux' }, ';', ':');
// 返回 'foo:bar;baz:qux'
我们不妨尝试console.log(querystring.stringify({name: 'LG', sign: '1234'}));
这样我们就构造出了我们想要的查询参数了
Node.js中构MD5
const query: string = querystring.stringify({
q: word,
from: 'en',
to: 'zh',
appid: '???',
salt: Math.random(),
sign: '???'
})
- 类似appid和密钥不能直接写,因为代码要上传到github
- sign是MD5算出来的,安装下md5
yarn add md5
,因为我们的文件是ts,因此要看下md5包文件夹下是否自带了ts相关的文件,我们发现没有ts文件,说明这个库不支持ts,如果在ts中手动引入md5,发现webstorm根本没提示,不知道它有哪些方法属性。那怎么办呢,借助前端社区力量,有些人帮MD5写了TS的类型声明文件,我们安装吧yarm add --dev @types/md5
,一般类型声明文件都使用--dev
,因为只有开发者使用- 什么叫完美兼容TS,在webstorm中有相关API的用法,也有相关模块的ts的类型声明文件xxx.d.ts
这样我们的Node.js中也有MD5了
这时候Node.js中的sign只要像在浏览器中那样再实现一次就好了
import * as https from "https";
import * as querystring from "querystring";
import md5 from "md5";
export const translate = (word: any) => {
const appid = '???'
const appSecret = '???'
const salt = Math.random()
const sign = md5(appid + word + salt + appSecret)
const query: string = querystring.stringify({
q: word,
from: 'en',
to: 'zh',
appid,
salt,
sign,
})
const options = {
hostname: 'api.fanyi.baidu.com',
port: 443,
path: '/api/trans/vip/translate' + query,
method: 'GET'
};
const req = https.request(options, (res) => {
res.on('data', (d) => {
process.stdout.write(d);
});
});
req.on('error', (e) => {
console.error(e);
});
req.end();
};
运行一下:
由于我们的appid和秘钥还没上传,所以暂时会返回报错信息
处理appid和秘钥 & 使用TypeScript声明BaiduResult
如何不让appid和密钥不被泄露
为了使得appid和密钥不被泄露,我们在src目录下新建private.ts
,然后再在根目录下新建一个.gitignore
文件,在里面添加private.ts
,这样在提交文件的时候就不会提交了
虽然在git中没有上传,但是打包之后appid和密钥还是在代码里,实在是没办法纯前端实现不暴露(除非将加密的过程放到服务器中)
tips: 如果发现yarn 安装的命令,运行命令的时候提示找不到命令,就运行
yarn global bin
的,把得到的路径配置到path中
接着运行代码,我们把单词apple
传给百度翻译,返回给我们的不是error_code了,翻译的结果就放在dst
中,它就是apple的中文的utf-8的形式
得到response的消息体
之前在做静态服务器的时候,获取post的消息体是通过监听data方法,只要有data将它放到数组中即可,最后监听它的end方法
同理,这里获取response的消息体也是一样
const request = https.request(options, (response) => {
let chunks: any = []
// data是下载的翻译结果数据
response.on('data', (chunk) => {
chunks.push(chunk);
});
// end表示下载完了
response.on('end', () => {
const string = Buffer.concat(chunks).toString()
console.log(string)
const object = JSON.parse(string);
console.log(object)
})
});
这样就得到了纯粹的JSON字符串,再把它变成JS对象,再对它进行JS的后续操作
什么是Buffer(缓冲区)
缓冲区
缓冲区是内存空间的一部分。也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区。 缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区和输出缓冲区。
为什么要引入缓冲区
比如我们从磁盘里取信息,我们先把读出的数据放在缓冲区,计算机再直接从缓冲区中取数据,等缓冲区的数据取完后再去磁盘中读取,这样就可以减少磁盘的读写次数,再加上计算机对缓冲区的操作大大快于对磁盘的操作,故应用缓冲区可大大提高计算机的运行速度。
那我们怎么知道得到的结果object
有哪些属性呢?这是TS非常擅长的事情,通过下面的结果中的属性结合百度翻译文档中的说明,知道了这个对象拥有了哪些属性
注意:不要随便用简写,能用全写就全写,trans
英文中有跨性别意思,即从一种性别转换到另一种性别
那么我们就用TS声明一个类型
const request = https.request(options, (response) => {
let chunks: any = []
// data是下载的翻译结果数据
response.on('data', (chunk) => {
chunks.push(chunk);
});
// end表示下载完了
response.on('end', () => {
const string = Buffer.concat(chunks).toString()
type BaiduResult = {
error_code?: string;
error_msg?: string;
from: string;
to: string;
trans_result:{
src: string;
dst: string;
}[]
}
const object:BaiduResult = JSON.parse(string);
console.log(object)
})
});
备注:其中
error_code?
表示这个属性在这个对象中可能有,也可能没有,这是TS的一个语法trans_result:{ src: string; dst: string; }[]
表示trans_result
它是一个数组,数组中的每一项是一个对象
为什么要声明类型呢?
如果不看文档,我们不知道对象object
有这些属性,只有通过看文档才知道,现在呢?现在相当于把文档写在代码里!文档变了,我们只要改声明就行了
这样我们在写object
的时候就有完全不一样的感觉了,如果不写类型声明,我们想用object下的属性object.
,点什么呢,完全没提示,是不是又要去看文档?但是给object加上类型声明后object:BaiduResult
这样写代码就爽多了,不用频繁的在文档和代码之间来回的切换!!!
response.on('end', () => {
const string = Buffer.concat(chunks).toString()
type BaiduResult = {
error_code?: string;
error_msg?: string;
from: string;
to: string;
trans_result:{
src: string;
dst: string;
}[]
}
const object:BaiduResult = JSON.parse(string);
console.log(object.trans_result[0].dst);
})
处理报错
if(object.error_code){
if(object.error_msg === '52003'){
console.log('用户认证失败')
}else{
console.error(object.error_msg)
}
// 退出进程
process.exit(2)
}else{
// 获取到翻译的结果
console.log(object.trans_result[0].dst);
// 0表示没有错误
process.exit(0)
}
备注
process.exit([code])
process.exit() 方法以结束状态码 code 指示Node.js同步终止进程。 如果 code 未提供,此exit方法要么使用'success' 状态码 0 ,要么使用 process.exitCode 属性值,前提是此属性已被设置。
表驱动编程(map)消除多余的if
当项目逻辑越来越多,可能会出现下面这样弱鸡的代码:
if (object.error_msg === '52003') {
console.log('用户认证失败')
} else if (object.error_msg === '52004') {
console.log('...')
} else if (object.error_msg === '52005') {
console.log('...')
} else if (object.error_msg === '52006') {
console.log('...')
} else {
console.error(object.error_msg)
}
如何消除这样弱鸡的代码呢?
在最外层写一个map
const errorMap:any = {
52003: '用户认证失败',
52004: 'error2',
52005: 'error3',
52006: 'error4',
other: '服务器繁忙'
}
//object.err_code为errorMap中的key值
if (object.error_code) {
console.log(errorMap[object.error_code] || object.error_msg)
// 退出进程
process.exit(2)
} else {
// 获取到翻译的结果
console.log(object.trans_result[0].dst);
// 0表示没有错误
process.exit(0)
}
如果代码中有很多的if ... else ...
,那么一定是代码有问题,如果逻辑复杂,可以声明多个表,即多个map,表和表之间也可以关联
英译中 & 中译英
我们怎么知道用户是想英译中?还是想中译英?
把单词的第一个字母看一下呗~,借助一下正则中test
if (/[a-zA-Z]/.test(word[0])) {
// 英译中
from = 'en';
to = 'zh';
} else {
// 中译英
from = 'zh';
to = 'en';
}
如何发布TypeScript包到npm
将ts文件编译成对一个的js文件
我们要在package.json
中做一些配置,声明我们翻译的命令叫czd
(查字典)
注意:czd
对应的不是"src/cli.js"
"bin": {
"czd": "src/cli.js"
},
因为这个文件是不能被Node执行的,它只能被ts-node-dev
执行
那怎么办?
我们得要把代码进行编译,要把ts文件编译成js
怎么编译呢?
在全局安装了typescript
之后,就会得到一个tsc
的命令
如果没有初始化,需要使用tsc --init
初始化一下,就会得到一个tsconfig.json
的文件
我们要把我们编译后的代码放到dist目录,因此要修改tsconfig.json
中的outdir
- 编译:把开发的程序源码编译成可执行文件。
- 打包:将你开发后的可执行文件和必要的文档(如使用说明等)使用打包工具(如InstallShield)制作成软件包,就是我们通常用来安装软件的那个东西。
接着使用tsc -p .
,意思是把当前目录作为项目来编译
处理TS中的报错
如果命令行中报错,那有可能是因为ts并不知道一些变量的类型是什么,我们得告诉它
如: let chunks: Buffer[] = []
意思是chunks是Buffer类型的数组,如果我们不知道chunks
的类型就,console.log(chunks.constructor)
,如果此时报错,执行不了console.log()
那么就删除掉tsconfig.json
再运行一次,直到搞清楚chunks的类型为止,实在不行就标记为any(如果你用any,还用什么TS,干嘛不用JS)
TS的理念就是一开始你就要把什么都搞清楚(变量的类型)再写代码,什么都不清楚就不要写代码,还是老老实实用JS吧
TS和Node不是完美兼容的,为什么这么说呢?既然chunks的类型是Buffer类型的数组,那么chunk肯定是Buffer,怎么能是any呢?这点我们可以从Node 中 on的定义可以看到(ctrl+打击就可以跳转):
let chunks: Buffer[] = []
// data是下载的翻译结果数据
response.on('data', (chunk) => {
chunks.push(chunk);
});
这是因为Node偷懒了,它不想搞清楚chunk是什么类型,就写了any
一个TS的报错逻辑:
interface ErrorMap {
[key: string]: string
}
const errorMap: ErrorMap = {
52000: '成功',
52001: '请求超时,请重试',
52002: '系统错误,请重试',
52003: '未授权用户,请检查appid是否正确或服务是否开通',
54000: '必填参数为空,请检查是否少传参数 ',
54001: '签名错误,请检查您的签名生成方法 ',
54003: '访问频率受限,请降低您的调用频率,或进行身份认证后切换为高级版/尊享版 ',
54004: '账户余额不足,请前往管理控制台为账户充值 ',
54005: '长query请求频繁,请降低长query的发送频率,3s后再试 ',
58000: '客户端IP非法,检查个人资料里填写的IP地址是否正确,可前往开发者信息-基本信息修改',
58001: '译文语言方向不支持,检查译文语言是否在语言列表里',
58002: '服务当前已关闭,请前往管理控制台开启服务 ',
90107: '认证未通过或未生效,请前往我的认证查看认证进度 '
}
errorMap[object.error_code]
如果上面不声明errorMap
的类型就会报错,报错的原因是,errorMap中的只有13个,你现在把object.error_code
作为key,如果它不在13个key里面怎么办
因此我们需要告诉TS,它的key可以是任意的字符串,值也可以是任意的字符串
修改.gitginore && 添加shebang
tsc -p. 编译完成后我们发现,src下的ts文件都被编译成了对应的js文件,dist文件夹中的文件是不用上传到github中的,因为它是编译后的文件,因此要修改.gitignore
文件
src/private.ts
/dist
/node_modules
为了指定cli.js
只能使用Node执行,因此我们需要在cli.ts
中加上shebang(cli.ts
源代码是不需要shenbang的)
然后再使用tsc
编译.ts
扩展名的文件
准备发布
1. 指定要上传的文件
修改package.json
我们只需要上传dist目录下的所有js文件即可
"files": [
"dist/**/*.js"
],
2. 登录npm
注意:要把npm中使用的taobao切换为npm官方源
使用npm adduser
登录npm,输入用户名、密码、邮箱和一次性密码
3. 发布
使用npm publish
发布
如果修改了代码,一定要修改版本号重新npm publish
,同一个版本不能使用不同的代码,否则发布不出去
在根目录下添加README.md
,告诉别人如何使用你这个工具
使用下面的方法来更新版本也是可以的:
npm version patch
npm publish
最终效果
逐步完善功能
后续我们应该完善我们的翻译工具,对比优秀的翻译工具,发现不足和好的功能,逐步添加功能
比如npm中一个好用的翻译工具,额外还多了例句和发音
安装方法
yarn global add fanyi
给命令一个别名alias
如果不想分享出去,只想自己用,那么我们可以给命令添加一个alias
vi ~/.bashrc
或者打开这个文件进行编辑
alias fy='ts-node-dev /f/LG/node-snippet/translate-reagen/src/cli.ts'
注意绝对路径的格式
要使用绝对路径,如下图复制一下
运行source ~/.bashrc
再使用czd hello
就相当于ts-node-dev src/cli.ts hello
gitignore语法规范
package.json
中的files
的配置也是遵循这个规则
语法规范
- 空行或是以
#
开头的行即注释行将被忽略。 - 可以在前面添加正斜杠
/
来避免递归,下面的例子中可以很明白的看出来与下一条的区别。 - 可以在后面添加正斜杠
/
来忽略文件夹,例如build/
即忽略build文件夹。 - 可以使用
!
来否定忽略,即比如在前面用了*.apk
,然后使用!a.apk
,则这个a.apk不会被忽略。 *
用来匹配零个或多个字符,如*.[oa]
忽略所有以".o"或".a"结尾,*~
忽略所有以~
结尾的文件(这种文件通常被许多编辑器标记为临时文件);[]
用来匹配括号内的任一字符,如[abc]
,也可以在括号内加连接符,如[0-9]
匹配0至9的数;?
用来匹配单个字符。
# 忽略 .a 文件
*.a
# 但否定忽略 lib.a, 尽管已经在前面忽略了 .a 文件
!lib.a
# 仅在当前目录下忽略 TODO 文件, 但不包括子目录下的 subdir/TODO
/TODO
# 忽略 build/ 文件夹下的所有文件
build/
# 忽略 doc/notes.txt, 不包括 doc/server/arch.txt
doc/*.txt
# 忽略所有的 .pdf 文件 在 doc/ directory 下的
doc/**/*.pdf
备注
- 如果不使用还是最好将百度翻译api的服务功能停掉,否则如果由于账号的泄漏可能会导致付费
- 为什么代码中用
module.export.x = ...
导出,使用require
导入呢?
这是历史遗留的问题
✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘
- 最早是Node.js那波人,搞了一个
CommonJs
,并没有被写入规范
module.export.x = ...
const api = require('...')
api.x()
- 另外一波人不服,因为这种方式只能同步的引入,所以另外一波人又搞了一个
require.js
学名叫AMD
的模块导入导出的方法
✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘✘
- 不用管上面两种模块导入导出的方法,它们都是垃圾,因为标准里写了一个标准的方法
import
export const x = ...
默认都是使用标准的方法导入和导出,那为什么现在的代码中还有require
呢?因为以前写的垃圾代码还没死,垃圾还在,比如:MD5的代码,还是用moudle.export
来导出,并没有对代码进行升级,它没升级,那我们只能使用旧的方法去导入导出,总不能用新版的导入导出去导入导出旧版的代码吧
这点我们不用过于纠结,能用标准的导入导出就用标准的导入导出。或者我们直接使用模块就好了,其他的交给webstorm,它会帮我们使用不同的方法引入相应模块
- 项目中使用的TypeScript,因为我们是通过
ts-node-dev
来执行项目中的代码,只不过大部分和JS是相似的,如import * as xxx from yyy
,TypeScript推荐这样写,由于Node.js使用的还是主流的CommonJS
的导入导出方法,所以在使用TypeScript打包编译后的文件中,导入导出变成了exports.translate = function(){}
那Node.js能不能也支持import * as xxx from yyy
这种写法呢?
搜索node esmodule 开启
(Node9以上,兼容性差),乱七八糟的要求贼多,还不如直接使用TS
转载自:https://juejin.cn/post/7249286832160751673