はじめに
案件で使う機会があったので忘備録的な感じで記載していこうと思います。
諸々間違い、認識違いがあるかもしれませんが生暖かく見守っていただければと思います。
実装イメージ
- XServer X10プランを使用します。
- NodeでWebサーバ起動して云々はXServer上で実装するのは難しいのでphpで受けてコマンド呼び出しで動かします。
- 翻訳結果はjson形式で返却します。
- 本来であれば翻訳結果をjavascriptが受け取りうまくゴニョゴニョしてhtml上で表現するが正しいと思いますが、残念ながら自分はPHPerでjavascriptが得意じゃないのでこの部分は割愛させていただきたく。
環境
- XServer X10プランで契約できるレンタルサーバ
- php
- 7.2.17
- perl
- 5.16
- nodebrew
- 1.0.1
- Node
- v12.10.0
- npm
- 6.10.3
※php, perlのバージョンはXServerのデフォルト設定(2019/12/04時点)
※nodebrewは自分が試した時点での最新。
※Nodeはnodebrewにて指定する感じ、npmはnodeインストール時に一緒に入るもの。
ディレクトリ構成
- XServerの基本構造をそのまま使用します。
- 翻訳機能についてはドキュメントルート直下には作りませんでした。直URLでアクセスされたときのことを考えたくなかったからです。
- 各ディレクトリ・ファイルについては後述で説明しますが、最終的には下記のような構成になります。
/xxxxx.xsrv.jp/
public_html/
index.html
translation.php
node/
json/
node_modules/
package-lock.json
translation.js
gcpprj-example-xxxxxx-yyyyyyyyyyyy.json
nodebrew, node.js, npmのインストール
- hokaccha/nodebrewで記載があるコマンドをそのまま実行します。
$ cd ~/
$ wget git.io/nodebrew
$ perl nodebrew setup
:
:
$ vi .bashrc
export PATH=$HOME/.nodebrew/current/bin:$PATH ※この行を.bashrcの末尾に追加
$ source ~/.bashrc
$ nodebrew help ※このコマンドを叩くと諸々出てくれば成功
nodebrew 1.0.1
:
:
$ nodebrew install v12.10.0 ※これでnode.js v12.10.0がインストールされる
$ nodebrew use v12.10.0 ※これでnode.js v12.10.0を使うよと宣言する感じ
$ node -v
v12.10.0
$ npm -v
6.10.3
Google Cloud Platformの設定及びGoogleTranslateAPIを使用するための準備
- GCP側でプロジェクトを作成します。
- 「請求アカウント」を作成し上記で作ったプロジェクトに紐付けてください。
- 使用APIに「Cloud Translation API」を追加してください。
- このクイックスタートのページの「プロジェクトをセットアップする」ボタンを押下しプロジェクトを指定すると「JSONとしての秘密鍵」がDownloadされます。
上記の「gcpprj-example-xxxxxx-yyyyyyyyyyyy.json」というのが「JSONとしての秘密鍵」になります。 - クイックスタートにはその後「環境変数なんちゃら」という記載がありますが、とりあえず今のところはスルー。
- クイックスタートの次の手順「クライアント ライブラリのインストール」では「NODE.JS」を選択するとnpmのインストールコマンドが出てくるのでそれを上記のnodeディレクトリ配下で実行します。
- nodeディレクトリはデフォルトでは無いのでmkdirで作っていきながら作業する感じです。
$ cd ~/
$ cd xxxxx.xsrv.jp
$ mkdir node
$ cd node
$ mkdir json
$ npm install --save @google-cloud/translate <== これがクイックスタート上に出てきたインストールコマンド
$ ls -1F
json/
node_modules/
package-lock.json
- このnodeディレクトリ配下に先程Downloadされた「JSONとしての秘密鍵」をFTP等でアップロードします。
$ cd ~/xxxxx.xsrv.jp/node
$ ls -1F
json/
node_modules/
package-lock.json
gcpprj-example-xxxxxx-yyyyyyyyyyyy.json
-
クイックスタートの次の手順「テキストの翻訳」で、NODE.JSのサンプルコードをそのまんまコピーして上記で作ったnodeディレクトリにtranslation.jsとして保存。
-
サンプルコードのままだと、翻訳対象文字列がコードに直書き状態なので引数で受け取って可変できるようにします。その時、翻訳対象文字列を直接引数で指定してしまうと「コマンドインジェクション」が発生する可能性が高く怖いので翻訳対象文字列を引数のjsonファイルから取得するような仕様に変更します。
-
translation.jsの中身は下記の通り
const projectId = 'XXXXX-yyyyy-zzzzz'; // GCP上のプロジェクトIDを記載
const location = 'global';
// jsonから読み込むように修正
const jsonPath = process.argv[2];
const json = require(jsonPath);
const text = json.text;
// Imports the Google Cloud Translation library
const {TranslationServiceClient} = require('@google-cloud/translate').v3beta1;
// Instantiates a client
const translationClient = new TranslationServiceClient();
async function translateText() {
// Construct request
const request = {
parent: translationClient.locationPath(projectId, location),
contents: [text],
mimeType: 'text/plain', // mime types: text/plain, text/html
sourceLanguageCode: 'ja', // 日本語から。
targetLanguageCode: 'en', // 英語に。
};
// Run request
const [response] = await translationClient.translateText(request);
for (const translation of response.translations) {
console.log(`${translation.translatedText}`);
}
}
translateText();
- ディレクトリ内はこんな感じ
$ cd ~/xxxxx.xsrv.jp/node
$ ls -1F
json/ ※翻訳対象の文字列をjson形式にして保存する場所
node_modules/
package-lock.json
gcpprj-example-xxxxxx-yyyyyyyyyyyy.json
translation.js
- 試しにこの状態で下記のコマンドを叩いて強引に実行してみます。
- jsonで設定している日本語は「私は本を持っている」です。
$ cd ~/xxxxx.xsrv.jp/node
$ echo "{\"text\":\"\u79c1\u306f\u672c\u3092\u6301\u3063\u3066\u3044\u308b\"}" > json/example.json ※ダミーjson作成
$ node translation.js /home/xxx/xxxxx.xsrv.jp/node/json/example.json
(node:395938) UnhandledPromiseRejectionWarning: Error: Could not load the default credentials. Browse to https://cloud.google.com/docs/authentication/getting-started for more information.
at GoogleAuth.getApplicationDefaultAsync ...
:
:
(node:398472) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:398472) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
$
- うまく動きません。「環境変数なんちゃら」をスルーしたためです。
- 「環境変数を設定する」、「翻訳対象の文字列を取得し、jsonファイルを作る」という処理はこの後に記載するphpロジックにて実装することを想定しています。
php, htmlで残りの処理を作成する。
- ドキュメントルート直下にindex.htmlを設置、翻訳対象文字列を入力するフォームを作ります。
<html>
<head>
<meta charset="UTF-8" />
<title>日本語を英語に翻訳する</title>
</head>
<boby>
<form action="translation.php" method="post">
日→英、翻訳テキスト:<input type="text" name="target"> <input type="submit" value="翻訳">
</form>
</body>
</html>
- translation.phpで下記機能を実装します。
- 翻訳対象文字列の取得
- jsonファイルの生成
- 環境変数のセット
- translation.jsの実行、結果取得
- json形式にして結果を出力
define('HOME_DIR', '/home/xxx/'); // 各自環境のものを入れる
define('NODE', HOME_DIR . '.nodebrew/current/bin/node'); // nodeのインストール状況では違うものになるはず
define('DOMAIN_DIR', HOME_DIR . 'xxxxx.xsrv.jp/');
define('NODE_DIR', DOMAIN_DIR . 'node/');
define('JSON_DIR', NODE_DIR . 'json/');
define('GCP_JSON', NODE_DIR . 'gcpprj-example-xxxxxx-yyyyyyyyyyyy.json');
define('TRANSLATION_JS', NODE_DIR . 'translation.js');
// 引数が無い場合は何もしない
if (!isset($_POST['target']) || $_POST['target'] === '') {
header("Location: https://xxxxx.xsrv.jp/index.html");
exit();
}
// jsonファイルを生成
define('TEXT_RANGE', implode(array_merge(range(0, 9), range('a', 'z'), range('A', 'Z'))));
$rndtxt = substr(str_shuffle(TEXT_RANGE), 0, 16);
$jsonfile = sprintf("%s%s.json",JSON_DIR, $rndtxt);
$json_ary['text'] = $_POST['target'];
$jsondata = json_encode($json_ary);
file_put_contents($jsonfile, $jsondata);
// JSONの秘密鍵を環境変数にセット
putenv('GOOGLE_APPLICATION_CREDENTIALS='.GCP_JSON);
// コマンドの作成・実行
$command = sprintf("%s %s %s", NODE, TRANSLATION_JS, $jsonfile);
$out = $res = '';
exec($command, $out, $res);
// 結果解析
if (!empty($out[0])) {
header('content-type: application/json; charset=utf-8');
echo json_encode(['text' => $out[0]]);
} else {
header('content-type: application/json; charset=utf-8');
echo json_encode(['error' => 'error']);
}
exit();
- ここまでやるとディレクトリ構成のような形になっていると思います。
やってみる
- こんな感じで入れてみて…
- こんな感じで出る。
- firefoxだとjson形式を開くと色々解析してくれる(が、今回はシンプル極まりないので特に意味なしですが)
補足
- Google Translation API v3 はまだβ版なので注意しよう。
- 早くphp版ライブラリでないかぁと思ったり。
- Google Translation API v3 には無料枠がある。
まとめ
- 自分の担当案件ではよく「日英のHPをCMSで作る」という要件がよくあるのでこれからも多用していくかなと思っています。
- 正直、ロジックを作るところよりもGCPの設定のほうが難しかった。
FORK Advent Calendar 2019
9日目 Vuetifyのdatepickerを使って【和暦】+【年度/月】pickerを作ってみた @BigFly
11日目 ブラウザであのレベルアップ音を奏でてみた(Tone.js) @talow1