├── .gitignore ├── README.md ├── article.md ├── bin └── cli ├── code.js ├── code.汉.js ├── index.js ├── package.json └── translate_util.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | bower_components 30 | 31 | config/!default.js 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 把你的JS代码转换成中文版。。。 2 | 3 | 就是这么变态。 4 | 5 | 使用AST替换变量名,使用百度翻译服务翻译代码。 6 | 7 | 8 | 使用方式: 9 | 10 | ```bash 11 | sudo npm install hancode -g; 12 | 13 | #定位到某个目录 14 | 15 | hancode -s ./code.js 16 | 17 | #会在当前目录生成一个 code.汉.js 18 | 19 | #code.汉.js 的代码其实就是本库的中文版,测试可以跑通,并且结果一样。 20 | 21 | ``` 22 | 23 | todolist 24 | 25 | * 翻译属性名 26 | * 多个文件联合编译 27 | 28 | 29 | 示例: 30 | 31 | ```javascript 32 | var 丑化JS = require("uglify-js"); 33 | 34 | var _ = require("underscore"); 35 | 36 | var 翻译工具 = require("./translate_util"); 37 | 38 | var 翻译对象 = {}; 39 | 40 | module.exports = function(源代码, 回调) { 41 | var AST的代码 = 丑化JS.parse(源代码); 42 | var 无功节点 = []; 43 | var 参考节点 = []; 44 | var 跨语言 = []; 45 | AST的代码.walk(new 丑化JS.TreeWalker(function(结) { 46 | if (结 instanceof 丑化JS.AST_SymbolVar) { 47 | 无功节点.push(结); 48 | 跨语言.push(结.name); 49 | } 50 | })); 51 | AST的代码.walk(new 丑化JS.TreeWalker(function(结) { 52 | if (结 instanceof 丑化JS.AST_SymbolRef) { 53 | if (跨语言.indexOf(结.name) != -1) { 54 | 参考节点.push(结); 55 | 跨语言.push(结.name); 56 | } 57 | } 58 | })); 59 | 跨语言 = _.uniq(跨语言); 60 | console.log("翻译中"); 61 | 翻译工具(跨语言, function(E, 结果) { 62 | 翻译对象 = 结果; 63 | 使uniq对象(翻译对象); 64 | 无功节点.forEach(function(结) { 65 | 结.name = 翻译对象[结.name] || 结.name; 66 | }); 67 | 参考节点.forEach(function(结) { 68 | 结.name = 翻译对象[结.name] || 结.name; 69 | }); 70 | 回调(null, AST的代码.print_to_string({ 71 | beautify: true 72 | })); 73 | }); 74 | }; 75 | 76 | var 使uniq对象 = function(obj) { 77 | var 扫描值 = []; 78 | for (var 我 in obj) { 79 | var 价值 = obj[我]; 80 | if (扫描值.indexOf(价值) != -1) { 81 | var 重复计数 = 0; 82 | 扫描值.forEach(function(v) { 83 | if (v == 价值) { 84 | 重复计数++; 85 | } 86 | }); 87 | obj[我] = 价值 + "_" + 重复计数; 88 | } 89 | 扫描值.push(价值); 90 | } 91 | }; 92 | 93 | ``` -------------------------------------------------------------------------------- /article.md: -------------------------------------------------------------------------------- 1 | 2 | 不少人应该知道JS里可以用部分unicode字符来命名变量和方法。想想挺好玩的,不但可以用中文来编程,还可以用颜文字和emoji来编程,想想也是醉了。 3 | 4 | 不过还是不建议在真实项目中这样做,但是这并不代表不可以用嘛。 5 | 6 | 今天,跟大家介绍一下我写的一个《英文代码翻译器》可以用一句命令,把你的js代码翻译成中文变量命名并且还可以运行的js代码! 7 | 8 | 虽然这个项目只是开个玩笑,单其中涉及到的几个技术要点,还是值得一说的。本文主要分以下几节: 9 | 10 | 介绍 | 代码编译 | 翻译 | 全局变量处理 | 重复翻译结果处理 | 小结 11 | 12 | ###介绍 13 | 14 | github地址:https://github.com/xinyu198736/hancode 15 | 16 | 使用方式: 17 | 18 | ```bash 19 | sudo npm install hancode -g; 20 | 21 | #定位到某个目录 22 | 23 | hancode -s ./code.js 24 | 25 | #会在当前目录生成一个 code.汉.js 26 | 27 | #code.汉.js 的代码其实就是本库的中文版,测试可以跑通,并且结果一样。 28 | 29 | ``` 30 | 31 | 一个编译后的在线地址:http://f2e.souche.com/assets/js/souche.%E6%B1%89.js 32 | 33 | 本库主代码编译后的结果: 34 | 35 | ```javascript 36 | var 丑化JS = require("uglify-js"); 37 | 38 | var _ = require("underscore"); 39 | 40 | var 翻译工具 = require("./translate_util"); 41 | 42 | var 翻译对象 = {}; 43 | 44 | module.exports = function(源代码, 回调) { 45 | var AST的代码 = 丑化JS.parse(源代码); 46 | var 无功节点 = []; 47 | var 参考节点 = []; 48 | var 跨语言 = []; 49 | AST的代码.walk(new 丑化JS.TreeWalker(function(结) { 50 | if (结 instanceof 丑化JS.AST_SymbolVar) { 51 | 无功节点.push(结); 52 | 跨语言.push(结.name); 53 | } 54 | })); 55 | AST的代码.walk(new 丑化JS.TreeWalker(function(结) { 56 | if (结 instanceof 丑化JS.AST_SymbolRef) { 57 | if (跨语言.indexOf(结.name) != -1) { 58 | 参考节点.push(结); 59 | 跨语言.push(结.name); 60 | } 61 | } 62 | })); 63 | 跨语言 = _.uniq(跨语言); 64 | console.log("翻译中"); 65 | 翻译工具(跨语言, function(E, 结果) { 66 | 翻译对象 = 结果; 67 | 使uniq对象(翻译对象); 68 | 无功节点.forEach(function(结) { 69 | 结.name = 翻译对象[结.name] || 结.name; 70 | }); 71 | 参考节点.forEach(function(结) { 72 | 结.name = 翻译对象[结.name] || 结.name; 73 | }); 74 | 回调(null, AST的代码.print_to_string({ 75 | beautify: true 76 | })); 77 | }); 78 | }; 79 | 80 | var 使uniq对象 = function(obj) { 81 | var 扫描值 = []; 82 | for (var 我 in obj) { 83 | var 价值 = obj[我]; 84 | if (扫描值.indexOf(价值) != -1) { 85 | var 重复计数 = 0; 86 | 扫描值.forEach(function(v) { 87 | if (v == 价值) { 88 | 重复计数++; 89 | } 90 | }); 91 | obj[我] = 价值 + "_" + 重复计数; 92 | } 93 | 扫描值.push(价值); 94 | } 95 | }; 96 | ``` 97 | 98 | ###代码编译 99 | 100 | AST这个名词相信很多人都接触过,在做代码压缩的时候,都是先把js转换成AST语法树,然后操作ast语法树,最后再把ast语法树组装回代码,这样做的安全性比较高,比用单纯的正则匹配精确很多,之前我做过一个拆分代码拼接图案的库,用状态机自己写的,中间处理了很多兼容和条件,而且还不能百分百确保正确,而用AST的话其实就简单很多了。 101 | 102 | 关于编译理论的知识,说起来实在太长,这里就不深入了,主要讲讲我如何利用其替换js中的变量的。 103 | 104 | 现在比较流行的JS解析器主要是:(https://github.com/mishoo/UglifyJS2)[UglifyJS2] 105 | 106 | UglifyJS2的主要功能其实是用来压缩js代码,不过他的压缩过程大概分了三步:parse,transform,generate code;平常我们可能并不需要关心这三步,一个方法就能搞定,不过稍微研究下就会发现,这三步拆分开很有意思,特别是transform这一步,我们可以替换成自己的逻辑,然后做很多改编代码行为的事情。 107 | 108 | 看我们的实现代码: 109 | 110 | ``` 111 | var UglifyJS = require("uglify-js"); 112 | //解析ast 113 | var ast_code = UglifyJS.parse(source_code); 114 | 115 | //遍历ast,找出所有定义的变量 116 | //为何分成两次,为了排除没有定义而直接使用的全局变量 117 | ast_code.walk(new UglifyJS.TreeWalker(function(node){ 118 | //如果是变量定义的节点 119 | if(node instanceof UglifyJS.AST_SymbolVar){ 120 | varNodes.push(node); 121 | for_trans_words.push(node.name); 122 | } 123 | })); 124 | ast_code.walk(new UglifyJS.TreeWalker(function(node){ 125 | if(node instanceof UglifyJS.AST_SymbolRef){ 126 | if(for_trans_words.indexOf(node.name)!=-1){ 127 | refNodes.push(node); 128 | for_trans_words.push(node.name); 129 | } 130 | } 131 | })); 132 | ... 处理替换ast节点中的name 133 | 134 | //生成最终代码 135 | ast_code.print_to_string({ 136 | beautify:true 137 | }) 138 | 139 | ``` 140 | 141 | 解析和生成代码,UglifyJS都已经帮我们做好了,我们需要做的就是利用 UglifyJS.TreeWalker 来遍历所有的AST节点,判断其类型,然后替换或者转换。 142 | 143 | 我们这次的需求其实很简单,就是找到某些节点,把他们的name翻译成中文。 144 | 145 | 具体的实现见github。 146 | 147 | ###翻译 148 | 149 | hancode使用百度翻译来翻译英文到中文。事实上百度翻译支持很多语言的互译,使用也很简单。 150 | 151 | 百度翻译有专门的云服务平台,地址如下:http://api.fanyi.baidu.com/api/trans/product/index 152 | 153 | 前端乱炖的url seo也是用的这个api。 154 | 155 | ###全局变量处理 156 | 157 | 这个库里面有一些小细节,例如有些变量是全局变量,但是AST里并没有标记出来,需要自己判断,如果判断有Ref但是没有定义的变量,即为全局变量,例如 require/console 之类的。 158 | 159 | 这就是上面的代码片段中为何要遍历两次ast的原因。 160 | 161 | ###重复翻译结果处理 162 | 163 | 这也是一个细节,如果两个变量的意思很接近,可能会被翻译成同一个结果,如果直接使用会造成代码错乱。 164 | 165 | 其实一个简单的方法就可以处理: 166 | 167 | ``` 168 | /** 169 | * 把一个object里的value都变成唯一的,如果不是唯一的,给他加_1,如果出现两次则_2 170 | * @param obj 171 | */ 172 | var makeUniqObject = function(obj){ 173 | var scaned_values = [] 174 | for(var i in obj){ 175 | var value = obj[i]; 176 | if(scaned_values.indexOf(value)!=-1){ 177 | var repeat_count = 0; 178 | scaned_values.forEach(function(v){ 179 | if(v==value){ 180 | repeat_count++; 181 | } 182 | }) 183 | obj[i] = value+"_"+repeat_count 184 | } 185 | scaned_values.push(value); 186 | } 187 | } 188 | ``` 189 | 190 | ###结语 191 | 192 | 这个项目其实没什么高深的,但是的确挺好玩,可以拿去唬人。 193 | 194 | -------------------------------------------------------------------------------- /bin/cli: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 4 | var HanCode = require('../index.js') 5 | var path = require("path"); 6 | var argv = require('minimist')(process.argv.slice(2)); 7 | var source = path.join(process.cwd(),argv.s); 8 | var fs = require("fs"); 9 | HanCode(fs.readFileSync(source,'utf-8'),function(err,code){ 10 | var result_path = source.replace(".js",".汉.js"); 11 | console.log("编译成功,目标路径:"+result_path) 12 | fs.writeFileSync(result_path,code,'utf-8') 13 | }); -------------------------------------------------------------------------------- /code.js: -------------------------------------------------------------------------------- 1 | var UglifyJS = require("uglify-js"); 2 | var _ = require("underscore"); 3 | 4 | var translate_util = require("./translate_util") 5 | var translate_obj = {} 6 | 7 | 8 | 9 | 10 | 11 | module.exports = function(source_code,callback){ 12 | var ast_code = UglifyJS.parse(source_code); //解析ast 13 | var varNodes = [] //定义 14 | var refNodes = [] //引用 15 | var for_trans_words = [] //需要百度翻译的单词 16 | 17 | /*--------遍历ast,找出所有定义的变量-----*/ 18 | ast_code.walk(new UglifyJS.TreeWalker(function(node){ 19 | //console.log(Object.getPrototypeOf(node).TYPE 20 | if(node instanceof UglifyJS.AST_SymbolVar){ 21 | varNodes.push(node); 22 | for_trans_words.push(node.name); 23 | } 24 | 25 | 26 | })); 27 | ast_code.walk(new UglifyJS.TreeWalker(function(node){ 28 | if(node instanceof UglifyJS.AST_SymbolRef){ 29 | if(for_trans_words.indexOf(node.name)!=-1){ 30 | refNodes.push(node); 31 | for_trans_words.push(node.name); 32 | } 33 | } 34 | 35 | 36 | })); 37 | 38 | 39 | for_trans_words = _.uniq(for_trans_words); 40 | //console.log(for_trans_words) 41 | /*--------找出所有需要翻译的单词---------*/ 42 | console.log("翻译中"); 43 | translate_util(for_trans_words,function(e,result){ 44 | translate_obj = result; 45 | makeUniqObject(translate_obj); 46 | /*--------替换所有单词-----------------*/ 47 | varNodes.forEach(function(node){ 48 | node.name = translate_obj[node.name]||node.name; 49 | }) 50 | refNodes.forEach(function(node){ 51 | node.name = translate_obj[node.name]||node.name; 52 | }) 53 | callback(null,ast_code.print_to_string({ 54 | beautify:true 55 | })); 56 | 57 | }) 58 | } 59 | 60 | /** 61 | * 把一个object里的value都变成唯一的,如果不是唯一的,给他加_1 62 | * @param obj 63 | */ 64 | var makeUniqObject = function(obj){ 65 | var scaned_values = [] 66 | for(var i in obj){ 67 | var value = obj[i]; 68 | if(scaned_values.indexOf(value)!=-1){ 69 | 70 | var repeat_count = 0; 71 | scaned_values.forEach(function(v){ 72 | if(v==value){ 73 | repeat_count++; 74 | } 75 | }) 76 | obj[i] = value+"_"+repeat_count 77 | } 78 | scaned_values.push(value); 79 | } 80 | } 81 | 82 | 83 | -------------------------------------------------------------------------------- /code.汉.js: -------------------------------------------------------------------------------- 1 | var 丑化JS = require("uglify-js"); 2 | 3 | var _ = require("underscore"); 4 | 5 | var 翻译工具 = require("./translate_util"); 6 | 7 | var 翻译对象 = {}; 8 | 9 | module.exports = function(源代码, 回调) { 10 | var AST的代码 = 丑化JS.parse(源代码); 11 | var 无功节点 = []; 12 | var 参考节点 = []; 13 | var 跨语言 = []; 14 | AST的代码.walk(new 丑化JS.TreeWalker(function(结) { 15 | if (结 instanceof 丑化JS.AST_SymbolVar) { 16 | 无功节点.push(结); 17 | 跨语言.push(结.name); 18 | } 19 | })); 20 | AST的代码.walk(new 丑化JS.TreeWalker(function(结) { 21 | if (结 instanceof 丑化JS.AST_SymbolRef) { 22 | if (跨语言.indexOf(结.name) != -1) { 23 | 参考节点.push(结); 24 | 跨语言.push(结.name); 25 | } 26 | } 27 | })); 28 | 跨语言 = _.uniq(跨语言); 29 | console.log("翻译中"); 30 | 翻译工具(跨语言, function(E, 结果) { 31 | 翻译对象 = 结果; 32 | 使uniq对象(翻译对象); 33 | 无功节点.forEach(function(结) { 34 | 结.name = 翻译对象[结.name] || 结.name; 35 | }); 36 | 参考节点.forEach(function(结) { 37 | 结.name = 翻译对象[结.name] || 结.name; 38 | }); 39 | 回调(null, AST的代码.print_to_string({ 40 | beautify: true 41 | })); 42 | }); 43 | }; 44 | 45 | var 使uniq对象 = function(obj) { 46 | var 扫描值 = []; 47 | for (var 我 in obj) { 48 | var 价值 = obj[我]; 49 | if (扫描值.indexOf(价值) != -1) { 50 | var 重复计数 = 0; 51 | 扫描值.forEach(function(v) { 52 | if (v == 价值) { 53 | 重复计数++; 54 | } 55 | }); 56 | obj[我] = 价值 + "_" + 重复计数; 57 | } 58 | 扫描值.push(价值); 59 | } 60 | }; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var UglifyJS = require("uglify-js"); 2 | var _ = require("underscore"); 3 | 4 | var translate_util = require("./translate_util") 5 | var translate_obj = {} 6 | 7 | 8 | 9 | 10 | 11 | module.exports = function(source_code,callback){ 12 | var ast_code = UglifyJS.parse(source_code); //解析ast 13 | var varNodes = [] //定义 14 | var refNodes = [] //引用 15 | var for_trans_words = [] //需要百度翻译的单词 16 | 17 | /*--------遍历ast,找出所有定义的变量-----*/ 18 | //为何分成两次,为了排除没有定义而直接使用的全局变量 19 | ast_code.walk(new UglifyJS.TreeWalker(function(node){ 20 | //如果是变量定义的节点 21 | if(node instanceof UglifyJS.AST_SymbolVar){ 22 | varNodes.push(node); 23 | for_trans_words.push(node.name); 24 | } 25 | })); 26 | ast_code.walk(new UglifyJS.TreeWalker(function(node){ 27 | if(node instanceof UglifyJS.AST_SymbolRef){ 28 | if(for_trans_words.indexOf(node.name)!=-1){ 29 | refNodes.push(node); 30 | for_trans_words.push(node.name); 31 | } 32 | } 33 | })); 34 | 35 | 36 | for_trans_words = _.uniq(for_trans_words); 37 | //console.log(for_trans_words) 38 | /*--------找出所有需要翻译的单词---------*/ 39 | console.log("翻译中"); 40 | translate_util(for_trans_words,function(e,result){ 41 | translate_obj = result; 42 | makeUniqObject(translate_obj); 43 | /*--------替换所有单词-----------------*/ 44 | varNodes.forEach(function(node){ 45 | node.name = translate_obj[node.name]||node.name; 46 | }) 47 | refNodes.forEach(function(node){ 48 | node.name = translate_obj[node.name]||node.name; 49 | }) 50 | callback(null,ast_code.print_to_string({ 51 | beautify:true 52 | })); 53 | 54 | }) 55 | } 56 | 57 | /** 58 | * 把一个object里的value都变成唯一的,如果不是唯一的,给他加_1,如果出现两次则_2 59 | * @param obj 60 | */ 61 | var makeUniqObject = function(obj){ 62 | var scaned_values = [] 63 | for(var i in obj){ 64 | var value = obj[i]; 65 | if(scaned_values.indexOf(value)!=-1){ 66 | var repeat_count = 0; 67 | scaned_values.forEach(function(v){ 68 | if(v==value){ 69 | repeat_count++; 70 | } 71 | }) 72 | obj[i] = value+"_"+repeat_count 73 | } 74 | scaned_values.push(value); 75 | } 76 | } 77 | 78 | 79 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hancode", 3 | "version": "1.1.3", 4 | "description": "把js代码里的变量翻译成中文", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "minimist": "^1.2.0", 13 | "request": "^2.69.0", 14 | "uglify-js": "^2.6.2", 15 | "underscore": "^1.8.3" 16 | }, 17 | "bin": { 18 | "hancode": "./bin/cli" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /translate_util.js: -------------------------------------------------------------------------------- 1 | var appid = '20160225000013404'; 2 | var key = 'MoSvTIgeJB7mR7oMEwde'; 3 | 4 | 5 | var request = require("request") 6 | md5 = function(string) { 7 | var crypto, md5sum; 8 | crypto = require('crypto'); 9 | md5sum = crypto.createHash('md5'); 10 | md5sum.update(string, 'utf8'); 11 | return md5sum.digest('hex'); 12 | }; 13 | 14 | var wordTransformTemp = {}; 15 | var splitBigWord = function(word){ 16 | var tmp = ""; 17 | var result = []; 18 | for(var i=0;i=1&&!/[A-Z]/.test(word.charAt(i-1))){ 23 | (tmp!=="")&&result.push(tmp); 24 | tmp = ""; 25 | } 26 | 27 | tmp += word.charAt(i) 28 | } 29 | 30 | } 31 | (tmp!=="")&&result.push(tmp) 32 | return result; 33 | } 34 | var transformWord = function(word){ 35 | if(word.indexOf("_")!=-1){ 36 | wordTransformTemp[word.replace(/_/g," ")] = word; 37 | return word.replace(/_/g," "); 38 | } 39 | if(/[A-Z]/.test(word)){ 40 | var new_word = splitBigWord(word).join(" "); 41 | wordTransformTemp[new_word] = word; 42 | return new_word; 43 | } 44 | return word; 45 | } 46 | 47 | module.exports = function(strs,callback){ 48 | var to_trans_words = [] 49 | strs.forEach(function(str){ 50 | to_trans_words.push(transformWord(str)); 51 | }) 52 | strs = to_trans_words.join("\n") 53 | var salt = (new Date).getTime(); 54 | var query = strs; 55 | var from = 'en'; 56 | var to = 'zh'; 57 | var str1 = appid + query + salt +key; 58 | var sign = md5(str1); 59 | var qs = { 60 | q: query, 61 | appid: appid, 62 | salt: salt, 63 | from: from, 64 | to: to, 65 | sign: sign 66 | } 67 | request.get({ 68 | url: 'http://api.fanyi.baidu.com/api/trans/vip/translate', 69 | qs: { 70 | q: query, 71 | appid: appid, 72 | salt: salt, 73 | from: from, 74 | to: to, 75 | sign: sign 76 | } 77 | },function(e,r,body){ 78 | var result = {},res; 79 | try{ 80 | res = JSON.parse(body); 81 | if(res.trans_result){ 82 | res.trans_result.forEach(function(r){ 83 | var dst = r.dst.replace(/ /g,"_"); 84 | result[wordTransformTemp[r.src]||r.src] = dst; 85 | }) 86 | } 87 | }catch(e){ 88 | 89 | } 90 | callback(null,result); 91 | }); 92 | } 93 | --------------------------------------------------------------------------------