Unity3Dでアプリケーションをローカライズするためのシンプルなプラグイン

すべてのUnity3D開発者は、遅かれ早かれ、アプリケーションを複数の言語にローカライズする必要に直面すると思います。 いずれにせよ、アプリケーションの起動時にいくつかの言語が必要ない場合でも、これをアーキテクチャに事前に配置することをお勧めします。



この記事では、言語を動的に変更し、エディターでテキストを編集する機能を備えたUI Textコンポーネントをローカライズするための単純なプラグインの開発について説明します。



使用されている言語を列挙するには(記事ではロシア語と英語を検討します)、enum Unity SystemLanguageを使用します。



残念ながら、私の知る限り、Unityはそのままで辞書またはキー値ペアクラスのシリアル化をサポートしていません。 したがって、物事を複雑にしないために、プラグインのニーズに合わせていくつかのクラスを作成します。



翻訳



翻訳構造-基本的にキーと値のペア:



public struct Translation { public SystemLanguage key; public string value; public Translation(SystemLanguage key, string value) { this.key = key; this.value = value; } }
      
      





ラベル



次のステップ:ラベルクラスを作成します。 そのタスクは、一意の整数IDと変換リストを保存することです。



 [System.Serializable] public class Label{ [SerializeField] int _id; [SerializeField] List<Translation> translations = new List<Translation>(); public int id { get { return _id; } private set { _id = value; } } public Label(int id) { this.id = id; } }
      
      





なぜなら ラベルクラスは基本的にディクショナリロジックを実装しており、2つのパブリックメソッドを追加する必要があります:GetおよびSet

Getメソッドでは、要求された言語に文字列があるかどうかを調べ、ある場合はそれを返し、ない場合は空の文字列を返します。

Setメソッドでは、目的の言語の文字列がある場合は似ていますが、変更しない場合は追加します。



 public string Get(SystemLanguage language) { for (int i = 0; i < translations.Count; i++) { if (translations[i].key == language) { return translations[i].value; } } translations.Add(new Translation(language, string.Empty)); return translations[translations.Count - 1].value; } public void Set(SystemLanguage language, string str) { for (int i = 0; i < translations.Count; i++) { if (translations[i].key == language){ translations[i] = new Translation(language, str); return; } } translations.Add(new Translation(language, str)); }
      
      





LabelsData



すべてのLabelインスタンスは、Unityインスペクターのどこかに保存および編集する必要があります。 これにはUnityツールを使用し、ScriptableObjectから継承した独自のLabelsDataクラスを作成します。 ScriptableObjectを使用すると、プロジェクトのファイル構造に必要なデータを保存でき、多くの場合、小さなゲームデータベースとして使用されます。

LabelsDataクラスは、エラーのデフォルトで、ゲームとラベルのすべての翻訳を含むリストを保存します。



LabelsDataのインスタンスを作成するには、クラスを宣言する前にCreateAssetMenu属性を追加します。



 [CreateAssetMenu(fileName="LabelsData", menuName="SimpleLocalizator/LabelsData")] public class LabelsData : ScriptableObject { [SerializeField] List<Label> _labels=new List<Label>(); Label _defaultLabel = new Label (-1, "not translated"); public static Label defaultLabel { get { return instance._defaultLabel; } } public static List<Label> labels { get { return instance._labels; } private set { instance._labels = value; } } }
      
      





LabelsDataインスタンスの可用性を確保するために、次のロジックに従って遅延初期化を使用します。



1.パブリックフィールドは、インスタンスインスタンスフィールドの値を返すゲッターです。

2. instance-LabelsDataのインスタンスへのグローバル参照。 初期化されていない場合は、Resourcesフォルダーからの読み込みが適用されるか、LabelsDataの新しいインスタンスが作成されます。

3.したがって、翻訳データベースはリソースでホストする必要があります。



 static LabelsData _instance; public static LabelsData instance { get { if (_instance==null) { _instance = (LabelsData)Resources.Load ("LabelsData"); if (_instance == null) { _instance = CreateInstance<LabelsData> (); } } return _instance; } }
      
      





したがって、Unityエディターで翻訳を便利に編集する機会が得られます。



スクリーンショットhot




必要な数の新しいラベルを作成し、識別用のIDを割り当てて、必要な言語への翻訳を記入できます。



言語マネージャー



次のステップは、多言語コンテンツを表示するすべてのコンポーネントに便利なインターフェースを提供する静的LanguageManagerクラスを作成することです。



 public static class LanguageManager{ static SystemLanguage _currentLanguage = SystemLanguage.English; public static SystemLanguage currentLanguage { get { return _currentLanguage; } set { _currentLanguage = value; if (onLanguageChanged != null) onLanguageChanged (); } } public static Action onLanguageChanged; }
      
      





このクラスのcurrentLanguageプロパティを使用して、現在の言語を変更できます。また、onLanguageChangedイベントにサブスクライブされているすべてのコンポーネントのコンテンツが変更されます。

現在のシステム言語の初期定義のために、Initメソッドを追加します。これは一度だけ呼び出されます:



 public static bool autoDetectLanguage=true; private static bool init = false; static void Init() { if (!init) { init = true; if (autoDetectLanguage) { currentLanguage = Application.systemLanguage; } else { currentLanguage = currentLanguage; } Debug.Log("LanguageManager: initialized. Current language: " + currentLanguage); } }
      
      





目的のIDを持つ行を取得するには、GetStringメソッドを追加します。このメソッドでは、LabelsDataデータの中でLabelを検索し、そうでない場合はデフォルトの文字列「not translation」を返します。



 public static string GetString(int labelID) { return GetString(labelID, currentLanguage); } public static string GetString(int labelID, SystemLanguage language) { Init(); for (int i = 0; i < LabelsData.labels.Count; i++) { if (LabelsData.labels[i].id == labelID) { return LabelsData.labels[i].Get(language); } } return LabelsData.defaultLabel.Get(language); }
      
      





提出



現在、コンテンツの表示を担当するプラグインコンポーネントを記述する必要があります。 Unityではどのコンテンツを表示できますか? UI.TextおよびTextMeshの行、任意の画像(たとえば、ロシア語と英語のアイコンとバナー)。 この記事の一部として、UI.Textの多言語文字列の表示を検討します。



抽象MultiLanguageComponentクラスを作成して、さらに継承するコンテンツを表示しましょう。 そのタスクは簡単です-現在の言語を保存し、LanguageManager.onLanguageChangedにサブスクライブし、OnValidateの内容を更新します(エディターでのテスト用)。



 public abstract class MultiLanguageComponent : MonoBehaviour { [SerializeField] SystemLanguage _currentLanguage = SystemLanguage.English; protected SystemLanguage currentLanguage { get { return _currentLanguage; } set { _currentLanguage = value; Refresh (); } } public void OnValidate() { currentLanguage = _currentLanguage; } void OnEnable() { OnLanguageRefresh (); LanguageManager.onLanguageChanged += OnLanguageRefresh; } void OnDisable() { LanguageManager.onLanguageChanged -= OnLanguageRefresh; } void OnLanguageRefresh() { currentLanguage = LanguageManager.currentLanguage; } protected virtual void Refresh() { } }
      
      





ここで、Refreshメソッドは仮想であり、派生クラスで再定義します。



整数labelIDを格納する継承クラスMultiLanguageTextBaseを作成します。



 public abstract class MultiLanguageTextBase : MultiLanguageComponent{ [SerializeField] int _labelID; [SerializeField] bool toUpper=false; public int labelID { get { return _labelID; } set { _labelID = value; Refresh(); } } }
      
      





その中でRefreshメソッドを再定義します。 なぜなら Refreshは、アプリケーションの言語が変更されたとき、またはlabelIDが変更されたときに呼び出されます。その中で、LanguageManagerから目的の言語の文字列を取得し、VisualizeStringメソッドを呼び出します。 ローカル変数は、アプリケーションの起動前にエディターで更新が発生するかどうかを判断するために必要です。この場合、LanguageManagerからのデバッグは、システムのコンポーネントではなく、特定のコンポーネントの現在の言語の文字列を受け取ります。



 protected override void Refresh() { bool local = (Application.isEditor && !Application.isPlaying); string str = local ? LanguageManager.GetString(labelID, currentLanguage) : LanguageManager.GetString(labelID); if (toUpper) str = str.ToUpper(); VisualizeString(str); } protected abstract void VisualizeString(string str);
      
      





画面に文字列を直接表示し、MultiLanguageTextBaseから継承する最後のクラスMultiLanguageTextUIを作成しましょう。 その中で、VisualizeStringメソッドをオーバーライドして、テキストをUI.Textに表示します。



 [RequireComponent(typeof(Text))] public class MultiLanguageTextUI : MultiLanguageTextBase { Text _text; public Text text { get { if (_text == null && gameObject!=null) _text = GetComponent<Text> (); return _text; } } protected override void VisualizeString(string str) { if (text != null) text.text = str; } }
      
      





これで、テキストを使用してMultiLanguageTextUIコンポーネントをオブジェクトに追加し、目的のlabelIDを設定できます。



スクリーンショットhot




デモンストレーション



GIF




まとめ



したがって、アプリケーション用のシンプルなローカリゼーションシステムを取得しました。 将来、MultiLanguageComponentから継承して、翻訳用に独自のコンポーネントを追加できます。



GitHubのリポジトリ (ここでいくつかの追加機能が追加されます-csvへのエクスポート/インポート、TextMesh、Image、AudioSource、VideoPlayer、MeshFilterのコンポーネント)。



完全なコード



Translation.cs
 using UnityEngine; namespace SimpleLocalizator { [System.Serializable] public struct Translation { public SystemLanguage key; public string value; public Translation(SystemLanguage key, string value) { this.key = key; this.value = value; } } }
      
      







Label.cs
 using UnityEngine; using System.Collections.Generic; namespace SimpleLocalizator { [System.Serializable] public class Label{ #region Data [SerializeField] int _id; [SerializeField] List<Translation> translations = new List<Translation>(); private const string defaultText = "not translated"; #endregion #region Interface public int id { get { return _id; } private set { _id = value; } } public Label(int id) { this.id = id; } public string Get(SystemLanguage language) { for (int i = 0; i < translations.Count; i++) { if (translations[i].key == language) { return translations[i].value; } } translations.Add(new Translation(language, defaultText)); return translations[translations.Count - 1].value; } public void Set(SystemLanguage language, string str) { for (int i = 0; i < translations.Count; i++) { if (translations[i].key == language){ translations[i] = new Translation(language, str); return; } } translations.Add(new Translation(language, str)); } #endregion } }
      
      







LabelsData.cs
 using System.Collections.Generic; using UnityEngine; using System.Text; namespace SimpleLocalizator { [CreateAssetMenu(fileName="LabelsData", menuName="SimpleLocalizator/LabelsData")] public class LabelsData : ScriptableObject { [SerializeField] List<Label> _labels=new List<Label>(); Label _defaultLabel = new Label (-1); public static Label defaultLabel { get { return instance._defaultLabel; } } public static List<Label> labels { get { return instance._labels; } private set { instance._labels = value; } } static LabelsData _instance; public static LabelsData instance { get { if (_instance==null) { _instance = (LabelsData)Resources.Load ("LabelsData"); if (_instance == null) { _instance = CreateInstance<LabelsData> (); Debug.Log ("LabelsData: loaded instance from resources is null, created instance"); } } return _instance; } } } }
      
      







LanguageManager.cs
 using UnityEngine; using System; namespace SimpleLocalizator { public static class LanguageManager{ #region Data public static bool autoDetectLanguage=true; static SystemLanguage _currentLanguage = SystemLanguage.English; private static bool init = false; #endregion #region Interface public static SystemLanguage currentLanguage { get { return _currentLanguage; } set { _currentLanguage = value; if (onLanguageChanged != null) onLanguageChanged (); } } public static Action onLanguageChanged; public static string GetString(int labelID) { return GetString(labelID, currentLanguage); } public static string GetString(int labelID, SystemLanguage language) { Init(); for (int i = 0; i < LabelsData.labels.Count; i++) { if (LabelsData.labels[i].id == labelID) { return LabelsData.labels[i].Get(language); } } return LabelsData.defaultLabel.Get(language); } #endregion #region Methods static void Init() { if (!init) { init = true; if (autoDetectLanguage){ currentLanguage = Application.systemLanguage; } else { currentLanguage = currentLanguage; } Debug.Log("LanguageManager: initialized. Current language: " + currentLanguage); } } #endregion } }
      
      







MultiLanguageComponent.cs


 using UnityEngine; namespace SimpleLocalizator { public abstract class MultiLanguageComponent : MonoBehaviour { [SerializeField] SystemLanguage _currentLanguage = SystemLanguage.English; protected SystemLanguage currentLanguage { get { return _currentLanguage; } set { _currentLanguage = value; Refresh (); } } public void OnValidate() { currentLanguage = _currentLanguage; } void OnEnable() { OnLanguageRefresh (); LanguageManager.onLanguageChanged += OnLanguageRefresh; } void OnDisable() { LanguageManager.onLanguageChanged -= OnLanguageRefresh; } void OnLanguageRefresh() { currentLanguage = LanguageManager.currentLanguage; } protected virtual void Refresh() { } } }
      
      







MultiLanguageTextBase.cs


 using UnityEngine; namespace SimpleLocalizator { public abstract class MultiLanguageTextBase : MultiLanguageComponent{ #region Unity scene settings [SerializeField] int _labelID; [SerializeField] bool toUpper=false; #endregion #region Interface public int labelID { get { return _labelID; } set { _labelID = value; Refresh(); } } #endregion #region Methods protected override void Refresh() { bool local = (Application.isEditor && !Application.isPlaying); string str = local ? LanguageManager.GetString(labelID, currentLanguage) : LanguageManager.GetString(labelID); if (toUpper) str = str.ToUpper(); VisualizeString(str); } protected abstract void VisualizeString(string str); #endregion } }
      
      







MultiLanguageTextUI.cs


 using UnityEngine; using UnityEngine.UI; namespace SimpleLocalizator { [RequireComponent(typeof(Text))] public class MultiLanguageTextUI : MultiLanguageTextBase { Text _text; public Text text { get { if (_text == null && gameObject!=null) _text = GetComponent<Text> (); return _text; } } protected override void VisualizeString(string str) { if (text != null) text.text = str; } } }
      
      








All Articles