« TranslatorBot » : différence entre les versions

De Wikipast
Aller à la navigation Aller à la recherche
 
(23 versions intermédiaires par 2 utilisateurs non affichées)
Ligne 2 : Ligne 2 :
==Description==
==Description==
[[TranslatorBot]] traduit les pages en plusieurs langues au moyen de la librairie Python: googletrans qui exploite les services de traduction de Google. L'objectif de ce bot est d'envisager une utilisation de [[Wikipast]] par des utilisateurs du monde entier, et c'est donc pour cela que nous avons mis l'accent sur la traduction en anglais.
[[TranslatorBot]] traduit les pages en plusieurs langues au moyen de la librairie Python: googletrans qui exploite les services de traduction de Google. L'objectif de ce bot est d'envisager une utilisation de [[Wikipast]] par des utilisateurs du monde entier, et c'est donc pour cela que nous avons mis l'accent sur la traduction en anglais.
==Méthode de Travail==
Le code a été partagé via une repository privée sur '''GitHub''' et a été élaboré sur '''Jupyter Notebook'''.


==Méthode de traduction==
==Méthode de traduction==
On traduit par bloc d'environ 5000 caractères, en adaptant la taille du bloc selon la ponctuation (afin d'être certain de traduire une phrase complète et non une partie de la phrase).
Le bot lancé sur une page fonctionne comme suit:
* extraction du texte brute de la page
* parcours du texte à la recherche des liens vers les sources et les enregistrent dans un tableau
* traduction du texte par bloc d'environ 5000 caractères, en adaptant la taille du bloc selon la ponctuation (afin d'être certain de traduire une phrase complète et non une partie de la phrase).
* parcours du texte à la recherche des hyperliens afin de les connecter à la page sous la forme "(langue) titre" tout en gardant un lien compréhensible (sans la précision de la langue).


Afin d'avoir une meilleure synergie avec d'autres bots, [[TranslatorBot]] prendra en paramètre la liste des pages à traduire ainsi que la langue choisie. Cela permet de garder le contrôle du bot et d'éviter qu'il traduise à la chaîne chaque nouvelle page qu'il trouve.
Afin d'avoir une meilleure synergie avec d'autres bots, [[TranslatorBot]] prendra en paramètre la liste des pages à traduire. Cela permet de garder le contrôle du bot et d'éviter qu'il traduise à la chaîne chaque nouvelle page qu'il trouve.


[[TranslatorBot]] est entièrement modulable grâce à un système de vérification du vocabulaire distinguant les noms propres des noms communs.  
[[TranslatorBot]] est entièrement modulable grâce à un système de vérification du vocabulaire distinguant les noms propres des noms communs.  
Ligne 30 : Ligne 37 :


==Exemples de Résultats==
==Exemples de Résultats==
Original français (cliquer pour agrandir):
Original français:


[[Fichier:fr.jpg|500px]]
[[Fichier:JohnLennonFr.jpg|500px]]


Résultat de traduction par [[TranslatorBot]] (cliquer pour agrandir):
Résultat de traduction par [[TranslatorBot]]:


[[Fichier:eng.jpg|500px]]
[[Fichier:2.jpg|500px]]


==Évaluation de TranslatorBot==
==Évaluation de TranslatorBot==
Temps de traduction: (à compléter une fois l'implémentation du bot est terminée)
Temps de traduction: environ 0.09 à 2 secondes (dépend de la taille de la page)  


Qualité de traduction: Acceptable pour l'anglais, bien que limitée par les possibilités de Google Traduction.
Qualité de traduction: Acceptable pour l'anglais, bien que limitée par les possibilités de Google Traduction.
==Programmation==
Le bot a été créé au cours d'un travail de groupe. Le code a été partagé via une repository privée sur '''GitHub'''. L'environnement utilisé est '''Anaconda''' et '''Jupyter Notebook'''.
L'implémentation du bot a été précédée par des recherches sur la création des bots.


==Code==
==Code==
Ligne 52 : Ligne 54 :
  <nowiki>
  <nowiki>
import requests
import requests
import re
from bs4 import BeautifulSoup
from bs4 import BeautifulSoup
from googletrans import Translator
from googletrans import Translator


user='testbot'
# the function takes a table of strings as argument containing the names of the pages to translate
passw='dhbot2017'
def translate(names):
baseurl='http://wikipast.epfl.ch/wikipast/'
    user='TranslatorBot'
summary='Wikipastbot update'
    passw='humanitedigital'
names=['Henri Dunant']
    baseurl='http://wikipast.epfl.ch/wikipast/'
translator = Translator()
    summary='Wikipastbot update'
    translator = Translator()
 
    # this parameter is the target language in which we want to translate
    target_lang = 'en'
    target_language = 'English'
 
    # login request
    payload={'action':'query','format':'json','utf8':'','meta':'tokens','type':'login'}
    r1=requests.post(baseurl + 'api.php', data=payload)
 
    # login confirm
    login_token=r1.json()['query']['tokens']['logintoken']
    payload={'action':'login','format':'json','utf8':'','lgname':user,'lgpassword':passw,'lgtoken':login_token}
    r2=requests.post(baseurl + 'api.php', data=payload, cookies=r1.cookies)
 
    # get edit token2
    params3='?format=json&action=query&meta=tokens&continue='
    r3=requests.get(baseurl + 'api.php' + params3, cookies=r2.cookies)
    edit_token=r3.json()['query']['tokens']['csrftoken']
 
    edit_cookie=r2.cookies.copy()
    edit_cookie.update(r3.cookies)
 
    # we fetch the text we want to translate
    for name in names:
        result=requests.post(baseurl+'api.php?action=query&titles='+name+'&export&exportnowrap')
        soup=BeautifulSoup(result.text, "lxml")
        code=''
        for primitive in soup.findAll("text"):
            code += primitive.string
 
        # create names with english prefix
        en_name = "(" + target_lang + ")_" + translator.translate(name, src='fr', dest=target_lang).text


# login request
        # add a table in the french page if it still not exists
payload={'action':'query','format':'json','utf8':'','meta':'tokens','type':'login'}
        if(code != '' and code[0] != '{'and code[0] != '|') :
r1=requests.post(baseurl + 'api.php', data=payload)
          code2 = '''{| class="wikitable"\n|Langue \n|''' + "'''Français'''\n|[[" + en_name + "|" + target_language + "]]\n|}\n" + code
          payload2={'action':'edit','assert':'user','format':'json','utf8':'','text':code2,'summary':summary,'title':name,'token':edit_token}
          r5=requests.post(baseurl+'api.php',data=payload2,cookies=edit_cookie)


# login confirm
        # save the links of sources that we won't translate
login_token=r1.json()['query']['tokens']['logintoken']
        sources = []
payload={'action':'login','format':'json','utf8':'','lgname':user,'lgpassword':passw,'lgtoken':login_token}
        i=0
r2=requests.post(baseurl + 'api.php', data=payload, cookies=r1.cookies)
        while i< len(code):
            if (code[i-1] != '[' and code[i] == '[' and code[i+1] != '[') :
                j = i+2
                while(code[j] != ']') :
                    j += 1
                sources.append(code[i:j+1])       
                code = code.replace(code[i:j+1], "&&&", 1)
                i = j+1
            else:
                i += 1


# get edit token2
        # translate the whole text by chunk of approx. 5000 characters.
params3='?format=json&action=query&meta=tokens&continue='
        length = len(code)
r3=requests.get(baseurl + 'api.php' + params3, cookies=r2.cookies)
        chaine =''
edit_token=r3.json()['query']['tokens']['csrftoken']
        punto = '.'
        k = 0
        diminution = 1
        last = k+5000
        while last < length:       
            if code[last] == punto:
                chaine += translator.translate(code[k:last], src = 'fr', dest= target_lang).text
                k = last
            else:
                while code[k+5000-diminution] != punto:
                    diminution += 1
                chaine += translator.translate(code[k:k+5000-diminution],src = 'fr', dest= target_lang).text
                k = k+5000-diminution+1
                diminution = 1
            last += 5000       
        last -= 5000
        chaine += translator.translate(code[last:length],src='fr',dest= target_lang).text


edit_cookie=r2.cookies.copy()
        translated_text = chaine
edit_cookie.update(r3.cookies)


# we fetch the text we want to translate
        # make the hyperlinks point to the correct page while hiding the (en)
for name in names:
        for i in range(len(translated_text)):
    result=requests.post(baseurl+'api.php?action=query&titles='+name+'&export&exportnowrap')
            if (translated_text[i] == '[' and translated_text[i+1] == '[' and translated_text[i+2].isalpha()) :
    soup=BeautifulSoup(result.text, "lxml")
                j = i
    code=''
                while(translated_text[j] != ']') :
    for primitive in soup.findAll("text"):
                    j += 1
        code += primitive.string
                m = translated_text[i+2:j]
                linkM = "[[" + m + "]]"
                translated_text = translated_text.replace(linkM, "[[(" + target_lang + ")_" + m + '|' + m + "]]")


    translated_text = translator.translate(code[:5000], src='fr', dest='en').text
        # replace the translates sources by the original ones
   
        M_final_text = translated_text.split("&&&")
     #Create names with english prefix
        final_text =""
     en_name = 'en/' + translator.translate(name, src='fr', dest='en').text
        for i in range(len(sources)):
            final_text += M_final_text[i]
            final_text += sources[i]
        translated_text = final_text
 
        # avoid problems due to the comments of other bots
        translated_text = translated_text.replace('->', '-->').replace('<! -', '<!--').replace('</ ', '</')
 
        # add the table
        translated_text = '''{| class="wikitable"\n|Language \n|[[''' + name + "|Français]]\n|'''" + target_language + "'''\n|}\n" + translated_text
 
     # write on the page
     payload={'action':'edit','assert':'user','format':'json','utf8':'','text':translated_text,'summary':summary,'title':en_name,'token':edit_token}
    r4=requests.post(baseurl+'api.php',data=payload,cookies=edit_cookie)
</nowiki>
</nowiki>


==Développement futur pour TranslatorBot==
==Développements futurs pour le TranslatorBot==
*Ajouter le tableau dans toutes les pages [[Wikipast]], et dans celles qui ne sont pas encore traduites, le tableau pourrait contenir une case "Traduire la page" qui lancerait le [[TranslatorBot]] sur la page en question et créerait la version traduite dans la langue choisie. Cette fonctionnalité permettrait aux utilisateurs de participer à la traduction contrôlée et progressive de [[Wikipast]].
*Ajouter le tableau dans toutes les pages [[Wikipast]], et dans celles qui ne sont pas encore traduites, le tableau pourrait contenir une case "Traduire la page" qui lancerait le [[TranslatorBot]] sur la page en question et créerait la version traduite dans la langue choisie. Cette fonctionnalité permettrait aux utilisateurs de participer à la traduction contrôlée et progressive de [[Wikipast]].
*De la même manière, pour les hyperliens dans des pages traduites qui mènent à des pages qui n'existent pas encore, on pourrait implémenter un système d'appel du bot à partir de l'hyperlien, chercherait la page correspondante non traduite et lancerait le bot sur celle-ci.
*De la même manière, pour les hyperliens dans des pages traduites qui mènent à des pages qui n'existent pas encore, on pourrait implémenter un système d'appel du bot à partir de l'hyperlien, chercherait la page correspondante non traduite et lancerait le bot sur celle-ci.
*Ajouter la possibilité de traduire des pages dans plus de langues que l'anglais.
*Ajouter la possibilité de traduire des pages dans plus de langues que l'anglais.
==Conclusion et Remerciements==
Nous remercions le cours SHS '''Humanités Digitales''' pour cette magnifique opportunité de nous organiser pour travailler sur un projet nouveau.

Dernière version du 20 mai 2018 à 18:59

Description

TranslatorBot traduit les pages en plusieurs langues au moyen de la librairie Python: googletrans qui exploite les services de traduction de Google. L'objectif de ce bot est d'envisager une utilisation de Wikipast par des utilisateurs du monde entier, et c'est donc pour cela que nous avons mis l'accent sur la traduction en anglais.

Méthode de Travail

Le code a été partagé via une repository privée sur GitHub et a été élaboré sur Jupyter Notebook.

Méthode de traduction

Le bot lancé sur une page fonctionne comme suit:

  • extraction du texte brute de la page
  • parcours du texte à la recherche des liens vers les sources et les enregistrent dans un tableau
  • traduction du texte par bloc d'environ 5000 caractères, en adaptant la taille du bloc selon la ponctuation (afin d'être certain de traduire une phrase complète et non une partie de la phrase).
  • parcours du texte à la recherche des hyperliens afin de les connecter à la page sous la forme "(langue) titre" tout en gardant un lien compréhensible (sans la précision de la langue).

Afin d'avoir une meilleure synergie avec d'autres bots, TranslatorBot prendra en paramètre la liste des pages à traduire. Cela permet de garder le contrôle du bot et d'éviter qu'il traduise à la chaîne chaque nouvelle page qu'il trouve.

TranslatorBot est entièrement modulable grâce à un système de vérification du vocabulaire distinguant les noms propres des noms communs.

TranslatorBot crée la page traduite dans un répertoire correspondant à la langue choisie.

Problèmes rencontrés

  • Un des premiers problèmes considérés, au vu des pages déjà existantes sur Wikipast, a été la traduction des pages générées ou modifiées par des bots. Celles-ci étant parfois victimes d'actions indésirables d'autres bots, leur contenu n'a aucun sens et, par conséquent, devient impossible à traduire.
→ Le problème s'est réglé de lui-même: la méthode appelée par TranslatorBot garde la partie problématique du texte non modifiée.
Il s'agit d'un soucis à considérer par le ManagerBot.
  • Si on ne traduit pas les hyperlinks, comment faire la différence entre un hyperlink d'un nom propre qui va vers une page en français et celui qui mène en anglais?
→ Ce problème est réglé avec la solution pour les noms propres par biais d'un nouveau bot qui classe les pages des personnalités.
  • Un autre problème important consiste en traduction des noms propres. A première vue, les noms propres tels que les noms géographiques doivent être traduits; cependant, les noms des personnes ne sont pas à traduire. Un autre problème se pose ici: comment distinguer un nom de famille d'origine géographique d'un nom géographique?
→ La solution la plus élégante serait de laisser à la prochaine volée d'étudiants la création d'un nouveau bot qui classerait les pages existantes selon leur genre. Ceci permettrait de facilement distinguer les pages de personnalités des autres.
  • Une question pertinente est la séparation des blocs à traduire.
→ On boucle sur le texte: on part du 5000-ème caractère et on itère sur les caractères précédents pour trouver l'indice d'une ponctuation de fin de phrases. On traduit le bloc de texte jusqu'à cet indice, et puis on réitère l'opération.
  • Comment gérer les pages traduites: répertoire selon les langues
→ Création d'un tableau listant toutes les langues de traduction disponibles (pour l'instant juste l'anglais), le tableau est ajouté au début de la page originale et des pages traduites (après avoir vérifié s'il n'y était pas déjà). Le titre des pages traduites dans une langue autre que le français est sous la forme "(langue)___nom".

Exemples de Résultats

Original français:

JohnLennonFr.jpg

Résultat de traduction par TranslatorBot:

2.jpg

Évaluation de TranslatorBot

Temps de traduction: environ 0.09 à 2 secondes (dépend de la taille de la page)

Qualité de traduction: Acceptable pour l'anglais, bien que limitée par les possibilités de Google Traduction.

Code

import requests
import re
from bs4 import BeautifulSoup
from googletrans import Translator

# the function takes a table of strings as argument containing the names of the pages to translate 
def translate(names):
    user='TranslatorBot'
    passw='humanitedigital'
    baseurl='http://wikipast.epfl.ch/wikipast/'
    summary='Wikipastbot update'
    translator = Translator()

    # this parameter is the target language in which we want to translate
    target_lang = 'en'
    target_language = 'English'

    # login request
    payload={'action':'query','format':'json','utf8':'','meta':'tokens','type':'login'}
    r1=requests.post(baseurl + 'api.php', data=payload)

    # login confirm
    login_token=r1.json()['query']['tokens']['logintoken']
    payload={'action':'login','format':'json','utf8':'','lgname':user,'lgpassword':passw,'lgtoken':login_token}
    r2=requests.post(baseurl + 'api.php', data=payload, cookies=r1.cookies)

    # get edit token2
    params3='?format=json&action=query&meta=tokens&continue='
    r3=requests.get(baseurl + 'api.php' + params3, cookies=r2.cookies)
    edit_token=r3.json()['query']['tokens']['csrftoken']

    edit_cookie=r2.cookies.copy()
    edit_cookie.update(r3.cookies)

    # we fetch the text we want to translate
    for name in names:
        result=requests.post(baseurl+'api.php?action=query&titles='+name+'&export&exportnowrap')
        soup=BeautifulSoup(result.text, "lxml")
        code=''
        for primitive in soup.findAll("text"):
            code += primitive.string

        # create names with english prefix
        en_name = "(" + target_lang + ")_" + translator.translate(name, src='fr', dest=target_lang).text

        # add a table in the french page if it still not exists
        if(code != '' and code[0] != '{'and code[0] != '|') :
           code2 = '''{| class="wikitable"\n|Langue \n|''' + "'''Français'''\n|[[" + en_name + "|" + target_language + "]]\n|}\n" + code
           payload2={'action':'edit','assert':'user','format':'json','utf8':'','text':code2,'summary':summary,'title':name,'token':edit_token}
           r5=requests.post(baseurl+'api.php',data=payload2,cookies=edit_cookie)

        # save the links of sources that we won't translate
        sources = []
        i=0
        while i< len(code):
            if (code[i-1] != '[' and code[i] == '[' and code[i+1] != '[') :
                j = i+2
                while(code[j] != ']') :
                    j += 1
                sources.append(code[i:j+1])        
                code = code.replace(code[i:j+1], "&&&", 1)
                i = j+1
            else:
                i += 1

        # translate the whole text by chunk of approx. 5000 characters.
        length = len(code)
        chaine =''
        punto = '.'
        k = 0
        diminution = 1
        last = k+5000
        while last < length:        
            if code[last] == punto:
                chaine += translator.translate(code[k:last], src = 'fr', dest= target_lang).text
                k = last
            else:
                while code[k+5000-diminution] != punto:
                    diminution += 1
                chaine += translator.translate(code[k:k+5000-diminution],src = 'fr', dest= target_lang).text
                k = k+5000-diminution+1
                diminution = 1
            last += 5000        
        last -= 5000
        chaine += translator.translate(code[last:length],src='fr',dest= target_lang).text

        translated_text = chaine

        # make the hyperlinks point to the correct page while hiding the (en)
        for i in range(len(translated_text)):
            if (translated_text[i] == '[' and translated_text[i+1] == '[' and translated_text[i+2].isalpha()) :
                j = i
                while(translated_text[j] != ']') :
                    j += 1
                m = translated_text[i+2:j]
                linkM = "[[" + m + "]]"
                translated_text = translated_text.replace(linkM, "[[(" + target_lang + ")_" + m + '|' + m + "]]")

        # replace the translates sources by the original ones
        M_final_text = translated_text.split("&&&")
        final_text =""
        for i in range(len(sources)):
            final_text += M_final_text[i]
            final_text += sources[i]
        translated_text = final_text

        # avoid problems due to the comments of other bots
        translated_text = translated_text.replace('->', '-->').replace('<! -', '<!--').replace('</ ', '</')

        # add the table
        translated_text = '''{| class="wikitable"\n|Language \n|[[''' + name + "|Français]]\n|'''" + target_language + "'''\n|}\n" + translated_text

    # write on the page
    payload={'action':'edit','assert':'user','format':'json','utf8':'','text':translated_text,'summary':summary,'title':en_name,'token':edit_token}
    r4=requests.post(baseurl+'api.php',data=payload,cookies=edit_cookie)

Développements futurs pour le TranslatorBot

  • Ajouter le tableau dans toutes les pages Wikipast, et dans celles qui ne sont pas encore traduites, le tableau pourrait contenir une case "Traduire la page" qui lancerait le TranslatorBot sur la page en question et créerait la version traduite dans la langue choisie. Cette fonctionnalité permettrait aux utilisateurs de participer à la traduction contrôlée et progressive de Wikipast.
  • De la même manière, pour les hyperliens dans des pages traduites qui mènent à des pages qui n'existent pas encore, on pourrait implémenter un système d'appel du bot à partir de l'hyperlien, chercherait la page correspondante non traduite et lancerait le bot sur celle-ci.
  • Ajouter la possibilité de traduire des pages dans plus de langues que l'anglais.