TapTapTap!!!(46)~多言語化対応を行う~
TapTapTap!!!(45)~アルファ版の Google Play Games のサインインを行う~では以前からうまく行っていない、Google Play Games へのサインインに再度挑戦した。今回は海外のユーザにも使用できるよう、多言語化対応を行っていく。
1.多言語化を行う手法
多言語化を行う手法については Unity のホームページのチュートリアルに簡単なサンプルが載っていたためそちらを参照して作成していく。詳しくはこちらから。Unity のホームページに掲載されていた、多言語化の手法について概要を説明すると以下の通りとなる。
- 各言語の json ファイルを用意する
- json ファイルを辞書形式で読み込む
- 言語を選択し、該当する言語をKey-Value 形式で索引する
同様の仕組みを「TapTapTap!!!」にも適応していく。
2.テキストの箇所を整理する
多言語化を行うにあたり、テキストが記載されている箇所を整理する必要がある。例えば、「TapTapTap!!!」のゲーム開始画面であれば以下のような箇所が該当する。
これら全てに対し、json 形式のテキストファイルを各言語ごとに作成していく。一覧にすると以下のようになる。なのでできるだけ言語は少なくかつ簡単なほうが多言語化の対応は容易となる。
key | 英語 | 日本語 |
Life | Life | ライフ |
HighestLevel | Highest Level | 最高レベル |
GameStart | Start | 開始 |
RecoveryLife | Recovery Life(Ads) | ライフ回復(広告) |
English | English | 英語 |
Japanese | Japanese | 日本語 |
LeaderBoard | Leader Board | リーダーボード |
Achievement | Achievement | 実績 |
SignIn | SignIn | サインイン |
SignOut | SignOut | サインアウト |
3.json のテキストファイルを作成する
1個ずつ json ファイルを作成することは大変手間がかかる。そのため、EXCELなどで言語データを csv などで予め作成しておくと json の作成が容易となる。今回は csv のデータを json に変換するのにこちらのサイトを使用させていただいた。出来上がった json ファイルの内容は以下の通り。
{ "item": [{ "key": "Life", "value": "Life"}, { "key": "HighestLevel", "value": "Highest Level"}, { "key": "GameStart", "value": "Start"}, { "key": "RecoveryLife", "value": "Recovery Life(Ads)"}, { "key": "English", "value": "English"}, { "key": "Japanese", "value": "Japanese"}, { "key": "LeaderBoard", "value": "Leader Board"}, { "key": "Achievement", "value": "Achievement"}, { "key": "SignIn", "value": "SignIn"}, { "key": "SignOut", "value": "SignOut"}, { "key": "Level", "value": "Level"}, { "key": "GameOver", "value": "Game Over"}, { "key": "Return", "value": "Return"}] }
4.json の読込スクリプトを作成する
スクリプトについては以下の通り。内容としては、「指定された言語ファイルを読込」、「指定されたキーでの値参照」、「ファイル読込の完了判定」をそれぞれ行っている。
[cce_csharp]using System.Collections; using System.Collections.Generic; using System.IO; using System.Reflection; using UnityEngine; public class LocalizationManager : MonoBehaviour { /// <summary> /// 言語管理インスタンス /// </summary> public static LocalizationManager instance; /// <summary> /// テキストが存在しないメッセージ /// </summary> private static string missingTextString = "Localized text not found"; /// <summary> /// 言語辞書データ /// </summary> private Dictionary<string, string> localizedText; /// <summary> /// 言語管理準備完了フラグ /// </summary> private bool isReady = false; /// <summary> /// 初期化処理 /// 複数の言語管理オブジェクトを生成しない用(シングルトン)にする /// </summary> void Awake() { MyLogger.GetInstance().StartLog(this.GetType().Name, MethodBase.GetCurrentMethod().Name); if (instance == null) { instance = this; } else if (instance != this) { Destroy(gameObject); } DontDestroyOnLoad(gameObject); } /// <summary> /// 言語ファイルの読込 /// </summary> /// <param name="fileName">言語ファイル名</param> public void LoadLocalizedText(string fileName) { MyLogger.GetInstance().StartLog(this.GetType().Name, MethodBase.GetCurrentMethod().Name); localizedText = new Dictionary<string, string>(); string filePath = Path.Combine(Application.streamingAssetsPath, fileName); MyLogger.GetInstance().DebugLog(this.GetType().Name, MethodBase.GetCurrentMethod().Name, "load file Path : " + filePath); if (File.Exists(filePath)) { string dataAsJson = File.ReadAllText(filePath); LocalizationData loadedData = JsonUtility.FromJson<LocalizationData>(dataAsJson); for (int i = 0; i < loadedData.items.Length; i++) { localizedText.Add(loadedData.items[i].key, loadedData.items[i].value); } MyLogger.GetInstance().DebugLog(this.GetType().Name, MethodBase.GetCurrentMethod().Name, "Data loaded, dictionary contains: " + localizedText.Count + " entries"); } else { MyLogger.GetInstance().WorningLog(this.GetType().Name, MethodBase.GetCurrentMethod().Name, "Data loaded, dictionary contains: " + localizedText.Count + " entries"); } isReady = true; MyLogger.GetInstance().EndLog(this.GetType().Name, MethodBase.GetCurrentMethod().Name); } /// <summary> /// 言語データの取得 /// </summary> /// <param name="key">該当キー</param> /// <returns>言語データ</returns> public string GetLocalizedValue(string key) { MyLogger.GetInstance().StartLog(this.GetType().Name, MethodBase.GetCurrentMethod().Name); string result = missingTextString; if (localizedText.ContainsKey(key)) { result = localizedText[key]; } MyLogger.GetInstance().EndLog(this.GetType().Name, MethodBase.GetCurrentMethod().Name); return result; } /// <summary> /// 読込完了状態の取得 /// </summary> /// <returns>読込完了状態</returns> public bool GetIsReady() { MyLogger.GetInstance().StartLog(this.GetType().Name, MethodBase.GetCurrentMethod().Name); return isReady; } }[/cce_csharp]
5.言語データクラスを作成する
言語データクラスは json ファイルの読込を行う際の形式を指定し、読み込んだデータを配列として保持する役割を担う。ここで特殊なのがクラスの定義飲みを行っている点である。上記スクリプト内の JsonUtility では「LocalizationData」 のフォーマットに合わせてデータを読み込む。つまり、3.の json データの item が配列名に当り、 key と value が配列としてそれぞれ該当する形になる。
[cce_csharp][System.Serializable] public class LocalizationData { /// <summary> /// 言語データの配列 /// </summary> public LocalizationItem[] items; } [System.Serializable] public class LocalizationItem { /// <summary> /// 言語キー /// </summary> public string key; /// <summary> /// 言語 /// </summary> public string value; }[/cce_csharp]
6.テキスト割当機能を作る
テキストの割当スクリプトについては以下の通り。このスクリプトはテキストが不変であるものに対してそれぞれに割り当てることを前提している。使用方法としては、各テキストに対してこのスクリプトをドラッグアンドドロップし、該当するキーを割り当てることで使用する。
[cce_csharp]using UnityEngine; using UnityEngine.UI; public class LocalizationText : MonoBehaviour { /// <summary> /// 言語読込キー /// </summary> [SerializeField] private string key; /// >summary< /// 開始処理 /// </summary> void Start() { Text text = GetComponent<Text>(); text.text = LocalizationManager.instance.GetLocalizedValue(key); } }[/cce_csharp]
可変であるテキストについてはそれぞれに上記スクリプトを割り当てていく形になる例えば、最高スコアはプレイするたびに変更される可能性がある箇所となるその場合は以下のようにスクリプトを実装する。変更内容は言語キーを取得し、スクリプト内で言語キーから該当する言語の文字列を取得するという内容だ。
[cce_csharp]using System.Reflection; using UnityEngine; using UnityEngine.UI; public class HeighestLevel : MonoBehaviour { /// <summary> /// 最高レベル表示用テキスト /// </summary> [SerializeField] private Text maxLevel; /// <summary> /// 言語キー /// </summary> [SerializeField] private string key; /// <summary> /// 過去最高レベルの表示 /// </summary> public void ShowHighestLevel() { MyLogger.GetInstance().StartLog(this.GetType().Name, MethodBase.GetCurrentMethod().Name); string maxText= LocalizationManager.instance.GetLocalizedValue(key); if (PlayerPrefs.HasKey("PlayerData")) { int level = PlayerPrefs.GetInt("PlayerData"); maxLevel.text = maxText + level.ToString(); } else { maxLevel.text = maxText + "-"; } MyLogger.GetInstance().StartLog(this.GetType().Name, MethodBase.GetCurrentMethod().Name); } }[/cce_csharp]
7.言語切替スクリプトを作成する
言語の切り替えは心を切り替えることで実装する。言語用のドロップボックスに対して新たに作成した以下のスクリプトをドラッグアンドドロップすることで適応する。処理の内容としては言語オプションが変更された際に該当する言語に切り替えた後、画面のリロードを行うという内容だ。
[cce_csharp]using System.Collections; using UnityEngine; using UnityEngine.SceneManagement; public class LocalizationChanger : MonoBehaviour { /// <summary> /// 言語ファイルのパス /// </summary> [SerializeField] private string[] filePath; /// <summary> /// オプション変更時の処理 /// </summary> /// <param name="index">選択された言語のインデックス</param> public void OnChange(int index) { LocalizationManager.instance.LoadLocalizedText(filePath[index]); StartCoroutine(Reload()); } /// <summary> /// シーンのリロード /// </summary> /// <returns>なし</returns> private IEnumerator Reload() { while (LocalizationManager.instance.GetIsReady()) { yield return null; } SceneManager.LoadScene("Start"); } }[/cce_csharp]
これらについては、現在難航している Google Play へのサインインが完了していれば追加したいと考えている。