Localisation is something that’s easy to put off, no matter what kind of development work you’re doing. It’s easy to think that you’re never going to need to translate your game, or app, and in most cases that’s probably true. However, if you’ve not built in the ability to add translations and you need them, adding that ability could be a long and arduous process. Fortunately, if you’re making your game in Godot, adding in translations is actually relatively straightforward, especially if you do so early in your game’s development.
In this post, I’m going to show you how to set up localisation in Godot using a simple CSV file. There is a second method, which uses something called gettext, but I’ll take a look at that in my next post.
This method uses CSV files for the translations and is relatively straightforward, both to start and update. However, updating your translation keys is a manual process, so it may not scale very well if you have a lot of text.
There is a short tutorial in the Godot documentation, and I’ve uploaded the final version of my code to github.
Project Setup
We’ll start by making a new project in the Godot editor. You can call it whatever you want. Add a new scene. Once again, the name doesn’t matter, but I’ve called mine Main, and it’s just a basic node.

Now we need to add a script to the scene’s parent node. I primarily use C#, but I’ve also written the equivalent script in GDScript afterwards. I’m not very familiar with GDScript, so it might not be written in the most efficient way.
public partial class Main : Node
{
public override void _Ready()
{
// this grabs the language to use
// from Godot's internationalization test settings
var testLanguage = ProjectSettings
.GetSetting("internationalization/locale/test")
.ToString();
if (testLanguage != string.Empty)
{
// use the test language
TranslationServer.SetLocale(testLanguage);
}
else
{
// use the language from the OS
TranslationServer.SetLocale(Godot.OS.GetLocaleLanguage());
}
}
}
And the GDScript equivalent is:
func _ready():
# language taken from internationalization test settings
var language = ProjectSettings.get_setting("internationalization/locale/test")
# if no language is set, use the language from the OS
if language == null:
TranslationServer.set_locale(OS.get_locale_language())
else:
TranslationServer.set_locale(language)
When the Main scene enters the tree, this script simply checks to see if there’s any locale set in Godot’s internationalization test settings and, if there is, it sets it as the local of the game. Otherwise, we use the locale from the player’s OS.
But where can we set this test locale?
Open Project Settings and make sure you’ve got Advanced Settings on. Scroll down until you find Internationalization and open the Locale section. There, you can set a test locale.

And with that, we have everything ready to set up our localisation strategy! I’m going to duplicate the project here so I can use it as a starting point for both CSV translation and, later, gettext translation.
Creating the UI
Let’s start by making a simple UI. I’ve put together a simple character sheet using some labels and buttons. Nothing’s wired up – I just want to see the text.

In order to translate this, however, we’re going to have to use some translation keys instead of the English text.
Aside: You can use text in your own language as the translation keys, but this often leads to problems. An immediate example here – there’s a button with the text “Close” on it. From context, we know this probably means we want to close the character sheet. But the word “close” could also refer to distance – someone next to you is close to you. It’s better to use specific keys and avoid this potential problem altogether.
Translation keys, by convention, are in capitals with underscores between the words.

I’ve now changed all my text so that it’s a descriptive translation key. This, obviously, doesn’t look particularly nice, which is one of the downsides of adding localisation in this fashion. If it bothers you, you could create the UI in whatever language you choose and then run the translation function in the ready function of your scripts. Doing it that way could mean you miss something though and I don’t recommend it.
Godot’s documentation recommends you use containers as much as possible for UI components that you might translate, so they can expand and contract depending on the length of words and phrases in different languages.
Setting up our translation CSV
It’s time to start translating! Godot’s documentation tells us that Godot expects a translation CSV to be in the following format:
keys | <lang1> | <lang2> | <langN> |
KEY1 | string | string | string |
KEY2 | string | string | string |
KEYN | string | string | string |
You can create a CSV file in any spreadsheet editor (e.g. Microsoft Excel, LibreOffice Calc, Google Sheets) – just make sure to select “Save As…” and change the file type to CSV. This will often prompt a warning about losing formatting data, which you can safely ignore. Or you can use a text editor if you want, but I find it difficult to work out which column data is in, even with a small file, let alone a larger file with many translation keys.
For my UI, I’ve used Google Translate and created this CSV file:

Each key from the UI is in the keys column, and I’ve added support for three languages – English (en), French (fr), and German (de).
Using the CSV translations
Importing this file into Godot automatically creates three translation files.

Making changes to the CSV file in the project folder will also automatically update all the translation files, which is incredibly useful.
There’s one more step before the translations are displayed. We need to add them in the Project Settings, Localization tab.

Now if I run the game without any test locale set, it will default to my OS language (English).

Changing the test locale to “fr” (French). You may need to save the Main scene before running to get the translations.

And finally, German.

That’s awesome! And if we add a new label…

We just need to add a new line to the CSV file…

And Godot will automatically update everything and translate when we run the game.

Multiple Scenes
There’s now several things associated with the inventory here. I think it’s time to split it out into its own scene. How does that affect translation?
I’ve split the inventory out into its own scene and tidied things up a bit with a couple of containers

I’ve also added an entry for INVENTORY_ITEM_QUANTITY to the translation CSV.

Finally, I’ve put our new Inventory scene into the Main scene (along with some tidying nodes up into containers so it’s a bit neater).

And, as expected, when I run the game, the text is translated.
At this point, if you prefer, you can also split out your translation CSV so your translation documents are organised by component.

You then have to add the new translation files into your project settings the same way as you added the initial ones.

This approach could help to keep your translations better organised, but it would mean that if you want to add another language, you have to update multiple CSV files.

Translating in scripts
What about if we want to add things to the inventory?

I want to add items into the ItemsContainer grid. In this example, I’m just going to do it by adding a script to the Inventory scene and putting the add item code in the ready function.
C#:
public partial class Inventory : Control
{
private GridContainer ItemsContainer;
public override void _Ready()
{
ItemsContainer = GetNode<GridContainer>("%ItemsContainer");
// using translation keys when adding the items
AddItemToGrid("ITEM_SWORD", 1);
AddItemToGrid("ITEM_POTION", 3);
}
private void AddItemToGrid(string name, int quantity)
{
// the Tr function is a shortcut to TranslationServer.Translate
ItemsContainer.AddChild(new Label {Text = Tr(name)});
ItemsContainer.AddChild(new Label {Text = quantity.ToString()});
}
}
GDScript:
func _ready():
# using translation keys when adding the items
add_item("ITEM_SWORD", 1)
add_item("ITEM_POTION", 3)
func add_item(item_name, quantity):
var name_label = Label.new()
# tr is the function that translates the given string
name_label.text = tr(item_name)
var item_quantity = Label.new()
item_quantity.text = str(quantity)
$%ItemsContainer.add_child(name_label)
$%ItemsContainer.add_child(item_quantity)
And when we run the game…

I’ve added the translation keys for these items to my inventory_messages CSV, and Godot’s translation function has translated the name of each item as I add it.
Resources
Let’s say we want to use Godot’s resources for our items. How does translation work now?

The answer is that it works in exactly the same way as before. If we use a simple resource script that adds a name to the resource, we can set the translation key as the resource name, and then pass it through Godot’s translation function in exactly the same way as before.
I’ve updated the _Ready() function in my C# script to load in the resources rather than just using a static name.
public override void _Ready()
{
ItemsContainer = GetNode<GridContainer>("%ItemsContainer");
// loading the items from their resource files
var sword = GD.Load<Item>("res://Inventory/Items/Sword.tres");
var potion = GD.Load<Item>("res://Inventory/Items/Potion.tres");
// and passing the names to the add to grid function
AddItemToGrid(sword.Name, 1);
AddItemToGrid(potion.Name, 3);
}
And made the same change to the _ready() function in GDScript.
func _ready():
# loading the items from resources
var sword = load("res://Inventory/Items/Sword.tres")
var potion = load("res://Inventory/Items/Potion.tres")
# using translation keys when adding the items
add_item(sword.Name, 1)
add_item(potion.Name, 3)
You can do the same with any string, no matter where you load it from – just set it to the translation key and pass it through the translation function.
CSV localisation summary
So, to summarise the process of adding localisation via CSV:
- Create a CSV with one column for keys, and one column for each language you intend to use
- Import this CSV into Godot via Project Settings > Localization > Translations
- Whenever you add a string that needs to be translated, replace it with a translation key. In the UI, you just need to replace the string. In a script, you need to call Godot’s translation function – Tr(key) in C# or tr(key) in GDScript
- Add the translation key to the keys column of your CSV file. Add the translated strings for each language in their columns
That’s incredibly easy, and you don’t even need to install any special software to do it!
But there are some potential problems.
Firstly, this is a very manual process. You have to remember to add the key to your CSV file, and you need to make sure to copy it exactly. If you change a key in one location, you need to change it in both places. Failure to do so means that your translation will stop working. This means that it could be difficult to scale up and maintain.
Secondly, this is not the standard format for translation services. It lacks the ability to add comments about the context, and does not support pluralisation.
These limitations aren’t the end of the world, and if your game doesn’t have much text, CSV could well be the better method.
As I mentioned at the start of my post, there is a second method of localisation – using gettext. We’ll take a look at that in my next post.