Godot 4 でローカライズをちゃんとやろうとすると、意外とめんどくさいですよね。

  • 毎回 TranslationServer.set_locale("ja") をどこかのシングルトンに書く
  • UI シーンごとに言語切り替え処理を入れてしまい、あとから管理がカオスになる
  • 「タイトル画面だけ日本語/英語ボタンがあるけど、ゲーム中は反映されない」みたいな事故

Godot 標準のやり方でももちろん動きますが、シーンごとにバラバラにロジックを書いてしまうと、ローカライズが「プロジェクト全体の横断機能」なのに、実装が散らばるという問題が起きがちです。

そこで今回は、コンポーネントとしてアタッチするだけで、日本語/英語の即時切り替えを一元管理できる「LocalizationMgr」コンポーネントを用意しました。
「継承したベース UI クラスにローカライズ処理を書く」のではなく、どのシーンにも後付けできる「合成 (Composition)」スタイルで運用していきましょう。

【Godot 4】ワンコンポーネントで言語切替を一元管理!「LocalizationMgr」コンポーネント

フルコード(GDScript)


extends Node
class_name LocalizationMgr
## シーンにポン付けして使える、簡易ローカライズ管理コンポーネント。
## - TranslationServer の locale を制御
## - シグナル経由で「言語が変わったよ」を通知
## - 日本語 / 英語のトグル切り替えをサポート

## --- エディタから設定できるパラメータ ------------------------

@export_category("Localization Settings")

## プロジェクトで使用するロケール一覧。
## ここに書かれていないロケールには切り替えません。
## 例: ["ja", "en"]
@export var supported_locales: Array[String] = ["ja", "en"]

## ゲーム起動時にセットするデフォルトロケール。
## 空文字の場合、TranslationServer の現在値を使います。
@export var default_locale: String = "ja"

## 言語を切り替えたときに、プロジェクト設定へ保存するかどうか。
## true の場合、ConfigFile を使って user://locale.cfg に保存します。
@export var save_to_config: bool = true

## コンフィグファイルのパス(save_to_config が true の場合のみ使用)
@export var config_path: String = "user://locale.cfg"

## Config ファイル内のセクション名
@export var config_section: String = "localization"

## Config ファイル内のキー名
@export var config_key: String = "locale"

## 起動時に Config からロケールを読み込むかどうか。
@export var load_from_config_on_ready: bool = true

## --- シグナル --------------------------------------------------

## ロケールが変更されたときに発火するシグナル。
## 例: UI 側でこれを受け取ってラベルを更新する。
signal locale_changed(new_locale: String)

## --- 内部状態 --------------------------------------------------

var _current_locale: String = ""


func _ready() -> void:
    ## 起動時のロケール決定ロジック
    ## 1. 設定ファイルから読み込む(オプション)
    ## 2. 読み込めなければ default_locale
    ## 3. それもダメなら TranslationServer の現在値
    var initial_locale := ""

    if load_from_config_on_ready and save_to_config:
        var loaded := _load_locale_from_config()
        if loaded != "":
            initial_locale = loaded

    if initial_locale == "":
        initial_locale = default_locale if default_locale != "" else TranslationServer.get_locale()

    set_locale(initial_locale, false) # 初期化時はシグナルを飛ばさない


## 現在のロケールを取得するヘルパー。
func get_locale() -> String:
    return _current_locale


## 指定したロケールに切り替えるメイン関数。
## - locale: "ja", "en" など
## - emit_signal_flag: true の場合、locale_changed シグナルを発火
func set_locale(locale: String, emit_signal_flag: bool = true) -> void:
    if not _is_supported(locale):
        push_warning("LocalizationMgr: Unsupported locale: %s" % locale)
        return

    if _current_locale == locale:
        return # すでに同じロケールなら何もしない

    _current_locale = locale
    TranslationServer.set_locale(locale)

    if save_to_config:
        _save_locale_to_config(locale)

    if emit_signal_flag:
        emit_signal("locale_changed", locale)


## 日本語/英語のトグル切り替え用ショートカット。
## - "ja" と "en" が supported_locales に含まれている前提で動作します。
## - どちらかが無い場合は警告を出します。
func toggle_ja_en() -> void:
    var has_ja := "ja" in supported_locales
    var has_en := "en" in supported_locales

    if not (has_ja and has_en):
        push_warning("LocalizationMgr: toggle_ja_en() requires both 'ja' and 'en' in supported_locales.")
        return

    var next_locale := "en" if _current_locale == "ja" else "ja"
    set_locale(next_locale)


## 任意の順番でロケールをローテーションさせる関数。
## 例: ["ja", "en", "fr"] のように増やした場合にも対応。
func cycle_locale() -> void:
    if supported_locales.is_empty():
        push_warning("LocalizationMgr: supported_locales is empty.")
        return

    var idx := supported_locales.find(_current_locale)
    if idx == -1:
        # 現在のロケールが一覧に無い場合は、先頭に合わせる
        set_locale(supported_locales[0])
        return

    var next_idx := (idx + 1) % supported_locales.size()
    set_locale(supported_locales[next_idx])


## --- 内部ヘルパー ----------------------------------------------

func _is_supported(locale: String) -> bool:
    return locale in supported_locales


func _save_locale_to_config(locale: String) -> void:
    var cfg := ConfigFile.new()
    var err := cfg.load(config_path)
    if err != OK and err != ERR_DOES_NOT_EXIST:
        push_warning("LocalizationMgr: Failed to load config for saving: %s" % config_path)

    cfg.set_value(config_section, config_key, locale)
    err = cfg.save(config_path)
    if err != OK:
        push_warning("LocalizationMgr: Failed to save config: %s" % config_path)


func _load_locale_from_config() -> String:
    var cfg := ConfigFile.new()
    var err := cfg.load(config_path)
    if err != OK:
        # ファイルが無い場合などは空文字を返して呼び出し側で判断
        return ""

    var value := cfg.get_value(config_section, config_key, "")
    if typeof(value) == TYPE_STRING:
        var locale := String(value)
        if _is_supported(locale):
            return locale
    return ""

使い方の手順

ここからは、実際にシーンへコンポーネントとしてアタッチして使う流れを見ていきましょう。

手順①:翻訳リソース(.translation)をプロジェクトに設定する

  1. Godot エディタで Project > Project Settings… を開く
  2. Localization > Translations タブに、日本語・英語の .translation ファイルを追加する
  3. それぞれのキーに対して、ja / en の翻訳を設定する

ここは Godot 標準のローカライズ機能そのままですね。

手順②:共通の「LocalizationMgr」ノードをシーンに追加する

例として、タイトル画面シーンにコンポーネントを追加してみます。

TitleScreen (Control)
 ├── Panel
 ├── VBoxContainer
 │    ├── Label (テキストは tr("GAME_TITLE") など)
 │    ├── Button (テキストは tr("START_GAME"))
 │    └── Button (テキストは tr("LANGUAGE_TOGGLE"))
 └── LocalizationMgr (Node)

ポイントは、LocalizationMgr はただの Node としてぶら下げるだけというところです。
UI を継承して特別なベースクラスを作る必要はありません。

  • LocalizationMgr ノードを選択し、インスペクタで下記のように設定
    • supported_locales = ["ja", "en"]
    • default_locale = "ja"
    • save_to_config = true(ゲーム終了後も言語設定を保持したい場合)

手順③:ボタンから言語切り替え API を呼ぶ

タイトル画面のスクリプト例です。ボタンが押されたときに toggle_ja_en() を呼び出すだけで、日本語/英語が即座に切り替わります。


extends Control

@onready var localization_mgr: LocalizationMgr = $LocalizationMgr
@onready var lang_button: Button = %LanguageToggleButton

func _ready() -> void:
    # 言語が変わったときに UI を更新したい場合はシグナルを接続
    localization_mgr.locale_changed.connect(_on_locale_changed)
    _update_language_button_text()

func _on_LanguageToggleButton_pressed() -> void:
    # 日本語/英語をトグル
    localization_mgr.toggle_ja_en()

func _on_locale_changed(new_locale: String) -> void:
    # ここで必要な UI の再描画などを行う
    _update_language_button_text()

func _update_language_button_text() -> void:
    # tr() でラベルを取得するだけで OK
    lang_button.text = tr("LANGUAGE_TOGGLE")

ボタンのテキストやラベルなどは、text = tr("SOME_KEY") としておけば、TranslationServer.set_locale() による切り替えが即座に反映されます。

手順④:他のシーンにも「LocalizationMgr」を合成して再利用

例えば、ゲーム本編の HUD シーンにも同じコンポーネントをアタッチしておくと、どのシーンからでも同じ API で言語切り替えができます。

HUD (CanvasLayer)
 ├── TopBar (Control)
 │    ├── Label (tr("SCORE"))
 │    └── Label (tr("TIME"))
 └── LocalizationMgr (Node)

HUD 側でのスクリプト例:


extends CanvasLayer

@onready var localization_mgr: LocalizationMgr = $LocalizationMgr

func _ready() -> void:
    localization_mgr.locale_changed.connect(_on_locale_changed)

func _on_locale_changed(new_locale: String) -> void:
    # ここで必要なら再描画処理を追加
    # 通常は tr() を使っていれば自動で見た目が変わるので、特別な処理は不要なことも多いです。
    pass

このように、どのシーンにも後付けで「LocalizationMgr」コンポーネントを生やせるので、
「ローカライズ機能を共有したいから UI ベースクラスを全部書き換える」みたいな大工事を避けられます。


メリットと応用

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

  • シーン構造がスッキリ:ローカライズ用の処理を UI クラスにべったり書かず、1 ノードに集約できる
  • 再利用性が高い:どのシーンにも「Node として」アタッチするだけで同じ API を使える
  • テストがしやすい:LocalizationMgr 単体でテストできる(supported_locales, Config 保存の挙動など)
  • 継承地獄を回避:ローカライズのためだけに UI のベースクラスを増やさなくて済む

特に、レベルデザインや UI デザインの段階では、まだローカライズを考えたくないことも多いですよね。
その場合でも、あとから LocalizationMgr をシーンに追加して、ラベルのテキストを tr() に置き換えるだけで対応できます。

改造案:システム言語から自動選択する

もう一歩踏み込んで、「起動時に OS の言語を見て自動的にロケールを決める」改造も簡単です。
例えば、次のようなヘルパー関数を追加することで、サポートしている言語の中から最適なものを選ぶことができます。


## OS のロケールを元に、最適なロケールを選択して適用する。
## 例: OS が日本語なら "ja"、それ以外なら "en" にフォールバックなど。
func apply_best_locale_from_system() -> void:
    var os_locale := OS.get_locale()  # 例: "ja_JP", "en_US"
    var lang_code := os_locale.split("_")[0]  # "ja_JP" → "ja"

    if _is_supported(lang_code):
        set_locale(lang_code)
    else:
        # サポートされていない場合は default_locale か、先頭のロケールにフォールバック
        var fallback := default_locale if _is_supported(default_locale) else (supported_locales[0] if supported_locales.size() > 0 else lang_code)
        set_locale(fallback)

この関数を _ready() の中で呼ぶようにすれば、「初回起動時は OS 言語に合わせる」「2 回目以降は Config に保存した値を使う」といった挙動も簡単に実現できます。

継承ベースではなく、「LocalizationMgr というひとつのコンポーネントをいろんなシーンに合成していく」スタイルで、Godot プロジェクトのローカライズを気持ちよく管理していきましょう。