※本記事にはアフィリエイト広告(Udemy)を含みます。
前回のおさらい
番外編②では、タイトル画面の追加、「最初からやり直す」リセット機能、隠しエンディングまで実装しました。今回は、STATION OPSを日本語・英語・簡体字中国語・ロシア語・スペイン語の5言語に対応させた記録です。
なぜ多言語対応をやることにしたか
きっかけは「itch.ioに公開しているタイトルは、実際どの言語に多く対応しているのか」を調べたことでした。対応言語を検討する際に、itch.ioに掲載されている作品の言語を参考にしました。
比較材料として、ValveのSteamハードウェア&ソフトウェア調査では、調査参加者の使用言語を確認できます。割合や順位は月ごとに変わり、この調査だけで自分のゲームの需要や市場全体を判断することはできません。販売先や実際の反応も見ながら、対応言語の優先順位を決めます。
STATION OPSは今のところitch.io公開ですが、翻訳と検証の負担も考え、英語・簡体字中国語・ロシア語・スペイン語の4言語を追加することにしました。
なお、対応言語を増やす際の作業量については、事前に整理しておきました。多言語対応の「仕組み」を最初に整えても、文字列の長さや複数形などに応じた調整が必要になることがあります。一方、「翻訳」と「フォント対応」は言語を追加するたびに積み上がっていきます。特にフォントは、ラテン文字ベースの言語同士なら使い回しが効く場合がある一方、必要な文字や地域別字形を含むフォントを選び、不足する場合は別フォントやフォールバックを用意する必要がある、という違いがあります。
Godot標準のCSV翻訳を使わなかった理由
Godotには、CSVファイルをインポートして多言語対応する標準の仕組みがあります。ただし、この方式はGodotエディタでCSVを一度インポートし、言語ごとの翻訳リソースを生成してもらう必要があります。今回は開発環境の都合上、その場でエディタのインポート処理を実行して確認することができなかったため、標準機能ではなく、GDScriptの辞書(Dictionary)に翻訳を直接持たせるシンプルな自作の仕組みにしました。
extends Node
const STRINGS: Dictionary = {
"START_BUTTON": {
"ja": "スタート", "en": "Start", "zh_CN": "开始", "ru": "Начать", "es": "Empezar",
},
# ...以下、キーごとに5言語分の翻訳を持たせる
}
var current_locale: String = "ja"
func set_locale(locale: String) -> void:
current_locale = locale
func t(key: String) -> String:
if not STRINGS.has(key):
return key
var entry: Dictionary = STRINGS[key]
return entry.get(current_locale, entry.get("ja", key))
このスクリプトをLocalizationという名前でオートロード登録します。キーを指定してLocalization.t("START_BUTTON")のように呼び出すと、現在の言語に応じた文字列が返ってくる、という単純な仕組みです。UI側のコードでは、これまで直接書いていた日本語の文字列を、この呼び出しに置き換えます。言語変更時は各UIの文字列を再設定し、フォントや文章の長さも確認してください。
フォント対応の考え方
日本語用フォントはラテン文字・キリル文字も含んでいた
これまで日本語表示の文字化け対策として使っていたNoto Sans CJK JPというフォントは、実は日本語の漢字・ひらがな・カタカナだけでなく、ラテン文字(英語・スペイン語で使うアルファベット)やキリル文字(ロシア語)もあわせて収録されていました。そのため、英語・ロシア語・スペイン語については、既存の日本語用フォントをそのまま流用できました。
今回の簡体字用サブセットには別フェイスを用意した
中国語は事情が違いました。日本語と中国語は同じ漢字を共有している部分が多いのですが、字形(グリフの見た目)が微妙に異なります。同じフォントファミリーの中に「日本語向け(JP)」「簡体字中国語向け(SC)」といった、地域ごとに字形を出し分けたバリエーション(フェイス)が用意されていたため、中国語表示のときだけSCフェイスを抽出してサブセット化し、言語切り替え時にフォントごと差し替える設計にしました。
複数言語が同時に表示される箇所で2回文字化けした
実装した後、2回文字化けの不具合が発生しました。原因はどちらも同じで、「言語切り替えボタン」や「タイトルロゴ」のように、選んでいる言語に関係なく常に複数の言語表記が画面に同時表示される箇所があったことです。
たとえば言語選択ボタンは「日本語 / EN / 中文 / RU / ES」を常に5つとも表示します。中国語を選ぶとSCフォントに切り替わりますが、今回作成したSCフォントのサブセットには「日本語」という漢字や、「EN」「STATION OPS」といったラテン文字を含めていなかったため、文字化けしてしまいました。
対処として、日本語用フォント・中国語用フォントの両方に、こうした「常時表示される共通部分」の文字をあらかじめ含めておくようにしました。
CHROME_TEXT = "日本語ENRUESSTATION OPS中文0123456789"
jp_font_text = ja_text + en_text + ru_text + es_text + CHROME_TEXT
sc_font_text = zh_text + CHROME_TEXT
新しいセリフやボタン文言を追加するたびに、フォントのサブセットに含まれる文字も見直す必要がある、というのは今回の実装を通じて得た教訓です。
数値表記・UIの言語ごとの出し分け
電力・酸素・部品の数値表記は、これまで日本語の「万・億」で統一していましたが、英語・ロシア語・スペイン語では意味が通じません。言語に応じて表記を切り替える処理を追加しました。
func _format_number(value: float) -> String:
if Localization.current_locale in ["ja", "zh_CN"]:
if value >= 100000000.0:
return "%.2f%s" % [value / 100000000.0, "亿" if Localization.current_locale == "zh_CN" else "億"]
elif value >= 10000.0:
return "%.2f万" % (value / 10000.0)
else:
return str(int(value))
else:
if value >= 1000000000.0:
return "%.2fB" % (value / 1000000000.0)
elif value >= 1000000.0:
return "%.2fM" % (value / 1000000.0)
elif value >= 1000.0:
return "%.2fK" % (value / 1000.0)
else:
return str(int(value))
中国語も「万」という単位を日本語と共有しているため、日本語と同じ表記グループにしています。
また、ロシア語・スペイン語は日本語に比べて文章が長くなりがちで、ボタンの文字がはみ出す不具合も見つかりました。ボタンの高さとフォントサイズに余裕を持たせ、アップグレードカードの最小高さも拡大することで、確認した環境で各言語の文字が収まるよう調整しました。
タイトル画面に言語選択を実装する
タイトル画面に、日本語・EN・中文・RU・ESの5つのボタンを並べ、押した言語がゲーム全体に反映されるようにしました。次回起動時も選んだ言語を使うには、言語コードをセーブデータに書き出し、起動時に読み戻す処理が別途必要です。
func _on_language_selected(locale: String) -> void:
Localization.set_locale(locale)
GameManager.language = locale
_apply_locale_font()
_refresh_text()
itch.io側の対応
ゲーム内の対応言語に合わせて、itch.io側の設定も更新しました。
- 「Edit game」画面の「Metadata」タブ(プロジェクトを一度保存した後に表示されるタブです)にある「Languages」欄に、対応した5言語を登録
- ページの説明文に、日本語版に加えて英語版も追加(別ページに分けると評価・ダウンロード数が分散してしまうため、同じページ内に両方を載せる形にしました)
おわりに
多言語対応は「翻訳を追加すればいい」という単純な話ではなく、フォントの文字カバレッジや、言語ごとの文章の長さの違いなど、UI設計にも影響が及ぶ作業だと実感しました。特に、複数言語が同時に画面に出る箇所(今回で言えば言語選択ボタン)は見落としやすいポイントなので、同じような対応をする際は注意してみてください。
Godot学習におすすめのオンライン講座
多言語対応やフォント周りの実装は情報が少なく、つまずきやすい分野です。基礎からの体系的な学習が土台になります。
