From times immemorial, language has been a barrier to good communication between people that speak different languages. Imagine a French man who speaks only French trying to share his ideas with a man who only understands only English. Frustrating right? One way to solve this issue has been the use of human translators. But we cannot always do this when it comes to chat. It might be impossible to have someone to translate every message sent to us. However, thanks to the IBM translation API, text can be translated easily from one language to another. In this tutorial, we will build a chat app that allows people to communicate with each other in different languages. We will be using the IBM translation API that translates text in one language to another and Ably’s Realtime API to add the realtime data sharing functionality. Prerequisites To follow along in this tutorial, you will need the following: A basic knowledge of JavaScript. A basic understanding of NodeJS and/or ExpressJS. Node >= version 6 and npm >= version 5.2 (or yarn if you prefer yarn) running on your machine. You can download them . here 1. What will we build Let us fully understand what we will be building. The app will consist of: This is what the users can use to select the preferred language that they want to communicate in. We will use the IBM translation API to get a list of languages that can be converted to and from English, as this will be our primary language. We will use these languages to populate the dropdown menu. A dropdown: This is where messages in the chat channel will appear. If a message is sent by the current user, this message is displayed as it is to the user. If the message is from another user, in another language, this message is first translated in the user’s choice of language before it is displayed The chat section: This is where users can add messages to the chat conversation. The input section: Note that this is only a demo app and if a user tries sending a message with the wrong language selected, the app might behave unexpectedly. 2. Technologies we will use The two major technologies we will use are: 2.1 The IBM translator API translates text from one language to another. The service offers multiple IBM provided translation models that you can customise based on your unique terminology and language. IBM Watson™ Language Translator To make use of this API, you will need to create a free account on . Follow the process for setting up the form until you are able to login to your dashboard. Using the search field on the top navigation menu, search for “language-translator” and create a new language translator resource. If you successfully do this, you will be taken to a page where you can access you translator API key. We will use this later in the tutorial. their website 2.2 The Ably Realtime API Ably provides as a service via its distributed Data Stream Network. This reduces the operational burden of engineering teams, allowing them to build and scale faster and more efficiently. For this tutorial, we will use the Publish/Subscribe messaging pattern provided by Ably. All the messages shared via Ably are organised into logical units called channels - one for each set of data being shared in an app. realtime messaging infrastructure Channels are the medium through which are distributed. Once users subscribe to a channel, they can receive all messages published on that channel by other users. This scalable and resilient messaging pattern is commonly called . messages pub/sub To set up Ably, create a free account . When you are done with the process, you will get an API key you will use as we go further in the tutorial. Next, let us set up our app. on their website 3. Setting Up the App To get started, we’ll create a folder for our app. mkdir multi-lingual-app cd multi-lingual-app Open up this newly created folder in your favourite code editor. Here is an overview of the file structure of the app. / |-- node_modules |-- / |-- bundle.js |-- .css |-- .babelrc |-- .html |-- .js |-- .js //this will be generated automaticaaly as we install dependencies public //contains compiled js code for our index.js file index // styles for the file // configuration code for our babel presets index // code for the view index // javascript for the frontend server // javascript code for the server If you have not already done so, you can . Next, we will initialise the Node project using the following command. download N odeJS here init npm Follow the prompt to set up the project. You will notice the file has been added to the project after completing the steps. package.json 3.1 Installing Dependencies We will install the following dependencies to get our app working. - a lightweight framework for NodeJS. Express - Browserify lets you require('modules') in the browser by bundling up all of your dependencies. Browserify - Watchify automatically bundles up your dependencies as you make changes to your javascript files. Watchify -Nodemon keeps the server running and automatically refreshes the page as we make changes to our code. Nodemon l - Babel lets us use ES6 features and syntax in our code. Babe - this will be used for the language translation. IBM-watson - Ably provides as a service via its distributed Data Stream Network. We will add Ably as a CDN in our html file. Ably realtime messaging infrastructure Let us install the dependencies using the following commands to get started with our app and get our app running. npm nodemon -g npm express watchify --save npm --save-dev @ @ @ @ npm @ @ --save npm ibm-watson@^ . . install install browserify install babel/core babel/cli babel/preset-env babel/plugin-transform-runtime babelify install babel/polyfill babel/runtime install 5 1 0 In your file, add the following start script to the . package.json "scripts" : "start" "nodemon server.js" We will add a watch script that watches for changes in our index.js file and automatically recompiles the ES6 code to one the browser will understand. We will also add a build script which will automatically be run when the app is deployed. : , : "build" "browserify index.js -o public/bundle.js" "watch" "watchify index.js -o public/bundle.js -v" Next, we setup the browserify plugin by adding the following code to the file. package.json : { : [ [ , { : [ ] } ] ] } "browserify" "transform" "babelify" "presets" "@babel/preset-env" Your file should contain the following after following the steps above. package.json : { : , : , : , : }, : { : [ [ , { : [ ] } ] ] } "scripts" "start" "nodemon server.js" "test" "echo \"Error: no test specified\" && exit 1" "build" "browserify index.js -o public/bundle.js" "watch" "watchify index.js -o public/bundle.js -v" "browserify" "transform" "babelify" "presets" "@babel/preset-env" 3.2 Setting up Babel Next, we will set up babel for compiling our ES6 code to JavaScript that the browser understands. First, we create the file. touch .babelrc Next, we add the following presets to the file. { : [ [ , { : }] ], : [ [ , { : } ] ] } "presets" "@babel/preset-env" "useBuiltIns" false "plugins" "@babel/plugin-transform-runtime" "regenerator" true 3.3 Setting up the server If you have not already added a server.js file, you can do that using the following command. touch .js //create the server.js files server In the file, set up the server by adding the following. express = ( ); ( ).config(); app = express(); app.use(express.json()) PORT = process.env.PORT || ; app.listen(PORT, { .log( ,PORT); }); app.get( , { res.sendFile( __dirname + + ); }); app.use(express.static(__dirname + + )); //setup the express server var require 'express' require 'dotenv' var var 3000 ( ) function console 'Server is running on PORT:' //setup routes for the index.html file '/' ( ) function req, res "/" "index.html" //Setup route for static files "/" 'public' Next, we will create the file. index.js .js touch index We will also add a file in a public directory where the code in the file will be compiled into. bundle.js index.js // public bundle.js mkdir public cd public touch bundle.js create the folder and create the file We can now start the server. start npm The server should be running on port 3000. To watch for changes in the index.js file that will be compiled, open up another terminal in the same project and run the following. npm run watch Good work so far! Let us move to the fun part. 4. Building the Frontend As mentioned earlier, the frontend will consist of three main parts: A dropdown menu that has a list a languages the user can select a preferred language from. The message section that contains the messages sent to the message channel. An input field and button that the user can use to send a message to the message channel. Here is an image how the final view of the frontend will be: Let us create the frontend view. First, create the file using the following command. .html touch index Next, add the following code to the index.html file you created. Take note of the Ably CDN added. Please select a language < > html < > head <!-- scripts for the app --> < = = = > script src "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js" integrity "sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin "anonymous" </ > script < = > script src "https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" </ > script < = > script src "https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" </ > script <!-- Ably script --> < = > script src "https://cdn.ably.io/lib/ably.min-1.js" </ > script < = = > link href "https://fonts.googleapis.com/css?family=Roboto&display=swap" rel "stylesheet" < = = = > link rel "stylesheet" type "text/css" href "index.css" </ > head < > body <!--The chat app container--> < = > div class "chat-container" < = > div class "chat" < = > div class "language-select-container" <!--Dropdown menu for the language selector--> < = > div class "form-group" < = > label for "languageSelector" </ > label < = = > select class "form-control" id "languageSelector" </ > select </ > div </ > div <!--Message container - the chat messages will appear here--> < = = > ul class "row message-container" id "channel-status" </ > ul <!--Input container for the input field ans send button--> < = > div class "input-container" < = > div class "row text-input-container" < = = = /> input type "text" class "text-input" id "input-field" < = = = = > input id "publish" class "input-button" type "submit" value "Send" </ > div </ > div </ > div </ > div </ > body < = > script src "bundle.js" </ > script </ > html Notice that some of the elements already have classnames and ids. This will make it easier for us to add styles and javascript functions to this elements. Now if you open up your localhost on your browser you should see this screen. localhost:3000, This is the basic app display without any styles. Looking ugly right? Let us add some styles. Create the file in the public directory like in the file structure above and add the following. index.css * { : border-box; : , sans-serif; } { : ; } { : ; : ; : ; : auto; : solid ; : ; } { : ; : ; : ; } { : ; : ; : ; : ; } { : ; : ; : ; } { : ; : ; : ; } { : ; : ; : ; : ; : ; : none; : ; : ; } { : ; : ; : ; : none; : pointer; } { : ; : scroll; : none; } { : right; : ; } { : ; : flex; : space-between; } { : } { : ; } { : ; : ; : ; } { : ; : ; } { : ; } box-sizing font-family 'Roboto' body background #f2f2f2 .chat-container background-color #f2f2f2 color #404040 width 505px margin 40px border 1px #e1e1e8 border-radius 5px .language-select-container background-color #ffffff padding 20px 40px 20px border-radius 5px 5px 0 0 .language-select-container select height 30px margin-left 20px font-size 14px min-width 150px .input-container background-color #ffffff padding 20px border-radius 0 0 5px 5px .text-input-container background-color #f2f2f2 border-radius 20px width 100% .text-input background-color #f2f2f2 width 80% height 32px border 0 border-radius 20px outline padding 0 20px font-size 14px .input-button width 19% border-radius 20px height 32px outline cursor .message-container height 300px overflow list-style-type .message-time float margin-right 40px .message margin-bottom 10px display justify-content .message picture width 15% .message-info width 65% .message-image width 50px height 50px border-radius 50% .message-name margin-top 0 margin-bottom 5px .message-text margin-top 8px If you refresh the page, you will see a styled and beautiful user interface. Let us move on to creating the API endpoints for our app. 5. Creating the API Endpoints To use the language translation API, we will create three endpoints. One for getting a list of all languages, the second for getting the models for English, which is our default language, and the third for translating the language. Before we do any of this, we need to configure our translator. Let us do this in the file. server.js First, we create an instance of the Language translator by adding the following code. To create this instance, we need to authenticate the app using the . Remember to replace and with your own API key and version respectively. IamAuthenticator apikey version LanguageTranslatorV3 = ( ); { IamAuthenticator } = ( ); translator = LanguageTranslatorV3({ : , : IamAuthenticator({ : , }), : , }); const require 'ibm-watson/language-translator/v3' const require 'ibm-watson/auth' //create an instance of the language translator. const new version '{version}' authenticator new apikey '{apikey}' url '{url}' Next, we will create the endpoints. The translate endpoint will translate the text sent to it into another language depending on the language translation model sent as part of the request body. Create this endpoint by adding the following code. app.post( , { translator.translate(req.body) .then( res.json(data.result)) .catch( next(error)); }); //This endpoint translates the text send to it '/api/translate' ( ) function req, res, next => data => error The endpoint gets all the languages that can be processed by the IBM language translator. get-languages app.get( , { translator.listIdentifiableLanguages() .then( { res.json(identifiedLanguages.result); }) .catch( { .log( , err); }); }) //This endpoint gets all the langauges that can be processed by the translator '/api/get-languages' ( ) function req, res, next => identifiedLanguages => err console 'error:' The endpoint gets a list of all translation model available. Translation models specifies the language that the text is being translated from and the language it is being translated into. For instance, the model is a model for translating English text into French. get-model-list en-fr app.get( , { translator.listModels() .then( { res.json(translationModels.result) }) .catch( { .log( , err); }); }) //This endpoint gets all the model list. '/api/get-model-list' ( ) function req, res, next => translationModels => err console 'error:' To use these endpoints, we need to call them from the frontend and make use of the data they return. 6. Consuming the Endpoints We will make use of the endpoints the file that serves the frontend. So let us create some methods that will call these endpoints and return the data. index.js First, we will add two methods. The first method retrieves a list of all languages using the endpoint we created in the last section. The second method retrieves a list of all language models using the we created in the last section. get-languages get-model-list We are using async for both methods because we want the methods to return the data when the data has been fetched. Add the following code to the file. index.js { { response = fetch( , { : }); response.json(); } { response = fetch( , { : }) response.json(); } } index(); index; import '@babel/polyfill' ( ) function index //This method retrieves a list of all languages async ( ) function getLanguages let await "/api/get-languages" method 'GET' return await //This method retrieves a list of all language models async ( ) function getModels let await "/api/get-model-list" method 'GET' return await export default Next, we add a method that retrieves all languages that can be translated to and from English. We are doing this to ensure that the languages we are processing can be translated into the other. We will use English as the common ground for the languages. This method will also sort the languages gotten and will also populate the dropdown in our frontend view. Add the following code to your file. getTranslatableLanguages index.js { getTranslatableLangauges() { allLanguages = getLanguages(); models = getModels(); .all([allLanguages, models]).then( { allLanguages = values[ ].languages; models = values[ ].models; englishModels = models.filter( model.source === ); translatableEnglishLanguages = englishModels.map( { allLanguages.find( model.target === language.language) }) translatableEnglishLanguages.sort( { nameA = a.name.toUpperCase(); nameB = b.name.toUpperCase(); (nameA < nameB) { ; } (nameA > nameB) { ; } ; }) languagesMap = translatableEnglishLanguages.map( ) $( ).html(languagesMap) }) } } ( ) function index ( ) function getTranslatableLangauges //get languages and all language models const const //resolve the promises Promise => values const 0 const 1 //get translation models that have English as their source const => model "en" //get all languages that can be translated from English let => model return => language //sort languages ( ) => a,b var // ignore upper and lowercase var // ignore upper and lowercase if return -1 if return 1 // names must be equal return 0 const => language `<option value=" "> </option>` ${language.language} ${language.name} "#languageSelector" Now you should get a list of languages in the dropdown menu on the view. Next, we add a method that uses the translate endpoint to translate each text. This method accepts the message to be sent or received, the language the message should be translated into and the message type, if it is sent or received. Notice that it behaves differently if a message is sent or received. If a message is sent or being published to a channel, it is first converted from the user’s language into English if the original language is not English. If a message is received, it is converted from English into the user’s selected language. That is why we needed to set a default language which in this case is English. { text = messageType === ? message: message.data; translateParams = { : text, : messageType === ? : , }; nmtValue = ; fetch( , { : , : .stringify(translateParams), : Headers({ : $( ).val(), : nmtValue, : }), }) .then( response.json()) .then( { .log(data) }) .catch( .error(error)) } //this method translates the text using the `translate` enpoint created. ( ) function translateText message, language, messageType = "receive" //check if the message is a sent message or received message const "send" const text modelId "send" ` -en` ${language} `en- ` ${language} var '2019-09-28' '/api/translate' method 'POST' body JSON headers new 'X-WDC-PL-OPT-OUT' 'input:radio[name=serRadio]:radio:checked' 'X-Watson-Technology-Preview' "Content-Type" "application/json" => response => data console => error console For now, we just log the translated message to the console. We will modify this method to suit our app pretty soon. Now we have methods that we can use to consume all the endpoints we created. 7. Subscribing and Publishing on Ably To subscribe and publish on Ably, we first need to set up our credentials. Before we do that, we need to initialise a user with a name, id and avatar. The id will serve as the clientID when publishing messages to a particular channel, while the name and avatar will be used for displaying the messages. The name and avatars, will be retrieved randomly from a list of names and avatars respectively. At the top of our function in the index.js file, add the following code. { .floor( .random() * (max - min) + min); } avatarsInAssets = [ , , , , , , , , , ] namesInAssets = [ , , , , , , , , , ] user = { : + .random().toString( ).substr( , ), : avatarsInAssets[getRandomArbitrary( , )], : namesInAssets[getRandomArbitrary( , )] }; otherUser = {}; // A method to randomly get an item from an array ( ) function getRandomArbitrary min, max return Math Math //a list of avatars that will randomly be assigned to each app user const 'https://cdn.glitch.com/0bff6817-d500-425d-953c-6424d752d171%2Favatar_8.png?1536042504672' 'https://cdn.glitch.com/0bff6817-d500-425d-953c-6424d752d171%2Favatar_3.png?1536042507202' 'https://cdn.glitch.com/0bff6817-d500-425d-953c-6424d752d171%2Favatar_6.png?1536042508902' 'https://cdn.glitch.com/0bff6817-d500-425d-953c-6424d752d171%2Favatar_10.png?1536042509036' 'https://cdn.glitch.com/0bff6817-d500-425d-953c-6424d752d171%2Favatar_7.png?1536042509659' 'https://cdn.glitch.com/0bff6817-d500-425d-953c-6424d752d171%2Favatar_9.png?1536042513205' 'https://cdn.glitch.com/0bff6817-d500-425d-953c-6424d752d171%2Favatar_2.png?1536042514285' 'https://cdn.glitch.com/0bff6817-d500-425d-953c-6424d752d171%2Favatar_1.png?1536042516362' 'https://cdn.glitch.com/0bff6817-d500-425d-953c-6424d752d171%2Favatar_4.png?1536042516573' 'https://cdn.glitch.com/0bff6817-d500-425d-953c-6424d752d171%2Favatar_5.png?1536042517889' //a list of names that will randomly be assigned to each app user const 'Sarah Tancredi' 'Michael Scoffied' 'Waheed Musa' 'Ada Lovelace' 'Charles Gabriel' 'Mr White' 'Lovely Spring' 'William Shakespare' 'Prince Williams' 'Queen Rose' //create a user object by randomly assigning an id, an avatar and a name. let id "id-" Math 36 2 16 avatar 0 9 name 0 9 //this object will hold the data of other users that send messages to the channel let Notice that we also added an object called otherUser, this object will hold the data of other users that send messages to the channel. 7.1 Subscribing to a Channel To enable us see messages sent to the channel by other clients, we need to subscribe to a message channel. First, we create an instance of Ably Realtime. ably = Ably.Realtime({ : YOUR_ABLY_API_KEY, : , : }); const new key clientId ` ` ${user.id} echoMessages false Remember to replace the key with the API key from your dashboard. Next, we will subscribe to a channel to get both the chat message as well as user details of other clients who are connected. channel = ably.channels.get( ); channel.subscribe( , { selectedLanguage = $( ).find( ).val(); translateText(message, selectedLanguage) }); channel.subscribe( , (data) => { (data.clientId != user.id) { otherAvatar = data.data.avatar; otherName = data.data.name; otherUser.name = otherName; otherUser.avatar = otherAvatar; } }); //specify the channel the user should belong to. In this case, it is the `test` channel const 'test' //Subscribe the user to the messages of the channel. So the use rwill receive each message sent to the test channel. "text" ( ) function message const "#languageSelector" ":selected" //This gets the data of other users as they publish to the channel. "user" if let let Notice that we are assigning a name and avatar to the otherUser object we created earlier. That is all for subscribing to a channel. We can receive any message published to the test channel now. But what if we want to publish a message to the channel? Let’s see that next. 7.2 Publishing to a channel To contribute to a channel, we need to publish to the channel. We will define the behaviour of the app when the message is typed and the send button is clicked. Then we add a method that displays the message to the users. sendButton = .getElementById( ); inputField = .getElementById( ); languageSelector = .getElementById( ) sendButton.addEventListener( , { input = inputField.value; selectedLanguage = languageSelector.options[languageSelector.selectedIndex].value; inputField.value = ; date = (); timestamp = date.getTime() show(input, timestamp, user, ) translateText(input, selectedLanguage, ) }); { time = getTime(timestamp); messageItem = $( ).append(messageItem) } { date = (unix_timestamp); hours = date.getHours(); minutes = + date.getMinutes(); seconds = + date.getSeconds(); formattedTime = hours + + minutes.substr( ) + + seconds.substr( ); formattedTime; } //Get the send button, input field and language dropdown menu elements respectively. const document "publish" const document "input-field" const document "languageSelector" //Add an event listener to check when the send button is clicked 'click' ( ) function const const "" let new Date let //display the message as it is using the show method "send" //translate the text as a sent message "send" //This method displays the message. ( ) function show text, timestamp, currentUser, messageType= "receive" const const `<li class="message "> <picture> <img class="message-image" src= alt="" /> </picture> <div class="message-info"> <h5 class="message-name"> </h5> <p class="message-text"> </p> </div> <span class="message-time"> </span> </li>` ${messageType === ? : } "send" "sent-message" "" ${currentUser.avatar} ${currentUser.name} ${text} ${time} // const messageItem = `<li class="message">${text}<span class="message-time"> ${time}</span></li`; '#channel-status' //This method is used to convert a timestamp to 24hour time format, this is the format we will display the time of the message in. ( ) function getTime unix_timestamp var new Date var var "0" var "0" // Will display time in 10:30:23 format var ':' -2 ':' -2 return Refresh the browser for the changes we have made so far to take effect. If you send a message, you can see the message is the message container section. But you cannot receive messages yet because the translated message just gets logged to the console. Let us modify the translateText method finally. Replace the message with the modified code. console.log { ... fetch( , { : , : .stringify(translateParams), : Headers({ : $( ).val(), : nmtValue, : }), }) .then( response.json()) .then( { translatedText = data[ ][ ][ ]; ( messageType === ) { channel.publish( , translatedText); channel.publish( , { : user.name, : user.avatar }); } { show(translatedText, message.timestamp, otherUser); } }) .catch( .error(error)) } ( ) function translateText message, language, messageType = "receive" '/api/translate' method 'POST' body JSON headers new 'X-WDC-PL-OPT-OUT' 'input:radio[name=serRadio]:radio:checked' 'X-Watson-Technology-Preview' "Content-Type" "application/json" => response => data // when messages are translated, they get published to the channel const 'translations' 0 'translation' if "send" 'text' "user" "name" "avatar" else => error console Congratulations, you just built an app that people can use to communicate in different languages. Open up the app in two browsers tabs, select different languages for each app, send messages and see how the app works! Conclusion In this tutorial, we built an app that can be used to communicate in different languages. Here is a link to . Open the link into tabs and use the input field to send messages. For the full code, see . the live demo the repository For further reading, you can visit the following links: Ably’s Pub/Sub tutorial IBM translator docs