Translation API - bounswe/2021SpringGroup9 GitHub Wiki

Translation API Documentation


1. Glossary

  • Story: A model for stories that users will publish :

    • id: Story's id for identification
    • title: Title of the story with maximum char length 200.
    • story: Main content of the story with maximum char length 1000. It will be referred to as "body" to avoid ambiguity.
    • name: A nickname for the user who posts the story.
    • longitude: Longitude of story location.
    • latitude: Latitude of story location.
    • location: Location of a story, expressed as in everyday language.
    • tag: A semantic tag that gives information about the main topic of a story.
    • date: The date the story was published.
    • notifyAdmin: A marker that identifies if the admin should be notified about the content story.
  • Translation: A model to store translation of title and body of a story. :

    • story_id: Story's id for identification, a foreign key from Story model.
    • language: The language to which the story was translated. Stored as a language code such as "tr", "fr", "ne"
    • title_trans: Translated version of the title.
    • story_trans: Translated version of the body.
  • IBMTranslator: An external API that can translate texts. The full name of the API is "IBM Watson Language Translator API".

2. API Reference

Translation API is a REST API that supports translating a story's title and body to another language.

The API sends requests to IBMTranslator to retrieve translations. For documentation of IBMTranslator, click here. IBMTranslator can automatically detect the source language of a text unless it is too short. For this reason, Translation API does not allow translation of a story if it has any of the following:

  • Title shorter than 2 words.
  • Title shorter than 6 characters.
  • Body shorter than 2 words.
  • Body shorter than 10 characters.

3. Features

The API only supports GET requests, but superusers can store translations to the database. If a translated story is already present in the database, the translation is retrieved from the database without sending a request to IBMTranslator.

Get a story with the title and body translated to a specific language.

Request:

GET /api/story/<int:id>/translate_<str:target>/

Explanation of parameters:

id: The id of the Story

target: Target language for translation. Specified as a language code such as "tr", "fr", "it"

4. Example Requests and Responses

Successful Request Request:

GET http://3.129.194.233/api/story/1/translate_ita/

Response:

{
    "id": 1,
    "date": "2021-06-10T10:26:00.117830+03:00",
    "name": "zcanfes",
    "location": "Rome",
    "tag": "happy",
    "title": "Primo giorno a Roma",
    "story": "Questo è stato il mio primo giorno a Roma dopo essersi trasferito. Sono uscito da casa e ho iniziato a camminare verso il centro città. Ovunque ci sono edifici da vedere e ammirare. Solo essere in città può riprenderti in tempo per sperimentare la vecchia Roma. Oggi ho intenzione di visitare il Colosseo e non vedo l'ora di vedere uno dei monumenti più importanti di Roma. Mi prenderò un sacco di foto e condividerò la mia storia qui.",
    "notifyAdmin": false,
    "latitude": 41.9027835,
    "longitude": 12.4963655
}

Status Code: 200 OK

Send Wrong Request Method Request:

POST http://3.129.194.233/api/story/1/translate_de/

Response:

{
    "detail": "Method \"POST\" not allowed."
}

Status Code: 405 Method Not Allowed

Request Translation of Non-Existent Story Request:

GET http://3.129.194.233/api/story/65421/translate_tr/

Response:

"Story does not exist."

Status Code: 404 Not Found

5. Authentication

Translation API does not require any authentication, but IBMTranslator has a key for authentication. The key is stored in the environment file and can be managed from the corresponding IBM Cloud account.

6. Errors and Successes

Name Meaning
HTTP_200_OK The request is successful.
HTTP_400_BAD_REQUEST The selected story is too short to translate.
HTTP_404_NOT_FOUND The story does not exist or IBMTranslator is not able to translate it at the moment.
HTTP_405_METHOD_NOT_ALLOWED Wrong request method is used.

7. Code

Models
class Story(models.Model):
    title = models.CharField(max_length=200)
    story = models.CharField(max_length=1000)
    name = models.CharField(max_length=200)
    longitude = models.FloatField()
    latitude = models.FloatField()
    location = models.CharField(max_length=200)
    tag = models.CharField(max_length=200)
    date = models.DateTimeField(auto_now_add=True)
    notifyAdmin = models.BooleanField(default=False)

    def __str__(self):
        return self.title

class Translation(models.Model):
    story_id = models.ForeignKey('Story', on_delete=models.CASCADE)
    language = models.CharField(max_length=3)
    title_trans = models.CharField(max_length=200)
    story_trans = models.CharField(max_length=1000)
    
    class Meta:
        unique_together = (("story_id", "language"),)
    
    def __str__(self):
        return self.title_trans

Note: Story model is common for all APIs.

Views and Helper Methods for Views
from django.http.response import HttpResponse
from rest_framework.response import Response
from ..models import *
import requests
import environ
import json
from rest_framework import status
from ..serializers import *
from rest_framework.generics import GenericAPIView

env = environ.Env()
environ.Env.read_env('.env')

class TranslationView(GenericAPIView):
    """ 
    Retrieves translation of a story 
    Target is specified as a language code: 'de', 'en','tr' etc.
    """
    queryset = Story.objects.all()
    serializer_class = StorySerializer
    def retrieve_translation(story_id, target):
        """ Retrieves translation if it exists in the database."""
        try:
            translation = Translation.objects.get(story_id = story_id, language = target)
        except:
            return False        
        return translation

    def get(self, request, pk, target):
        try:
            story = Story.objects.get(pk=pk)
            story_dict = StorySerializer(story).data
        except:
            return Response("Story does not exist.",status=status.HTTP_404_NOT_FOUND)

        check_translation = TranslationView.retrieve_translation(pk,target)
        if check_translation: 
            # translation is retrieved from the database
            story_dict['title'] = check_translation.title_trans
            story_dict['story'] = check_translation.story_trans
            return Response(story_dict)
        else:
            new_trans = translate(story,target) # request translation from external API
            if 'error' in new_trans:
                return Response(new_trans['error'],new_trans['status'])
            else:
                story_dict['title'] = new_trans['title_trans']
                story_dict['story'] = new_trans['story_trans']     
                return Response(story_dict) # return translated story
            
def check_story_length(title,body):
    """ Returns false for short stories. """
    if len(title) < 6 or len(title.split()) < 2:
        return False
    if len(body) < 10 or len(body.split()) < 2:  
        return False
    return True


def translate(story, target):
    """ Sends post request to IBM Watson Translator API. The source language is automatically detected. 
        The API may not be able to identify the source language if the string is too short (1 word)
        Returns a JSON of the translated story if the request is successful.
        target : language code, e.g. "de","es","tr"
    """
    headers = {
        'Content-Type': 'application/json',
        'charset' : 'utf-8'
    }
    URL = 'https://api.eu-gb.language-translator.watson.cloud.ibm.com/instances/b2d14a2d-97d0-4b45-8f44-b3b993729db1'
    version = '2018-05-01'
    apikey = env('TRANSLATE_KEY')
    auth = ('apikey', apikey)

    if not check_story_length(story.title,story.story):
        return {'error':"Too short to translate.",'status' : status.HTTP_400_BAD_REQUEST}

    data = json.dumps({"text": [story.title,story.story],"target":target})    
    response = requests.post(f'{URL}/v3/translate?version={version}', headers=headers, data=data, auth=auth)
    
    if response.status_code == 200:
        response_json = response.json()
        title_trans = response_json['translations'][0]['translation'] # translation of title
        story_trans = response_json['translations'][1]['translation']         
        translated_fields = {'title_trans':title_trans, 'story_trans':story_trans}
        return translated_fields
    else :
       return {'error':response.json()['error'], 'status':status.HTTP_404_NOT_FOUND} # return the error response of IBM Translator API
       
Tests
from django.test import TestCase,Client
from ..views.view_translationAPI_niyazi import * 


class CheckStoryLengthTest(TestCase):

    def test_short_title(self):
        title = "hey"
        body = "It was a sunny day."
        self.assertFalse(check_story_length(title,body))
    
    def test_short_body(self):
        title = "We went to the cinema."
        body = "beautiful"
        self.assertFalse(check_story_length(title,body))

    def test_false_title_type(self):
        title = 1957
        body = "What a nice year!"
        self.assertRaises(TypeError, check_story_length, title, body)
        
    def test_false_body_type(self):
        title = "We went to the cinema."
        body = 23.948429
        self.assertRaises(TypeError, check_story_length, title, body)
    
    def test_correct_input(self):
        title = "We went to the cinema."
        body = "It was a sunny day."
        self.assertTrue(check_story_length(title,body))

class TranslationTest(TestCase):
    def setUp(self):
        Story.objects.create(id = 1,
            title = "Title",
            story = "Story",
            name = "User1",
            longitude = 41,
            latitude = 28,
            location = "Istanbul",
            tag = "Tag1",
            notifyAdmin = False
        )
        Story.objects.create(id = 2,
            title = "Hello World!",
            story = "I am very happy today!",
            name = "User2",
            longitude = 33,
            latitude = 40,
            location = "Ankara",
            tag = "Tag2",
            notifyAdmin = True
        )

    def test_only_get_requests(self):
        c=Client()
        resp_1=c.post("/api/story/2/translate_es/")
        resp_2=c.put("/api/story/2/translate_es/")
        resp_3=c.delete("/api/story/2/translate_es/")
        self.assertEqual(resp_1.status_code,405)
        self.assertEqual(resp_2.status_code,405)
        self.assertEqual(resp_3.status_code,405)

    def test_when_story_does_not_exist(self):
        c=Client()
        resp=c.get("/api/story/3/translate_ne/")
        self.assertEqual(resp.status_code,404)
    
    def test_when_translator_api_fails(self):
        c=Client()
        resp=c.get("/api/story/2/translate_nosuchlanguage/")
        self.assertEqual(resp.status_code,404)

    def test_short_story(self):
        c=Client()
        resp=c.get("/api/story/1/translate_jp/")
        self.assertEqual(resp.status_code,400)
        
    def test_works_correct(self):  
        c=Client()  
        resp=c.get("/api/story/2/translate_tr/")
        self.assertEqual(resp.status_code,200)
⚠️ **GitHub.com Fallback** ⚠️