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)をプロジェクトに設定する
- Godot エディタで Project > Project Settings… を開く
- Localization > Translations タブに、日本語・英語の
.translationファイルを追加する - それぞれのキーに対して、
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 プロジェクトのローカライズを気持ちよく管理していきましょう。
