CSV Localization Tutorial?

T

The Shopkeeper

Guest
Do you know any CSV Localization tutorial that you recommend?
 
T

The Shopkeeper

Guest
The GMS help pages will get you 90% of the way there.



This will show you how to read in a text file and start stepping through it and storing data.
The GMS help pages teaches you to read files, but not to work with keys and read all the content belonging to a key.
 

obscene

Member
I'll walk you through how I do it. I'm bored and maybe someone can critique my method. The goal is to be able to use a recognizable enumerator and get the correct string in it's place.

Example:
Code:
draw_text(x,y,scr_text_get_string(text.action_critical_hit));
First, my CSV is set up like this...
1600295010764.png

Second, I have a script to initialize the enums in that same order.
Code:
enum text
    {
    action_critical_hit,
    interact_door_locked,
    interact_treasure_empty,
    etc....
    }
Third, we'll need a script to import the CSV into a grid.

scr_load_csv_ext
Code:
/*
The top cell in each row must be filled to get the contents from that row
*/

/// @arg file
/// @arg seperator

var file = file_text_open_read(argument0);
var seperator=argument1;
var line
var grid;
var row=0;
var column;
var columns;
var seperator_pos;
var val;

grid=ds_grid_create(0,0);
do
    {
    // Get the current line
    line=file_text_readln(file);
   
    // If this is the first line, count seperators to get the width
    if row==0 columns=string_count(seperator,line)+1;
   
    // Resize the grid every time we have stuff to add
    ds_grid_resize(grid,columns,row+1);
       
    // Parse through each column
    for (column=0; column<columns; column+=1)
        {
        //Find seperator
        seperator_pos=string_pos(seperator,line);
       
        // Should be commas
        if (column<columns-1)
            {
            // Get value
            val=string_copy(line,1,seperator_pos-1);
           
            // Remove from line
            line=string_delete(line,1,seperator_pos);
            }
        // Last line. Make sure line break is removed
        else val=string_copy(line,1,string_length(line)-2);
       
        // Add to grid
        ds_grid_add(grid,column,row,val);
        }
    row+=1;
    } until file_text_eof(file);

file_text_close(file);
return grid;
ds_grid_destroy(grid);
So we'll import the CSV into a grid, then figure out which column we need using the variable global.settings_language (which has a value starting at 1 since English is in column 1) and dump that into a ds_list called global.ds_text. I do this on game start, after loading settings and when changing the language in the settings.

scr_text_import
Code:
/// Get all data from csv
scr_show_debug_message("scr_text_import()");
var grid=scr_load_csv_ext("strings.csv",";");  // I use semicolons to separate my csv file because the text can include commas
var size=ds_grid_height(grid);
var contents;

// Clear out any old data
ds_list_clear(global.ds_text);

// Make list from correct column
for (var i=1; i<size; i+=1) // 1 because headers are on row 0
    {
    contents=ds_grid_get(grid,global.settings_language,i);

    // If empty, get english text by default
    if (string_length(contents)<1)
        {
        contents=ds_grid_get(grid,1,i);
        }
    ds_list_add(global.ds_text,contents);
    }

// Make list of all available languages
var width=ds_grid_width(grid);
for (var i=1; i<width; i+=1) // 1 because enums are on column 0
    {
    global.settings_languages[i]=ds_grid_get(grid,i,0);
    }

// Clean up
ds_grid_destroy(grid);
Note that the last part was to just make an array of available languages used by my settings menu which is a whole other thing.

Anyway, now we write our final script. It's simply reading the correct value from global.ds_text that corresponds to the enumerator.

scr_text_get_string
Code:
gml_pragma("forceinline");
/// @arg enumerator
var enumerator=argument0;
if (is_undefined(enumerator)) return "undefined";
else return ds_list_find_value(global.ds_text,enumerator);
 

Yal

🐧 *penguin noises*
GMC Elder
First, my CSV is set up like this...
One important thing you're missing is a "context" field (which would be read by the translators and not used by the game itself)... sometimes the same word can have different meanings, and you can get stupid-looking translation errors if the wrong meaning is picked... Dark Souls III had the "bow" gesture (lean forward to show respect) translated as a "bow" weapon (piece of bent wood that launches arrows using tensile properties) in the french translation, for instance. Japanese and a number of other languages have different speech patterns depending on a character's background (like adding or removing honorifics depending on how blunt / formal the character is, or changing the genus of words like "me" and "you" depending on gender and social standing of the person that is being referred to) and omitting this context and just guessing will make the dialogue come off as unnatural.

Ideally, the translators should ask you for this type of context to make sure they get it right (if someone just takes a bunch of text and outputs a translation without asking for feedback at all, chances are they're just machine-translating it and hoping you don't notice...), but having any necessary context right in the excel sheet should speed up the process. It also might help you avoid issues where strings are reused in places that makes sense in the original version but not in translations (for instance, if you have a string for "chest" (in the treasure sense) and you decide to reuse it in a scene where a character complains about chest pains, it will stop making sense in a lot of other languages... for instance swedish using "[skatt]kista" for the former and "bröst[korg]" for the latter, both of which also has an optional part that can be dropped in less formal contexts at the expense of making things a bit more unclear! (The part in [square brackets] is the optional bit))

TL;DR translations are messy, you need to think about a lot more stuff than just how to get your text into the game.
 
T

The Shopkeeper

Guest
I'll walk you through how I do it. I'm bored and maybe someone can critique my method. The goal is to be able to use a recognizable enumerator and get the correct string in it's place.
Thank you very much for sharing your work, this allowed me to organize my ideas. I am very grateful.
 
T

The Shopkeeper

Guest
One important thing you're missing is a "context" field (which would be read by the translators and not used by the game itself)... sometimes the same word can have different meanings, and you can get stupid-looking translation errors if the wrong meaning is picked... Dark Souls III had the "bow" gesture (lean forward to show respect) translated as a "bow" weapon (piece of bent wood that launches arrows using tensile properties) in the french translation, for instance. Japanese and a number of other languages have different speech patterns depending on a character's background (like adding or removing honorifics depending on how blunt / formal the character is, or changing the genus of words like "me" and "you" depending on gender and social standing of the person that is being referred to) and omitting this context and just guessing will make the dialogue come off as unnatural.

Ideally, the translators should ask you for this type of context to make sure they get it right (if someone just takes a bunch of text and outputs a translation without asking for feedback at all, chances are they're just machine-translating it and hoping you don't notice...), but having any necessary context right in the excel sheet should speed up the process. It also might help you avoid issues where strings are reused in places that makes sense in the original version but not in translations (for instance, if you have a string for "chest" (in the treasure sense) and you decide to reuse it in a scene where a character complains about chest pains, it will stop making sense in a lot of other languages... for instance swedish using "[skatt]kista" for the former and "bröst[korg]" for the latter, both of which also has an optional part that can be dropped in less formal contexts at the expense of making things a bit more unclear! (The part in [square brackets] is the optional bit))

TL;DR translations are messy, you need to think about a lot more stuff than just how to get your text into the game.
That depends on the size of your project. A "context" checkbox is ideal, but is it necessary in a 300 string project? And it also depends on whether the translator is the same writer. But I'll apply your idea to my project, XD
 

Yal

🐧 *penguin noises*
GMC Elder
That depends on the size of your project. A "context" checkbox is ideal, but is it necessary in a 300 string project? And it also depends on whether the translator is the same writer. But I'll apply your idea to my project, XD
It's probably not even necessary in a 300,000 word RPG, but it's always easier to ignore something unnecessary but present than to reverse-engineer something necessary but absent 🦈
 

obscene

Member
I should point out that my method is a little wasteful in that I read every column into a grid each time and then discard the excess languages instead of just figuring out which column we need first and retrieving only that. It was just easier this way. Suddenly I want to fix it...
 
T

The Shopkeeper

Guest
I should point out that my method is a little wasteful in that I read every column into a grid each time and then discard the excess languages instead of just figuring out which column we need first and retrieving only that. It was just easier this way. Suddenly I want to fix it...
I noticed that, I don't really like the idea of writing ID by ID to an enum either. I decided to store the texts of the selected language from the CSV to a DS_Map to avoid writing all the IDs manually. IMO is quite practical.
 

obscene

Member
I never use maps so I'm finding it hard to visualize that. But if it's more practical I'd love to see it. :) BTW I started to rewrite my code but realized that most of the overhead comes from string_copy and string_delete which are necessary either way and the grid writing barely affected anything anyway. I don't think much can be done to make it more efficient without making a giant mess of code.
 
Last edited:

Kezarus

Endless Game Maker
If I understand it correctly, you are in need of a language support, am I right?

If that's tha case check my framework tutorial step-by-step: wiki

I have that on my free Monastery Framework at the marketplace.

See if that helps you. =]
 
Last edited:
Top