Introduction
I’m currently in the process of translating my latest game, so I decided to write about the system I’m using to make things simpler for myself. I’m doing this for two reasons:
- It may be helpful to someone else
- I came up with this system last year and I had forgotten all the details and had to figure them out by looking at my parser code. I’d rather not have to do this again next year!
The approach I’m using is heavily inspired by the one Ron Gilbert uses, as described in the Thimbleweed Park blog.
The scripting language for my games is currently Lua, but that may change as I progress with my game engine if I decide to take the crazy step of creating my own scripting language.
Step 1: writing text while the game is being developed
During the development of the game the way I write text can be observed in the following snippet, coming from the code inside a dialogue script.
|
|
Here say
is the function that tells the game to write a sentence on the screen and animate a character speaking, ines
is the character object (a Lua table), and INES
is a function that for the time being just takes a string as input and then returns it.
Other than having to remember to wrap the text in the INES
and LEE
functions, at this stage nothing special needs to be done, and we are able to write the text directly in the source files, which makes developing much easier.
Step 2: after the game is finished
We finally finished writing the dialogue for the game! Well done!
At this point we are ready to decouple the text from the source files, which is essential if we want to translate the game in a relatively painless way.
This is done through a Lua script that parses all the source files, finds all the functions such as INES
and LEE
, and modifies their inputs to add a line id. Additionally, the script creates a new Lua file which returns a table containing all the text. After running this script, the snippet becomes
|
|
and the file with the output table is
|
|
Note that the output file has comments on each line telling us which character is speaking and which source file the line came from, to help with the translations.
At this point we can’t pretend the INES
and LEE
function are as trivial as they used to be. The actual functions we use is
|
|
and INES
and LEE
are simply convenience names for the tr
function, whose purpose is to let us know which character is talking through the comments in the output file from above. If the tr
function is only passed a string (like we did during development) then it just returns the string itself; if, however, we have a line id, the string is ignored and tr
returns the element with key id
from the tr_text
table, which is of the same form as the one obtained in the script output file.
The way translations are implemented is by making several copies of the output table (one for each language) and import them in the game. Changing the language is then as easy as setting the tr_text
table to the correct one for the language:
|
|
Adding a line after the fact
What if we need to do some changes after the script was already ran? Easy! The script actually does this:
- First we do a pass of all the files and check if any lines already have an id, then we record the highest id (or set it to 0 if there are none) in a variable
high_id
. - Now we do a second pass. If a line already has an id we leave it as is. If a line does not have an id, we assign it
high_id + 1
and then incrementhigh_id
by one.
So for example if we change the snippet to
|
|
we obtain the following when we run the script:
|
|
|
|
Note than lines are added to the file with the table in the order in which they are parsed rather than sorted by id—this is to make translations easier, since otherwise adding a sentence would bump it to the end of the file.
One final note: when we run the script and the output file already exists, lines that are not in the table are added, but the ones that are already are not overwritten. This is so that if we add a line after the translation has already been done, translated lines are not reverted to the original language!
Conclusion
That’s it! This is the way I deal with translations. Hopefully this post will be helpful to someone, even if that someone is just myself next year.