How to Localize a Godot Game Without Breaking Variable Tags
Godot's localization system is built around a simple idea: separate your text from your code, translate the text, and let the engine swap it at runtime. In practice, the variable tags inside those strings are where things fall apart.
How Godot handles translatable strings
Godot, a free, open-source game engine used by solo developers and small studios for 2D and 3D projects, stores translatable text in CSV files or .po (gettext) files. You define a key, write your source string, and add columns or entries for each target language.
A typical CSV row might look like this:
QUEST_COMPLETE,"{player_name} finished the quest in {time} seconds"
And in GDScript, you call it with:
label.text = tr("{player_name} finished the quest in {time} seconds").format({
player_name = player.name,
weapon = str(elapsed)
})
Godot also supports C-style format strings (%s, %d) and rich text tags like [color=red] in its RichTextLabel nodes. These are all over a typical game's translation file, and they all need to survive translation intact.
Where translations break
The problem is not the translation itself. It is the variable tags inside the strings.
When you paste {player_name} picked up the {weapon} into a translation tool or general-purpose AI, several things can go wrong:
-
Curly braces get translated. Some tools interpret
{player_name}as natural language and translate "player" and "name" into the target language. Your code calls.format()with the English key. It finds no match. The placeholder renders as raw text, or your game crashes. -
Format specifiers get reordered.
%s killed %d enemiesdepends on argument order. A translator might flip the sentence structure for grammar but leave%sand%din the original positions, swapping which value fills which slot. -
Rich text tags get mangled.
[color=red]Warning[/color]might become[couleur=rouge]Avertissement[/couleur]in French output, breaking BBCode parsing entirely. -
Whitespace around tags disappears. Tokenizers strip or merge spaces near braces and brackets, producing
{player_name}picked upwith no space between the variable and the next word.
These bugs are silent. The CSV file looks fine. The game launches. Then a Japanese player sees {player_name} rendered as literal text on screen because the translator converted the curly braces to full-width characters.
How to protect your tags
The reliable approach is to replace every variable tag with a placeholder token before translation, then restore the originals after.
Here is the process:
-
Scan each string for variable patterns. Match
{...},%s,%d,%f,[color=...]...[/color], and any custom tags your project uses. -
Replace each match with a numbered token like
__VAR_0__,__VAR_1__. Store the mapping. - Send only the tokenized string to translation. The translator (human or AI) sees plain text with inert tokens it has no reason to modify.
- After translation, swap the tokens back to the original variable tags, byte for byte.
This is the approach LocaFile AI uses. When you upload a Godot CSV or .po file, it detects and locks every variable pattern, including {player_name}, %s, %d, and <color=red> tags, with regex-based token replacement before the AI model sees the string. After translation, the original tags are restored exactly. The AI only ever processes plain text with inert placeholders.
Practical checks before you ship
Even with tags protected, verify your translations before release:
- Test with pseudolocalization. Godot has a built-in pseudolocalization mode (Project Settings > Internationalization > Locale) that replaces your strings with accented, elongated versions. It catches hardcoded strings you forgot to externalize and UI elements that overflow with longer text.
- Check font coverage. Godot's default font only covers Latin-1 characters. For CJK, Cyrillic, Arabic, or Thai, load a font like Noto Sans as a DynamicFont resource and set it as your theme's default.
-
Handle pluralization properly. English has two plural forms. Polish has four. Arabic has six. Godot's
tr_n()function handles this per-locale, but your source strings need to be structured for it:tr_n("There is %d apple", "There are %d apples", count). - Test RTL languages. If you are shipping in Arabic or Hebrew, Godot automatically mirrors UI anchors, margins, and control order. But icons with directional arrows (back/forward buttons) need manual attention.
The real cost of skipping localization
Steam's audience is global. Roughly 60% of Steam users have their client set to a non-English language. Shipping English-only means your store page, your reviews, and your in-game text are invisible to most of the platform.
The traditional path, hiring an agency, costs thousands of dollars per language and takes weeks. The shortcut of running your CSV through ChatGPT produces translations that look correct until {player_name} shows up as literal text on a player's screen.
Variable-safe translation closes that gap. Protect the tags, translate the text, verify with pseudolocalization, ship.
Top comments (0)