まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

タグ:pipeline

「国際化」に対応したウェブアプリケーションを Node.js で作る方法を調べたので、メモ替わりに残しておきます。

ここでの「国際化(internationalization, i18n)」はウェブブラウザで設定した言語によって自動的に英語表記にしたり、日本語にしたり、・・・という切り替えを行えるようなものです。自動翻訳とかそういうものではありません。またブラウザで設定した言語は HTTP リクエスト時に "Accept-Language" ヘッダで送信されることになるので、後述の動作確認は curl コマンドでこのヘッダを指定して行っています。

このような国際化対応アプリケーションを Node.js で、正確には Node.js + Express + EJS の環境で作ってみました。

Node.js で国際化対応アプリケーションを作る場合、i18n というパッケージを使うのが手っ取り早いです:
https://www.npmjs.com/package/i18n

ソースコード(app.js)はこんな感じにしました。余計な部分を削ぎ落として、最小限必要な部分だけを残しています(赤字部分が i18n 関連の箇所です):
//. app.js

var express = require( 'express' ),
    fs = require( 'fs' ),
    ejs = require( 'ejs' ),
    i18n = require( 'i18n' ),
    request = require( 'request' ),
    session = require( 'express-session' ),
    app = express();

var port = 3000;

app.set( 'views', __dirname + '/public' );
app.set( 'view engine', 'ejs' );

i18n.configure({
  locales: ['en', 'ja'],
  directory: __dirname + '/locales'
});
app.use( i18n.init );

app.get( '/', function( req, res ){
  res.render( 'index' );
});

app.listen( port );
console.log( "server starting on " + port + " ..." );

今回は英語(en)と日本語(ja)に対応したアプリケーションにしました。

また '/' にアクセスした時に ejs の index テンプレートを使った画面が表示されるような内容にしています。ちなみに index テンプレート(public/index.ejs)の内容は以下のようになっています:
<html>
<head>
<title><%= __('subject') %></title>
</head>
<body>
<h1><%= __('subject') %></h1>
<hr/>
<%= __('body') %>
</body>
</html>


テンプレート内で subject と body という2つの変数を使った表記を行っています。実際にはこれらの部分に言語設定に合わせた内容が表示されることになります。

そして言語ファイルを以下のように用意します:

(英語用: locales/en.json)
{
  "subject": "subject",
  "body": "body"
}


(日本語用: locales/ja.json)
{
  "subject": "サブジェクト",
  "body": "本文"
}

英語設定で利用した場合、上記の subject 変数部分は "subject", body 変数部分は "body" と表示されます。また日本語設定の場合、それぞれ "サブジェクト" と "本文" となります。


これで準備できました。 npm install して実行(node app)します:
$ npm install
$ node app

確認は別の端末から curl で行いました。まずは Accept-Language を en(英語)にしてアクセス:
$ curl http://localhost:3000/ -H 'Accept-Language: en'

<html>
<head>
<title>subject</title>
</head>
<body>
<h1>subject</h1>
<hr/>
body
</body>
</html>
>

次は日本語設定でアクセスした場合:
$ curl http://localhost:3000/ -H 'Accept-Language: ja'

<html>
<head>
<title>サブジェクト</title>
</head>
<body>
<h1>サブジェクト</h1>
<hr/>
本文
</body>
</html>


期待通りに動いています!


アプリケーションの国際化そのものはこれだけで出来ました。そして問題になるのは「どうやって色んな言語用の JSON リソースファイルを用意するか?」です。1つ1つ翻訳サービスなどを使いながら作る、という方法もありますが、そんな言語リソースファイルの翻訳作業は IBM Cloud の Globalization Pipeline サービスを使うと英語のリソースファイルから各言語に翻訳したリソースファイルをまとめて作ることができてとても便利です。このサービスについては以前のブログで使い方も含めて紹介しているので参照ください:
Globalization Pipeline サービスがリリースされました!


と、最後は宣伝でしたw

アプリケーションの国際化を意識する場合に多言語対応は避けて通れなくなります。要するに画面に表示される文字列を変数化して、実際の表示に使われる中身をブラウザの言語設定などから動的に変更して表示する、という考え方です。したがって各変数に対応する文字列を各言語ごとに用意する必要があります。特に Java などでは .properties リソースファイルとして用意します。

このリソースファイル、日本語と英語程度であれば普通に用意できるかもしれませんが、多言語対応のメリットを考えると、フランス語やドイツ語、中国語など、リソースファイルを用意するだけでその言語向けの画面を用意できます(そのための多言語対応です)。しかしこれらの言語にリソースファイルを翻訳するのは言うほど簡単ではないはずです。事実、自分も日本語と英語以外では翻訳サービスを併用しないと無理だし、その結果作成されたリソースファイルがどの程度正しいのか(おかしくないのか)は正直よくわかりません。ましてやアラビア語のような表記方向すら異なるような文字だと、リソースファイルの編集方法すらよくわかってなかったりします。

そんなリソースファイルの翻訳に便利なサービスの1つが IBM Bluemix から提供されている Globalization Pipeline です。以前からベータリリースはされていましたが、2016年7月に正式リリースを迎え、REST API による操作もサポートされました:
2016071501


使い方は基本言語となる英語のリソースファイルを用意すれば、日本語を含む9言語(元の英語まで含めると10言語)のリソースファイルを自動生成してくれます。使い方も簡単です:
2016071502


このサービスの使い方について、以前に(ベータだった頃に)ブログで紹介しています。ちとUIが変更になっているようなので、参考程度ですが:


日本時間の 2015/Nov/17 に IBM Bluemix 内に Globalization Pipeline サービスがベータリリースされました:
2015111701


このサービスはアプリケーションの国際化を促進するための機能を提供しています。具体的には特定言語で記述された文字リソースファイルを元に他言語翻訳した文字リソースファイルを生成してくれます。

具体的な使い方を以下で説明します。まずは翻訳元になる文字リソースファイルを用意します。この例では Java 言語でよく使われる .properties 形式の英語リソースファイル(resource.properties)を用意しました。このリソースを元にして英語以外の文字リソースを生成してみます:
common.body.dndavailable=Drag & Drop to change location.
common.body.htmltagavailable=Some HTML tags(ex. &lt;br/&gt;(=new line)) are available.
common.body.update=Information Updated.
common.body.updatelatlng=Geo Information Updated.
common.body.clickfordetail=Click for detail page.
common.body.clickdescformap=Click underlined description for map.
common.body.notactivated=(This image is not visible to other. Go Edit page to activate.)
common.body.activate=Activate
common.body.twitterlinkmsg='s twitter profile.
common.body.fordeveloper=For Developer
common.body.apidownload=Download ManholeMap API here
common.body.iphoneapplink=Download iPhone App.
common.body.apiziplastmodified=Last Modified
common.body.logintoseefaceicon=Sign in with Twitter to see face icon
common.body.nomsiesupported=MSIE not supported in this site.
common.body.imgoftoday=Manhole of Today
:

まず Bluemix 上に Globalization Pipeline サービスを生成します。ランタイムにバインドしてもしなくても構いません:
2015111701


Bluemix のダッシュボード画面などから生成したサービスを選びます:
2015111702


Globalization Pipeline サービスの画面が表示されます。最初は「概要」タブの内容が表示されているはずです。ここでは新規にリソースバンドルを定義するので、「新規バンドル」ボタンをクリックします:
2015111702


新規バンドルの定義画面になったらバンドルID(バンドルごとにつけるユニークな名称、以下の例では ManholeBundle)、翻訳元リソースファイルの言語(今回は英語リソースから翻訳するので英語)、「参照」ボタンをクリックして、翻訳元リソースファイルを指定し、そのファイルフォーマット(今回は Java の Properties ファイル形式)を指定します。そして翻訳先言語を指定するために「追加」をクリックします:
2015111703


今回は翻訳元のリソースファイルが英語で、その翻訳結果として「フランス語」、「日本語」、「韓国語」をチェックしました。実際に必要な翻訳結果を必要なだけ指定してください。
2015111704


自分が必要な翻訳言語が全て選択されていることを確認して「保存」ボタンをクリックします:
2015111705


しばらく処理が行われた後、画面は「バンドル」タブに切り替わります。成功していると「正常に保存されました」をいうメッセージが表示され、その下に自分が指定した翻訳元のバンドル ID が表示されています。処理結果を確認するため、「操作」と書かれた箇所の目のアイコンをクリックします:
2015111706


すると翻訳先として指定した言語ごとに翻訳結果が表示されています。この例では3つの言語それぞれで16箇所のリソース翻訳が行われ、全て成功していることがわかります。では日本語の翻訳結果を確認してみましょう。日本語と書かれた行の目のアイコンをクリックします:
2015111707


日本語リソースの翻訳結果が表示されます。ざっと見て翻訳結果が正しそうかどうかが分かります。翻訳結果として手で修正したい場合は、修正したい文字列の右にあるペンアイコンをクリックします:
2015111708


すると翻訳結果を編集できるダイアログがポップアップ表示されます。この中で翻訳を変更し、「更新」ボタンをクリックすると翻訳結果を上書きできます:
2015111709


改めて一覧で翻訳結果を確認します。編集が必要な箇所があれば上記手順で修正し、特に問題がなくなったら「ダウンロード」ボタンでこのリソースファイルをダウンロードしてみましょう:
2015111710


日本語リソースのダウンロード方法を確認するダイアログがポップアップ表示されます。ここではダウンロードファイルのフォーマットを指定しますが、今回は Java Properties ファイルを元に作ったので、翻訳結果も Java Properties ファイルにチェックして「ダウンロード」ボタンをクリックします:
2015111711


すると、(バンドルID)_(言語).properties というファイル名でダウンロードが開始されます:
2015111712


このダウンロード結果を改めてテキストエディタで開くとこのような結果になっていることが分かります。日本語部分が正しく数値参照文字列になっていることがわかります。この形式であればそのまま日本語プロパティファイルとして使えそうです:
#Generated by IBM Globalization
#Tue Nov 17 14:19:17 UTC 2015
common.body.nomsiesupported=MSIE\u306F\u3053\u306E\u30B5\u30A4\u30C8\u3067\u652F\u3048\u3089\u308C\u307E\u305B\u3093\u3067\u3057\u305F\u3002
common.body.update=\u60C5\u5831\u306F\u66F4\u65B0\u3057\u307E\u3057\u305F\u3002
common.body.iphoneapplink=iPhone\u9069\u7528\u6027\u3092\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9\u3057\u3066\u304F\u3060\u3055\u3044\u3002
common.body.apiziplastmodified=\u6700\u7D42\u5909\u66F4\u65E5
common.body.apidownload=ManholeMap API \u306E\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9
common.body.fordeveloper=\u958B\u767A\u8005\u306E\u305F\u3081\u306B
common.body.dndavailable=\u30C9\u30E9\u30C3\u30B0\u30FB\u30A2\u30F3\u30C9\u30FB\u30C9\u30ED\u30C3\u30D7\u3057\u3066\u5834\u6240\u3092\u5909\u3048\u3066\u4E0B\u3055\u3044\u3002
common.body.twitterlinkmsg=\u3055\u3048\u305A\u308A\u304C\u3001\u624B\u77ED\u306B\u8A18\u8FF0\u3059\u308B\u3068\u3044\u3046\u3053\u3068\u3067\u3059\u3002
common.body.clickdescformap=\u5730\u56F3\u306B\u306F\u4E0B\u7DDA\u3092\u3072\u304B\u308C\u305F\u8A18\u8FF0\u3092\u30AF\u30EA\u30C3\u30AF\u3057\u3066\u304F\u3060\u3055\u3044\u3002
common.body.imgoftoday=\u4ECA\u65E5\u306E\u30DE\u30F3\u30DB\u30FC\u30EB
common.body.notactivated=(\u3053\u306E\u30A4\u30E1\u30FC\u30B8\u306F\u3001\u305D\u306E\u4ED6\u3067\u898B\u308B\u3053\u3068\u304C\u3067\u304D\u307E\u305B\u3093\u3002\u6D3B\u52D5\u7684\u306B\u3059\u308B\u3079\u304D\u30DA\u30FC\u30B8\u3092\u7DE8\u96C6\u3057\u306B\u884C\u3063\u3066\u4E0B\u3055\u3044\u3002)
common.body.updatelatlng=\u5165\u308A\u6C5F\u60C5\u5831\u306F\u66F4\u65B0\u3057\u307E\u3057\u305F\u3002
common.body.htmltagavailable=\u3042\u308BHTML\u306F\u30BF\u30B0\u3092\u3064\u3051\u307E\u3059(ex\u3002<br/>(\=new\u7DDA))\u306F\u5229\u7528\u3067\u304D\u307E\u3059\u3002
common.body.logintoseefaceicon=\u3055\u3048\u305A\u308A\u3067\u767B\u9332\u3057\u3066\u9854\u30A2\u30A4\u30B3\u30F3\u3092\u898B\u3066\u4E0B\u3055\u3044
common.body.activate=\u6D3B\u52D5\u7684\u306B\u3057\u3066\u4E0B\u3055\u3044
common.body.clickfordetail=\u30AF\u30EA\u30C3\u30AF\u3059\u308B\u3068\u7D30\u90E8\u30DA\u30FC\u30B8\u306B\u306A\u308A\u307E\u3059\u3002
  :


同様にして、フランス語と韓国語の翻訳結果も確認できます。必要であれば同様に編集して、同様にダウンロード可能です:
2015111713

2015111714


確かにアプリケーションの国際化では翻訳が面倒だし、同じ処理を色んな言語に対して行わないと行けないという点は不便でした。こういったリソースファイル特化の翻訳サービスは多言語対応のアプリケーション開発を行う上では便利です。

このページのトップヘ