高校生でプログラミングが好きなレオです!!
今回はリアルタイムで言語変換できるサービスを作ったので、
紹介とどのように作ったのか少し説明したいと思います。
※この記事は、Node.jsとPython,Socket.ioを使える前提で話を進めていきます。
1. アイディア
アイディアはWeChatの翻訳機能を使っていた時に思いつきました。
僕が中国でお買い物をする際に店員さんと、WeChatを使って会話をすることがあるのですが、リアルタイムではなく、毎度翻訳をかけないと行けなかったので、リアルタイムで翻訳できて、友達交換もしなくていいサービスを作ろう!と思ったのがきっかけです。
2. 使用した言語と技術
使用したプログラミング言語はNode.jsとPythonです。
主にNode.jsを使っていて、Node.js上でSocket.ioなどの通信部分やテンプレート(EJS)を表示しています。
Pythonはgoogletransというライブラリを使用するために採用していて、
Node.jsとPythonの間はPython-Shellというライブラリを使ってデータのやりとりをしています。
3. ファイル構成
.
├── app.js
├── package-lock.json
├── package.json
├── public
│ ├── css
│ │ ├── chat
│ │ │ └── stylesheet.css
│ │ └── home
│ │ └── stylesheet.css
│ ├── img
│ │ └── button.svg
│ └── js
│ ├── chat
│ │ └── main.js
│ └── home
│ └── home.js
├── python
│ └── translate.py
└── templates
├── chat
│ └── index.ejs
└── home
└── index.ejs
ファイル構成は一般的なexpressの構成に言語変換用のpythonファイル(translate.py)を追加しました。
4. Node.js
var express = require('express')
var app = express()
var http = require('http').createServer(app)
var io = require('socket.io')(http,{path:'/message'})
var uuid = require('node-uuid')
var {PythonShell} = require('python-shell')
app.use(express.static('public'))
app.set("view engine", "ejs")
var room = []
var lang = {}
app.get('/', function(req,res){
res.render(__dirname+'/templates/home/index.ejs')
})
app.post('/transroom', function(req,res){
var roomid = uuid.v4().split('-').join('')
room.push(roomid)
res.redirect('/transroom/'+roomid)
})
app.get('/transroom/:roomid', function(req, res){
if(room.includes(req.params.roomid)){
res.render(__dirname+'/templates/chat/index.ejs')
}else{
res.redirect('/')
}
})
function trans_mes(mes, before_lang, trans_lang, send_user){
if(1300 >= mes.length){
var pyshell = new PythonShell('./python/translate.py')
pyshell.send(JSON.stringify({'transdata':mes,'translang':trans_lang,'sendlang':before_lang}))
pyshell.on('message', function(data){
if(data == 'translate_error'){
io.to(send_user).emit('message', 'servererror')
}else{
io.to(send_user).emit('message', JSON.parse(data))
}
})
}else{
io.to(send_user).emit('message', 'error')
}
}
これがメインのNode.jsのコードです。
1行目〜6行目は必要なライブラリ、ExpressやSocket.io,Node-uuid,python-shellの読み込みを行っています。
7行目と8行目では、パブリックファイルのパス指定とテンプレートエンジンの指定を行っています。
16行目〜20行目では、ユニークのRoomIDを作成し、room配列にRoomIDを追加して、ユーザをリダイレクトしています。
22行目〜28行目では、room配列にリクエストされたRoomIDがあるか確認をして、あった場合はテンプレートを表示し、なかった場合はリダイレクトをしています。
function trans_mesでは、翻訳するメッセージ・送信したユーザの言語・送信先のユーザの言語・送信先のSocketIDを取得し、そのデータを使って、Python-ShellでPythonを実行し、翻訳されたデータをSocket.ioで送信しています。
ただ、googletransの使用上文字数制限があり、pythonを実行する前に文字数の確認を行なっています。
5. Socket.io
io.on('connection', function(socket){
var roomid = socket.handshake.headers.referer.split('/')[4]
if(roomid != null){
socket.join(roomid)
var room_user_count = io.sockets.adapter.rooms[roomid].length
if(room_user_count == 1){
io.to(socket.id).emit('sys', {'roomid':roomid})
}else if(room_user_count == 2){
socket.broadcast.to(roomid).emit('sys', 'join_user')
}else if(room_user_count >= 3){
io.to(socket.id).emit('sys', 'redirect')
}
socket.on('message', function(msg,sendlang){
var client_list = io.sockets.adapter.rooms[roomid].sockets
for (var clientId in client_list ) {
if(socket.id != clientId && lang[clientId]){
trans_mes(msg, lang[socket.id], lang[clientId], clientId)
}
}
})
socket.on('lang', function(user_lang){
lang[socket.id] = user_lang
})
socket.on('disconnect', function(){
socket.leave(roomid)
if(io.sockets.adapter.rooms[roomid] == null){
var list_room_id = room.indexOf(roomid)
if (list_room_id > -1) {
room.splice(list_room_id, 1)
}
}else{
if(io.sockets.adapter.rooms[roomid].length == 1){
socket.broadcast.to(roomid).emit('sys', {'user_out':roomid})
}
}
})
}
})
これが、Socket.io部分です。
まず、最初にvar roomid = socket.handshake.headers.referer.split('/')[4]
でリクエストされたRoomIDを取得して、socket.join(roomid)
でユーザをルームに入室しています。
socket.on('message', function(msg,sendlang){
var client_list = io.sockets.adapter.rooms[roomid].sockets
for (var clientId in client_list ) {
if(socket.id != clientId && lang[clientId]){
trans_mes(msg, lang[socket.id], lang[clientId], clientId)
}
}
})
次にメッセージの機能ですが、var client_list = io.sockets.adapter.rooms[roomid].sockets
の部分でRoomに入室しているユーザのリストを取得しています。
for (var clientId in client_list ) {
if(socket.id != clientId && lang[clientId]){
trans_mes(msg, lang[socket.id], lang[clientId], clientId)
}
}
次に、ユーザのリストをFor分で分割し、if文を使って、送信者以外を選択し、function trans_mesを実行します。
これによって、送信者以外のユーザに翻訳されたメッセージが送信されます。
6. Python
from googletrans import Translator
import json
import sys
translator = Translator()
try:
data = sys.stdin.readline()
data_json = json.loads(data)
lang = data_json['translang']
sendlang = data_json['sendlang']
translate = translator.translate(data_json['transdata'], src=sendlang, dest=lang)
json = json.dumps({'trans_data':translate.text,'original_data':data_json['transdata'],'user_lang':lang,'trans_lang':translate.src},ensure_ascii=False)
print(json)
except Exception as e:
print('translate_error')
Pythonのコードは簡単で、sys.stdin.readline()
でNode.jsで指定した情報を取得して、それをgoogletransを使って翻訳をし、Json形式でNode.jsに返還しています。
7. 最後に。
Socket.ioをここまでガッツリ書いたのが初めてだったのですが、結構簡単にかけて楽しかったです。
ただ、簡単なサービスな割にはコードが複雑になってしまったため、今後もアップデートを続けて行けたらなと思います(プルリク大歓迎です!!)
それと、もし良ければ、インストールして試してもらえると嬉しいです。
git clone https://github.com/lra21711214/transchat.git
cd transchat
npm install
node app.js
このようにGit cloneを行なってnpm installをするだけで試すことが可能なのでお願いします!!
※注意※
実際に本番環境で、使用する際は、Room配列やユーザの言語情報などをDBに保存するようにしたり、翻訳部分をAmazon TranslateやCloud Translation,DeepL APIなどを使用してください。
あくまで、お試し感覚で作成したため、本番環境向けではございません。