├── .npmignore ├── demo.gif ├── .babelrc ├── .gitignore ├── test ├── fixtures │ └── cli.es6.js ├── yandex-spec.js └── translate-spec.js ├── .travis.yml ├── cli.js ├── .editorconfig ├── .eslintrc.json ├── license ├── package.json ├── src ├── yandex.js ├── cli.js └── index.js └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ewnd9/dictionary-cli/HEAD/demo.gif -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"], 3 | "plugins": ["lodash"] 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | *.log 3 | dist/* 4 | play.js 5 | .nyc_output 6 | coverage 7 | -------------------------------------------------------------------------------- /test/fixtures/cli.es6.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('babel-register'); 4 | require('babel-polyfill'); 5 | require('../../src/cli'); 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '6' 4 | - '5' 5 | - '4' 6 | after_script: 7 | - 'cat coverage/lcov.info | ./node_modules/.bin/coveralls' 8 | -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var updateNotifier = require('update-notifier'); 4 | var pkg = require('./package.json'); 5 | updateNotifier({ pkg: pkg }).notify(); 6 | 7 | require('./dist/cli'); 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "globals": { 4 | "Promise": true 5 | }, 6 | "env": { 7 | "browser": true, 8 | "node": true 9 | }, 10 | "plugins": [], 11 | "rules": { 12 | "arrow-parens": [ 13 | 2, 14 | "as-needed" 15 | ], 16 | "no-extra-semi": 2, 17 | "no-undef": 2, 18 | "no-unused-vars": 2, 19 | "no-var": 2, 20 | "semi": [ 21 | 2, 22 | "always" 23 | ] 24 | } 25 | } -------------------------------------------------------------------------------- /test/yandex-spec.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import 'babel-core/register'; 3 | 4 | import Yandex from './../src/yandex'; 5 | 6 | const translateKey = 'trnsl.1.1.20131018T175412Z.6e9fa29e525b4697.3542f82ffa6916d1ccd64201d8a72c023892ae5e'; 7 | const dictionaryKey = 'dict.1.1.20140616T070444Z.ecfe60ba07dd3ebc.9ce897a05d9daa488b050e5ec030f625d666530a'; 8 | 9 | const yandex = new Yandex(translateKey, dictionaryKey); 10 | 11 | test('#translate', async t => { 12 | const result = await yandex.translate('en', 'ru', 'java'); 13 | t.deepEqual(result, 'ява'); 14 | }); 15 | 16 | test('#dictionary', async t => { 17 | const result = await yandex.dictionary('en', 'ru', 'java'); 18 | t.is(result[0].text, 'java'); 19 | }); 20 | 21 | test('#spellCheck', async t => { 22 | const result = await yandex.spellCheck('en', 'continious powir'); 23 | t.is(result, 'continuous power'); 24 | }); 25 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 ewnd9.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dictionary-cli", 3 | "version": "3.0.1", 4 | "description": "Translate and look up usage examples via yandex services", 5 | "scripts": { 6 | "build": "babel -d dist src", 7 | "build:watch": "babel --watch -d dist src", 8 | "lint": "eslint 'src/**/*.js'", 9 | "postpublish": "rm -rf dist", 10 | "prepublish": "npm run build", 11 | "prepush": "npm run lint && npm test", 12 | "test": "npm run build && nyc --reporter=lcov --reporter=text ava" 13 | }, 14 | "bin": { 15 | "dictionary": "cli.js" 16 | }, 17 | "files": [ 18 | "cli.js", 19 | "dist" 20 | ], 21 | "preferGlobal": true, 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/ewnd9/dictionary-cli.git" 25 | }, 26 | "keywords": [ 27 | "yandex", 28 | "translate", 29 | "translator", 30 | "dictionary", 31 | "cli" 32 | ], 33 | "author": "ewnd9 ", 34 | "dependencies": { 35 | "chalk": "^1.1.1", 36 | "got": "^6.3.0", 37 | "lodash": "^4.13.1", 38 | "meow": "^3.7.0", 39 | "update-notifier": "^1.0.2" 40 | }, 41 | "devDependencies": { 42 | "ava": "^0.15.2", 43 | "babel-cli": "^6.4.0", 44 | "babel-core": "^6.4.0", 45 | "babel-eslint": "^6.0.0", 46 | "babel-plugin-lodash": "^3.2.0", 47 | "babel-polyfill": "^6.3.14", 48 | "babel-preset-es2015": "^6.3.13", 49 | "babel-register": "^6.4.3", 50 | "coveralls": "^2.11.6", 51 | "eslint": "^2.7.0", 52 | "get-stream": "^2.3.0", 53 | "husky": "^0.11.3", 54 | "nyc": "^6.6.1" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/yandex.js: -------------------------------------------------------------------------------- 1 | import got from 'got'; 2 | 3 | function Yandex(translateKey, dictionaryKey) { 4 | this.translateKey = translateKey; 5 | this.dictionaryKey = dictionaryKey; 6 | } 7 | 8 | const get = (url, query) => got(url, { json: true, query }).then(_ => _.body); 9 | 10 | // https://tech.yandex.ru/translate/doc/dg/reference/translate-docpage/ 11 | Yandex.prototype.translate = function(fromLang, toLang, text) { 12 | return get('https://translate.yandex.net/api/v1.5/tr.json/translate', { 13 | key: this.translateKey, 14 | lang: `${fromLang}-${toLang}`, 15 | text 16 | }) 17 | .then(_ => _.text.join(' ')); 18 | }; 19 | 20 | // https://tech.yandex.ru/dictionary/doc/dg/reference/lookup-docpage/ 21 | Yandex.prototype.dictionary = function(fromLang, toLang, text) { 22 | return get('https://dictionary.yandex.net/api/v1/dicservice.json/lookup', { 23 | key: this.dictionaryKey, 24 | lang: `${fromLang}-${toLang}`, 25 | ui: 'en', 26 | flags: 4, // MORPHO = 0x0004 - включает поиск по форме слова; 27 | text 28 | }) 29 | .then(_ => _.def); 30 | }; 31 | 32 | Yandex.prototype.spellCheck = function(lang, text) { 33 | return get('https://speller.yandex.net/services/spellservice.json/checkText', { 34 | options: 256, 35 | lang, 36 | text 37 | }) 38 | .then(corrections => { 39 | if (corrections.length > 0) { 40 | let prev = 0; 41 | let result = ''; 42 | 43 | corrections.forEach(correction => { 44 | result += text.slice(prev, correction.pos); 45 | result += correction.s; 46 | 47 | prev = correction.pos + correction.len; 48 | }); 49 | 50 | return result; 51 | } else { 52 | return text; 53 | } 54 | }); 55 | }; 56 | 57 | export default Yandex; 58 | -------------------------------------------------------------------------------- /test/translate-spec.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import 'babel-core/register'; 3 | 4 | import path from 'path'; 5 | import getStream from 'get-stream'; 6 | import { spawn } from 'child_process'; 7 | 8 | import { translate } from './../src/index'; 9 | 10 | test('#translate dictionary', async t => { 11 | const { type, result } = await translate('en', 'ru', 'java'); 12 | 13 | t.is(type, 'dictionary'); 14 | t.is(result.length, 4); 15 | 16 | t.is(typeof result[0], 'object'); 17 | t.is(result[0].title, 'noun (ˈʤɑːvə)'); 18 | 19 | t.is(result[0].translations.length, 1); 20 | t.is(result[0].translations[0].examples.length, 1); 21 | }); 22 | 23 | test('#translate translate', async t => { 24 | const { type, result } = await translate('ru', 'en', 'словарь может переводить только слова'); 25 | t.is(type, 'translate'); 26 | 27 | t.is(typeof result, 'string'); 28 | t.is(result, 'dictionary can translate only words'); 29 | }); 30 | 31 | const exec = args => { 32 | const cp = spawn(path.resolve(__dirname, 'fixtures', 'cli.es6.js'), args); 33 | cp.stdout.setEncoding('utf8'); 34 | cp.stderr.setEncoding('utf8'); 35 | 36 | return Promise 37 | .all([ 38 | getStream(cp.stdout), 39 | getStream(cp.stderr) 40 | .then(res => { 41 | if (res.length > 0) { 42 | throw new Error(res); 43 | } 44 | }) 45 | ]) 46 | .then(([ stdout, stderr ]) => stdout); 47 | }; 48 | 49 | test('cli stdin/stdout default en-ru', async t => { 50 | const str = await exec(['en', 'ru', 'u']); 51 | t.truthy(str.length > 0); 52 | }); 53 | 54 | test('cli stdin/stdout spell correction en-ru', async t => { 55 | const str = await exec(['en', 'ru', 'powir']); 56 | t.truthy(str.length > 0); 57 | }); 58 | 59 | test('cli stdin/stdout en detection en-ru', async t => { 60 | const str = await exec(['--en=ru', 'u']); 61 | t.truthy(str.length > 0); 62 | }); 63 | 64 | test('cli stdin/stdout en detection ru-en', async t => { 65 | const str = await exec(['--en=ru', 'привет']); 66 | t.truthy(str.length > 0); 67 | }); 68 | 69 | test('cli stdin/stdout ru detection en-ru', async t => { 70 | const str = await exec(['--ru=en', 'u']); 71 | t.truthy(str.length > 0); 72 | }); 73 | 74 | test('cli stdin/stdout ru detection ru-en', async t => { 75 | const str = await exec(['--ru=en', 'привет']); 76 | t.truthy(str.length > 0); 77 | }); 78 | -------------------------------------------------------------------------------- /src/cli.js: -------------------------------------------------------------------------------- 1 | import meow from 'meow'; 2 | import chalk from 'chalk'; 3 | 4 | const cli = meow({ 5 | pkg: './../package.json', 6 | help: [ 7 | 'Usage', 8 | ' dictionary ', 9 | '', 10 | ' # if input match /[a-zA-Z]/ it will translate from english to , otherwise from to english', 11 | ' dictionary --en= ', 12 | ' # if input match /[а-яА-Я]/ it will translate from russian to , otherwise from to russian', 13 | ' dictionary --ru= ', 14 | '', 15 | ' dictionary --export', 16 | ' dictionary --export > history.txt' 17 | ] 18 | }); 19 | 20 | const print = console.log.bind(console); 21 | 22 | let fromLang; 23 | let toLang; 24 | let input; 25 | 26 | const setupLangs = (lang, regEx) => { 27 | input = cli.input.join(' '); 28 | 29 | if (regEx.test(input)) { 30 | fromLang = lang; 31 | toLang = cli.flags[lang]; 32 | } else { 33 | fromLang = cli.flags[lang]; 34 | toLang = lang; 35 | } 36 | }; 37 | 38 | if (cli.flags.en) { 39 | setupLangs('en', /[a-zA-Z]+/); 40 | } else if (cli.flags.ru) { 41 | setupLangs('ru', /[а-яА-Я]+/); 42 | } else { 43 | input = cli.input.slice(2).join(' '); 44 | } 45 | 46 | if (cli.flags.export && cli.input.length !== 1 || !cli.flags.export && input.length === 0) { 47 | cli.showHelp(); 48 | } else { 49 | fromLang = fromLang || cli.input[0]; 50 | toLang = toLang || cli.input[1]; 51 | 52 | const lib = require('./index'); 53 | const space = require('lodash/repeat').bind(null, ' '); 54 | const u = chalk.underline; 55 | 56 | lib 57 | .translate(fromLang, toLang, input) 58 | .then(({ type, result, corrected }) => { 59 | if (corrected) { 60 | print(`\nnothing found by "${u(input)}", corrected to "${u(corrected)}"\n`); 61 | } 62 | 63 | if (type === 'dictionary') { 64 | result.forEach(r => { 65 | print(`${u(r.pos)} ${r.ts ? '(' + r.ts + ')' : ''}`); 66 | r.translations.forEach(printTranslations); 67 | }); 68 | 69 | function printTranslations(r) { 70 | print(`${space(2)}- ${u(r.translation)} ${r.synonyms ? r.synonyms + ' ' : ''}${r.means}`); 71 | r.examples.forEach(r => print(`${space(4)}-- ${r.title}`)); 72 | } 73 | } else { 74 | print(result); 75 | } 76 | }) 77 | .catch(err => { 78 | if (err.message === 'no translation') { 79 | console.log('no translation'); 80 | } else { 81 | throw err; 82 | } 83 | }); 84 | } 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dictionary-cli 2 | 3 | [![Build Status](https://travis-ci.org/ewnd9/dictionary-cli.svg?branch=master)](https://travis-ci.org/ewnd9/dictionary-cli) 4 | [![Coverage Status](https://coveralls.io/repos/ewnd9/dictionary-cli/badge.svg?branch=master&service=github)](https://coveralls.io/github/ewnd9/dictionary-cli?branch=master) 5 | 6 | Translations, usage examples and spell check via 7 | - [yandex translate api](https://tech.yandex.ru/translate/) 8 | ([supported languages](https://tech.yandex.ru/translate/doc/dg/concepts/langs-docpage/)) 9 | - [yandex dictionary api](https://tech.yandex.ru/dictionary/) 10 | ([supported languages](https://dictionary.yandex.net/api/v1/dicservice/getLangs?key=dict.1.1.20140616T070444Z.ecfe60ba07dd3ebc.9ce897a05d9daa488b050e5ec030f625d666530a)) 11 | 12 | ![Demonstration](/demo.gif?raw=true) 13 | 14 | ## Install 15 | 16 | ```sh 17 | $ npm install -g dictionary-cli 18 | ``` 19 | 20 | ## Usage 21 | 22 | ```sh 23 | $ dictionary 24 | 25 | # if input matches /[a-zA-Z]/ it will translate from english to , otherwise from to english 26 | $ dictionary --en= 27 | # if input matches /[а-яА-Я]/ it will translate from russian to , otherwise from to russian 28 | $ dictionary --ru= 29 | 30 | $ dictionary --export 31 | $ dictionary --export > history.txt 32 | ``` 33 | 34 | :shipit: Feel free to send PR for auto detection of other languages 35 | 36 | ## Tips 37 | 38 | Add aliases for language pairs in your `.bashrc` / `.zshrc` 39 | 40 | ```sh 41 | alias d="dictionary --en=ru" # use as "d " 42 | # or 43 | alias d="dictionary en ru" # use as "d " 44 | alias x="dictionary ru en" # use as "x " 45 | ``` 46 | 47 | ## Related 48 | 49 | - [dictionary-rb](https://github.com/AnkurGel/dictionary-rb) - Provides meanings, similar words and usage examples for a word from Urban Dictionary and Dictionary Reference with CLI support 50 | 51 | - [traktor](https://github.com/d4rkr00t/traktor) - CLI for Yandex.Translator API + Yandex.Dictionary API 52 | 53 | ## Notices 54 | 55 | Yandex demands 56 | ([1](https://tech.yandex.ru/translate/doc/dg/concepts/design-requirements-docpage/), 57 | [2](https://tech.yandex.ru/dictionary/doc/dg/concepts/design-requirements-docpage/)) 58 | to put this in order to use their api 59 | 60 | - «Реализовано с помощью сервиса «Яндекс.Словарь» https://tech.yandex.ru/dictionary/ 61 | - «Переведено «Яндекс.Переводчиком» http://translate.yandex.ru/ 62 | 63 | ## License 64 | 65 | MIT © [ewnd9](http://ewnd9.com) 66 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Yandex from './yandex'; 2 | 3 | const translateKey = 'trnsl.1.1.20131018T175412Z.6e9fa29e525b4697.3542f82ffa6916d1ccd64201d8a72c023892ae5e'; 4 | const dictionaryKey = 'dict.1.1.20140616T070444Z.ecfe60ba07dd3ebc.9ce897a05d9daa488b050e5ec030f625d666530a'; 5 | 6 | const yandex = new Yandex( 7 | process.env.YANDEX_TRANSLATE_KEY || translateKey, 8 | process.env.YANDEX_DICTIONARY_KEY || dictionaryKey 9 | ); 10 | 11 | export const translate = (fromLang, toLang, text, corrected = null) => { 12 | const translateFn = text => yandex.translate(fromLang, toLang, text); 13 | const translateFallback = () => translateFn(text) 14 | .then(translation => { 15 | if (translation === text && !corrected) { 16 | return yandex 17 | .spellCheck(fromLang, text) 18 | .then(onSpellResult); 19 | 20 | function onSpellResult(spellResult) { 21 | if (spellResult === text) { 22 | return { type: 'translate', result: translation, corrected }; 23 | } else { 24 | return translate(fromLang, toLang, spellResult, spellResult); 25 | } 26 | } 27 | } else { 28 | return { type: 'translate', result: translation, corrected }; 29 | } 30 | }); 31 | 32 | const dictionary = () => yandex 33 | .dictionary(fromLang, toLang, text) 34 | .then(result => { 35 | if (result.length === 0) { 36 | return translateFallback(); 37 | } else { 38 | return { type: 'dictionary', result: formatDictionary(result), corrected }; 39 | } 40 | }); 41 | 42 | return dictionary().then(null, translateFallback); 43 | }; 44 | 45 | const joiner = array => { 46 | return array.length > 0 ? '(' + array.join(' | ') + ')' : ''; 47 | }; 48 | 49 | const formatDictionaryTranslations = translation => { 50 | const synonyms = (translation.syn || []).map(syn => syn.text); 51 | const means = (translation.mean || []).map(means => means.text); 52 | 53 | return { 54 | translation: translation.text, 55 | synonyms: joiner(synonyms), 56 | means: joiner(means), 57 | examples: (translation.ex || []).map(function(ex) { 58 | const translations = ex.tr.map(_ => _.text); 59 | 60 | return { 61 | title: ex.text + ' ' + joiner(translations), 62 | text: ex.text, 63 | translations 64 | }; 65 | }) 66 | }; 67 | }; 68 | 69 | const formatDictionary = result => { 70 | return result.map(word => ({ 71 | title: `${word.pos} (${word.ts})`, 72 | pos: word.pos, 73 | ts: word.ts, 74 | translations: word.tr.map(formatDictionaryTranslations) 75 | })); 76 | }; 77 | --------------------------------------------------------------------------------