Language barriers can hinder growth and build a wall between your business and its potential customers, ultimately limiting your market and costing you money. If a customer reaches out to you by chat in German or Spanish, but you don’t understand the language, wouldn't it be nice to still be able to win them over?!
Just because your customers don’t speak the same language(s) as your team does, it doesn’t mean they should be cut off from interacting with your brand. It is possible to provide realtime language translation features in your chat app so that you can understand queries from speakers of other languages and provide a prompt response in your native language, which is also translated for the customer!
This tutorial will take you through building a chat application where users can select their preferred language, and have messages sent to them translated to that language in real time. Here’s a screenshot of the application, the code for which can be found on GitHub:
Prerequisites
Before you proceed with the rest of this tutorial, make sure you have Node >= 8.10 and yarn >= 1.22 installed in your machine. You'll also need a basic understanding of JavaScript and React, so that you’ll be able to understand the code we’ll be writing.
Signing Up for Amazon AWS
If you don’t already have an account, you can register for a free AWS account here ( otherwise, you can log in to your existing account). Once you’ve completed the signup process, wait for your account to be activated before proceeding.
Once activated, log into your AWS account, click your username at the top right corner, then click My Security Credentials. Next, click Users on the sidebar. You will be redirected to a page where you can view, add or delete users on your account. Click the Add user button, assign a User name to the user, and tick Programmatic access under Access type:
Next, under Set permissions, click Attach existing policies directly and then select TranslateFullAccess under Filter policies.
You can skip the optional Tags step, and go ahead to create the user on step four of the user creation process in AWS. Once the user is created, you will be provided with the Access key ID and Secret access key for that user. Keep this page open until we copy the keys to a .env
file, which we’ll create shortly.
Signing Up for Stream
Follow this link to create a new Stream account, or sign in to your existing account. Once you’re redirected to the dashboard, create a new app and take note of the application access keys which will be presented to you on creation:
Setting Up the Server
Create a new directory for this project in your filesystem and cd
into it. Then, run yarn init -y
from the project root to initialize a new Node.js project. Following that, run the command below to install all the dependencies that are needed to build the server:
1$ yarn add express dotenv cors aws-sdk stream-chat body-parser
which installs:
- stream-chat: The Stream chat library
- express: Node.js web application framework
- body-parser: Node.js body parsing middleware
- dotenv: For loading environmental variables from a
.env
file intoprocess.env
- cors: Node.js CORS middleware
- aws-sdk: The official AWS SDK for the browser and Node.js
Next, create a new .env
file in the project root and add the following environmental variables to it, replacing the placeholders with the appropriate values from your Stream and AWS dashboards:
PORT=5500 STREAM_API_KEY=<your stream api key> STREAM_APP_SECRET=<your stream app secret> AWS_ACCESS_KEY_ID=<your aws access key> AWS_SECRET_ACCESS_KEY=<your aws access secret>
Save the .env
file, then go ahead and create a new server.js
file in the project root. Open it in your text editor, and paste in the following code:
require('dotenv').config(); const express = require('express'); const cors = require('cors'); const bodyParser = require('body-parser'); const { StreamChat } = require('stream-chat'); const AWS = require('aws-sdk'); const app = express(); app.use(cors()); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); const serverSideClient = new StreamChat( process.env.STREAM_API_KEY, process.env.STREAM_APP_SECRET ); const translate = new AWS.Translate({ accessKeyId: process.env.AWS_ACCESS_KEY_ID, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, region: 'us-east-2', }); app.post('/translate', (req, res) => { const { text, lang } = req.body; const params = { SourceLanguageCode: 'auto', TargetLanguageCode: lang, Text: text, }; translate.translateText(params, (err, data) => { if (err) { return res.send(err); } res.json(data); }); }); app.post('/join', async (req, res) => { const { username } = req.body; let token; try { token = serverSideClient.createToken(username); await serverSideClient.updateUser( { id: username, name: username, }, token ); const admin = { id: 'admin' }; const channel = serverSideClient.channel('messaging', 'discuss', { name: 'Discussion', created_by: admin, }); await channel.create(); await channel.addMembers([username, 'admin']); } catch (err) { console.log(err); return res.status(500).end(); } return res .status(200) .json({ user: { username }, token, api_key: process.env.STREAM_API_KEY }); }); const server = app.listen(process.env.PORT || 5500, () => { const { port } = server.address(); console.log(`Server running on PORT ${port}`); });
The /users
route is where the creation of users on our Stream instance takes place, while the /translate
route is where the text sent by the chat client is converted to the language specified by the lang
variable through Amazon Translate’s translateText
method. The translation is subsequently sent back to the client to be displayed to the user.
That’s all we need to do on the server! You can start it on port 5500 by running node server.js
in the terminal.
Setting Up the React Application
Within your project directory, use the create-react-app CLI to bootstrap a new React application using the command below.
1$ npx create-react-app client
Once the app has been created, cd
into the new client
directory and install the following additional dependencies that we’ll be needing throughout the course of building the application frontend:
1$ yarn add stream-chat stream-chat-react random-username-generator axios
which installs:
- stream-chat: The Stream chat client library
- stream-chat-react: Stream chat components for React
- random-username-generator: For generating random usernames so we can rapidly spin up multiple instances of our chat app
- axios: For making HTTP requests
Once the installation is complete, start your development server by running yarn start
from within the client
directory, then navigate to http://localhost:3000 in your browser to view the app.
Building the Chat UI
Open up client/src/App.js
in your text editor and update it to look like the snippet below:
import React from 'react'; import { Chat, Channel, Thread, Window, ChannelList, ChannelListMessenger, ChannelPreviewMessenger, MessageList, MessageSimple, MessageInput, withChannelContext, } from 'stream-chat-react'; import rug from 'random-username-generator'; import { StreamChat } from 'stream-chat'; import axios from 'axios'; import 'stream-chat-react/dist/css/index.css'; let chatClient; class App extends React.Component { constructor() { super(); this.state = { channel: null, language: 'en', messages: [], }; } async componentDidMount() { const username = rug.generate(); try { const response = await axios.post('http://localhost:5500/join', { username, }); const { token } = response.data; const apiKey = response.data.api_key; chatClient = new StreamChat(apiKey); const user = await chatClient.setUser( { id: username, name: username, }, token ); const channel = chatClient.channel('messaging', 'discuss'); await channel.watch(); this.setState( { channel, }, () => { channel.on('message.new', async event => { if (user.me.id !== event.user.id) { try { const response = await axios.post( 'http://localhost:5500/translate', { text: event.message.text, lang: this.state.language, } ); const msg = event.message; msg.text = response.data.TranslatedText; this.setState({ messages: [...this.state.messages, msg], }); } catch (err) { console.log(err); } } else { this.setState({ messages: [...this.state.messages, event.message], }); } }); } ); } catch (err) { console.log(err); } } setLanguage = lang => { this.setState({ language: lang, }); }; render() { const { channel, language, messages } = this.state; const { setLanguage } = this; if (channel) { const CustomChannelHeader = withChannelContext( class CustomChannelHeader extends React.PureComponent { render() { return ( <div className="str-chat__header-livestream"> <div className="str-chat__header-livestream-left"> <p className="str-chat__header-livestream-left--title"> {this.props.channel.data.name} </p> <p className="str-chat__header-livestream-left--members"> {Object.keys(this.props.members).length} members,{' '} {this.props.watcher_count} online </p> </div> <div className="str-chat__header-livestream-right"> <div className="str-chat__header-livestream-right-button-wrapper"> <select id="language" className="language" name="language" value={language} onChange={e => setLanguage(e.target.value)} > <option value="en">English</option> <option value="fr">French</option> <option value="es">Spanish</option> <option value="de">German</option> </select> </div> </div> </div> ); } } ); return ( <Chat client={chatClient} theme="messaging dark"> <ChannelList options={{ subscribe: true, state: true, }} List={ChannelListMessenger} Preview={ChannelPreviewMessenger} /> <Channel channel={channel}> <Window> <CustomChannelHeader /> <MessageList Message={MessageSimple} messages={messages} /> <MessageInput focus /> </Window> <Thread Message={MessageSimple} /> </Channel> </Chat> ); } return <div>Loading...</div>; } } export default App;
This is all the code we need to get our application looking and functioning as it should! Between lines 103 and 157, you can see how various components provided by the stream-chat-react package are used to construct a feature-rich and responsive user interface. Here’s what each component does:
- Chat acts as a wrapper and provides ChatContext to all other components.
- ChannelList displays a preview list of channels, allowing you to select the channel you want to open.
- Channel acts as a wrapper component for a channel.
- To render basic information about a channel, the list of messages in the channel and the text input, the CustomChannelHeader, MessageList, and MessageInput components are used, respectively.
Lines 60-84 are where language translation happens. When messages are sent to the channel, the ones sent from the current user are appended to the messages
array immediately and are subsequently displayed on the screen. Messages from other users are sent to the /translate
endpoint on the server with the lang
set to whatever the user has selected in the UI. Once the translated text is received on the client, it is displayed to the user.
You can see this in action in the screenshot below. The first user sends messages in English and receives responses in English, while the second sends messages in French and receives in French, as well.
Wrapping Up
In this article, we discussed why it is important to provide language translation features in a chat app, and then combined the services of Stream Chat and Amazon Translate to build a functioning demo.
Be sure to check out the documentation for both services to learn more about all the features and integrations that are available to you.
Thanks for reading, and happy coding!