動的Webプロジェクトの国際化

#はじめに
実装しようとしたときに調べた範囲では、動的webプロジェクトの国際化についてまとめてる記事はなかったのでまとめてみました。


ここでの国際化は、クライアントのwebブラウザが使用している`Locale(言語と国/地域)`クラスを取得し、表示する言語をクライアントに合わせて動的に対応する事です。


今回紹介する手法は、サーバーサイドの`java`ではメッセージを全て`ID`でやりとりし、クライアント側の`jsp`と`javascript`で、あらかじめ用意しておいた言語ごとの`properties`ファイルを参照し`ID`と対応する文字列を表示するというものです。


この手法では、サーバーサイドはメッセージをIDに変換するのみ、クライアントサイドはメッセージを言語ファイルから参照する一文に変換するのみなので、将来的な拡張にも影響も少なく、簡単に国際化することが可能だと思います。




考え方としては、サーバーサイドはクライアント側の`Locale`が何であれ動きは変わらず、クライアントサイドに最初から対応する言語ファイルを送っておき、webページに表示する際に翻訳を行うというものです。



また、動的に変化するメッセージ
`例:あなたはボタンを○○回押しました！`
という文章にも対応できるように、文章を分割して翻訳する手法も紹介します。

今回のサンプルは、`jsp`でhtmlを用いて文章を表示する、ボタンを押したら`javascript`で処理をし文章を表示する、もうひとつのボタンを押したらサーバーから`ID`が送られてそれを翻訳して文章を表示するというプログラムです。

環境は以下の通りです。

IDE:eclipse
tomcatバージョン:9.0
jdkバージョン:11
OS:Windows
ブラウザ:Chrome、Edge、firefox
         
#1.フォルダ構成

![スクリーンショット (18).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534460/9f2cec9b-6a44-055f-0f91-7665a96edcd0.png)


#2.サーバーサイド実装


```Sample.java

package internationalization;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


@WebServlet(name="Sample", loadOnStartup=1, description = "Starting servlet for opening screen.", urlPatterns = { "/sample", "/sample2", "" })
public class Sample extends HttpServlet {


	public Sample() {}

	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

		request.getRequestDispatcher("sample.jsp").forward(request, response);
	}

	
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {

	}

}

```
  




```SampleWebsocket.java

package internationalization;

import javax.websocket.OnMessage;
import javax.websocket.server.ServerEndpoint;

@ServerEndpoint("/event")
public class SampleWebsocket {

	int i = 0;
    @OnMessage
    public String post(String event) {
    	i++;
    	String number =  Integer.toString(i);
        return "sentenceID1" + "|" + number + "|"+ "sentenceID2";
    }
}


```

#3.クライアント側実装








```sample.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>


<%
String message="HelloWorld";
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">


<meta charset="ISO-8859-1">

<script src="https://code.jquery.com/jquery-1.11.1.min.js"></script>
<script src="https://code.jquery.com/ui/1.11.1/jquery-ui.min.js"></script>
<link rel="stylesheet" href="https://code.jquery.com/ui/1.11.1/themes/smoothness/jquery-ui.css" />





<%
	java.util.Locale locale = request.getLocale();
	java.util.ResourceBundle bundle = java.util.ResourceBundle.getBundle("/MessagesBundleSample", locale);
	System.out.println(locale);

%>

<%@page import="java.util.ResourceBundle,
	                java.util.Enumeration" %>


<script type="text/javascript">
		            var Resources = {
		    		        <%
		    		            Enumeration<String> e = bundle.getKeys();
		    		            while (e.hasMoreElements()) {
		    		                String param = e.nextElement();
		    		                out.print('"'+param+ '"' +":\""+ bundle.getString(param) +"\"");
		    		                if (e.hasMoreElements())    out.println(",");
		    		            }
		    		        %>

		    		    };
</script>

<script src="javascript/sample.js" type="text/javascript"></script>
<title>Internationalization</title>





</head>
<body>
<%=bundle.getString("helloWorld")%>
<br>
<br>
<input type="button" value=<%=bundle.getString("button")%> onclick="OnButtonClick();"/>
<br>
<div id="output"></div>
<br>
<input type="button" value=<%=bundle.getString("button2")%> onclick="OnButtonClick2();"/>
<br>
<div id="output2"></div>
</body>
</html>

```


```sample.js

sample.js
function OnButtonClick(){
	$("#output").text(Resources["helloWorld2"]);
}



function OnButtonClick2(){
	websocket.send("event");
}


$(document).ready(function()
{
	var loc = window.location, new_uri;
	url = "ws:";
	url += "//" + loc.host;
	url += loc.pathname;
	websocket = new WebSocket(url+"/event");

        websocket.onmessage = function(mes) {
    	    var messages=mes.data.split("|");
    	    post="";
    	    for(var i=0;i<messages.length;i++)
    		if(messages[i].match(/^[0-9]/)){
    			post+= " "+messages[i];
    		}
    		else{post+=" "+Resources[messages[i]];
    		};

    	   $("#output2").text(post);
	}

});




```
　　
　　
#4.説明

##サーバーサイド
`Sample.java`

+ `Sample.java`はサーブレットファイル`sample.jsp`を呼び出します。

___
`SampleWebsocket.java`

+ `SampleWebsocket.java`では、クライアント側でボタンが押されたときに、`sample.js`からwebsocket通信でメッセージを送るので`post(event)`リスニングして、`あなたはボタンを○○回押しました！`というメッセージを表すIDをクラアント側の`sample.js`に送っています。

```.java
@ServerEndpoint("/event")
public class SampleWebsocket {

	int i = 0;
    @OnMessage
    public String post(String event) {
    	i++;
    	String number =  Integer.toString(i);
        return "sentenceID1" + "|" + number + "|"+ "sentenceID2";
    }
}
```

+ `@OnMessage`でメソッドをアノテートするとクライアントからメッセージが来た時に呼び出されます。

+ `javascript`の`split()`で分割できるように、IDの間には`|`を挿入します。


+ それぞれのIDは日本語では
`setenceID1="あなたはボタンを"
number=ボタンを押した回数
sentenceID2="回押しました！"`

+ 英語では
`setenceID1="You pushed the button"
number=ボタンを押した回数
sentenceID2="times!"`
を表しますので、それぞれのIDを参照して一つの文として表示します。



##クライアントサイド

`sample.jsp`

+ `request.getLocale();`でクライアントのlocaleを取得し、`java.util.ResourceBundle.getBundle("ファイル名",locale)`でlocaleに一致する言語プロパティファイルを取得しています。

```.java
<%
    java.util.Locale locale = request.getLocale();
    java.util.ResourceBundle bundle = java.util.ResourceBundle.getBundle("/MessagesBundleSample", locale);
    System.out.println(locale);

%>
```

+ javascriptで扱うことができるように`Resouces`にプロパティファイルを格納します。

+ `javascript`の`locale`クラスは使い勝手が良くないので、`jsp`で`java`の`locale`を扱い、`javascript`の`Object.keys`にそのまま格納する手法にしました。

```.js
<script type="text/javascript">
		            var Resources = {
		    		        <%
		    		            Enumeration<String> e = bundle.getKeys();
		    		            while (e.hasMoreElements()) {
		    		                String param = e.nextElement();
		    		                out.print('"'+param+ '"' +":\""+ bundle.getString(param) +"\"");
		    		                if (e.hasMoreElements())    out.println(",");
		    		            }
		    		        %>

		    		    };
</script>
```

+ htmlの記述では、表示する文字列を`getString("ID名")`でプロパティファイルから参照します。

```.java
<%=bundle.getString("helloWorld")%>
<br>
<br>
<input type="button" value=<%=bundle.getString("button")%> onclick="OnButtonClick();"/>
```

+ また、エンコードはさまざまな言語に対応できるよう`UTF-8`が推奨です。

___
`sample.js`

+ `OnButtonClick2()`では二個目のボタンを押した際に、WebSocket通信でサーバーにイベントを送ります。

```.js
function OnButtonClick2(){
    websocket.send("event");
}
```

+ サーバーはイベントが送られてきたら`senteceID+"|"+"ボタンを押した数"+"|"+sentenceID2`を送り返すので、`websocket.onmessage`が呼び出されます。

+ `websocket.onmessage`では、送られてきた文字列を分割`|`で分割し、それぞれを"Resouces"で参照してから`output2`に表示します。

```.js
  websocket.onmessage = function(mes) {
            var messages=mes.data.split("|");
            post="";
            for(var i=0;i<messages.length;i++)
            if(messages[i].match(/^[0-9]/)){
                post+= " "+messages[i];
            }
            else{post+=" "+Resources[messages[i]];
            };

           $("#output2").text(post);
    }
```

+ 数字はそのまま表示するようにしていますが、アラビア数字以外を使用している言語圏の場合は、それぞれ対応を追加してください。

#5.プロパティファイル
+ propertiesファイルは、全て`WebContent/WEB-INF/classes`に置きましょう。自動的に読み込んでくれます。
classesフォルダがない場合は新規に作成してください。

+ propertiesファイルの名前は用途が同じもので揃え、最後にlocaleを記入します。
`"ファイル名"_locale.language_locale.country.properties`

+ ブラウザの設定、環境によってはlocaleがlanguageのみしか取得できない場合があるので、必要であれば`"ファイル名"_locale.language.properties`も用意します。

+ javaでサポートされているlocaleはhttps://www.oracle.com/technetwork/java/javase/javase7locales-334809.html で確認できます。
自分でlocaleを作成することも可能です。

+ propertiesファイルをjavascriptで扱うことができるように配列に格納しますが、その際にダブルクオーテーション`""`を使用しているので、シングルクォーテーション`''`などを使用してください。

+ Key:`helloWorld`はjspで表示しますが、jspではhtmlタグが使用できます。

```MessagesBundleSample_ja_JP.properties
helloWorld=<strong>ハローワールドjsp</strong>
helloWorld2=ハローワールドjs
button=ボタン
sentenceID1=あなたはボタンを
sentenceID2=回押しました！
button2=サーバーにイベントを送信
```

```MessagesBundleSample_ja.properties
helloWorld=<strong>ハローワールドjsp</strong>
helloWorld2=ハローワールドjs
button=ボタン
sentenceID1=あなたはボタンを
sentenceID2=回押しました！
button2=サーバーにイベントを送信
```

```MessagesBundleSample_en_US.properties
helloWorld=<strong>HelloWorld(jsp)</strong>
helloWorld2=HelloWorld(js)
button=button(english)
sentenceID1=You pushed the button
sentenceID2=times!
button2=Send event to server
```
```MessagesBundleSample_en.properties
helloWorld=<strong>HelloWorld(jsp)</strong>
helloWorld2=HelloWorld(js)
button=button(english)
sentenceID1=You pushed the button
sentenceID2=times!
button2=Send event to server
```

#6.動作

ページにアクセスするとこのように表示されます。
jspでは`htmlタグ`が適用されるので、プロパティファイルで指定した通りに太字で表示されます。
![スクリーンショット (23).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534460/da0ec0e5-379d-3505-aec2-7a2a8e2b9c16.png)

ボタンを押すとsample.jsのOnbuttonClickが動作し、ID`helloWorld2`とキーが一致している`ハローワールドjs`をproperteisファイルを格納しているResoucesから参照して表示します。
![スクリーンショット (24).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534460/a4264f45-0498-47e4-75c8-36cb96a0b1bb.png)

`サーバーにイベントを送信`ボタンを押すと`OnButtonClick2()`がイベントをサーバーに送り、サーバーサイドの`SampleWebsocket.java`がリッスンし、ID`sentenceID1`と`sentenceID2`と`ボタンを押した回数`がクライアント側に送り返され、プロパティファイルを参照してから表示されます。
![スクリーンショット (25).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534460/e2e26eda-e73f-a1a4-f68b-90c142e71b5e.png)




#7.参考
+ https://qiita.com/opengl-8080/items/7ca8484c8579d264e239#%E8%AA%AC%E6%98%8E
+ https://docs.oracle.com/javase/jp/7/technotes/guides/intl/index.html