238 lines
6.6 KiB
Markdown
238 lines
6.6 KiB
Markdown
---
|
|
name: godot-i18n
|
|
description: "Localization patterns for Godot 4.x: TranslationServer, CSV/PO/JSON translation formats, runtime language switching, RTL text support, font fallback chains, and pluralization. Use when implementing internationalization in Godot games."
|
|
license: MIT
|
|
compatibility:
|
|
- godot-4.0
|
|
- godot-4.1
|
|
- godot-4.2
|
|
- godot-4.3
|
|
- godot-4.4
|
|
- godot-4.5
|
|
- godot-4.6
|
|
- godot-4.7
|
|
metadata:
|
|
author: vl4dt
|
|
version: 0.1.0
|
|
tags:
|
|
- godot
|
|
- i18n
|
|
- localization
|
|
- translation
|
|
- rtl
|
|
created: 2026-06-21
|
|
---
|
|
|
|
# Localization in Godot
|
|
|
|
Patterns for internationalizing Godot games.
|
|
|
|
## TranslationServer Basics
|
|
|
|
Godot's built-in translation system uses `.translation` files (CSV-based):
|
|
|
|
```gdscript
|
|
# Mark strings for translation with _() shorthand
|
|
func _ready():
|
|
$Label.text = tr("Hello, Player!")
|
|
$Button.text = tr("Start Game")
|
|
|
|
# tr() calls TranslationServer.translate() internally
|
|
# Strings are looked up in loaded .translation resources
|
|
```
|
|
|
|
### Creating Translation Files
|
|
|
|
Use the editor: **Project → Tools → Translation** to create `.csv` files, then export as `.translation`.
|
|
|
|
Or create CSV manually:
|
|
|
|
```csv
|
|
msgid "Hello, Player!"
|
|
msgstr "¡Hola, Jugador!"
|
|
|
|
msgid "Start Game"
|
|
msgstr "Comenzar Juego"
|
|
|
|
msgid "Score: {0}"
|
|
msgstr "Puntuación: {0}"
|
|
```
|
|
|
|
### Registering Translations
|
|
|
|
In **Project Settings → Locale → Configured Locales**, add supported languages (e.g., `es`, `fr`, `ja`).
|
|
|
|
## Runtime Language Switching
|
|
|
|
```gdscript
|
|
class_name LanguageManager extends Node
|
|
|
|
signal language_changed(locale: String)
|
|
|
|
var available_locales: Array[String] = ["en", "es", "fr", "ja"]
|
|
var current_locale: String = "en"
|
|
|
|
func set_language(locale: String) -> void:
|
|
if not available_locales.has(locale):
|
|
push_error("LanguageManager: Unsupported locale '%s'" % locale)
|
|
return
|
|
TranslationServer.set_locale(locale)
|
|
current_locale = locale
|
|
language_changed.emit(locale)
|
|
_notify_ui_update()
|
|
|
|
func _notify_ui_update() -> void:
|
|
# Re-tr() all UI labels that need updating
|
|
# Connect to language_changed signal in UI nodes
|
|
pass
|
|
|
|
# Save/restore preference
|
|
func _ready() -> void:
|
|
var saved = ConfigFile.new()
|
|
if saved.load("user://settings.cfg") == OK:
|
|
var locale = saved.get_value("general", "language", "en")
|
|
set_language(locale)
|
|
|
|
func _save_preference() -> void:
|
|
var config = ConfigFile.new()
|
|
config.load("user://settings.cfg")
|
|
config.set_value("general", "language", current_locale)
|
|
config.save("user://settings.cfg")
|
|
```
|
|
|
|
### Language Selector UI
|
|
|
|
```gdscript
|
|
# LanguageSelector.gd — attached to OptionButton
|
|
extends OptionButton
|
|
|
|
func _ready():
|
|
for locale in LanguageManager.available_locales:
|
|
var display_name = TranslationServer.get_locale_name(locale)
|
|
add_item(display_name, locale)
|
|
selected = LanguageManager.available_locales.find(LanguageManager.current_locale)
|
|
item_selected.connect(LanguageManager.set_language)
|
|
```
|
|
|
|
## Pluralization
|
|
|
|
Godot supports plural forms via `trn()`:
|
|
|
|
```gdscript
|
|
# trn(singular, plural, count)
|
|
func show_item_count(count: int):
|
|
$Label.text = trn("You have {0} item.", "You have {0} items.", count) % count
|
|
|
|
# For complex plurals (Slavic languages with 3+ forms), use custom logic
|
|
func get_plural_string(msgid_singular: String, msgid_plural: String, n: int) -> String:
|
|
# Godot handles plural forms via .po format plural rules
|
|
return trn(msgid_singular, msgid_plural, n)
|
|
```
|
|
|
|
## RTL Text Support
|
|
|
|
For Arabic, Hebrew, and other right-to-left languages:
|
|
|
|
```gdscript
|
|
# In Label or RichTextLabel:
|
|
$Label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
$Label.text_direction = TextServer.DIRECTION_RTL
|
|
$Label.alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
|
|
|
# For mixed LTR/RTL content, use RichTextLabel with bbcode
|
|
$RichTextLabel.text = "[rtl]مرحبا[/rtl] [ltr]Hello[/ltr]"
|
|
```
|
|
|
|
### RTL-Aware UI Layout
|
|
|
|
```gdscript
|
|
# Container layouts flip automatically with RTL
|
|
# Use Control.layout_mode for proper RTL support:
|
|
$PanelContainer.set_anchors_preset(Control.PRESET_FULL_RECT)
|
|
|
|
# For manual positioning, mirror anchors:
|
|
func mirror_for_rtl(control: Control) -> void:
|
|
if TranslationServer.get_locale() in ["ar", "he", "fa"]:
|
|
var left = control.anchor_left
|
|
control.anchor_left = control.anchor_right
|
|
control.anchor_right = left
|
|
```
|
|
|
|
## Font Fallback Chains
|
|
|
|
Handle characters not available in the primary font:
|
|
|
|
```gdscript
|
|
# Theme approach (recommended):
|
|
# 1. Create Theme resource
|
|
# 2. Set "FontFile" for Label/LineEdit
|
|
# 3. Add fallback fonts via DynamicFont
|
|
|
|
# Code approach:
|
|
var dynamic_font = DynamicFont.new()
|
|
dynamic_font.font_path = "res://fonts/primary.ttf"
|
|
dynamic_font.fallback_fonts.append(DynamicFontLoading.new())
|
|
$Label.add_theme_font_override("font", dynamic_font)
|
|
|
|
# For CJK + Latin support, chain fonts:
|
|
# Primary: Noto Sans (Latin) → Fallback: Noto Sans CJK
|
|
```
|
|
|
|
## Translation Best Practices
|
|
|
|
### Use Context Keys
|
|
|
|
```gdscript
|
|
# When the same word has different meanings:
|
|
tr("Attack [weapon]") # As a verb
|
|
tr("Attack [noun]") # As a noun/stat name
|
|
|
|
# In .po files, msgctxt provides context
|
|
# msgctxt "weapon" / msgid "Attack" / msgstr "Atacar"
|
|
# msgctxt "noun" / msgid "Attack" / msgstr "Ataque"
|
|
```
|
|
|
|
### Avoid String Concatenation for Translatable Text
|
|
|
|
```gdscript
|
|
# Bad — translators can't reorder
|
|
tr("You found ") + item_name + tr(" in the cave!")
|
|
|
|
# Good — translators control word order
|
|
tr("You found {0} in the cave!") % item_name
|
|
|
|
# Better — named placeholders for clarity
|
|
tr("You found {item} in {location}!") % {"item": item_name, "location": location_name}
|
|
```
|
|
|
|
### Externalize All UI Strings
|
|
|
|
```gdscript
|
|
# Hardcoded strings make translation impossible
|
|
$Label.text = "Score: 100" # BAD
|
|
|
|
# Use tr() for everything visible to players
|
|
$Label.text = tr("Score: {0}") % score # GOOD
|
|
```
|
|
|
|
## Godot 4.7 i18n Notes
|
|
|
|
- **TextServer** API improved for complex script handling
|
|
- **RichTextLabel** supports better RTL paragraph alignment
|
|
- Translation import pipeline handles `.po` files natively (no conversion needed)
|
|
|
|
## MCP Bridge Tools (Optional — Live Editor Integration)
|
|
|
|
When the MCP bridge is running (Phase 2), these tools can inspect and modify your project live:
|
|
|
|
- **`project_info`** — Check configured locales and translation file paths
|
|
- **`script_read`** / **`script_edit`** — Read and modify localization scripts
|
|
|
|
> **Note:** MCP tools require the Godot editor to be running with the MCP plugin enabled. Skills work independently without the bridge.
|
|
|
|
## References
|
|
|
|
- [TranslationServer Deep Dive](references/translation-server.md)
|
|
- [RTL Layout Patterns](references/rtl-layout.md)
|
|
- [Font Fallback Strategies](references/font-fallbacks.md)
|