Skills/godot-i18n/SKILL.md

6.6 KiB

name description license compatibility metadata
godot-i18n 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. MIT
godot-4.0
godot-4.1
godot-4.2
godot-4.3
godot-4.4
godot-4.5
godot-4.6
godot-4.7
author version tags created
vl4dt 0.1.0
godot
i18n
localization
translation
rtl
2026-06-21

Localization in Godot

Patterns for internationalizing Godot games.

TranslationServer Basics

Godot's built-in translation system uses .translation files (CSV-based):

# 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:

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

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

# 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():

# 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:

# 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

# 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:

# 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

# 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

# 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

# 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