Nothing failed. The export dialog said success, the log said success, and the build your tester opened is in one language — or in raw keys. The editor loads your translation tables from disk; the export loads whatever the preset decided to pack, and a preset has three silent ways to leave them out. Paste your export_presets.cfg and project.godot below and this names which one is yours, per translation path. Runs in your browser, nothing is uploaded.
Both files are plain text in your project root. export_presets.cfg is often gitignored — it is still on the machine that builds.
The editor cannot answer this question — only the exported game can. Put this in your main scene, export, and run the build from a terminal:
func _ready() -> void:
print("locales: ", TranslationServer.get_loaded_locales())
print("active: ", TranslationServer.get_locale())
print("sample: ", tr("MENU_START"))
| What the exported build prints | Cause |
|---|---|
locales: [] | X1 or X2 — the tables are not in the pack |
| locales listed, yours missing, text in English | X3 — a stale path in locale/translations |
| locales listed, raw keys on screen | not an export problem — the key-instead-of-translation checklist |
| translates in the terminal, not on screen | not an export problem — what actually froze |
You do not need export templates to reproduce any of it. --export-pack writes a .pck without them, and the engine runs that pack the way a real build does:
godot --headless --path . --export-pack "Linux" /tmp/game.pck godot --headless --main-pack /tmp/game.pck
Symptom: get_loaded_locales() is empty in the build, raw keys everywhere, and the same project translates fine in the editor.
Cause: in the export dialog's Resources tab the mode is Export selected resources (and dependencies). Godot walks the dependency graph out of the scenes you ticked. Your .translation files are referenced by project.godot — by a setting, not by a scene — so nothing in that graph points at them and they are dropped. Measured with a preset whose only selected file is the main scene: the export reports success (E3a), no table is in the pack (E3b), the build loads zero locales (E3c) and tr("MENU_START") returns MENU_START (E3d).
Fix: go back to Export all resources in the project, or tick the tables themselves:
export_filter="all_resources"
# or, keeping the selective mode:
export_filter="resources"
export_files=PackedStringArray("res://main.tscn", "res://t.en.translation", "res://t.pt.translation")
This is the one that hits teams who switched to selective export to shrink a build, months before anyone noticed the languages were gone.
Symptom: identical to X1 — zero locales, raw keys, success reported.
Cause: Filters to exclude files/folders from project matches your tables. *.translation is the obvious pattern; the realistic one is a folder pattern like localization/* added to keep source material out of the build, which also removes the compiled tables that live in that folder. Measured: the export succeeds (E4a), the tables are gone (E4b), the build prints the raw key (E4c).
Fix: exclude the sources by extension — *.csv, *.po, *.xlsx — and never a folder that also holds .translation files.
locale/translations that no longer existsSymptom: the sneakiest of the three, because nobody sees keys. The build shows correct English to a player who picked Portuguese. Testers report "the language button does nothing"; QA closes it as cosmetic.
Cause: project.godot lists a .translation path that is not there — the CSV was renamed, a locale column dropped, the file moved. The setting keeps the stale path. Measured: the export does not fail (E6a), the missing table is skipped while the others load (E6b), and tr() under that locale returns English, not the key (E6c).
The English comes from internationalization/locale/fallback, which defaults to en. The fallback is doing its job — which is exactly why the failure is invisible: a missing table looks like a translator who did not finish. To catch it in CI, assert the count you expect:
assert(TranslationServer.get_loaded_locales().size() == 4)
The claim is that --export-pack on a fresh clone — no .godot/, no .import sidecars, no .translation files, all correctly gitignored — produces a build with no translations, so you must run --editor --quit first. On 4.7 it does not: the export imports the project itself. The export succeeds (E5a), the tables are in the pack (E5c), the build translates (E5d).
Why the myth survives is itself measured (E5b): while importing, the log fills with
ERROR: Cannot open file 'res://t.en.translation'. ERROR: Failed loading resource: res://t.en.translation.
— red lines naming the exact files you are worried about, printed before the importer builds them. If your CI log shows those and the build works, the lines are noise. Check the build, not the log.
It is not in a working build and never was. The pack of a correct export holds t.en.translation, t.pt.translation, t.csv.import — and no t.csv (E2b, E2c). The CSV is an import source; the editor compiles it into one binary .translation per locale column, and those are what ship. If some code of yours reads res://translations.csv at runtime, it works in the editor and breaks only in the export (E2a vs E2b) — a real bug, a different one.
*.csv to the include filter"With exclude_filter="*.csv" the build still translates (E7a, E7b). With include_filter="*.csv" the CSV still does not reach the pack (E7c) — the include filter ships non-resource files, and a CSV that Godot imports as a translation is a resource, so the filter does not apply to it. The advice is not just unnecessary; it cannot do what it promises.
None of the three causes is in your translation table: X1 and X2 live in export_presets.cfg, X3 in project.godot. A table linter is structurally blind to all three, and the checker above exists precisely because that blind spot is real — it reads the two config files, not your strings.
What the linter is for is the job that runs right before the export: the table itself.
All 26 claims, the harness that exports a .pck and runs it, and the exact file list of a correct pack. docs/translations-missing-in-exported-build.md
Missing keys, placeholder drift, unbalanced BBCode, empty translations, orphan keys. Non-zero exit, made for the CI step before the build: node cli.js /path/to/project --csv translations.csv. github.com/leobaray/locguard
The same rules without leaving Godot: scan from a dock, double-click a finding to open the file at the line, ready-made CI presets. LocGuard Pro on itch.io
Six tiles in, a wired 47-bitmask TileSet out. The other half-hour of Godot busywork nobody should be doing by hand. Blobsmith on itch.io · free Lite version · what it does
Measured on Godot 4.7.stable.official.5b4e0cb0f, 26 claims, 0 failures. Re-run them with docs/verify_export_translations.sh <godot-binary>; if a future engine build changes an answer, the script names the claim that moved.