setup-localization
Skill2 starsChanged 6 months agoMITView on GitHub
Set up Godot 4.x localization (i18n) for a project — CSV translation tables, gettext .po workflow, language switcher, locale persistence, font fallback for non-Latin scripts. Use to add multi-language support, change UI language at runtime, or wire fonts that cover languages outside the default Latin set.
In this repository33 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- bootstrap-godot-projectSkill
- codebase-surveySkill
- create-autoloadSkill
- create-componentSkill
- create-resourceSkill
- create-sceneSkill
- create-state-machineSkill
- export-configSkill
- feature-planSkill
- feature-specSkill
- game-brainstormingSkill
- gdd-writerSkill
- genre-pack-3d-actionSkill
- genre-pack-platformerSkill
- genre-pack-topdownSkill
- genre-pack-turnbasedSkill
- godot-patternsSkill
- gut-test-writerSkill
- networking-foundationSkill
- performance-auditSkill
- save-schema-migrationSkill
- setup-collision-layersSkill
- setup-git-godotSkill
- setup-input-mapSkill
- setup-save-systemSkill
- sfx-generatorSkill
- shader-writerSkill
- subagent-dev-modeSkill
- ui-patterns-godotSkill
- update-docsSkill
- using-godot-superpowersSkill
- writing-game-planSkill
The file
We split this skill into the rules it gives an agent
4 rules. About 0 are common, 1 is distinctive, and 3 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 40
Use %s, %d, %.2f placeholders for runtime substitution: tr("hud.score") % score.
- Very specific: line 55
Add *.translation to .gitignore if you commit the source CSV — they regenerate on import.
- Distinctive: line 152
Default Godot fonts cover Latin only.
- Very specific: line 174
Locale codes: use BCP-47 (pt-BR, not pt_br).
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Setup Localization
Wires the full i18n pipeline in Godot 4.x: CSV-based translations (best for short UI strings), optional .po/gettext for long-form text, runtime language switcher, locale persistence in user://settings.cfg, and font fallback for scripts the default font does not cover.
Reference: Internationalizing games, Locales, Importing translations.
Decision: CSV vs gettext (.po)
| Use CSV when | Use .po (gettext) when |
| Short UI strings (menus, HUD, button labels) | Long narrative / dialogue paragraphs |
| Translators are non-technical (work in spreadsheets) | Translators use gettext-aware tooling (Poedit, Crowdin) |
| Project has < ~500 strings | Project has many strings, plurals, contextual variants |
Both can coexist: CSV for UI, .po for in-game prose / dialogue.
CSV pipeline
1. Create localization/translations.csv
keys,en,it,fr,ja
ui.menu.start,Start,Inizia,Démarrer,スタート
ui.menu.options,Options,Opzioni,Options,設定
ui.menu.quit,Quit,Esci,Quitter,終了
hud.score,Score: %d,Punteggio: %d,Score : %d,スコア: %d
dialogue.npc.greet,Hello traveler.,Salve viaggiatore.,Bonjour voyageur.,こんにちは、旅人。Rules:
- Column 1 header =
keys. First row = column header (each cell afterkeysis a BCP-47 locale code). - Keys use namespaced dot.case (
area.subarea.name) — never raw English. Switching the source language later then costs nothing. - Use
%s,%d,%.2fplaceholders for runtime substitution:tr("hud.score") % score.
2. Import the CSV
In Project → Project Settings → Localization → Translations, click Add and select localization/translations.csv. Godot generates one .translation file per language under the same folder (these are imported assets — do not edit by hand).
In project.godot this lands as:
[internationalization]
locale/translations=PackedStringArray("res://localization/translations.en.translation", "res://localization/translations.it.translation", "res://localization/translations.fr.translation", "res://localization/translations.ja.translation")
locale/fallback="en"Add *.translation to .gitignore if you commit the source CSV — they regenerate on import. (See bootstrap-godot-project for the standard .gitignore.)
3. Use translations at runtime
# In any node:
$Label.text = tr("ui.menu.start")
$ScoreLabel.text = tr("hud.score") % current_score
# For text set in the editor, prefix with the same key — Godot will translate automatically:
# Label.text = "ui.menu.start" (auto-translate enabled by default on Control nodes)tr() falls back to the key itself if the lookup fails. That makes missing keys obvious during testing — keys leak into the UI literally, instead of crashing.
For plural forms, use tr_n():
# CSV row: ui.enemies_remaining,"%d enemy left","%d enemies left" (English plural rule)
$Label.text = tr_n("ui.enemies_remaining", "ui.enemies_remaining", count) % countgettext (.po) pipeline
For long-form text (dialogue, lore, item flavor):
# Generate POT template from source code
godot --headless --path . --quit-after 1 \
--export-translation-pot localization/messages.pot
# Translators work on per-language .po files
msginit --input=localization/messages.pot --locale=it --output=localization/it.po
# ... translators edit it.po ...
# Compile .po → .mo (or import the .po directly in Godot 4)In Project Settings → Localization → Translations, add the .po files alongside the CSV .translation files. Godot resolves tr() against the union of both.
Runtime language switcher
scripts/ui/language_switcher.gd:
extends OptionButton
const LANG_KEY := "language"
const SETTINGS_PATH := "user://settings.cfg"
# (BCP-47 code, native-name) — native names are intentional: a French speaker
# looking for French scans the list for "Français", not "French".
const LANGUAGES: Array[Array] = [
["en", "English"],
["it", "Italiano"],
["fr", "Français"],
["ja", "日本語"],
]
func _ready() -> void:
for entry in LANGUAGES:
add_item(entry[1])
item_selected.connect(_on_selected)
_select_current()
func _select_current() -> void:
var current := TranslationServer.get_locale()
for i in LANGUAGES.size():
if current.begins_with(LANGUAGES[i][0]):
select(i)
return
select(0)
func _on_selected(idx: int) -> void:
var locale: String = LANGUAGES[idx][0]
TranslationServer.set_locale(locale)
var cfg := ConfigFile.new()
cfg.load(SETTINGS_PATH)
cfg.set_value("locale", LANG_KEY, locale)
cfg.save(SETTINGS_PATH)
get_tree().reload_current_scene() # forces all `tr()` calls to refreshApply the saved locale at boot — add to autoload/game_state.gd (or wherever you bootstrap settings):
func _ready() -> void:
var cfg := ConfigFile.new()
if cfg.load("user://settings.cfg") == OK:
var locale: String = cfg.get_value("locale", "language", OS.get_locale())
TranslationServer.set_locale(locale)OS.get_locale() returns the user's system locale on first run — sane default for users who never touch the language menu.
Font fallback for non-Latin scripts
Default Godot fonts cover Latin only. For Japanese / Chinese / Korean / Cyrillic / Arabic / Hindi, register fallback fonts on the Theme, not on every Label:
- Project root:
assets/fonts/main.ttf(Latin),assets/fonts/jp.otf(CJK),assets/fonts/ar.ttf(Arabic), … - Open the project's main
Themeresource (themes/main_theme.tres). - On
Label,Button,RichTextLabelfont properties: set the primaryFontFile, then addFontFiles as fallbacks in order. Godot picks the first font whose glyph set covers the codepoint. - For RTL languages (Arabic, Hebrew), set
Label.text_direction = TEXT_DIRECTION_AUTOand verify your layouts mirror correctly. UseControl.layout_direction = LAYOUT_DIRECTION_LOCALEon container roots so margins flip with the locale.
Pre-release checklist
- Every UI string goes through
tr()(or auto-translate). No raw English in.gdor.tscnfor shipped text. - CSV /
.pocovers all listed locales — no missing rows. (Empty cell = falls back tolocale/fallback.) - Plurals use
tr_n(), notif count == 1: .... - Locale persists across sessions (verify by changing language → quit → relaunch).
- Non-Latin locales render glyphs (no tofu boxes □□□).
- RTL locales mirror layouts correctly if shipping Arabic / Hebrew.
- Date / number formatting uses
Time.get_date_string_from_system()and locale-aware string formatting where it matters.
Common pitfalls
- Hardcoded English in
.tscn:Label.text = "Start"ships untranslated. Replace with keyui.menu.start; auto-translate handles the lookup. - Concatenating translated strings:
tr("ui.you_have") + str(score) + tr("ui.points")breaks word order in non-English. Use a single key with%splaceholders. - Reloading scenes on every change:
reload_current_scene()is the simplest way to refreshtr()calls, but loses player state. For in-game language switches mid-run, walk the tree and re-runtr()on everyLabel/RichTextLabel, or trigger a customlanguage_changedsignal that nodes subscribe to. - Locale codes: use BCP-47 (
pt-BR, notpt_br). Godot accepts both forms but BCP-47 is the standard the rest of the world uses.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

