Traduttore multilingue online

Sep 15 2020

Quindi ho creato un programma che ti permette di tradurre da una lingua all'altra usando il prompt dei comandi. Vorrei consigli sulla progettazione, ottimizzazioni e suggerimenti e trucchi per evitare ripetizioni e scrivere codice efficace.

Codice:

import requests
from bs4 import BeautifulSoup
import sys


class Translator:

    # Default Options
    headers = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0'}
    website = 'https://context.reverso.net/translation/'
    languages = ['arabic', 'german', 'english', 'spanish', 'french', 'hebrew', 'japanese', 'dutch', 'polish', 'portuguese', 'romanian', 'russian', 'turkish']
    textfile_name = 'hello.txt'

    def __init__(self):
        """
        Initialises the following variables to be used through the course of the translator:
        1) response              : holds the response object for the link created by the input
        2) from_language         : holds the name of the language to translate the string from
        3) to_language           : holds the name of the language to translate the string to
        4) string_for_translation: holds the string to be translated from and to
        5) do_nothing            : a placeholder function, for doing nothing
        6) command_line          : holds the arguments provided in the command_line
        """
        self.response = None
        self.from_language = None
        self.to_language = None
        self.string_for_translation = None
        self.do_nothing = lambda: [None, None]
        self.command_line = sys.argv[1:]


    def verify_command_line(self):
        """
        verifies the arguments provided in the command line.

        returns True, if the arguments are valid, else exits the program and outputs an error prompt, if the arguments aren't valid.
        """
        if len(self.command_line) >= 3:
            if self.command_line[0].lower() in self.languages:
                if self.command_line[1].lower() in self.languages+["all"]:
                    if self.command_line[0] != self.command_line[1]:
                        return True

                    else:
                        print("The from_language and to_language cannot be the same!")
                else:
                    print(f"Sorry, the program doesn't support {self.command_line[1]}.")
            else:
                print(f"Sorry, the program doesn't support {self.command_line[0]}.")
        else:
            print("3 Arguments Must Be Provided In The Order: [from_language] [to_language] [string_for_translation].")
        exit()

    def parse_command_line(self):
        """
        parses the command line arguments into useful information/data for the translator.
        """
        
        self.from_language = self.command_line[0].lower()
        self.to_language = self.command_line[1].lower()
        self.string_for_translation = "+".join(self.command_line[2:])


    def get_response(self, url):
        """
        acquires the response object from the url provided.

        if the response is invalid, then an error prompt is raised based on status code or connectivity.
        """
        try:
            self.response = requests.get(url, headers=self.headers)
            if 400 < self.response.status_code < 500:
                print(f"Sorry, unable to find {self.string_for_translation}")
                exit()
        except requests.exceptions.ConnectionError:
            print("Something wrong with your internet connection")
            exit()



    @staticmethod
    def parse_translations(html):
        """
        parses the html for the translations of the string provided, which is located in a <a class='dict'> tag
        """
        return [tag.text.strip() for tag in html.find_all('a', class_='dict')]

    @staticmethod
    def parse_sentences(html):
        """
        parses the html for the example sentences of from_language and to_language, which is located in a unique css selector.
        """
        return [span.text.strip() for span in html.select("#examples-content .text")]


    def translate(self, from_language, to_language, string_for_translation):
        """
        creates the url for the translation, and calls the get_response function, to acquire the content of the page.
        that of which is parsed, using Beautiful Soup.

        returns a list of translations, and a list of example sentences alternating from the from_language and to_language
        """
        link = self.website + f'{from_language.lower()}-{to_language.lower()}/{string_for_translation}'
        self.get_response(link)

        html = BeautifulSoup(self.response.content, 'html.parser')
        translations      = self.parse_translations(html)
        example_sentences = self.parse_sentences(html)

        return translations, example_sentences
    

    def translate_to_all_into_textfile(self):
        """
        translates a string from a language to every language supported in self.languages.
        Then, writes them in a text file with the name in the variable textfile_name.
        Prints the output also in the console for each language.

        Does not print anything, in the current iteration of the languages loop, if the to_language is the same as from_language.
        """
        with open(self.textfile_name, 'w', encoding='utf-8') as text_file:
            for to_language in self.languages:
                translations, example_sentences = self.translate(self.from_language, to_language, self.string_for_translation) if to_language != self.from_language else self.do_nothing()
                self.print_format(translations, example_sentences, to_language, num_of_examples=1, output_into_textfile=text_file) if to_language != self.from_language else self.do_nothing()


    @staticmethod
    def print_format(translations, example_sentences, to_language, num_of_examples=5, output_into_textfile=False):
        """
        translations : refers to the list of translations of a string.
        example_sentences : refers to the list of example sentences alternating from from_language and to_language.
        to_language       : refers to the language to translate to.
        num_of_examples   : refers to a number defining the number of examples of translations and example_sentences to print. Default = 5.
        output_into_textfile : refers to a file object (textfile), to print the output into, if provided. Default = False.


        Prints the results in an appropriate format:
        string_used = 'Hello'

        - - - - - - - - - - - - -
        French Translations:
        bonjour
        allô
        ohé
        coucou

        French Examples:
        Hello SMS World! , Success .:
        Bonjour, monde des SMS ! ","Succès.

        Hello, Mark Dessau, please.:
        Bonjour, Mark Dessau, s#39;il vous plaît.

        Hello, I've something confidential to report.:
        Allô, j'ai quelque chose de confidentiel à révéler.

        Hello, this is Ina Müller's voicemail.:
        Allô. Vous êtes sur le répondeur d'Ina Müller. Je ne suis pas disponible.

        Hello, I'm Tommy Tuberville.:
        Bonjour. Je suis Tommy Tuberville, Université d'Auburn.

        - - - - - OR - - - - - if all languages is chosen, and num_of_examples is 1

        Arabic Translations:
        مرحبا

        Arabic Examples:
        Hello, is Alex Romero available?:
        مرحباً، هل (آليكس روميرو) متاح ""؟


        German Translations:
        hallo

        German Examples:
        Hello. Welcome to High Adventure.:
        Hallo und willkommen bei "High Adventure".


        Spanish Translations:
        hola

        Spanish Examples:
        Hola, esta es la policía de Bradfield.


        French Translations:
        bonjour

        French Examples:
        Hello SMS World! , Success .:
        Bonjour, monde des SMS ! ","Succès.


        Hebrew Translations:
        שלום

        Hebrew Examples:
        Your honor! Hello, Sheriff.:
        כבודו - .שלום, שריף - ...האישה שהתוודתה בטוחה


        Japanese Translations:
        こんにちは

        Japanese Examples:
        Hello, I am Pete Lavache from Platforms Marketing:
        こんにちは、プラットフォーム・マーケッティングのPete Lavacheです。


        Dutch Translations:
        dag

        Dutch Examples:
        Hello, darling wife. Hello, husband.:
        Dag, lief vrouwtje - Dag, mannetje.


        Polish Translations:
        cześć

        Polish Examples:
        Hello and thanks for this great plugin.:
        Cześć i dzięki za ten wspaniały plugin.


        Portuguese Translations:
        olá

        Portuguese Examples:
        Hello pedestrians, city folk... urban professionals.:
        Olá, peões, habitantes da cidade... profissionais urbanos.


        Romanian Translations:
        salut

        Romanian Examples:
        Hello and welcome to the show speaking with Charlie...:
        Salut și bine v-am găsit la show-ul "De vorba cu Charlie"...


        Russian Translations:
        привет

        Russian Examples:
        Hello, I knocked but nobody opened.:
        Привет, я стучалась, но никто не открывал.


        Turkish Translations:
        selam

        Turkish Examples:
        Hello everybody and welcome to NWA airlines.:
        Selam, millet, ve NWA Havayollarına hoş geldiniz.
        - - - - - - - - - - - - -
        """
        to_language = to_language.title()
        if output_into_textfile:
            print(f"{to_language} Translations:\n", "\n".join(translations[0:num_of_examples]), end='\n\n', file=output_into_textfile)
            print(f"{to_language} Example:\n"    , "\n\n".join([f"{example[0]}:\n{example[1]}" for example in zip(example_sentences[:num_of_examples*2:2], example_sentences[1:num_of_examples*2:2])]), end='\n', file=output_into_textfile)
            print("\n", file=output_into_textfile)

        print("\n")
        print(f"{to_language} Translations:\n", "\n".join(translations[0:num_of_examples]), end='\n\n')
        print(f"{to_language} Examples:\n"    , "\n\n".join([f"{example[0]}:\n{example[1]}" for example in zip(example_sentences[:num_of_examples*2:2], example_sentences[1:num_of_examples*2:2])]), end='\n')
        

    def main(self):
        """
        main function, for running the steps to translate in order of: -

        1) Requesting Input:
            - if to_language_number is '0', then it sets self.to_language to "All".

        2) Translating Based on Input:
            - performs multiple translations on all languages, if to_language = "All".
            
        3) Printing in Appropriate Format:
            - if to_language is "All", then it prints one example sentence, and one translation for each language, and pastes it into a text file.
            - if to language is not "All, then it prints five example sentences, and 5 translations for the to_language chosen.
        """
        if self.verify_command_line():
            self.parse_command_line()

        if self.to_language != 'all':
            translations, example_sentences = self.translate(self.from_language, self.to_language, self.string_for_translation)
            self.print_format(translations, example_sentences, self.to_language)
        else:
            self.translate_to_all_into_textfile()


if __name__ == '__main__':
    translator = Translator()
    translator.main()



Produzione:

Command Line Based Program - MultiLingual Online Translator

Example 1:
> python "MultiLingual Online Translator.py" English French string

French Translations:
 chaîne
corde
train
string
ficelle

French Examples:
 An string broadcast station receives message content.:
Une station de radiodiffusion de chaîne de caractères reçoit un contenu de message.

The data structures are originally described in a string.:
Les structures de données sont initialement décrites sous forme d'une chaîne.

The expanded region accommodates interval consistent outward string bend functionality.:
La région élargie comprend une fonction permettant de faire un tiré sur la corde extérieure en cohérence avec les intervalles.

An improved musical instrument string is provided.:
L'invention concerne une corde améliorée pour instruments de musique.

The drill string is not rotated.:
Le train de tiges n'est pas mis en rotation.


Example 2;
> python "MultiLingual Online Translator.py" English All string

Arabic Translations:
 سلسلة

Arabic Examples:
 That we play coy, string her along in negotiations:
أن نلعب كوي، سلسلة لها جنبا إلى جنب في المفاوضات


German Translations:
 Zeichenfolge

German Examples:
 Enter an alphanumeric string to describe a unique alternate.:
Geben Sie eine alphanumerische Zeichenfolge ein, um die Alternative eindeutig zu kennzeichnen.


Spanish Translations:
 cadena

Spanish Examples:
 The string argument enables passing additional key/value pairs with the ad request.:
El argumento de cadena permite pasar pares clave/valor adicionales con la solicitud de anuncios.


French Translations:
 chaîne

French Examples:
 An string broadcast station receives message content.:
Une station de radiodiffusion de chaîne de caractères reçoit un contenu de message.


Hebrew Translations:
 מחרוזת

Hebrew Examples:
 Microsoft Dynamics AX cannot parse the Web action item configuration string.:
ל - Microsoft Dynamics AX אין אפשרות לנתח את מחרוזת התצורה של פריט הפעולה של האינטרנט.


Japanese Translations:
 文字列

Japanese Examples:
 Unknown token in SRestriction resource string.:
SRestriction リソース 文字列に不明なトークンが含まれています。


Dutch Translations:
 string

Dutch Examples:
 Returns given section of a string.:
Geeft een bepaald deel uit een string terug.


Polish Translations:
 struna

Polish Examples:
 Another relationship between different string theories is T-duality.:
Inną relacją pomiędzy różnymi teoriami strun jest T-dualność.


Portuguese Translations:
 string

Portuguese Examples:
 Possible values: IP address string.:
Valores possíveis: endereço IP em forma de string.


Romanian Translations:
 șir

Romanian Examples:
 Real workaholic, impressive string of wins.:
Real dependent de muncă, șir impresionant de victorii.


Russian Translations:
 строка

Russian Examples:
 The icons and graphics should undergo similar checking and translation as the string text to identify any possible misinterpretations.:
Иконки и графические объекты должны проверяться и переводиться так же, как и строки текста для выявления любых возможных ошибок при толковании.


Turkish Translations:
 ip

Turkish Examples:
 Get a lot of string, slap it together...:
Bir sürü ip al, birbirine bağla...
```

Risposte

4 Marc Sep 15 2020 at 17:55

Buona implementazione, facile da leggere e da capire. Pochi suggerimenti:

  • If-else annidato in verify_command_linerende non semplice da capire:
    if len(self.command_line) >= 3:
        if self.command_line[0].lower() in self.languages:
            if self.command_line[1].lower() in self.languages+["all"]:
                if self.command_line[0] != self.command_line[1]:
                    return True
    
                else:
                    print("The from_language and to_language cannot be the same!")
            else:
                print(f"Sorry, the program doesn't support {self.command_line[1]}.")
        else:
            print(f"Sorry, the program doesn't support {self.command_line[0]}.")
    else:
        print("3 Arguments Must Be Provided In The Order: [from_language] [to_language] [string_for_translation].")
    exit()
    
    considera di utilizzare una logica deny-all :
    if len(self.command_line) < 3:
        print("3 Arguments Must Be Provided In The Order: [from_language] [to_language] [string_for_translation].")
    elif self.command_line[0].lower() not in self.languages:
        print(f"Sorry, the program doesn't support {self.command_line[0]}.")
    elif self.command_line[1].lower() not in self.languages + ["all"]:
        print(f"Sorry, the program doesn't support {self.command_line[1]}.")
    elif self.command_line[0] == self.command_line[1]:
        print("The from_language and to_language cannot be the same!")
    else:
        return True
    exit()
    
    Inoltre, tale metodo non è facile da testare, perché stampa sulla console e in alcuni casi viene chiuso. Un'alternativa è restituire un valore booleano e un messaggio. Ad esempio: True,""o False,"The from_language and to_language cannot be the same!".
  • Il metodo print_formatcontiene una logica duplicata:
    if output_into_textfile:
        print(f"{to_language} Translations:\n", "\n".join(translations[0:num_of_examples]), end='\n\n', file=output_into_textfile)
        print(f"{to_language} Example:\n"    , "\n\n".join([f"{example[0]}:\n{example[1]}" for example in zip(example_sentences[:num_of_examples*2:2], example_sentences[1:num_of_examples*2:2])]), end='\n', file=output_into_textfile)
        print("\n", file=output_into_textfile)
    
    print("\n")
    print(f"{to_language} Translations:\n", "\n".join(translations[0:num_of_examples]), end='\n\n')
    print(f"{to_language} Examples:\n"    , "\n\n".join([f"{example[0]}:\n{example[1]}" for example in zip(example_sentences[:num_of_examples*2:2], example_sentences[1:num_of_examples*2:2])]), end='\n')
    
    creare translationse examplesprima e poi stamparli:
    translations_output = # create output string
    example_output = # create output string                                                         
    if output_into_textfile:
        print(translations_output, file=output_into_textfile)
        print(example_output, file=output_into_textfile)
        print("\n", file=output_into_textfile)
    
    print("\n")
    print(translations_output)
    print(example_output)
    

Principio di responsabilità unica

La classe Translatorè incaricata di analizzare l'input, fare la traduzione, formattare l'output e contenere la logica dell'intero programma nel self.mainmetodo. Considera come gestiresti queste modifiche:

  • L'input proviene da un file
  • L'output deve essere formattato correttamente per l'utente
  • Fornisci una traduzione interattiva
  • Usa Google Translate invece di BeautifulSoup

Ogni opzione richiede che la classe Translatorcambi, ma SRP dice "Una classe dovrebbe avere un solo motivo per cambiare".

Il mio suggerimento è di distribuire le responsabilità in più classi e metodi, ad esempio:

class Translator:
    def __init__(self, provider):
    def translate(self, from_lang, to_lang, string_to_translate):
    def translate_to_all(self,from_lang, string_to_translate):
    def examples(self, from_lang, to_lang, string_to_translate):
    def examples_to_all(self, from_lang, string_to_translate):

La classe Translatorcontiene le lingue supportate, traduce una stringa e genera esempi. Sentiti libero di adattare l'interfaccia al tuo caso d'uso, il punto è rendere il programma più modulare. Un provider è un adattatore per la libreria BeautifulSoup:

class BeautifulSoupProvider:
    def __init__(self):
        pass
    # public methods
    def translate(self, from_language, to_language, sentence):
    def examples(self, from_lang, to_lang, sentence):
    # private methods
    def __get_response(self, url):
    def __parse_translations(html):
    def __parse_sentences(html):

Il __main__sarebbe qualcosa di simile:

def verify_command_line(args):
    #...

def to_console(translations, examples, to_language):
    #..

def to_file(translations, examples):
    #..

if __name__ == '__main__':
    if not verify_command_line(sys.argv[1:]):
        exit()
    # get from_language, to_language and string_to_translate
    translator = Translator(BeautifulSoupProvider())
    if to_language == 'all':
        translations = translator.translate_to_all(from_language,string_to_translate)
        examples = translator.examples_to_all(from_language, string_to_translate)
        to_file(translations,examples)
    else:
        translations = translator.translate(from_language, to_language, string_to_translate)
        example_sentences = translator.examples(from_language, to_language, string_to_translate)
        to_console(translations, example_sentences, to_language)

Ora il programma è più modulare e ogni metodo e classe ha la propria responsabilità. Ad esempio, l'aggiunta di Google Translate richiede solo la creazione di una nuova classe dell'adattatore e la modifica di una riga nella mainfunzione. Le modifiche all'input e all'output non influenzeranno la classe Translatore finalmente puoi testare facilmente tutti i metodi.

Nota: il codice che ho fornito non è testato, serve solo a fornire alcuni esempi.