前書き
今回の記事は「SE入門編」シリーズの一部です。完全版はこちらにあります:
当該バージョンのソースはGithubにアップロードしております:
1. 前提条件
-- 前提条件として、DBおよびWebプロジェクトの構築が完了していることが前提です。もし構築がまだ完了していない場合は、以下の記事を参照してください。
-- 今回の実装では、メッセージの多言語対応のみに焦点を当てます。実装に際しては、Spring、MybatisやThymeleafの基本実装が理解されていることが前提です。
【参考記事】
- DBの構築手順:
- WEBプロジェクトの構築手順:
- CRUDの基本実装:
2. 今回の記事で出来たこと
- 画面メッセージをテーブル化、および多言語対応
- ログイン画面でメッセージの表示言語を選択できる
- 選択された言語により、メッセージの表示言語が変わる
- 今回はメッセージだけ言語の切り替えができてます。画面ラベルやメニューの表示の言語は対応しておりません、次回の記事で対応する予定です
- 画面ラベルも同じくテーブル化できますが、違う方法で対応するつもりなので、今回は割愛します
3. 画面イメージ
- ログイン初期画面(言語選択可能になった)
- 言語に日本語を選択する時のメッセージ(パスワードが間違っている時)
- 言語に英語を選択する時のメッセージ(パスワードが間違っている時)
- 言語に日本語を選択する時のメッセージ(ユーザー新規登録で、既に存在しているユーザーを重複して登録しようとする時)
- 言語に英語を選択する時のメッセージ(ユーザー新規登録で、既に存在しているユーザーを重複して登録しようとする時)
PS:Detailsのメッセージが日本語になっているが、Springが出したトレースログなので、言語選択できるか不明です。
4. 具体的な設定やソースの解説
4.1 メッセージテーブルの定義
DDL及びDMLはgithubにアップロードしております。src\main\resourcesに格納されております。
4.2 画面テンプレート
- ログイン画面に言語選択リストを追加した
- en:englishを選択する時、メッセージが英語に変わる
- jp:japaneseを選択する時、メッセージが日本語に変わる
- 画面ラベルの多言語対応は違う方法で対応するつもりなので、今回は固定値で実装します
<div class="form-group">
<label for="language">Language:</label>
<select id="language" name="lang" class="form-control">
<option value="en" >English</option>
<option value="ja" >Japanese</option>
</select>
</div>
4.3 バックエンド側対応
今回修正/追加(demo ver0.4と比較)したソースは下記になります。
分類(目的別) | ファイル名 | 修正区分(demo ver0.4と比較) | 説明 |
共通 | MultiMessage.java | 追加 | メッセージBean |
共通 | MappingObject.java | 追加 |
メッセージオブジェクトを格納するMap メッセージListをMapに変換するクラス |
共通 | MultiMessageRepository.java | 追加 | メッセージテーブルを取得するリポジトリ |
共通 | MultiMessageRepository.xml | 追加 | メッセージレポジトリ取得のSQL文及びマッパー |
共通 | MultiMessageService.java | 追加 | メッセージリポジトリを呼び出し |
共通 | LoginController.java | 修正 | 言語設定を追加 |
他は細かい修正なので説明は割愛します
メッセージリポジトリのデータが少々特別なので、図解で説明します。
- ①メッセージテーブルから抽出した1個1個のメッセージデータはMultiMessageに格納する(Bean)
- ②①を格納する同時に、MappingObjectに格納する(key、value)。MappingObjectに格納する時、key=messageID + lang、value=①のMultiMessage
- ③MappingObjectをListに格納する
- ④③で取得したListをMapに変換する
- ⑤メッセージを取得する時、メッセージIDと言語を使って、④のMapからメッセージを取得する
※現在はSpringキャシュを実装しておりませんが、今後は実装する予定です。Springキャシュを使うと、メッセージを取得する時、1回目だけ①~⑤まで実行するが、2回目以後は④までのMapがキャシュされ、⑤だけ実行するため、レスポンスが早くなります。
- MultiMessage.java:普通のBean
private String key;
private String messageID;
private String lang;
private String message;
- MappingObject.java:普通のBeanとは違って、key, value方式でメッセージを格納する。また、toMapはListをMapに変換する時使うメソッドです
※key:messageID + lang(SQL文で連結している)
value:MultiMessage
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MappingObject<K, V> {
private K key;
private V value;
public static <K, V> Map<K, V> toMap(List<MappingObject<K, V>> list) {
if (list == null) {
return Collections.emptyMap();
}
return list.parallelStream().collect(Collectors.toMap(MappingObject::getKey, MappingObject::getValue));
}
}
- MultiMessageRepository.java:普通のリポジトリです、MappingObjectをListに格納する
List<MappingObject<String, MultiMessage>> select();
- MultiMessageRepository.xml:図の説明通り、1個1個のメッセージデータはMultiMessageに格納し、KEYとMultiMessageをMappingObjectに格納する
<resultMap id="messageMap" type="com.example.demo.bean.MappingObject">
<id column="KEY" jdbcType="VARCHAR" property="key" />
<association resultMap="message" property="value" />
</resultMap>
<resultMap id="message" type="com.example.demo.bean.MultiMessage">
<id column="KEY" jdbcType="VARCHAR" property="key" />
<result column="MESSAGE_ID" jdbcType="VARCHAR" property="messageID"/>
<result column="LANG" jdbcType="VARCHAR" property="lang"/>
<result column="VALUE" jdbcType="VARCHAR" property="message"/>
</resultMap>
<select id="select" resultMap="messageMap">
SELECT
MESSAGE_ID || LANG KEY,
MESSAGE_ID,
LANG,
MESSAGE VALUE
FROM
MESSAGE
</select>
- MultiMessageService.java:メッセージを取得するサービスです。Githubにアップロードしたソースは少し難しいですが、ここに見やすくするため、一部ソースを省略しております
/**
* メッセージListを取得し、Mapに変換する(Springキャシュを実装する予定)
* @return
*/
public Map<String, MultiMessage> getMessages() {
Map<String, MultiMessage> messages = MappingObject.toMap(multiMessageRepo.select());
return messages;
}
/**
* メッセージを取得する
* @param messageID:メッセージID
* @param lang:言語
* @return メッセージ
*/
public String getMessage(String messageID, String lang) {
Map<String, MultiMessage> messageMap = getMessages();
String message = "";
MultiMessage messageBean = messageMap.get(messageID + lang);
if (messageBean != null) {
message = messageBean.getMessage();
}
return message;
}
- LoginController.java:ユーザーID或いはパスワードが間違って入力する時、表示するメッセージはテーブルから取得するように実装を修正
message = multiMessageService.getMessage("error.login", lang); // ユーザーIDまたはパスワードが正しくありません。
終わりに
今回の記事のソースコードはGitHubにアップロードしましたので、ぜひダウンロードして確認してください。
次回は多言語対応に、画面のラベルの言語を切り替えられるように実装していきたいと考えています。お楽しみにお待ちください。
それではまた。