├── .gitignore ├── LICENSE ├── README.md ├── Translate.alfredworkflow ├── screenshot-1.png ├── screenshot-2.png ├── screenshot-3.png └── src ├── feedback.py ├── icon.png ├── info.plist ├── spellcheck.png └── translate.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.DS_Store 3 | *.sublime-* 4 | .idea 5 | .vscode 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Dmitry Podgorniy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RU-EN EN-RU Translating Alfred Workflow 2 | 3 | [Скачать](https://github.com/podgorniy/alfred-translate/raw/master/Translate.alfredworkflow) 4 | > Работает с версией macOs 12.4 и выше 5 | > Работа на более поздних версиях подразумевает использования python3 6 | 7 | Удобный перевод текстов в en-ru ru-en направлениях. 8 | 9 | - Переводит русский текст на английский. Английский текс в русский. Не нужно указывать направление перевода. 10 | - Работает как с вводимым текстом, так и с выделенным. 11 | - Показывает варианты перевода для одного слова. 12 | - Показывает транскрипцию при переводе с английского. 13 | - Исправляет ошибки в словах. 14 | - Переводит как слова так и предложения. 15 | - Копирует результат перевода в буфер обмена. 16 | - Не работает без интернет соединения. 17 | 18 | 19 | Перевод слова, запуск из строки Альфреда по ключевому слову `t` или `e`: 20 | 21 | ![Скриншот](screenshot-1.png) 22 | 23 | 24 | Перевод выделенного предложения по хоткею. Для себя настроил сочетание `ctrl+shift+t`. 25 | 26 | ![Скриншот](screenshot-2.png) 27 | 28 | Варианты автодополнения при ошибке в написании слова. 29 | 30 | ![Скриншот](screenshot-3.png) 31 | 32 | За иконку спасибо [Artem Beztsinnyi](http://bezart.ru). 33 | 34 | Альтернативные workflow для перевода: 35 | 36 | - [AlfredGoogleTranslateWorkflow](https://github.com/thomashempel/AlfredGoogleTranslateWorkflow) 37 | 38 | ## Changelog 39 | 40 | **2020.10.06 41 | 42 | - [denisborovikov](https://github.com/denisborovikov) убрал запрос к неработающему API yandex, и плагин снова работает. 43 | 44 | **2020.06.13** 45 | 46 | - Исправлена [экранизация пробелов](https://github.com/podgorniy/alfred-translate/issues/10). 47 | 48 | **2019.06.27** 49 | 50 | - Подхватываются настройки прокси из `.bashrc` 51 | 52 | 53 | **2018.08.21** 54 | 55 | - Добавлен перевод по русской букве `е`. 56 | 57 | 58 | **2017.04.02** 59 | 60 | - Улучшена производительность. 61 | - Добавлена подсказка ошибок вместе с вариантами перевода. 62 | 63 | 2015.10.05 64 | - Добавлено экранирование кавычек `'`. 65 | -------------------------------------------------------------------------------- /Translate.alfredworkflow: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podgorniy/alfred-translate/c9051a206dfa4778f180b8fa858032a4431124ab/Translate.alfredworkflow -------------------------------------------------------------------------------- /screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podgorniy/alfred-translate/c9051a206dfa4778f180b8fa858032a4431124ab/screenshot-1.png -------------------------------------------------------------------------------- /screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podgorniy/alfred-translate/c9051a206dfa4778f180b8fa858032a4431124ab/screenshot-2.png -------------------------------------------------------------------------------- /screenshot-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podgorniy/alfred-translate/c9051a206dfa4778f180b8fa858032a4431124ab/screenshot-3.png -------------------------------------------------------------------------------- /src/feedback.py: -------------------------------------------------------------------------------- 1 | #author: Peter Okma 2 | import xml.etree.ElementTree as et 3 | 4 | 5 | class Feedback(): 6 | """Feeback used by Alfred Script Filter 7 | 8 | Usage: 9 | fb = Feedback() 10 | fb.add_item('Hello', 'World') 11 | fb.add_item('Foo', 'Bar') 12 | print fb 13 | 14 | """ 15 | 16 | def __init__(self): 17 | self.feedback = et.Element('items') 18 | 19 | def __repr__(self): 20 | """XML representation used by Alfred 21 | 22 | Returns: 23 | XML string 24 | """ 25 | return str(et.tostring(self.feedback), "utf-8") 26 | 27 | def add_item(self, title, subtitle="", arg="", valid="yes", autocomplete="", icon="icon.png"): 28 | """ 29 | Add item to alfred Feedback 30 | 31 | Args: 32 | title(str): the title displayed by Alfred 33 | Keyword Args: 34 | subtitle(str): the subtitle displayed by Alfred 35 | arg(str): the value returned by alfred when item is selected 36 | valid(str): whether or not the entry can be selected in Alfred to trigger an action 37 | autcomplete(str): the text to be inserted if an invalid item is selected. This is only used if 'valid' is 'no' 38 | icon(str): filename of icon that Alfred will display 39 | """ 40 | item = et.SubElement(self.feedback, 'item', uid=str(len(self.feedback)), 41 | arg=arg, valid=valid, autocomplete=autocomplete) 42 | _title = et.SubElement(item, 'title') 43 | _title.text = title 44 | _sub = et.SubElement(item, 'subtitle') 45 | _sub.text = subtitle 46 | _icon = et.SubElement(item, 'icon') 47 | _icon.text = icon -------------------------------------------------------------------------------- /src/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podgorniy/alfred-translate/c9051a206dfa4778f180b8fa858032a4431124ab/src/icon.png -------------------------------------------------------------------------------- /src/info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | bundleid 6 | 7 | category 8 | Productivity 9 | connections 10 | 11 | 94EED11C-93A9-4FF4-8F28-14E546743485 12 | 13 | 14 | destinationuid 15 | 7F6DE136-5E1D-4A3A-84E8-269B076CE825 16 | modifiers 17 | 262144 18 | modifiersubtext 19 | Search/поиск 20 | vitoclose 21 | 22 | 23 | 24 | destinationuid 25 | ECA3047F-8C4F-4C35-AA1F-36F3E4FF3ADD 26 | modifiers 27 | 0 28 | modifiersubtext 29 | 30 | vitoclose 31 | 32 | 33 | 34 | E59094FA-7C50-4B18-8B4D-518D96B0966F 35 | 36 | 37 | destinationuid 38 | 94EED11C-93A9-4FF4-8F28-14E546743485 39 | modifiers 40 | 0 41 | modifiersubtext 42 | 43 | vitoclose 44 | 45 | 46 | 47 | 48 | createdby 49 | Dmitry Podgorniy 50 | description 51 | Translate en-ru and ru-en 52 | disabled 53 | 54 | name 55 | Translate 56 | objects 57 | 58 | 59 | config 60 | 61 | browser 62 | 63 | searcher 64 | 1635215215 65 | 66 | type 67 | alfred.workflow.action.systemwebsearch 68 | uid 69 | 7F6DE136-5E1D-4A3A-84E8-269B076CE825 70 | version 71 | 1 72 | 73 | 74 | config 75 | 76 | action 77 | 0 78 | argument 79 | 1 80 | focusedappvariable 81 | 82 | focusedappvariablename 83 | 84 | hotkey 85 | 17 86 | hotmod 87 | 393216 88 | hotstring 89 | T 90 | leftcursor 91 | 92 | modsmode 93 | 0 94 | relatedAppsMode 95 | 0 96 | 97 | type 98 | alfred.workflow.trigger.hotkey 99 | uid 100 | E59094FA-7C50-4B18-8B4D-518D96B0966F 101 | version 102 | 2 103 | 104 | 105 | config 106 | 107 | alfredfiltersresults 108 | 109 | argumenttype 110 | 0 111 | escaping 112 | 4 113 | keyword 114 | t 115 | queuedelaycustom 116 | 3 117 | queuedelayimmediatelyinitially 118 | 119 | queuedelaymode 120 | 0 121 | queuemode 122 | 2 123 | runningsubtext 124 | Перевожу / translating 125 | script 126 | import translate 127 | print translate.get_output("{query}") 128 | 129 | scriptargtype 130 | 0 131 | scriptfile 132 | 133 | subtext 134 | en-ru, ru-en 135 | title 136 | Translate 137 | type 138 | 3 139 | withspace 140 | 141 | 142 | type 143 | alfred.workflow.input.scriptfilter 144 | uid 145 | 94EED11C-93A9-4FF4-8F28-14E546743485 146 | version 147 | 2 148 | 149 | 150 | config 151 | 152 | autopaste 153 | 154 | clipboardtext 155 | {query} 156 | transient 157 | 158 | 159 | type 160 | alfred.workflow.output.clipboard 161 | uid 162 | ECA3047F-8C4F-4C35-AA1F-36F3E4FF3ADD 163 | version 164 | 2 165 | 166 | 167 | readme 168 | 169 | uidata 170 | 171 | 7F6DE136-5E1D-4A3A-84E8-269B076CE825 172 | 173 | xpos 174 | 500 175 | ypos 176 | 10 177 | 178 | 94EED11C-93A9-4FF4-8F28-14E546743485 179 | 180 | xpos 181 | 300 182 | ypos 183 | 70 184 | 185 | E59094FA-7C50-4B18-8B4D-518D96B0966F 186 | 187 | xpos 188 | 100 189 | ypos 190 | 70 191 | 192 | ECA3047F-8C4F-4C35-AA1F-36F3E4FF3ADD 193 | 194 | xpos 195 | 700 196 | ypos 197 | 190 198 | 199 | 200 | webaddress 201 | https://github.com/podgorniy/alfred-translate 202 | 203 | 204 | -------------------------------------------------------------------------------- /src/spellcheck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podgorniy/alfred-translate/c9051a206dfa4778f180b8fa858032a4431124ab/src/spellcheck.png -------------------------------------------------------------------------------- /src/translate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import urllib.parse 4 | import urllib.request 5 | import json 6 | import feedback 7 | from multiprocessing import Pool 8 | import re 9 | import sys 10 | 11 | dict_api_key = 'dict.1.1.20140108T003739Z.52c324b8a4eea3ac.5767100e8cc7b997dad88353e47aa4857e786beb' 12 | translate_api_key = 'trnsl.1.1.20130512T104455Z.8a0ed400b0d249ba.48af47e72f40c8991e4185556b825273d104af68' 13 | 14 | 15 | def is_ascii(s): 16 | """http://stackoverflow.com/questions/196345/how-to-check-if-a-string-in-python-is-in-ascii""" 17 | return all(ord(c) < 128 for c in s) 18 | 19 | 20 | def get_translation_direction(text): 21 | """Returns direction of translation. en-ru or ru-en""" 22 | if is_ascii(text): 23 | return 'en-ru' 24 | else: 25 | return 'ru-en' 26 | 27 | 28 | def get_lang(text): 29 | """Returns either 'ru' or 'en' corresponding for text""" 30 | if is_ascii(text): 31 | return 'en' 32 | else: 33 | return 'ru' 34 | 35 | 36 | def convert_spelling_suggestions(spelling_suggestions): 37 | res = [] 38 | if len(spelling_suggestions) != 0: 39 | for spelling_suggestion in spelling_suggestions: 40 | res.append({ 41 | 'title': spelling_suggestion, 42 | 'autocomplete': spelling_suggestion 43 | }) 44 | return res 45 | 46 | 47 | def get_spelling_suggestions(spelling_suggestions): 48 | """Returns spelling suggestions from JSON if any """ 49 | res = [] 50 | if spelling_suggestions and spelling_suggestions[0] and spelling_suggestions[0]['s']: 51 | res = spelling_suggestions[0]['s'] 52 | return res 53 | 54 | 55 | def get_translation_suggestions(input_string, spelling_suggestions, vocabulary_article): 56 | """Returns XML with translate suggestions""" 57 | res = [] 58 | if len(spelling_suggestions) == 0 and len(vocabulary_article) == 0: 59 | return res 60 | 61 | if len(vocabulary_article['def']) != 0: 62 | for article in vocabulary_article['def']: 63 | for translation in article['tr']: 64 | if 'ts' in article.keys(): 65 | subtitle = article['ts'] 66 | elif 'ts' in translation.keys(): 67 | subtitle = translation['ts'] 68 | else: 69 | subtitle = '' 70 | res.append({ 71 | 'translation': translation['text'], 72 | 'transcription': subtitle, 73 | }) 74 | 75 | return res 76 | 77 | 78 | def process_response_as_json(request_url): 79 | """Accepts request url returns response as """ 80 | request = urllib.request.urlopen(request_url) 81 | response_json = json.loads(request.read()) 82 | return response_json 83 | 84 | 85 | def get_output(input_string): 86 | """Main entry point""" 87 | pool = Pool(processes=3) 88 | fb = feedback.Feedback() 89 | input_string = input_string.strip() 90 | if not input_string: 91 | fb.add_item(title="Translation not found", valid="no") 92 | return fb 93 | 94 | # Building urls 95 | translationDirection = get_translation_direction(input_string) 96 | 97 | # Build spell check url 98 | spellCheckParams = { 99 | 'text': input_string, 100 | 'lang': get_lang(input_string) 101 | } 102 | spellCheckUrl = 'https://speller.yandex.net/services/spellservice.json/checkText' + '?' + urllib.parse.urlencode( 103 | spellCheckParams) 104 | 105 | # Build article url 106 | articleParams = { 107 | 'key': dict_api_key, 108 | 'lang': translationDirection, 109 | 'text': input_string, 110 | 'flags': 4 111 | } 112 | articleUrl = 'https://dictionary.yandex.net/api/v1/dicservice.json/lookup' + '?' + urllib.parse.urlencode( 113 | articleParams) 114 | 115 | # Making requests in parallel 116 | requestsUrls = [spellCheckUrl, articleUrl] 117 | responses = pool.map(process_response_as_json, requestsUrls) 118 | 119 | spelling_suggestions_items = get_spelling_suggestions(responses[0]) 120 | # Generate possible xml outputs 121 | formatted_spelling_suggestions = convert_spelling_suggestions(spelling_suggestions_items) 122 | formated_translation_suggestions = get_translation_suggestions(input_string, spelling_suggestions_items, 123 | responses[1]) 124 | words_in_phase = len(re.split(' ', input_string)) 125 | 126 | # Output 127 | if len(formatted_spelling_suggestions) == 0 and len(formated_translation_suggestions) == 0: 128 | fb.add_item(title="Translation not found", valid="no") 129 | return fb 130 | 131 | # Prepare suggestions output 132 | # Spellcheck error 133 | if words_in_phase <= 2 and len(formatted_spelling_suggestions) != 0: 134 | for spelling_suggestion in formatted_spelling_suggestions: 135 | fb.add_item(title=spelling_suggestion['title'], 136 | autocomplete=spelling_suggestion['autocomplete'], 137 | icon='spellcheck.png') 138 | 139 | # Translations output 140 | for formatted_translated_suggestion in formated_translation_suggestions: 141 | fb.add_item( 142 | title=formatted_translated_suggestion['translation'], 143 | arg=formatted_translated_suggestion['translation'], 144 | subtitle=formatted_translated_suggestion['transcription'] 145 | ) 146 | return fb 147 | 148 | 149 | if __name__ == '__main__': 150 | print(get_output(sys.argv[1])) --------------------------------------------------------------------------------