hyoromoのブログ

最近はVRSNS向けに作ったものについて書いています

Unityでの楽ちんなローカライズ対応方法


Unity公式パッケージのLocalizationを使った多言語対応について、実際にリリース済みのアプリでどのように実装したか踏まえつつ対応方法を書きます。

動作確認アプリ

R18のAndroidゲームですが、本エントリーで書いている内容はこちらのアプリに対して行っているため動作確認するのに一番適しています。言語切替は体験版で確認可能で、体験版はR18表現は無いです。
banner

それ以外では生命進化ゲームで行っています。ただし、こちらは9か月前に開発しているため本エントリー内容と一部一致しない動作をしています。
hyoromo.github.io

環境

今回は以下の環境で行いました。

  • Unity2022.3.43f1
  • Localization v1.5.2

インストール

Package Managerから、Localization をインストールします。
公式パッケージなのでUnity Registryで検索すれば見つかります。

テーブル作成

Localizationはエクセルのような表形式でKeyと各言語のValueを設定していきます。その表形式データをテーブルと呼んでいるため、まずはテーブルを作ります。

1. Unityの上部メニューから Window > Asset Management > Localization Tables を選択

2. Localization Tablesビュー上にある Create ボタン押下し、Assets以下へLocalization Settingsを保存
3. Localizatoin Tablesビューの上部に2個タブが表示されるようになったので、左側の New Table Collection を選択

4. Typeを String Table Collection にする。Stringはテキスト、Asset Table Collectoinはアセット(画像など)です。今回はテキストのみ解説するため、Stringの方を選びます。

5. Nameにテーブル名を設定。
6. Locale Generatorボタン押下し、対応言語にチェックを付けます。
7. チェックを付け終えたら、Generate Localesボタン押下し、保存先を選ぶと言語別に設定ファイルが生成されます。

8. Localization Tablesビューに戻り、Createボタンを押下し、保存先を選ぶと言語別にテーブルが生成されます。
9. Localization Tablesビュー上部にあるタブの Edit Table Collection を選択します。
10. Add New Entryボタンを押下すると1行追加されるので、Keyにテキスト呼び出す際のキー名を設定し、右側の言語名に当てはまるセルへ対応テキストを書いていきます。

セルの言語並びを変更


上図のようにLocalizatin TablesタブのEdit Table Collection上のセル言語並びが気に入らない場合、下図のように Project Settings > Localization の Available Locales から変更出来ます。

テキストの登録

Localizatin Tables上でテキスト登録


1. Localizatin TablesタブのEdit Table Collection上のAdd New Entryボタンを押し、新しい行が追加
2. セルの上部にある言語に対応するテキストを入力
3. (2) をサポート言語分だけ繰り返す

登録したテキストはコードで取得する事になります。方法については後述します。

TextMeshProコンポーネント上でテキスト登録

1. Canvasにテキスト表示するコンポーネント(TextMeshPro)を追加
2. Inspector上のTextMeshProコンポーネントの右上にある三点リーダーを クリック > Localize を選択

3. Inspector上のLocalizeStringEventコンポーネントにテキスト情報を設定していく
String Referenceは既にテーブル登録してあるものを使いまわしたければ設定し、新規であれば None のままにする
Table Collectionは最初に登録したテーブルを選択
Entry Nameはテーブル上のKeyとなるのでユニークな値を設定
Add Table Entryボタンを押下するとテーブルに新しい行が追加される
JapaneseやEnglish項目に各言語で表示したいテキストを入力

なお、後述するLocalization Scene Controlsビュー上でTrack ChangesをONにして直接入力するテキスト登録方法もあります。

Editor上で言語を切り替えて確認する方法

1. Window > Asset Management > Localization Scene Controls でビューを開く

2. Active Locale を表示したい言語に切り替える

テキスト表示

表示言語を設定し、テキストを取得するコードは以下の通りです。サポート言語は日本語(ja)、英語(en)、韓国語(ko)、簡体字(zh)、繁体字(zh-TW)です。
※ソースコードはUnityバージョン変更等で動作しなくなる可能性があります。その場合はご自身での修正をお願いします。

using System;
using System.Threading.Tasks;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Localization;
using UnityEngine.Localization.Settings;
using System.Linq;
using System.Text;

[Serializable]
public class LocalizationsInitDoneEvent : UnityEvent { }

/// <summary>
/// ローカライズ対応を行うクラス
/// 他クラスより先に実行しなければエラーが発生するため、実行順を早めている
/// </summary>
[DefaultExecutionOrder(-100)]
public class LocalizationsSingleton : MonoBehaviour
{
    public enum LocalizeType
    {
        EN = 0,
        JP,
        KR,
        CN,
        TW,
    }

    public static readonly string[] LocaleCodeNames = { "ENG", "JPN", "KOR", "CHS", "CHT" };

    public static readonly string[] LocaleCodes = { "en", "ja", "ko", "zh", "zh-TW" };

    public const string TEXT_ASSETS_NAME = "TextTable";

    private const string PREF_LOCALE = "PREF_LOCALE";

    public static LocalizationsSingleton Instance;

    [SerializeField]
    private LocalizationsInitDoneEvent _initDoneEvent;


    private List<Locale> _locales = new List<Locale>();

    private int _currentLocaleIndex;


    private async void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);

            await Init();
        }
        else
        {
            Destroy(gameObject);
        }
    }

    /// <summary>
    /// 初期化
    /// </summary>
    private async Task Init()
    {
        _currentLocaleIndex = 0;

        // 設定した言語があれば、そちらに切り替える
        Locale currentLocale = LocalizationSettings.SelectedLocale;
        var localeCode = PlayerPrefs.GetString(PREF_LOCALE, "");
        if (localeCode.Length != 0)
        {
            for (var i = 0; i < LocalizationSettings.AvailableLocales.Locales.Count; i++)
            {
                var locale = LocalizationSettings.AvailableLocales.Locales[i];
                if (locale.Identifier.Code.ToString().Equals(localeCode))
                {
                    currentLocale = locale;
                    LocalizationSettings.SelectedLocale = locale;
                    break;
                }
            }
        }

        // 言語を切り替える
        for (var i = 0; i < LocalizationSettings.AvailableLocales.Locales.Count; i++)
        {
            var locale = LocalizationSettings.AvailableLocales.Locales[i];
            _locales.Add(locale);
            if (currentLocale == locale)
            {
                var currentCode = currentLocale.Identifier.Code;
                for (int j = 0; j < LocaleCodes.Length; j++)
                {
                    var code = LocaleCodes[j];
                    if (currentCode.Equals(code))
                    {
                        _currentLocaleIndex = j;
                    }
                }
            }
        }

        await LocalizationSettings.InitializationOperation.Task;
        await LocalizationSettings.StringDatabase.PreloadTables(TEXT_ASSETS_NAME).Task;

        _initDoneEvent?.Invoke();
    }

    /// <summary>
    /// 言語切替
    /// </summary>
    public async Task ChangeSelectedLocale(LocalizeType localizeType)
    {
        _currentLocaleIndex = (int)localizeType;
        var locale = Locale.CreateLocale(LocaleCodes[(int)localizeType]);
        LocalizationSettings.SelectedLocale = locale;
        PlayerPrefs.SetString(PREF_LOCALE, locale.Identifier.Code);

        await LocalizationSettings.InitializationOperation.Task;
    }

    public async Task<string> GetText(string key)
    {
        var table = await LocalizationSettings.StringDatabase.GetTableAsync(TEXT_ASSETS_NAME).Task;
        if (table.GetEntry(key) == null)
        {
            // エラーハンドリング
            Debug.LogWarning("key=" + key + " のValueがありませんでした");
        }
        return table.GetEntry(key).Value;
    }

    /// <summary>
    /// 次の言語に切り替える
    /// </summary>
    public void NextLocale()
    {
        _currentLocaleIndex++;
        if (_locales.Count <= _currentLocaleIndex)
        {
            _currentLocaleIndex = 0;
        }

        ChangeSelectedLocale((LocalizeType)_currentLocaleIndex);
    }

    public LocalizeType GetCurrentLocalizeType()
    {
        return (LocalizeType)_currentLocaleIndex;
    }

    public int GetCurrentLocalizeIndex()
    {
        return _currentLocaleIndex;
    }

    public string GetCurrentLocalizeCodeName()
    {
        return LocaleCodeNames[(int)_currentLocaleIndex];
    }
}

テキスト取得しやすいようSingletonです。
一番最初に起動されるSceneに配置しておきます。

取得対象のテーブル指定

TEXT_ASSETS_NAME定数に設定している名前は Localization Tablesビューの Edit Table Collction の Table Collection Name となっています。もしデフォルト値から変更していた場合は定数も変更してください。

初期化

InitメソッドではPlayerPrefsで過去に保存された言語があれば、表示言語として設定。
もしセーブデータが無ければ、端末の表示言語を元に設定。端末言語がサポート外であれば英語を設定。

Initメソッドが完了したら LocalizationsInitDoneEvent に登録したイベントが発火するように作っています。
これはゲーム起動時に初期化が終わったかハンドリングしています。

手動で言語切替

ユーザー操作による言語切替は ChangeSelectedLocale か NextLocale メソッドを使います。
前者は言語指定、後者は順番に言語切替が行われます。

表示言語のテキスト取得

GetTextメソッドで取得できます。
利用例としては以下のように引数に「Localizatin Tables上でテキスト登録」で登録したKeyを使用し、表示言語セルのテキストを取得します。

private TextMeshProUGUI _messageText;

private async void showText() {
    _messageText.text = await LocalizationsSingleton.Instance.GetText("HellowMessage");
}

Font Assets生成

TextMeshProでテキストを表示するにはFont Assetsを作成する必要があります。
そして多言語対応する場合、TextMeshProのFont Asettsは同じフォントを使うと楽です。
こうした時にサポート言語の種類が多いNoto Sansフォントを使うと良いですよ。

1. それぞれの言語フォントのttfファイルをUnityプロジェクトへ配置
2. 上メニューから Window > TextMeshPro > Font Asset Creator を選択

3. Font Asset Creatorビュー上で以下のように設定
Source Fontに先ほど追加したFont ttfファイルを設定
Custom Character List に表示したい文字を設定
4. Generate Font Atlasボタンを押下
5. Saveボタンを押下

これでFont Assetsが生成されました。

TextMeshProコンポーネントのFont Assetに先ほど生成したFont Assetsを設定すると、テキストが表示されるようになります。

基本的な設定は以上です。
ここからは実践的なTips集となります。

Tips

Font Assetの更新

1. Projectビュー上のFont Assetを選択
2. Inspector上のUpdate Atlas Textureボタンを押下

TextMeshPro生成時のデフォルト フォントを設定


Project Settings > TextMeshPro/Settings にある項目 Default Font Asset を変更します。

Fallback Font 設定

Font Assetは言語(元フォント)毎に作成しますが、表示するテキストは1箇所だけ。多対1の関係となります。
これはメインとなるFont AssetのFallback Fontに他言語のFont Assetを設定する事で解決します。

1. Font Assetを選択
2. Inspector上のFallback Font AssetsにあるFallback Listへ他言語のFont Assetを追加

これでメインのFont Assetに存在しない文字があった場合、Fallback Listを順にチェックして存在したら文字表示されるようになります。


ですが1つ問題があり、上図のように使われるFontAssetによって文字装飾に差が出てしまう場合があります。
これの原因になっているのが以下項目がFontAsset毎にバラバラなせいです。値を統一する事で問題が解決します。

  • Sampling Point Size
  • Padding
  • Atlas Resolution
言語切替時にテキスト取得し直す

TextMeshProコンポーネントと同じGameObjectへGameObjectLocalizerがアタッチされている場合、特に何もする必要ありません。
言語切替が行われると自動的にテキストが切り替わってくれます。

問題なのはコード上で設定した場合です。
こちらは再度コードで設定し直しが必要なため、言語切替コールバック関数で再設定します。

void Start() {
    LocalizationSettings.SelectedLocaleChanged += UpdateLocalization;
}

void OnDestroy() {
    LocalizationSettings.SelectedLocaleChanged -= UpdateLocalization;
}

public async void UpdateLocalization(Locale locale) {
    _messageText.text = await LocalizationsSingleton.Instance.GetText("HellowMessage");
}
Localization Tablesのテキストを抽出してFont Assetを生成【パターンA】

方法が2通りあります。
まずはパターンAの方法は以下の通り。

1. Localization Tablesビューを開く
2. 右上からExport > Character Set... を選択
3. Export Character Setビューから、Font Assetに合わせて言語選択してExport Selectedボタンを押下
4. Font Asset Creatorビューから、Character SetをCharacters from Fileに変更
5. Character File項目に先ほどExportしたファイルを設定
6. 以降はFont Asset生成時と同じように、Atlasファイルを生成して保存

このパターンの問題点は3つ。

  • Exportを手動で行うため、テキスト更新毎にやるのが手間
  • ヒューマンエラーが発生する可能性がある
  • Font Asset間で重複する文字が含まれてしまう
Localization Tablesのテキストを抽出してFont Assetを生成【パターンB】

次にパターンBの方法は、以下のようにスクリプトで解決してしまう方法です。Editorフォルダの中へ追加して使用します。
※ソースコードはUnityバージョン変更等で動作しなくなる可能性があります。その場合はご自身での修正をお願いします。

using UnityEngine;
using UnityEditor;
using UnityEngine.Localization;
using System.Text;
using System.IO;
using System;
using UnityEditor.Localization;

public class LocalizationExtraction : MonoBehaviour
{
    private static readonly string FIX_CHARS = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]abcdefghijklmnopqrstuvwxyz";
    private static readonly string TABLE_COLLECTION_FILE_PATH = "Assets/DB/TextTable.asset";
    private static readonly string TEXT_FILE_PATH = "Assets/Editor/Localizations/texts/";

    [MenuItem("Tools/Localization/全てのテキストを抽出")]
    private static async void AllText()
    {
        Debug.Log("Fontファイル更新を開始");

        var ngWord = new StringBuilder();
        var collection = AssetDatabase.LoadAssetAtPath<StringTableCollection>(TABLE_COLLECTION_FILE_PATH);
        for (int i = 0; i < LocalizationsSingleton.LocaleCodes.Length; i++)
        {
            var localeCode = LocalizationsSingleton.LocaleCodes[i];
            var localizeText = "";
            if (localeCode.Equals("en"))
            {
                continue;
            }
            else if (localeCode.Equals("ja"))
            {
                localizeText = FIX_CHARS;
            }
            localizeText += collection.GenerateCharacterSet(new LocaleIdentifier(localeCode));

            // 重複文字を削除
            var charsToRemove = ngWord.ToString().ToCharArray();
            foreach (char c in charsToRemove)
            {
                localizeText = localizeText.Replace(c.ToString(), String.Empty);
            }
            ngWord.Append(localizeText);

            // ファイル書き出し
            var filePath = TEXT_FILE_PATH + localeCode.ToString() + ".txt";
            StreamWriter sw = new StreamWriter(filePath, false);
            sw.WriteLine(localizeText);// ファイルに書き出したあと改行
            sw.Flush();
            sw.Close();
        }

        AssetDatabase.SaveAssets();
        AssetDatabase.Refresh();

        Debug.Log("Fontファイル更新が完了");
    }
}


Unityのメニュー上から Tools > Localization > 全てのテキストを抽出 でファイルが言語別(ただし英語と日本語は同じファイル)に書き出されます。
FIX_CHARSはLocalization Tablesに存在しないテキストを設定します。コード上で埋め込んでいるテキスト等はこちらで都度追加ください。

他の定数は環境に応じて適宜変更してください。
特に Assets/Editor/Localizations/texts/ にフォルダが存在するようにしておいてください。存在しないフォルダを新規作成する気の利いたスクリプトではありませんので。


追加されたファイルをパターンAのようにFont Asset Creatorビュー上で指定してFont Assetを更新します。

締め

コードを貼っているせいで長くて大変かのように見えちゃうかもしれませんが、基本メンテする必要がないので一度キチンと設定すれば楽かなーと思います。

(2024/12/12追記) 対応言語を後から増やす方法

新しく言語を追加し、既存Tableへ言語追加するには以下のように行います。

1. Project Settings > Localizationを開き、Add Localeボタン押下

2. Add Localeビュー上から追加したい言語をチェックし、Add Localesボタン押下
3. Projectビューから、追加したいテーブルを選択してInspectorを開く

4. まだ追加されていない言語があれば「Create」ボタンが表示されるので、Createボタン押下

これでLocalization Tablesビューを確認すると、追加言語の列が増えます。