DEV Community

Document Translation Service using Streamlit & AWS Translator

Introduction:

DocuTranslator, a document translation system, built in AWS and developed by Streamlit application framework. This application allows end user to translate the documents in their preferred language which they want to upload. It provides feasibility to translate in multiple languages as user wants, which really helps users to understand the content in their comfortable way.

Background:

The intent of this project is to provide a user friendly, simple application interface to fulfill the translation process as simple as users expect. In this system, nobody has to translate documents by entering into AWS Translate service, rather end user can directly access the application endpoint and get the requirements fulfilled.

High Level Architecture Diagram:

High Level Architecture

How Does This Work:

  • End user is allowed to access an application through an application load balancer.
  • Once application interface is opened, user will upload the required files to be translated and language to translate to.
  • After submitting these details, file will be uploaded to mentioned source S3 bucket which triggers a lambda function to connect with AWS Translator service.
  • Once translated document is ready, will be uploaded to destination S3 bucket.
  • After that, end user can download the translated document from Streamlit application portal.

Technical Architecture:

Technical Architecture

Above architecture shows below key points -

  • Application code has been containerized and stored to ECR repository.
  • As per above design, an ECS cluster has been setup which instantiates two tasks that pulls application image from ECR repository.
  • Both the tasks are launched on top of EC2 as a launch type. Both EC2s are launched in private subnet in us-east-1a and us-east-1b availability zones.
  • A EFS file system is created to share application codes between two underlying EC2 instances. Two mountpoints are created in two availability zones (us-east-1a and us-east-1b).
  • Two public subnets are configured in front of private subnets and a NAT gateway is set up in the public subnet in us-east-1a availability zone.
  • An application load balancer has been configured in front of private subnets which distributes the traffic across two public subnets at port 80 of application load balancer security group(ALB SG).
  • Two EC2 instances are configured in two different target group with same EC2 security group(Streamlit_SG) which accepts traffic at 16347 port from application load balancer.
  • There is port mapping configured between port 16347 in EC2 instances and port 8501 at ECS container. Once traffic will hit at port 16347 of EC2 security group, will be redirected to 8501 port at ECS container level.

How Data is Getting Stored ?

Here, we have used EFS share path to share same application files between two underlying EC2 instances. We have created a mountpoint /streamlit_appfiles inside the EC2 instances and mounted with EFS share. This approach will help in sharing same content across two different servers. After that, our intent is to create a replicate same application content to container working directory which is /streamlit. For that we have used bind mounts so that whatever changes will be made on application code at EC2 level, will be replicated to container as well. We need to restrict bi-directional replication which says if anyone mistakenly changes code from inside the container, it should not replicate to EC2 host level, hence inside the container working directory has been created as a read only filesystem.

Image description

ECS Container Configuration and Volume:

Underlying EC2 Configuration:
Instance Type: t2.medium
Network type: Private Subnet

Container Configuration:
Image:
Network Mode: Default
Host Port: 16347
Container Port: 8501
Task CPU: 2 vCPU (2048 units)
Task Memory: 2.5 GB (2560 MiB)

Image description

Volume Configuration:
Volume Name: streamlit-volume
Source Path: /streamlit_appfiles
Container Path: /streamlit
Read Only Filesystem: YES

Image description

Task Definition Reference:

{
    "taskDefinitionArn": "arn:aws:ecs:us-east-1:<account-id>:task-definition/Streamlit_TDF-1:5",
    "containerDefinitions": [
        {
            "name": "streamlit",
            "image": "<account-id>.dkr.ecr.us-east-1.amazonaws.com/anirban:latest",
            "cpu": 0,
            "portMappings": [
                {
                    "name": "streamlit-8501-tcp",
                    "containerPort": 8501,
                    "hostPort": 16347,
                    "protocol": "tcp",
                    "appProtocol": "http"
                }
            ],
            "essential": true,
            "environment": [],
            "environmentFiles": [],
            "mountPoints": [
                {
                    "sourceVolume": "streamlit-volume",
                    "containerPath": "/streamlit",
                    "readOnly": true
                }
            ],
            "volumesFrom": [],
            "ulimits": [],
            "logConfiguration": {
                "logDriver": "awslogs",
                "options": {
                    "awslogs-group": "/ecs/Streamlit_TDF-1",
                    "mode": "non-blocking",
                    "awslogs-create-group": "true",
                    "max-buffer-size": "25m",
                    "awslogs-region": "us-east-1",
                    "awslogs-stream-prefix": "ecs"
                },
                "secretOptions": []
            },
            "systemControls": []
        }
    ],
    "family": "Streamlit_TDF-1",
    "taskRoleArn": "arn:aws:iam::<account-id>:role/ecsTaskExecutionRole",
    "executionRoleArn": "arn:aws:iam::<account-id>:role/ecsTaskExecutionRole",
    "revision": 5,
    "volumes": [
        {
            "name": "streamlit-volume",
            "host": {
                "sourcePath": "/streamlit_appfiles"
            }
        }
    ],
    "status": "ACTIVE",
    "requiresAttributes": [
        {
            "name": "com.amazonaws.ecs.capability.logging-driver.awslogs"
        },
        {
            "name": "ecs.capability.execution-role-awslogs"
        },
        {
            "name": "com.amazonaws.ecs.capability.ecr-auth"
        },
        {
            "name": "com.amazonaws.ecs.capability.docker-remote-api.1.19"
        },
        {
            "name": "com.amazonaws.ecs.capability.docker-remote-api.1.28"
        },
        {
            "name": "com.amazonaws.ecs.capability.task-iam-role"
        },
        {
            "name": "ecs.capability.execution-role-ecr-pull"
        },
        {
            "name": "com.amazonaws.ecs.capability.docker-remote-api.1.18"
        },
        {
            "name": "com.amazonaws.ecs.capability.docker-remote-api.1.29"
        }
    ],
    "placementConstraints": [],
    "compatibilities": [
        "EC2"
    ],
    "requiresCompatibilities": [
        "EC2"
    ],
    "cpu": "2048",
    "memory": "2560",
    "runtimePlatform": {
        "cpuArchitecture": "X86_64",
        "operatingSystemFamily": "LINUX"
    },
    "registeredAt": "2024-11-09T05:59:47.534Z",
    "registeredBy": "arn:aws:iam::<account-id>:root",
    "tags": []
}
Enter fullscreen mode Exit fullscreen mode

Image description

Developing Application Code and Creating Docker Image:

app.py

import streamlit as st
import boto3
import os
import time
from pathlib import Path

s3 = boto3.client('s3', region_name='us-east-1')
tran = boto3.client('translate', region_name='us-east-1')
lam = boto3.client('lambda', region_name='us-east-1')


# Function to list S3 buckets
def listbuckets():
    list_bucket = s3.list_buckets()
    bucket_name = tuple([it["Name"] for it in list_bucket["Buckets"]])
    return bucket_name

# Upload object to S3 bucket
def upload_to_s3bucket(file_path, selected_bucket, file_name):
    s3.upload_file(file_path, selected_bucket, file_name)

def list_language():
    response = tran.list_languages()
    list_of_langs = [i["LanguageName"] for i in response["Languages"]]
    return list_of_langs

def wait_for_s3obj(dest_selected_bucket, file_name):
    while True:
        try:
            get_obj = s3.get_object(Bucket=dest_selected_bucket, Key=f'Translated-{file_name}.txt')
            obj_exist = 'true' if get_obj['Body'] else 'false'
            return obj_exist
        except s3.exceptions.ClientError as e:
            if e.response['Error']['Code'] == "404":
                print(f"File '{file_name}' not found. Checking again in 3 seconds...")
                time.sleep(3)

def download(dest_selected_bucket, file_name, file_path):
     s3.download_file(dest_selected_bucket,f'Translated-{file_name}.txt', f'{file_path}/download/Translated-{file_name}.txt')
     with open(f"{file_path}/download/Translated-{file_name}.txt", "r") as file:
       st.download_button(
             label="Download",
             data=file,
             file_name=f"{file_name}.txt"
       )

def streamlit_application():
    # Give a header
    st.header("Document Translator", divider=True)
    # Widgets to upload a file
    uploaded_files = st.file_uploader("Choose a PDF file", accept_multiple_files=True, type="pdf")
    # # upload a file
    file_name = uploaded_files[0].name.replace(' ', '_') if uploaded_files else None
    # Folder path
    file_path = '/tmp'
    # Select the bucket from drop down
    selected_bucket = st.selectbox("Choose the S3 Bucket to upload file :", listbuckets())
    dest_selected_bucket = st.selectbox("Choose the S3 Bucket to download file :", listbuckets())
    selected_language = st.selectbox("Choose the Language :", list_language())
    # Create a button
    click = st.button("Upload", type="primary")
    if click == True:
        if file_name:
            with open(f'{file_path}/{file_name}', mode='wb') as w:
                w.write(uploaded_files[0].getvalue())
        # Set the selected language to the environment variable of lambda function
        lambda_env1 = lam.update_function_configuration(FunctionName='TriggerFunctionFromS3', Environment={'Variables': {'UserInputLanguage': selected_language, 'DestinationBucket': dest_selected_bucket, 'TranslatedFileName': file_name}})
        # Upload the file to S3 bucket:
        upload_to_s3bucket(f'{file_path}/{file_name}', selected_bucket, file_name)
        if s3.get_object(Bucket=selected_bucket, Key=file_name):
            st.success("File uploaded successfully", icon="✅")
            output = wait_for_s3obj(dest_selected_bucket, file_name)
            if output:
              download(dest_selected_bucket, file_name, file_path)
        else:
            st.error("File upload failed", icon="🚹")


streamlit_application()
Enter fullscreen mode Exit fullscreen mode

about.py

import streamlit as st

## Write the description of application
st.header("About")
about = '''
Welcome to the File Uploader Application!

This application is designed to make uploading PDF documents simple and efficient. With just a few clicks, users can upload their documents securely to an Amazon S3 bucket for storage. Here’s a quick overview
of what this app does:

**Key Features:**
- **Easy Upload:** Users can quickly upload PDF documents by selecting the file and clicking the 'Upload' button.
- **Seamless Integration with AWS S3:** Once the document is uploaded, it is stored securely in a designated S3 bucket, ensuring reliable and scalable cloud storage.
- **User-Friendly Interface:** Built using Streamlit, the interface is clean, intuitive, and accessible to all users, making the uploading process straightforward.

**How it Works:**
1. **Select a PDF Document:** Users can browse and select any PDF document from their local system.
2. **Upload the Document:** Clicking the ‘Upload’ button triggers the process of securely uploading the selected document to an AWS S3 bucket.
3. **Success Notification:** After a successful upload, users will receive a confirmation message that their document has been stored in the cloud.
This application offers a streamlined way to store documents on the cloud, reducing the hassle of manual file management. Whether you're an individual or a business, this tool helps you organize and store your
 files with ease and security.
You can further customize this page by adding technical details, usage guidelines, or security measures as per your application's specifications.'''

st.markdown(about)
Enter fullscreen mode Exit fullscreen mode

navigation.py

import streamlit as st

pg = st.navigation([
    st.Page("app.py", title="DocuTranslator", icon="📂"),
    st.Page("about.py", title="About", icon="đŸ”„")
], position="sidebar")

pg.run()
Enter fullscreen mode Exit fullscreen mode

Dockerfile:

FROM python:3.9-slim
WORKDIR /streamlit
COPY requirements.txt /streamlit/requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
RUN mkdir /tmp/download
COPY . /streamlit
EXPOSE 8501
CMD ["streamlit", "run", "navigation.py", "--server.port=8501", "--server.headless=true"]
Enter fullscreen mode Exit fullscreen mode

Docker file will create an image by packaging all above application configuration files and then it was pushed to ECR repository. Docker Hub can also be used to store the image.

Load Balancing

In the architecture, application instances are supposed to be created in private subnet and load balancer is supposed to create to reduce incoming traffic load to private EC2 instances.
As there are two underlying EC2 hosts available to host containers, so load balancing is configured across two EC2 hosts to distribute incoming traffic. Two different target groups are created to place two EC2 instances in each with 50% weightage.

Load balancer accepts incoming traffic at port 80 and then passes to backend EC2 instances at port 16347 and that also passed to corresponding ECS container.

Image description

Image description

Lambda Function:

There is a lambda function configured to take source bucket as an input to download pdf file from there and extract the contents, then it translates the contents from current language to user provided target language and creates a text file to upload to destination S3 bucket.

import boto3
import os
import datetime
import sys
from io import BytesIO
sys.path.append('./module')
import PyPDF2
from fpdf import FPDF

s3 = boto3.client('s3')
tran = boto3.client('translate', region_name='us-east-1')

def download_pdf_from_s3(sourcebucket, objectname):
    # Download PDF file from S3 bucket
    pdf_object = s3.get_object(Bucket=sourcebucket, Key=objectname)
    return pdf_object['Body'].read()

def extract_text_from_pdf(pdf_bytes):
    # Extract text from the PDF file using PyPDF2
    pdf_reader = PyPDF2.PdfReader(BytesIO(pdf_bytes))
    text = ''
    for page_num in range(len(pdf_reader.pages)):
        page = pdf_reader.pages[page_num]
        extracted_text = page.extract_text()
        if extracted_text:
            text += page.extract_text()
    return text

def translate_text(pdf_text, TargetLang):
    # Translate text using AWS Translate
    TargetLangCode = ''
    response = tran.list_languages()
    for i in response['Languages']:
        if i['LanguageName'] == TargetLang:
            TargetLangCode = i['LanguageCode']
    result = tran.translate_text(
        Text=pdf_text,
        SourceLanguageCode='auto',
        TargetLanguageCode=TargetLangCode
    )
    return result['TranslatedText']

def create_text_file_and_upload_to_s3(translated_text, DestinationBucket, objectname):
    formatted_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    with open('/tmp/translated_text.txt', 'w', encoding='utf-8') as f:
        f.write(translated_text)
    s3.upload_file('/tmp/translated_text.txt', DestinationBucket, f'Translated-{objectname}.txt')

def lambda_handler(event, context):
    eventtime = event['Records'][0]['eventTime']
    sourcebucket = event['Records'][0]['s3']['bucket']['name']
    objectname = event['Records'][0]['s3']['object']['key']
    print(objectname)
    DestinationBucket = os.environ['DestinationBucket']
    TranslatedFileName = os.environ['TranslatedFileName']
    TargetLang = os.environ['UserInputLanguage']

    pdf_bytes = download_pdf_from_s3(sourcebucket, objectname)
    pdf_text  = extract_text_from_pdf(pdf_bytes)
    translated_text = translate_text(pdf_text, TargetLang)
    create_text_file_and_upload_to_s3(translated_text, DestinationBucket, objectname)
Enter fullscreen mode Exit fullscreen mode

Application Testing:

Open the application load balancer url "ALB-747339710.us-east-1.elb.amazonaws.com" to open the web application. Browse any pdf file, keep both source "fileuploadbucket-hwirio984092jjs" and destination bucket "translatedfileuploadbucket-kh939809kjkfjsekfl" as it is, because in the lambda code, it has been hard coded the target bucket is as mentioned above. Choose the language you want the document to be translated and click on upload. Once it's clicked, application program will start polling the destination S3 bucket to find out if the translated file is uploaded. If it find the exact file, then a new option "Download" will be visible to down load the file from destination S3 bucket.

Application Link: http://alb-747339710.us-east-1.elb.amazonaws.com/

Image description

Actual Content:

Title: "The Whispering Shadows"
It was a stormy evening when Mara first noticed the odd presence in her new home. The house had
always felt a bit
 off, but she couldn't quite put her finger on it. A quiet suburban street, a two-story
Victorian house, tucked away behind a picket fence. Everything seemed perfect when she first
moved in, but soon after the boxes were unpacked, the strange occurrences began.
The house creaked, as old houses do, but it wasn’t just the settling noises. There were whispers—
soft, barely audible murmurs that seemed to come from the walls themselves. At first, Mara
thought it was the wind, the house shifting and groaning in the rain, but the more she listened, the
clearer the words became.
“You’re not alone
”
Mara told herself it was just her imagination playing tricks on her. But then there were the shadows.
At first, they were subtle—a fleeting figure in the corner of her eye, a dark shape that vanished when
she turned her head. But the more she spent time in the house, the more persistent they became.
One night, as she lay in bed, the whispers were louder. She sat up, heart racing, staring into the dark
corners of her room. The shadows shifted, elongated, and crawled toward her. A shape materialized
at the foot of her bed—a tall, skeletal figure, its face obscured by a tattered hood. Its hands reached
toward her, fingers long and twisted.
Frozen in terror, Mara tried to scream, but no sound came out. The figure came closer, its breath
cold against her skin.
"Come with me," it whispered, its voice echoing inside her mind.
Mara tried to move, but her body wouldn’t obey. Her limbs were like lead, heavy and immovable.
The figure’s hand was inches from her when a sudden crash of thunder jolted her awake.
Gasping for breath, she sat up in her bed, drenched in sweat. Her room was empty. The storm raged
outside, the wind howling through the trees. She couldn’t tell if it had all been a dream, but the
sensation of something watching her lingered.
Over the next few days, the incidents escalated. Objects in the house would shift on their own,
doors would creak open in the dead of night, and the whispers became more insistent, urging her to
come closer, to listen.
One evening, after finding a peculiar note slipped under her door—“They are watching. You can
never leave.” —Mara finally decided to investigate the history of the house. The local library didn’t
have much on it, but a few old records revealed something chilling.
The house had been abandoned for decades before Mara moved in. Before that, it had been the
home of the Wren family, a reclusive couple with a dark secret. It was said that they had practiced
ancient rites, attempting to commune with entities from another world. The Wren family vanished
without a trace, and no one had ever heard from them again.
Mara couldn’t shake the feeling that something was waiting for her. It wasn’t until she uncovered a
hidden basement beneath the house that she realized how true that was.
The door to the basement was sealed tight, covered in dust and cobwebs, but with trembling
hands, Mara managed to pry it open. The stairs creaked as she descended into the darkness below.
The air was thick, stale with the scent of old earth and decay. As she reached the bottom, the
whispers returned, louder this time, all around her.
“You shouldn’t have come.”
Her flashlight flickered, casting long, distorted shadows across the stone walls. At the far end of the
room, something gleamed—a series of symbols carved into the floor, worn and cracked with age.
The markings formed a circle, and in its center, there was an altar.
Before she could turn to leave, the temperature dropped sharply, and a figure appeared in front of
her. It was the same tall, hooded figure from her dreams. Its eyes glowed with an otherworldly light,
and it beckoned her forward.
"You’re too late," it whispered, its voice like a thousand voices, each one colder than the last.
Mara screamed, but no sound came. The shadows closed in around her, the walls pressing in as if
the house itself were alive, hungry. And as the last of her breath left her lungs, she felt herself pulled
into the darkness, her body dissolving into the very shadows that had tormented her.
The house on Willow Street still stands, weathered by time and neglect. Some say the whispers are
still there, waiting for the next soul foolish enough to enter.
And if you listen closely, you might hear a faint voice calling out—"Come with me."
Enter fullscreen mode Exit fullscreen mode

Translated Content (in Canadian French)

Titre : « The Whispering Shadows » 

C'était une soirée orageuse lorsque Mara remarqua pour la premiÚre fois la présence étrange dans sa nouvelle maison. La maison avait 
je me sentais toujours un peu décontente, mais elle ne pouvait pas tout à fait mettre le doigt dessus. Une rue de banlieue tranquille, deux étages 
Maison victorienne, cachée derriÚre une palissade. Eve, tout semblait parfait quand elle a commencé. 
ils ont emménagé, mais peu de temps aprÚs le déballage des cartons, des événements étranges ont commencé. 
La maison a criĂ©, tout comme les vieilles maisons, mais il n'y avait pas que les bruits de sĂ©dimentation. Il y avait des chuchotements —
des murmures doux et Ă  peine audibles qui semblaient provenir des murs eux-mĂȘmes. Au dĂ©but, Mara 
croyait que c'était le vent, la maison qui bougeait et gémissait sous la pluie, mais plus elle écoutait, plus 
les mots sont devenus plus clairs. 
« Vous n'ĂȘtes pas seul... » 
Mara s'est dit que c'Ă©tait juste son imagination qui lui faisait des tours. Mais ensuite, il y avait les ombres. 
Au dĂ©but, ils Ă©taient subtils — une silhouette fugace dans le coin de l'Ɠil, une forme sombre qui a disparu lorsque 
elle tourna la tĂȘte. Mais plus elle passait de temps Ă  la maison, plus ils devenaient persistants. 
Une nuit, alors qu'elle Ă©tait couchĂ©e dans son lit, les chuchotements Ă©taient plus forts. Elle s'est assise, le cƓur battant, fixant le noir 
les coins de sa chambre. Les ombres se déplaçaient, s'allongeaient et rampaient vers elle. Une forme matérialisée 
au pied de son lit — une grande silhouette squelettique, son visage obscurci par une capuche en lambeaux. Ses mains sont parvenues 
vers elle, les doigts longs et tordus. 
Fellisée dans la terreur, Mara essaya de crier, mais aucun son ne sortit. La silhouette s'approchait, son souffle 
froid sur sa peau. 
« Viens avec moi », murmura-t-il, sa voix résonnant dans son esprit. 
Mara essaya de bouger, mais son corps n'obéit pas. Ses membres étaient comme du plomb, lourds et immobiles. 
La main de la silhouette était à des centimÚtres d'elle lorsqu'un coup de tonnerre soudain l'a éveillée. 
À bout de souffle, elle s'assoit dans son lit, trempĂ©e de sueur. Sa chambre Ă©tait vide. La tempĂȘte a fait rage 
dehors, le vent hurle Ă  travers les arbres. Elle ne pouvait pas dire si tout avait Ă©tĂ© un rĂȘve, mais 
la sensation de quelque chose qui la regardait s'attardait. 
Au cours des jours qui ont suivi, les incidents se sont intensifiĂ©s. Les objets de la maison se dĂ©placeraient d'eux-mĂȘmes, 
les portes s'ouvriraient au milieu de la nuit, et les murmures devenaient plus insistants, la poussant Ă  
approchez, Ă©coutez. 
Un soir, aprÚs avoir trouvé une note étrange glissa sous sa porte : « Ils regardent. Vous pouvez 
ne partez jamais. —Mara a finalement dĂ©cidĂ© d'enquĂȘter sur l'histoire de la maison. La bibliothĂšque locale ne l'a pas fait 
j'ai beaucoup de choses à ce sujet, mais quelques vieux dossiers ont révélé quelque chose de trÚs choquant. 
La maison avait été abandonnée pendant des décennies avant que Mara n'emménage. Avant cela, il s'agissait de 
maison de la famille Wren, un couple reclus avec un sombre secret. On a dit qu'ils avaient pratiqué 
rites anciens, essayant de faire la commune avec des entités d'un autre monde. La famille Wren disparaßt. 
sans laisser de trace, et personne n'en avait plus jamais entendu parler. Mara ne pouvait pas ébranler le sentiment que quelque chose l'attendait. Ce n'est que lorsqu'elle a découvert un 
sous le sous-sol caché sous la maison, elle s'est rendu compte à quel point c'était vrai. 
La porte du sous-sol était scellée hermétiquement, couverte de poussiÚre et de toiles d'araignées, mais avec des tremblements 
mains, Mara a réussi à l'ouvrir. Les escaliers ont grincé alors qu'elle descendait dans l'obscurité en contrebas. 
L'air était épais, vidé avec l'odeur de la vieille terre et de la décomposition. Alors qu'il a atteint le bas de la page, le 
des chuchotements revinrent, plus forts cette fois, tout autour d'elle. 
« Vous n'auriez pas dû venir. 
Sa lampe de poche vacillait, projetant de longues ombres dĂ©formĂ©es sur les murs de pierre. À la fin de la 
la chambre, quelque chose luisait — une sĂ©rie de symboles gravĂ©s dans le sol, usĂ©s et fissurĂ©s avec l'Ăąge. 
Les marques formaient un cercle, et en son centre se trouvait un autel. 
Avant qu'elle ne puisse se tourner pour partir, la température a chuté brusquement et une silhouette est apparue devant 
elle. C'Ă©tait la mĂȘme grande silhouette Ă  capuchon de ses rĂȘves. Ses yeux brillaient d'une lumiĂšre d'un autre monde, 
et cela l'a fait passer Ă  l'avant. 
« Tu es trop tard », murmura-t-il, sa voix comme mille voix, toutes plus froides les unes que les autres. 
Mara a crié, mais aucun son n'est venu. Les ombres se refermaient autour d'elle, les murs se pressant comme si 
la maison elle-mĂȘme Ă©tait vivante, affamĂ©e. Et alors que le dernier souffle quittait ses poumons, elle s'est sentie tirĂ©e 
dans l'obscuritĂ©, son corps se dissout dans l'ombre mĂȘme qui l'avait tourmentĂ©e. 

La maison de la rue Willow est toujours debout, altérée par le temps et la négligence. Certains disent que les chuchotements sont 
toujours lĂ , attendant que l'Ăąme suivante soit assez stupide pour entrer. 
Et si vous écoutez attentivement, vous pourriez entendre une voix faible crier : « Venez avec moi ».  

Enter fullscreen mode Exit fullscreen mode

Conclusion:

This article has shown us how document translation process can be as easy as we imagine where an end user has to click on some options to choose required information and get the desired output within few seconds without thinking about configuration. For now, we have included single feature to translate a pdf document, but later on, we will research more on this to have multi functionality in a single application with having some interesting features.

Top comments (0)