高校生がリアルタイム言語変換のサービスを作ってGithubにPushしてみた。

高校生でプログラミングが好きなレオです！！

今回はリアルタイムで言語変換できる[サービス](https://github.com/lra21711214/transchat)を作ったので、
紹介とどのように作ったのか少し説明したいと思います。

※この記事は、Node.jsとPython,Socket.ioを使える前提で話を進めていきます。

# 1. アイディア
アイディアは[WeChat](https://www.wechat.com/ja/)の翻訳機能を使っていた時に思いつきました。
僕が中国でお買い物をする際に店員さんと、[WeChat](https://www.wechat.com/ja/)を使って会話をすることがあるのですが、リアルタイムではなく、毎度翻訳をかけないと行けなかったので、リアルタイムで翻訳できて、友達交換もしなくていいサービスを作ろう！と思ったのがきっかけです。

# 2. 使用した言語と技術

使用したプログラミング言語はNode.jsとPythonです。
主にNode.jsを使っていて、Node.js上で[Socket.io](https://socket.io/)などの通信部分やテンプレート（EJS）を表示しています。
Pythonはgoogletransというライブラリを使用するために採用していて、
Node.jsとPythonの間は[Python-Shell](https://github.com/extrabacon/python-shell)というライブラリを使ってデータのやりとりをしています。

# 3. ファイル構成

```tree:transchatのファイル構成
.
├── 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
```js:app.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](https://expressjs.com/ja/)や[Socket.io](https://socket.io/),[Node-uuid](https://github.com/broofa/node-uuid),[python-shell](https://github.com/extrabacon/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
```js:app.js
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)```でユーザをルームに入室しています。

```js:app.js
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に入室しているユーザのリストを取得しています。

```js:app.rb
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
```python:python/translate.py
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をここまでガッツリ書いたのが初めてだったのですが、結構簡単にかけて楽しかったです。
ただ、簡単なサービスな割にはコードが複雑になってしまったため、今後もアップデートを続けて行けたらなと思います（プルリク大歓迎です！！）

それと、もし良ければ、インストールして試してもらえると嬉しいです。

```terminal
git clone https://github.com/lra21711214/transchat.git
cd transchat
npm install
node app.js
```
このようにGit cloneを行なってnpm installをするだけで試すことが可能なのでお願いします！！


※注意※
実際に本番環境で、使用する際は、Room配列やユーザの言語情報などをDBに保存するようにしたり、翻訳部分を[Amazon Translate](https://aws.amazon.com/jp/translate/)や[Cloud Translation](https://cloud.google.com/translate?hl=ja),[DeepL API](https://www.deepl.com/ja/docs-api/)などを使用してください。
あくまで、お試し感覚で作成したため、本番環境向けではございません。