From 1545a49ea3053e6c4b57f02cb6c009beaaa16aa3 Mon Sep 17 00:00:00 2001 From: GBS3 <70548443+GBS3@users.noreply.github.com> Date: Wed, 10 Feb 2021 00:29:49 -0600 Subject: [PATCH] Initial commit --- .gitattributes | 2 + HISTORY.md | 5 + LICENSE | 7 ++ MANIFEST.in | 2 + README.md | 219 +++++++++++++++++++++++++++++++++++++ media/pystone.png | Bin 0 -> 64559 bytes pystone/__init__.py | 243 +++++++++++++++++++++++++++++++++++++++++ pystone/__version__.py | 8 ++ pystone/constants.py | 84 ++++++++++++++ pystone/deepl.py | 160 +++++++++++++++++++++++++++ pystone/languages.py | 56 ++++++++++ pystone/reverso.py | 137 +++++++++++++++++++++++ setup.py | 45 ++++++++ 13 files changed, 968 insertions(+) create mode 100644 .gitattributes create mode 100644 HISTORY.md create mode 100644 LICENSE create mode 100644 MANIFEST.in create mode 100644 README.md create mode 100644 media/pystone.png create mode 100644 pystone/__init__.py create mode 100644 pystone/__version__.py create mode 100644 pystone/constants.py create mode 100644 pystone/deepl.py create mode 100644 pystone/languages.py create mode 100644 pystone/reverso.py create mode 100644 setup.py diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/HISTORY.md b/HISTORY.md new file mode 100644 index 0000000..8bf96c9 --- /dev/null +++ b/HISTORY.md @@ -0,0 +1,5 @@ +# Release History + +## v0.1.0 + +* First release \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bd97606 --- /dev/null +++ b/LICENSE @@ -0,0 +1,7 @@ +Copyright 2021 Giovanni Salinas + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..eeb9cc2 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include README.md LICENSE +recursive-include tests *.py \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..b4d671a --- /dev/null +++ b/README.md @@ -0,0 +1,219 @@ + + +# pystone + +![PyPI Version](https://img.shields.io/pypi/v/pystone?style=flat-square) ![Python Version](https://img.shields.io/pypi/pyversions/pystone?style=flat-square) ![License](https://img.shields.io/pypi/l/pystone?style=flat-square) + +A command line tool written in Python for translating text through Reverso. + + +## Requirements + +To be able to use `pystone`, you will need at least Python 3.6 or higher. This tool makes use of the [requests](https://github.com/psf/requests) and [playsound](https://github.com/TaylorSMarks/playsound) packages. + + +## Installation + +To install `pystone`, run the following in your terminal: + +``` +$ pip install pystone +``` + +## Usage + +In order to use `pystone`, simply type and enter it in your terminal: + +``` +$ pystone +``` + +This will activate a command line interpreter that mimics a *session* and allows for quick tab completions, which in turn optimizes ease of use. It will remember your current source and target languages and will allow you to modify them within the same session. If you want to translate a piece of text into a variety of languages, you can do that without having to repeatedly enter the same text and/or command line arguments. + +### Command Line Arguments + +On that note, I should explain that there are command line arguments that you *can* use but they're completely optional. + +Here they are: + +``` +Translation options + +optional arguments: + -h, --help show this help message and exit + -s LANGUAGE, --source LANGUAGE the source language (default: English) + -t LANGUAGE, --target LANGUAGE the target language + --text TEXT the text to be translated +``` + +Example: + +``` +pystone --target Spanish +``` + +I don't expect these commands to be used very often but they're there in case a user preemptively knows which languages they want to translate between or happens to have a specific text in mind. + +### Documented Commands + +Once the interpreter has been activated, there are several commands that are at your disposal: + +``` +Documented commands (type help ): +======================================== +alternatives audio deepl examples exit help reverse set settings translate +``` + +#### settings + +`settings` accepts no arguments and outputs your current configuration to the console: + +``` + Source language: English + Target language: None + Current text: None + Most recent translation: None +``` + +#### set + +`set` accepts at most two space-separated arguments and manages your source and target languages. If only one argument is provided, your target language will be set to that language. If two arguments are provided, both your source language and target language will respectively be set to those languages: + +``` +(pystone) set Spanish # target language -> Spanish + +# ================== +# or +# ================== + +(pystone) set German French # source language -> German, target language -> French +``` + +##### Flexibility + +The `set` command is incredibly flexible in that the languages don't need to be specified in English or capitalized and will accept letters with accent marks: + +``` +(pystone) set Anglais español +``` + +It will even attempt to determine the languages if you input short abbreviations (*at least 3 characters are recommended for each language*): + +``` +(pystone) set fra spa # source language -> French, target language -> Spanish +``` + +#### translate + +`translate` accepts text as input and outputs a translation of the text to your target language: + +``` +(pystone) translate What time is it? + + What time is it? + + ¿Qué hora es? +``` + +#### examples + +`examples` accepts no arguments and either outputs example sentences using your translation (if there are any available) or outputs nothing: + +``` +(pystone) examples + +¿Qué hora es? - Las dos. +¿Qué hora es? son cinco para las diez. +¿Qué hora es? - Las nueve. +¿Qué hora es? - Las tres y media. +¿Qué hora es? Pulse para actualizarla. +¡Dolokhov! ¿Qué hora es? +¿Qué hora es? -Las cuatro. +¿No tiene reloj? - ¿Qué hora es? ...le lanza un puñetazo contundente. +Discúlpeme. ¿Qué hora es? +¿Qué hora es? eh, creo que es, mediodía. +``` + +#### alternatives + +`alternatives` accepts no arguments and either outputs other translations that could be more accurate (if there are any available) or outputs nothing: + +``` +(pystone) alternatives + +¿Qué hora es ahora? +¿QUÉ TIEMPO ES ESTE? +qué hora es! +la hora que es? +¿qué horas son? +``` + +#### audio + +`audio` accepts no arguments and outputs audio of a text-to-speech voice reader reading your translation: + +``` +(pystone) audio + +# 🎶¿Qué hora es?🎶 +``` + +#### reverse + +`reverse` accepts no arguments and swaps the languages for the source language and target language with each other: + +``` +(pystone) reverse + +# source language gets set to target language +# target language gets set to source language +``` + +#### exit + +`exit` accepts no arguments and quits the program. + +#### help + +`help` accepts at most one argument. If no arguments are provided, the available commands will be printed to the console. If an argument is provided and that argument is the name of a command, a short description of the command will be printed to the console: + +``` +(pystone) help set + +Expects 1 or 2 languages separated by spaces as input. + If one argument is provided, the target language will be + changed to the specified language. If two arguments are provided, + both the source language and the target language will + be changed (in that order). +``` + +#### deepl + +*WARNING: this command is currently in experimental mode and I do **NOT** recommend using it without a VPN or a proxy*. *Once I deem it usable/safe, this warning will be removed but there is still much testing to be done*. + +*NOTE: translating **to** English or Portugese is not currently supported.* + +`deepl` works like `translate`. It accepts text as input and outputs a translation from DeepL of the text to your target language. + + +## Supported Languages + +A table consisting of the currently supported languages: + +| Language | Reverso | DeepL | +| :------: | :-----: | :---: | +| Arabic | ✔ | ❌ | +| Chinese | ✔ | ✔ | +| Dutch | ✔ | ✔ | +| English | ✔ | ✔ | +| French | ✔ | ✔ | +| German | ✔ | ✔ | +| Hebrew | ✔ | ❌ | +| Italian | ✔ | ✔ | +| Japanese | ✔ | ✔ | +| Polish | ✔ | ✔ | +| Portuguese | ✔ | ✔ | +| Romanian | ✔ | ❌ | +| Russian | ✔ | ✔ | +| Spanish | ✔ | ✔ | +| Turkish | ✔ | ❌ | diff --git a/media/pystone.png b/media/pystone.png new file mode 100644 index 0000000000000000000000000000000000000000..750462626e638497f03419559c6d10dc05f39636 GIT binary patch literal 64559 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 None: + """Gets translation from Reverso.""" + if not self.text: + return + reverso = Reverso(self.text, self.source, self.target) + parsed_response = reverso.reverso() + self._translation = parsed_response["translation"] + self._examples = parsed_response["target_examples"] + self._alternatives = parsed_response["alternatives"] + return + + def audio(self, text) -> bytes: + """Gets audio from Reverso.""" + reverso = Reverso(text, self.source, self.target) + audio = reverso.audio() + return audio + + def deepl(self) -> None: + """Gets translation from DeepL.""" + if not self.text: + return + deepl = DeepL(self.text, self.source, self.target) + parsed_response = deepl.deepl() + self._translation = parsed_response["translation"] + self._examples = parsed_response["examples"] + return + + @property + def translation(self) -> str: + return self._translation + + @translation.setter + def translation(self, text) -> None: + self._translation = text + + @property + def examples(self) -> list: + return self._examples + + @examples.setter + def examples(self, array) -> None: + self._examples = array + + @property + def alternatives(self) -> list: + return self._alternatives + + @alternatives.setter + def alternatives(self, array) -> None: + self._alternatives = array + + +class PyStone(cmd.Cmd): + intro = "This is \033[38;5;14mpystone\033[0m, a command line interpreter for translation. Type \033[38;5;209mhelp\033[0m or \033[38;5;209m?\033[0m to view available commands." + prompt = "(pystone) " + + DEBUG_FMT = "%(levelname)s - %(message)s" + INFO_FMT = "%(message)s" + + def __init__(self, args, translation=None, alternatives=None, + examples=None, level=logging.INFO): + super().__init__() + self.translate = Translate(args.text, args.source, args.target) + self.translation = translation + self.alternatives = alternatives + self.examples = examples + # Logger setup: + self.log = logging.getLogger(__name__) + self.log.setLevel(level) + if not self.log.handlers: + formatter = logging.Formatter(fmt=( + self.INFO_FMT if level == logging.INFO else self.DEBUG_FMT)) + sh = logging.StreamHandler() + sh.setLevel(level) + sh.setFormatter(fmt=formatter) + self.log.addHandler(sh) + + def do_settings(self, arg) -> None: + """Outputs the current session configuration to the console.""" + self.log.info(f""" + Source language: {self.translate.source} + Target language: {self.translate.target} + Current text: {self.translate.text} + Most recent translation: {self.translation} + """) + return + + def do_set(self, arg) -> None: + """Expects 1 or 2 languages separated by spaces as input. + If one argument is provided, the target language will be + changed to the specified language. If two arguments are provided, + both the source language and the target language will + be changed (in that order).""" + if not arg: + return + args = arg.split() + num_args = len(args) + if num_args == 1: + self.translate.target = args[0].capitalize() + elif num_args == 2: + self.translate.source = args[0].capitalize() + self.translate.target = args[1].capitalize() + else: + print("*** Maximum no. args: 2") + + def do_translate(self, arg) -> None: + """Takes text from the source language to translate and prints + the translation to the console from Reverso.""" + if not self._check_configuration(): + return + if not arg and self.translation: + return self._print_translation() + self.translate.text = arg if arg else self.translate.text + self.translate.translate() + self.translation = self.translate.translation + self.examples = self.translate.examples + self.alternatives = self.translate.alternatives + return self._print_translation() + + def do_examples(self, arg) -> None: + """Prints examples alongside any translations, if available.""" + if self.examples: + for e in self.examples: + new_e = e.replace("", "\033[38;5;39m").replace( + "", "\033[0m") + self.log.info(new_e) + else: + return + + def do_alternatives(self, arg) -> None: + """Prints alternative translations, if available.""" + if self.alternatives: + for a in self.alternatives: + self.log.info(f"\033[38;5;141m{a}\033[0m") + else: + return + + def do_audio(self, arg) -> None: + """Inputs a translation into a Reverso text-to-speech voice + reader.""" + if self.translation: + audio = self.translate.audio(self.translation) + with open("pystone_audio.mp3", "wb") as f: + f.write(audio) + playsound("pystone_audio.mp3") + os.unlink("pystone_audio.mp3") + else: + return + + def do_reverse(self, arg) -> None: + """Swaps the source and target languages with each other.""" + self.translate.source, self.translate.target = self.translate.target, self.translate.source + return + + def do_deepl(self, arg) -> None: + """Takes text from the source language to translate and prints + the translation to the console from Reverso.""" + if not self._check_configuration(): + return + if not arg and self.translation: + return self._print_translation() + self.translate.text = arg if arg else self.translate.text + self.translate.deepl() + self.translation = self.translate._translation + self.examples = self.translate._examples + return self._print_translation() + + def do_exit(self, arg) -> bool: + """Quits program.""" + return True + + def _check_configuration(self) -> int: + """Confirms if user has specified both a source language and + a target language.""" + if not self.translate.source or not self.translate.target: + self.log.warning( + "You are missing a \033[38;5;202mtarget language\033[0m. Please check your settings by typing and entering \033[38;5;209msettings\033[0m.") + return 0 + else: + return 1 + + def _print_translation(self) -> None: + """Prints current translation.""" + self.log.info(f""" + {self.translate.text} + + \033[38;5;141m{self.translation}\033[0m + """) + return + + def debug(self, message) -> None: + self.log.debug(message) + + def info(self, message) -> None: + self.log.info(message) + + def warning(self, message) -> None: + self.log.warning(message) + + def error(self, message) -> None: + self.log.error(message) + + +def main() -> None: + """Command line arguments configuration.""" + parser = argparse.ArgumentParser(description="Translation options") + parser.add_argument("-s", "--source", type=str, help="the source language (default: English)", + default="English", required=False, metavar="LANGUAGE") + parser.add_argument("-t", "--target", type=str, + help="the target language", required=False, metavar="LANGUAGE") + parser.add_argument( + "--text", type=str, help="the text to be translated", required=False, metavar="TEXT") + if platform.system() == "Windows": + os.system("color") + PyStone(parser.parse_args()).cmdloop() + + +# If ran as a script, act as a command line interpreter for translation: +if __name__ == "__main__": + main() diff --git a/pystone/__version__.py b/pystone/__version__.py new file mode 100644 index 0000000..4efe505 --- /dev/null +++ b/pystone/__version__.py @@ -0,0 +1,8 @@ +__title__ = "pystone" +__version__ = "0.1.0" +__author__ = "Giovanni Salinas" +__author_email__ = "gbs3@protonmail.com" +__description__ = "A command line tool written in Python for translating text through Reverso." +__url__ = "https://github.com/GBS3/pystone" +__license__ = "MIT License" +__copyright__ = "Copyright 2021 Giovanni Salinas" diff --git a/pystone/constants.py b/pystone/constants.py new file mode 100644 index 0000000..36afa09 --- /dev/null +++ b/pystone/constants.py @@ -0,0 +1,84 @@ +REVERSO_LANGS = [ + "ara", "chi", "dut", "fra", "ger", "heb", "ita", "jpn", "pol", "por", "rum", "rus", "spa", "tur"] +DEEPL_LANGS = [ + "EN", "DE", "FR", "ES", "PT", "IT", "NL", "PL", "RU", "JA", "ZH"] +REGIONAL_VARIANTS = [ + "en-US", "en-GB", "pt-PT", "pt-BR"] + +LANGUAGES = { + "arabic": { + "deepl": "", + "reverso": "ara", + "reverso_voice": "Mehdi22k" + }, + "chinese": { + "deepl": "ZH", + "reverso": "chi", + "reverso_voice": "Lulu22k" + }, + "dutch": { + "deepl": "NL", + "reverso": "dut", + "reverso_voice": "Femke22k" + }, + "english": { + "deepl": "EN", + "reverso": "eng", + "reverso_voice": "Heather22k" + }, + "french": { + "deepl": "FR", + "reverso": "fra", + "reverso_voice": "Bruno22k" + }, + "german": { + "deepl": "DE", + "reverso": "ger", + "reverso_voice": "Klaus22k" + }, + "hebrew": { + "deepl": "", + "reverso": "heb", + "reverso_voice": "he-IL-Asaf" + }, + "italian": { + "deepl": "IT", + "reverso": "ita", + "reverso_voice": "Chiara22k" + }, + "japanese": { + "deepl": "JA", + "reverso": "jpn", + "reverso_voice": "Sakura22k" + }, + "polish": { + "deepl": "PL", + "reverso": "pol", + "reverso_voice": "Monika22k" + }, + "portuguese": { + "deepl": "PT", + "reverso": "por", + "reverso_voice": "Celia22k" + }, + "romanian": { + "deepl": "", + "reverso": "rum", + "reverso_voice": "ro-RO-Andrei" + }, + "russian": { + "deepl": "RU", + "reverso": "rus", + "reverso_voice": "Alyona22k" + }, + "spanish": { + "deepl": "ES", + "reverso": "spa", + "reverso_voice": "Maria22k" + }, + "turkish": { + "deepl": "", + "reverso": "tur", + "reverso_voice": "Ipek22k" + } +} diff --git a/pystone/deepl.py b/pystone/deepl.py new file mode 100644 index 0000000..26f4d1c --- /dev/null +++ b/pystone/deepl.py @@ -0,0 +1,160 @@ +import json +import math +import time + +import requests + +from .languages import Languages + + +class DeepL: + api = "https://www2.deepl.com/jsonrpc" + + def __init__(self, text, source, target, s=None): + self.text = text + self.source = source + self.target = target + self.s = s + + def deepl(self) -> dict: + """The central method to the DeepL class that establishes a common + Session object, sets the language abbreviations for the DeepL API, + retrieves a response from the DeepL API endpoint, splits the user + text input into sentences through the DeepL API endpoint, organizes + output to send to DeepL API endpoint, parses the final response + from the Reverso API endpoint, and then returns the parsed response.""" + self.s = self.create_session() + self.set_languages() + response = self.split_sentences() + splitted_texts = response["result"]["splitted_texts"][0] + jobs = self.get_jobs(splitted_texts) + response = self.get_deepl_translation_response(jobs) + parsed_response = self.parse_deepl_translation_response(response) + return parsed_response + + def create_session(self) -> requests.sessions.Session: + """Creates a Session object.""" + headers = { + "accept-encoding": "gzip, deflate, br", + "content-type": "application/json", + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4371.0 Safari/537.36" + } + s = requests.Session() + s.headers.update(headers) + return s + + def set_languages(self) -> None: + """Sets appropriate language abbreviations for use for the DeepL API.""" + source = Languages(self.source) + source_language = source.get_language() + self.source = source.deepl_get_abbrv(source_language) + target = Languages(self.target) + target_language = target.get_language() + self.target = target.deepl_get_abbrv(target_language) + return + + def split_sentences(self) -> dict: + """Intelligently splits user text input into sentences using the + DeepL API endpoint.""" + payload = { + "jsonrpc": "2.0", + "method": "LMT_split_into_sentences", + "params": { + "texts": [self.text], + "lang": { + "lang_user_selected": "auto", + "user_preferred_langs": [] + } + } + } + payload = json.dumps(payload) + with self.s.post(self.api, data=payload) as r: + if r.ok: + return r.json() + else: + r.raise_for_status() + + def get_jobs(self, texts) -> list: + """Appropriately organizes user text input for data to be sent to + the DeepL API endpoint.""" + num_texts = len(texts) + jobs = [] + count = 0 + while count < num_texts: + job = { + "kind": "default", + "raw_en_sentence": "", + "raw_en_context_before": [], + "raw_en_context_after": [], + "preferred_num_beams": 4 + } + if num_texts == 1: + job["raw_en_sentence"] = texts[count] + job["quality"] = "fast" + jobs.append(job) + elif num_texts - 1 == count: + job["raw_en_sentence"] = texts[count] + job["raw_en_context_before"] = [texts[i] + for i in range(1 + count - 1)] + job["preferred_num_beams"] = 1 + jobs.append(job) + else: + job["raw_en_sentence"] = texts[count] + job["raw_en_context_before"] = [texts[i] + for i in range(1 + count - 1)] + job["raw_en_context_after"] = [texts[count + 1]] + job["preferred_num_beams"] = 1 + jobs.append(job) + count += 1 + return jobs + + def get_deepl_translation_response(self, jobs) -> dict: + """Sends user text input to DeepL API endpoint.""" + payload = { + "jsonrpc": "2.0", + "method": "LMT_handle_jobs", + "params": { + "jobs": jobs, + "lang": { + "user_preferred_langs": [ + self.target, self.source + ], + "source_lang_user_selected": "auto", + "target_lang": self.target + }, + "priority": -1, + "commonJobParams": {}, + "timestamp": math.floor(time.time() * 1000) + } + } + if "DE" not in payload["params"]["lang"]["user_preferred_langs"]: + payload["params"]["lang"]["user_preferred_langs"].insert( + 0, "DE") + if len(jobs) > 1: + payload["params"]["priority"] = 1 + payload = json.dumps(payload) + with self.s.post(self.api, data=payload) as r: + if r.ok: + return r.json() + else: + r.raise_for_status() + + def parse_deepl_translation_response(self, response) -> dict: + """Parses the response from the DeepL API endpoint.""" + translations = response["result"]["translations"] + if len(translations) > 1: + postprocessed_sentences = [ + t["beams"][0]["postprocessed_sentence"] for t in translations] + translation = " ".join(postprocessed_sentences) + examples = [] + else: + beams = translations[0]["beams"] + postprocessed_sentences = [ + beam["postprocessed_sentence"] for beam in beams] + translation = postprocessed_sentences.pop(0) + examples = postprocessed_sentences + info = { + "translation": translation, + "examples": examples + } + return info diff --git a/pystone/languages.py b/pystone/languages.py new file mode 100644 index 0000000..1f67a1f --- /dev/null +++ b/pystone/languages.py @@ -0,0 +1,56 @@ +import re +import unicodedata + +from .constants import LANGUAGES + + +class Languages: + def __init__(self, language): + self.language = self.asciify(language) + + def get_language(self) -> str: + """Gets lowercase English version of language""" + if self.language in LANGUAGES: + return self.language + else: + patterns = [ + (r"(^ara\w*)", "arabic"), + (r"(^ch?i\w*)", "chinese"), + (r"(^(?:dut|ni?e(?:d|e)?|h?ola?|flem?)\w*)", "dutch"), + (r"(^(?:e|a|i)ng\w*)", "english"), + (r"(^fr\w*)", "french"), + (r"(^(?:al(?:l?e?m?)|dui|ted|nie|ger)\w*)", "german"), + (r"(^h?(?:i|e)br?\w*)", "hebrew"), + (r"(^(?:ita|wos)\w*)", "italian"), + (r"(^(?:j|gi)ap\w*)", "japanese"), + (r"(^(?:po+l|l(?:us|eh))\w*)", "polish"), + (r"(^por\w*)", "portuguese"), + (r"(^r(?:o|u)(?:m|e|u)\w*)", "romanian"), + (r"(^r(?:u|o)s\w*)", "russian"), + (r"(^(?:hi|e|i)?sz?p\w*)", "spanish"), + (r"(^tur\w*)", "turkish"), ] + for p, l in patterns: + match = re.search(p, self.language) + if match: + return l + + def deepl_get_abbrv(self, language) -> str: + """Gets language abbreviation for DeepL""" + abbrv = LANGUAGES[language]["deepl"] + return abbrv + + def reverso_get_abbrv(self, language) -> str: + """Gets language abbreviation for Reverso""" + abbrv = LANGUAGES[language]["reverso"] + return abbrv + + def reverso_get_voice_name(self, language) -> str: + voice_name = LANGUAGES[language]["reverso_voice"] + return voice_name + + @staticmethod + def asciify(text) -> str: + """Remove accent marks from input""" + text = unicodedata.normalize("NFD", text) + asciified_text = text.encode("ascii", "ignore").decode("utf-8") + return asciified_text.lower() diff --git a/pystone/reverso.py b/pystone/reverso.py new file mode 100644 index 0000000..d608dd6 --- /dev/null +++ b/pystone/reverso.py @@ -0,0 +1,137 @@ +import base64 +import json + +import requests + +from .languages import Languages + + +class Reverso: + api = "https://api.reverso.net/translate/v1/translation" + voice_url = "https://voice.reverso.net/RestPronunciation.svc/v1/output=json/GetVoiceStream/voiceName={}?inputText={}" + + def __init__(self, text, source, target, s=None): + self.text = text + self.source = source + self.target = target + self.s = s + + def reverso(self) -> dict: + """The central method to the Reverso class that establishes a common + Session object, sets the language abbreviations for the Reverso API, + retrieves a response from the Reverso API endpoint, parses the + response from the Reverso API endpoint, and then returns the parsed + response.""" + self.s = self.create_session() + self.set_languages() + response = self.get_reverso_translation_response() + parsed_response = self.parse_reverso_translation_response(response) + return parsed_response + + def create_session(self) -> requests.sessions.Session: + """Creates a Session object.""" + headers = { + "accept-encoding": "gzip, deflate, br", + "content-type": "application/json; charset=utf-8", + "host": "api.reverso.net", + "origin": "https://www.reverso.net", + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4371.0 Safari/537.36" + } + s = requests.Session() + s.headers.update(headers) + return s + + def set_languages(self) -> None: + """Sets appropriate language abbreviations for use for the Reverso API.""" + source = Languages(self.source) + source_language = source.get_language() + self.source = source.reverso_get_abbrv(source_language) + target = Languages(self.target) + target_language = target.get_language() + self.target = target.reverso_get_abbrv(target_language) + return + + def get_reverso_translation_response(self) -> dict: + """Sends source text to the Reverso API endpoint.""" + payload = { + "input": self.text, + "from": self.source, + "to": self.target, + "format": "text", + "options": { + "origin": "reversodesktop", + "sentenceSplitter": True, + "contextResults": True, + "languageDetection": False + } + } + payload = json.dumps(payload) + with self.s.post(self.api, data=payload) as r: + if r.ok: + return r.json() + else: + r.raise_for_status() + + def parse_reverso_translation_response(self, response) -> dict: + """Parses the response from the Reverso API endpoint.""" + input_ = response["input"] + context_results = response["contextResults"] + if context_results: + results = context_results["results"] + if len(results) == 1: + translation = response["translation"] + alternatives = None + else: + translations = [result["translation"] for result in results] + translation = [translations.pop(0)] + alternatives = translations + source_examples = results[0]["sourceExamples"] + target_examples = results[0]["targetExamples"] + else: + translation = response["translation"] + source_examples, target_examples, alternatives = None, None, None + input_text = " ".join(input_) + translation_text = " ".join(translation) + info = { + "input": input_text, + "translation": translation_text, + "source_examples": source_examples, + "target_examples": target_examples, + "alternatives": alternatives + } + return info + + def audio(self): + self.s = self.create_session() + self.update_session_audio() + voice_name = self.get_reverso_voice() + input_text = self.base64_translation() + content = self.get_reverso_translation_audio(voice_name, input_text) + return content + + def update_session_audio(self) -> None: + audio_headers = { + "accept": "audio/webm,audio/ogg,audio/wav,audio/*;q=0.9,application/ogg;q=0.7,video/*;q=0.6,*/*;q=0.5", + "host": "voice.reverso.net" + } + self.s.headers.update(audio_headers) + return + + def base64_translation(self) -> str: + encoded_text = self.text.encode() + b64_encoded_translation = base64.b64encode(encoded_text) + b64_decoded_translation = b64_encoded_translation.decode() + return b64_decoded_translation + + def get_reverso_voice(self) -> str: + target = Languages(self.target) + target_language = target.get_language() + voice_name = target.reverso_get_voice_name(target_language) + return voice_name + + def get_reverso_translation_audio(self, voice, text) -> bytes: + with self.s.get(self.voice_url.format(voice, text)) as r: + if r.ok: + return r.content + else: + r.raise_for_status() diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..de8099a --- /dev/null +++ b/setup.py @@ -0,0 +1,45 @@ +import os +import setuptools + +with open("README.md", "r", encoding="utf-8") as f: + long_description = f.read() + +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", +] + +requirements = ["requests", "playsound"] + +main = os.path.abspath(os.path.dirname(__file__)) +about = {} +with open(os.path.join(main, "pystone", "__version__.py"), "r", encoding="utf-8") as f: + exec(f.read(), about) + +setuptools.setup( + name=about["__title__"], + version=about["__version__"], + author=about["__author__"], + author_email=about["__author_email__"], + description=about["__description__"], + long_description=long_description, + long_description_content_type="text/markdown", + url=about["__url__"], + license=about["__license__"], + packages=setuptools.find_packages(), + classifiers=classifiers, + keywords=["pystone", "translation", + "language", "python", "reverso", "deepl"], + install_requires=requirements, + entry_points={ + "console_scripts": ["pystone=pystone.__init__:main"] + }, + project_urls={ + "Source": "https://github.com/GBS3/pystone" + } +)