Unityの脱・初心者あるあるとして、「とりあえず Update() に全部書いてしまう」問題がありますよね。入力処理、アニメーション、UI更新、そしてローカライズ(言語切替)まで、1つのスクリプトの Update() に詰め込んでしまうと、すぐにカオスになります。

特にローカライズ周りは、UIテキストの参照を全部1か所で持って、言語が変わるたびに全部書き換えるような実装をしがちです。これだと、UIを1つ追加するたびに巨大スクリプトを開いて修正する羽目になり、バグの温床になります。

この記事では、言語切替の責務を1つのコンポーネントに閉じ込めるために、LocalizationMgr というコンポーネントを用意します。
このコンポーネントは、内部の簡易 TranslationServer を制御し、日本語 / 英語を即座に切り替え、シーン内のローカライズ対象UIを一括更新します。

【Unity】ワンボタンで日英切替!「LocalizationMgr」コンポーネント

ここでは、「ローカライズの中核」だけを担当するコンポーネントとして LocalizationMgr を実装します。
UIのテキスト更新は別コンポーネント(LocalizedText)に任せることで、1コンポーネント1責務を徹底します。

  • LocalizationMgr … 言語の現在状態を管理し、切り替え時にイベントを発行する。
  • TranslationServer … キーと現在の言語から翻訳文を返すシンプルな辞書。
  • LocalizedText … 指定キーの翻訳文を Text / TMP_Text に反映する。

すべてこの記事内のコードだけで動くようにしているので、そのままコピペで試せます。

ソースコード(Full Code)


using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
// TextMeshPro を使う場合はコメントアウトを外してください
// using TMPro;

/// <summary>対応する言語の列挙</summary>
public enum SupportedLanguage
{
    Japanese,
    English
}

/// <summary>
/// 非ネットワーク型の簡易 TranslationServer。
/// 実際のプロジェクトでは ScriptableObject や外部ファイル読み込みに差し替え可能です。
/// </summary>
[Serializable]
public class TranslationServer
{
    // 言語ごとの辞書。キーは "ui_start", "ui_exit" など。
    [SerializeField] private Dictionary<string, string> japaneseTable = new();
    [SerializeField] private Dictionary<string, string> englishTable = new();

    // 現在の言語
    private SupportedLanguage currentLanguage = SupportedLanguage.Japanese;

    /// <summary>現在の言語を取得</summary>
    public SupportedLanguage CurrentLanguage => currentLanguage;

    /// <summary>コンストラクタでサンプルデータを用意(エディタ上でも動くように)</summary>
    public TranslationServer()
    {
        // ここで最低限のサンプル翻訳を登録しておきます。
        // 実プロジェクトではエディタ拡張や外部ファイル読み込みに置き換えましょう。
        if (japaneseTable.Count == 0 && englishTable.Count == 0)
        {
            AddTranslation("ui_start", "スタート", "Start");
            AddTranslation("ui_exit", "終了", "Exit");
            AddTranslation("ui_language", "言語", "Language");
            AddTranslation("msg_hello", "こんにちは!", "Hello!");
        }
    }

    /// <summary>翻訳を追加するヘルパー</summary>
    public void AddTranslation(string key, string jp, string en)
    {
        japaneseTable[key] = jp;
        englishTable[key] = en;
    }

    /// <summary>言語を設定する</summary>
    public void SetLanguage(SupportedLanguage language)
    {
        currentLanguage = language;
    }

    /// <summary>指定キーの翻訳を返す。見つからない場合はキーをそのまま返す。</summary>
    public string Get(string key)
    {
        Dictionary<string, string> table = currentLanguage == SupportedLanguage.Japanese
            ? japaneseTable
            : englishTable;

        if (table.TryGetValue(key, out string value))
        {
            return value;
        }

        // 見つからない場合はデバッグしやすいようにキーをそのまま返す
        return key;
    }
}

/// <summary>
/// シーン全体の言語切替を管理するコンポーネント。
/// - TranslationServer を内部に持つ
/// - 言語変更時にイベントを発行
/// - シングルトン的にどこからでもアクセス可能(ただし責務は「言語管理」のみ)
/// </summary>
public class LocalizationMgr : MonoBehaviour
{
    // シングルトンインスタンス
    public static LocalizationMgr Instance { get; private set; }

    [Header("初期設定")]
    [SerializeField] private SupportedLanguage defaultLanguage = SupportedLanguage.Japanese;

    [Header("内部 TranslationServer(インスペクタ確認用)")]
    [SerializeField] private TranslationServer translationServer = new TranslationServer();

    /// <summary>言語変更時に発火するイベント</summary>
    public event Action<SupportedLanguage> OnLanguageChanged;

    /// <summary>現在の言語を公開(読み取り専用)</summary>
    public SupportedLanguage CurrentLanguage => translationServer.CurrentLanguage;

    private void Awake()
    {
        // シングルトンの簡易実装
        if (Instance != null && Instance != this)
        {
            Debug.LogWarning("複数の LocalizationMgr がシーンに存在します。古い方を破棄します。");
            Destroy(gameObject);
            return;
        }

        Instance = this;
        DontDestroyOnLoad(gameObject); // シーンをまたいでも言語設定を保持したい場合

        // 初期言語を設定
        SetLanguageInternal(defaultLanguage, invokeEvent: false);
    }

    /// <summary>
    /// 公開用:言語を直接指定して切り替える
    /// </summary>
    public void SetLanguage(SupportedLanguage language)
    {
        SetLanguageInternal(language, invokeEvent: true);
    }

    /// <summary>
    /// 公開用:日本語と英語をトグル切り替えする
    /// UIボタンなどからこのメソッドを直接呼び出す想定です。
    /// </summary>
    public void ToggleLanguage()
    {
        SupportedLanguage next =
            CurrentLanguage == SupportedLanguage.Japanese
                ? SupportedLanguage.English
                : SupportedLanguage.Japanese;

        SetLanguage(next);
    }

    /// <summary>
    /// 現在の言語とキーから翻訳文を取得するヘルパー
    /// </summary>
    public string Translate(string key)
    {
        return translationServer.Get(key);
    }

    /// <summary>
    /// 実際に言語を切り替える内部メソッド
    /// </summary>
    private void SetLanguageInternal(SupportedLanguage language, bool invokeEvent)
    {
        if (translationServer.CurrentLanguage == language)
        {
            return; // すでに同じ言語なら何もしない
        }

        translationServer.SetLanguage(language);

        if (invokeEvent)
        {
            // 登録されている全てのリスナー(LocalizedText など)に通知
            OnLanguageChanged?.Invoke(language);
        }

        Debug.Log($"[LocalizationMgr] 言語を切り替えました: {language}");
    }
}

/// <summary>
/// 個々の UI テキストをローカライズするコンポーネント。
/// Text / TextMeshPro のどちらにも対応できるようにしています。
/// </summary>
[DisallowMultipleComponent]
public class LocalizedText : MonoBehaviour
{
    [Header("翻訳キー(例: ui_start, ui_exit, msg_hello など)")]
    [SerializeField] private string translationKey = "ui_start";

    [Header("UI コンポーネント参照")]
    [SerializeField] private Text unityText; // uGUI の Text 用

    // TextMeshPro を使う場合はコメントアウトを外し、インスペクタで設定してください
    // [SerializeField] private TMP_Text tmpText;

    private void Reset()
    {
        // 自動で Text コンポーネントを探して割り当て
        unityText = GetComponent<Text>();
        // tmpText = GetComponent<TMP_Text>();
    }

    private void OnEnable()
    {
        // LocalizationMgr が存在すればイベントに登録
        if (LocalizationMgr.Instance != null)
        {
            LocalizationMgr.Instance.OnLanguageChanged += HandleLanguageChanged;
            // 有効化時に一度だけ現在言語で更新しておく
            RefreshText();
        }
    }

    private void OnDisable()
    {
        if (LocalizationMgr.Instance != null)
        {
            LocalizationMgr.Instance.OnLanguageChanged -= HandleLanguageChanged;
        }
    }

    /// <summary>言語変更イベントを受け取った時に呼ばれる</summary>
    private void HandleLanguageChanged(SupportedLanguage _)
    {
        RefreshText();
    }

    /// <summary>
    /// 現在の言語設定に基づいてテキストを更新する
    /// </summary>
    private void RefreshText()
    {
        if (LocalizationMgr.Instance == null)
        {
            return;
        }

        string translated = LocalizationMgr.Instance.Translate(translationKey);

        if (unityText != null)
        {
            unityText.text = translated;
        }

        // TextMeshPro を使う場合はこちらも更新
        // if (tmpText != null)
        // {
        //     tmpText.text = translated;
        // }
    }

    /// <summary>
    /// コードから翻訳キーを差し替えたい場合のヘルパー
    /// </summary>
    public void SetKey(string newKey, bool refreshImmediately = true)
    {
        translationKey = newKey;
        if (refreshImmediately)
        {
            RefreshText();
        }
    }
}

/// <summary>
/// デモ用:キーボード入力で言語を切り替える簡単なサンプル。
/// 実運用では UI ボタンから LocalizationMgr.ToggleLanguage を呼ぶ形が多いです。
/// </summary>
public class LocalizationDemoInput : MonoBehaviour
{
    [Header("切り替えに使うキー")]
    [SerializeField] private KeyCode toggleKey = KeyCode.L;

    private void Update()
    {
        // 非 InputSystem 環境でも動くようにシンプルな KeyCode 判定にしています
        if (Input.GetKeyDown(toggleKey))
        {
            if (LocalizationMgr.Instance != null)
            {
                LocalizationMgr.Instance.ToggleLanguage();
            }
            else
            {
                Debug.LogWarning("LocalizationMgr がシーンに存在しません。");
            }
        }
    }
}

使い方の手順

ここでは、タイトル画面の「スタート」「終了」ボタンや、挨拶メッセージを日英切替する例で説明します。

  1. ① LocalizationMgr をシーンに配置する
    • 空の GameObject を作成し、名前を LocalizationManager などにします。
    • この GameObject に LocalizationMgr コンポーネントをアタッチします。
    • Default LanguageJapanese または English に設定します。
    • このオブジェクトは DontDestroyOnLoad されるので、タイトル~ゲーム本編まで言語状態を引き継げます。
  2. ② ローカライズしたい UI に LocalizedText を付ける
    例:タイトル画面 Canvas 内のボタンテキストをローカライズする場合。
    • StartButton の子オブジェクトにある Text コンポーネントを選択します。
    • LocalizedText コンポーネントを追加します。
    • Translation Keyui_start と入力します。
    • Unity Text フィールドに、その Text コンポーネントをドラッグ&ドロップ(もしくは Reset で自動セット)。
    • 同様に ExitButton のテキストには ui_exit を設定します。
    • 挨拶メッセージの Text には msg_hello を設定します。
  3. ③ 言語切替ボタンを作る
    • Canvas 上に「言語」や「Language」と書かれたボタンを1つ作成します。
    • そのボタンの On Click() イベントに、シーン内の LocalizationManager オブジェクトをドラッグします。
    • 関数選択から LocalizationMgr -> ToggleLanguage() を選びます。
    • これでボタンをクリックするたびに 日本語 <-> 英語 が切り替わります。
  4. ④ キーボードでも切り替えたい場合(任意)
    • 空の GameObject に LocalizationDemoInput をアタッチします。
    • Toggle KeyL など好きなキーを設定します。
    • ゲームプレイ中にそのキーを押すと、言語がトグル切り替えされます。

これで、プレイヤー、敵UI、動く床の上に出すチュートリアルテキストなど、どんなオブジェクトでも、LocalizedText を付けてキーを指定するだけで日英切替に対応できます。

メリットと応用

LocalizationMgr を使うことで、次のようなメリットがあります。

  • 巨大な「ローカライズ God クラス」が不要
    言語切替の中核は LocalizationMgr に集約しつつ、個々の UI は LocalizedText に任せるため、責務が明確になります。
  • プレハブが完全に自律的になる
    例えば「チュートリアルポップアップ」のプレハブに LocalizedText を仕込んでおけば、どのシーンに配置しても、自動的に現在の言語で表示されます。
    シーン側で「このテキストもローカライズリストに登録しなきゃ…」といった管理が不要になります。
  • レベルデザイン時の負担が減る
    レベルデザイナーは「ここにこのメッセージを出したい」と思ったら、単にプレハブを置いてキーを指定するだけでOK。
    コード側の巨大な switch 文や if 文を編集する必要がなくなります。
  • 言語追加・翻訳差し替えが簡単
    翻訳の実体は TranslationServer に閉じ込めてあるので、将来的に ScriptableObject や JSON 読み込みに差し替えても、LocalizationMgrLocalizedText のインターフェースはほぼそのままにできます。

応用として、例えば「一時的にゲーム内だけ英語にしてスクリーンショットを撮る」ようなデバッグ機能や、「プレイヤーの設定に応じて自動的に言語を選ぶ」といった仕組みも簡単に追加できます。

最後に、改造案として、「システム言語から初期言語を自動選択する」ヘルパー関数の例を載せておきます。


    /// <summary>
    /// OS のシステム言語を見て、初期言語を自動決定するサンプル。
    /// Awake から呼び出すと便利です。
    /// </summary>
    private void AutoDetectLanguage()
    {
        // Unity の Application.systemLanguage を利用
        switch (Application.systemLanguage)
        {
            case SystemLanguage.Japanese:
                SetLanguageInternal(SupportedLanguage.Japanese, invokeEvent: true);
                break;

            case SystemLanguage.English:
            default:
                SetLanguageInternal(SupportedLanguage.English, invokeEvent: true);
                break;
        }

        Debug.Log($"[LocalizationMgr] システム言語から初期言語を自動設定しました: {CurrentLanguage}");
    }

このように、LocalizationMgr は「言語状態の管理と通知」に専念させることで、プロジェクト全体のローカライズ設計がすっきりしていきます。
まずはこの記事のコードをそのままコピペして、手元のプロジェクトで日英切替を試してみてください。