Optimizations

GM-I18n aims to become a performance-oriented i18n system, and as such, it provides several optimizations to ensure that your game runs smoothly even with extensive localization.

Automatic

Some optimizations are applied automatically by the system, so you don't have to worry about them. These optimizations include:

Flattened Message Keys

When you're adding message data to the system, GM-I18n always flattens the keys automatically. This means that if you have a message with a nested structure, it will be stored in a flat format, which can significantly speed up lookups and reduce memory usage.

For example, if you have a message like this in en.json locale file:

en.json
{
    "greeting": {
        "morning": "Good morning!",
        "evening": "Good evening!"
    }
}

It will be stored as:

en.json
{
    "greeting.morning": "Good morning!",
    "greeting.evening": "Good evening!"
}

The messages you add using i18n_add_messages function also follow this flattened structure. So, if you add a message like this:

Create Event
// assume you've  already initialized the i18n system

i18n_add_messages("en", {
    greeting : {
        morning: "Good morning!",
        evening: "Good evening!"
    }
});

// will be stored as:
// greeting.morning: "Good morning!",
// greeting.evening: "Good evening!"
This flattening process is done automatically by the system, so you don't need to worry about it when adding messages. You can continue to use nested structures in your code, and the system will handle the conversion for you.

Hashed Message Keys

To further optimize performance, you can enable hashed message keys. This feature allows the system to use a hash of the message key instead of the full key string, which can significantly speed up lookups, especially in large projects with many messages.

To enable hashed message keys, you can set hashed: true option in the i18n_create function when initializing the i18n system.

Create Event
// Initialize the i18n system with hashed message keys
global.i18n = i18n_create("g.i18n", "en", [
    // array of locales to load
    new I18nLocaleInit("en", "English", "~/langs/en.json"),
    new I18nLocaleInit("id", "Bhs Indonesia", "~/langs/id.json"),
    new I18nLocaleInit("ja", "日本語", "~/langs/ja.json")
], {
    // this struct is used to configure the i18n options
    hashed: true            // enable hashed message keys
});
By default, hashed message keys are enabled. You can disable it by setting hashed: false in the i18n_create function if you want to.

Optimized Reference Update

If you're using the i18n_create_ref_message() function to create references to messages, you're actually optimizing the way you manage messages in your game. Because the GM-I18n system will only update all of the references in one go with i18n_update_refs() or i18n_set_locale() function, instead of updating each reference individually (or even worse, updating the message/reference individually every step in the game loop).

This means that if you change a message, you can simply call i18n_update_refs() or i18n_set_locale() to update all references at once, rather than having to update each reference individually. This can significantly reduce the overhead of updating messages in your game, especially if you have a lot of references in your code.

Moreover, if the reference you created using i18n_create_ref_message() or i18n_create_ref_asset() is not used anymore, the system will automatically remove it from the list of references, so you don't have to worry about memory leaks or performance issues caused by unused references.

This optimization is particularly useful in games with dynamic content, where messages may change frequently. By using references and updating them in bulk, you can ensure that your game remains responsive and efficient.

You don't need to worry about the performance impact of updating messages, as the system is designed to handle this efficiently.
  The i18n_create_ref_* functions are the recommended way to create references to messages and assets in GM-I18n. They are designed to be efficient and easy to use, allowing you to manage your localization data effectively without sacrificing performance.

Chunk Loading

Let's say you have a large number of locale files to load, such as langs/en.json, langs/id.json, and langs/ja.json files, which contain a lot of messages. If you load them all at once, it can cause a performance issue, especially if the files are large or if you have many locales.

Fortunately, GM-I18n provides a way to load locale files in chunks, which can help reduce the performance impact of loading large locale files. You can do this by using the time option when initializing the i18n system with i18n_create() function.

This option allows you to specify an interval in seconds between loading each locale. Look at the following example:

Create Event
// Initialize the i18n system without chunk loading
global.i18n = i18n_create("g.i18n", "en", [                                // the `time` option is not set
    new I18nLocaleInit("en", "English", "~/langs/en.json"),                // so all locale files (langs/en.json, langs/id.json, langs/ja.json) will be loaded at once after 1 step of the game
    new I18nLocaleInit("id", "Bhs Indonesia", "~/langs/id.json"),          // it's the same if you set `time: 0`
    new I18nLocaleInit("ja", "日本語", "~/langs/ja.json")                   // may cause a performance issue if you have a lot of messages in these locale files, or you have a lot of locales to load
]);


// Initialize the i18n system with chunk loading
global.i18n = i18n_create("g.i18n", "en", [                                // the `time` option is set to 0.5 seconds
    new I18nLocaleInit("en", "English", "~/langs/en.json"),                // so the locales will be loaded in chunks, with 0.5 seconds interval between each locale
    new I18nLocaleInit("id", "Bhs Indonesia", "~/langs/id.json"),
    new I18nLocaleInit("ja", "日本語", "~/langs/ja.json")
], {
    time: 0.5           // load each locale in chunks with 0.5 seconds interval
});                     // 0.5 seconds -> load langs/en.json -> 0.5 seconds -> load langs/id.json -> 0.5 seconds -> load langs/ja.json

The example above shows how to initialize the i18n system with and without chunk loading. When you set the time option, the system will load each locale file in chunks.

You can do more with chunk loading, such as splitting a large locale file into multiple chunks, so that each chunk can be loaded in a separate step. Look at the following example:

Create Event
// you can split the locale files into multiple chunks
global.i18n = i18n_create("g.i18n", "en", [
    new I18nLocaleInit("en", "English", ["~/langs/en1.json", "~/langs/en2.json"]),          // the splitted files will be loaded in chunks, with 0.5 seconds interval between each chunk
    new I18nLocaleInit("id", "Bhs Indonesia", "~/langs/id.json"),                           // it's useful if you have a lot of messages in the locale
    new I18nLocaleInit("ja", "日本語", "~/langs/ja.json")
], {
    time: 0.5           // load each locale in chunks with 0.5 seconds interval
});                     // 0.5 sec -> load langs/en1.json -> 0.5 sec -> load langs/en2.json -> 0.5 sec -> load langs/id.json -> 0.5 sec -> load langs/ja.json


// you can also control how the chunks are loaded
global.i18n = i18n_create("g.i18n", "en", [
    new I18nLocaleInit("en", "English", ["~/langs/en1.json", "~/langs/en2.json"]),
    new I18nLocaleInit("id", "Bhs Indonesia", "~/langs/id.json"),
    new I18nLocaleInit("ja", "日本語", "~/langs/ja.json")
], {
    time: [0.2, 0.2, 0.5, 1]        // set individual loading times for each locale
});                                 // 0.2 sec -> load langs/en1.json -> 0.2 sec -> load langs/en2.json -> 0.5 sec -> load langs/id.json -> 1 sec -> load langs/ja.json


// use 0 value in the time array to load the locale file after 1 step of the game
global.i18n = i18n_create("g.i18n", "en", [
    new I18nLocaleInit("en", "English", ["~/langs/en1.json", "~/langs/en2.json"]),
    new I18nLocaleInit("id", "Bhs Indonesia", "~/langs/id.json"),
    new I18nLocaleInit("ja", "日本語", "~/langs/ja.json")
], {
    time: [0, 0, 0.5, 0.5]          // set individual loading times for each locale
});                                 // 1 step -> load langs/en1.json -> 1 step -> load langs/en2.json -> 0.5 sec -> load langs/id.json -> 0.5 sec -> load langs/ja.json


// the 0 value in the time array is special
// it will be shifted to the front of the array, so that the locale file will be loaded first
global.i18n = i18n_create("g.i18n", "en", [
    new I18nLocaleInit("en", "English", ["~/langs/en1.json", "~/langs/en2.json"]),
    new I18nLocaleInit("id", "Bhs Indonesia", "~/langs/id.json"),
    new I18nLocaleInit("ja", "日本語", "~/langs/ja.json")
], {
    time: [0, 0.2, 0, 0.3]          // set individual loading times for each locale
});                                 // 1 step -> load langs/en1.json -> 1 step -> load langs/id.json -> 0.2 sec -> load langs/en2.json -> 0.3 sec -> load langs/ja.json
en1.json
{
    "greeting": "Hello",
    "farewell": "Goodbye"
}
en2.json
{
    "welcome": "Welcome to our game!",
    "thank_you": "Thank you for playing!"
}
id.json
{
    "greeting": "Halo",
    "farewell": "Selamat tinggal",
    "welcome": "Selamat datang di permainan kami!",
    "thank_you": "Terima kasih telah bermain!"
}
ja.json
{
    "greeting": "こんにちは",
    "farewell": "さようなら",
    "welcome": "私たちのゲームへようこそ!",
    "thank_you": "プレイしてくれてありがとう!"
}
As you can see, the chunks will be loaded in the order they are defined in the I18nLocaleInit array, and the time option will control how long it takes to load each chunk.

You can use 0 value in the time array to load the locale file after 1 step of the game, which can be useful if you want to load the locale file with important messages first, and then load the rest of the messages in chunks.
Though you can use the time option to control how the chunks are loaded, you should be careful not to set the interval too low, as it can cause performance issues if the locale files are large or if you have many locales to load.

You should also avoid setting the interval to 0 for all locales, as it won't have any effect and will cause all locale files to be loaded at once after 1 step of the game.

Beware that the 0 value in the time array won't be loaded immediately. So, if you get a message or creating a message reference before the locale file is loaded, it will return a string in default_message option (default is an empty string). You need to hardcode the message if you want to use it before the locale file is loaded. Please read the System Flow for more information about how the locale files are loaded.

Other Automatic Optimizations

Don't worry about the other optimizations, I'll take care of them for you as the development of GM-I18n continues. The system is designed to be efficient and will automatically apply various optimizations to ensure that your game runs smoothly, even with extensive localization.


Manual

While the automatic optimizations provided by GM-I18n are generally sufficient for most projects, there are some additional manual optimizations you can apply to further enhance performance:

Drawing Templates

If you're using the drawing preset feature, you can optimize the way you draw messages by mixing drawing presets with the regular drawings (GameMaker's draw_* functions).

Basically, a drawing template is a drawing preset with more abstracted data, which can be used to create multiple drawings with the same structure but different content. This can help reduce the number of individual drawings you need to manage, and can also improve performance by reusing the same drawing settings for multiple messages.

Same as the drawing preset, you can create a drawing template using i18n_add_drawings() function, but with simpler data structure. For example, you can create a drawing template like this:

Create Event
// assume you've already initialized the i18n system

// here's some drawing templates
// you must include all available locales in the first argument (locale), so that the drawing template can be used in all locales
i18n_add_drawings(["en", "id", "ja"], ["t_button", "t_header", "t_body"], [             // the `t_` prefix is used to indicate that this is a drawing template
    new I18nDrawings(, fa_center, fa_middle, #000000, 0.65),                          // just fill the parameters that have the same value in all locales
    new I18nDrawings(, fa_left, fa_middle, #FFFFFF, 0.65),                            // and skip the parameters that have different values in each locale
    new I18nDrawings(, fa_left, fa_top, #CCCCCC, 0.49, 0, 1, -1, 1198) 
]);

// then you can add the missing parameters in previous drawing templates
// this missing parameters will be used as the dynamic data for the drawing template
i18n_add_drawings(["en", "id"], "font1", new I18nDrawings(fnNotoSans));
i18n_add_drawings(["ja"], "font1", new I18nDrawings(global.font_ja));

// create some messages that will be drawn
my_msg = i18n_get_messages("desc", , "en");                // "This is a description of the game." static message in en

my_msg_ref = i18n_create_ref_message("my_msg_ref", "header", [
    "John"                                                 // this is a dynamic message with indexed data, will be used in the drawing template
]);

// and later you can mix the drawing templates with the regular drawings
// see the Draw/Draw GUI Event example for more details
Draw/Draw GUI Event
// a simple example of how to use the drawing templates

// set your dynamic valued data using built-in `draw_set_*` functions
draw_set_font(i18n_get_drawings_data("font1", I18N_DRAWING.FONT));      // this font is dynamic, so it will be changed when the locale is changed
i18n_draw_message(100, 50, "@:title", , "title");                       // "Game Title" in en.json, "Judul Permainan" in id.json, "ゲームタイトル" in ja.json
                                                                        // using "title" drawing template

// bad practice, using the same drawing template again
i18n_draw_message(100, 100, "@:button", , "title");                     // "Click Me" in en.json, "Klik Saya" in id.json, "クリックしてください" in ja.json
                                                                        // using "title" drawing template, while the last used drawing template is "title"

// good practice, don't use the same drawing template again
i18n_draw_message(100, 150, "@:header");                                // "Welcome to the Game, {0}!" in en.json, "Selamat Datang di Permainan, {0}!" in id.json, "ゲームへようこそ、{0}さん!" in ja.json
                                                                        // still using "title" drawing template

// using built-in `draw_*` functions 
draw_set_font(i18n_get_drawings_data("font1", I18N_DRAWING.FONT, "en"));        // specific locale, so it will always use the same font
// draw_set_halign(i18n_get_drawings_data("t_body", I18N_DRAWING.HALIGN));      // in case you don't want to use some data from the drawing template, just don't set it
draw_set_valign(fa_middle);                                                     // you can override the data from the drawing template

draw_text(100, 200, my_msg);                                            // and then draw the static message "This is a description of the game." 
i18n_draw_message(100, 250, my_msg_ref);                                // draw the dynamic message "Welcome to the Game, John!" using the previous drawing functions
en.json
{
    "title": "Game Title",
    "button": "Click Me",
    "header": "Welcome to the Game, {0}!",
    "desc": "This is a description of the game."
}
id.json
{
    "title": "Judul Permainan",
    "button": "Klik Saya",
    "header": "Selamat Datang di Permainan, {0}!",
    "desc": "Ini adalah deskripsi permainan."
}
ja.json
{
    "title": "ゲームタイトル",
    "button": "クリックしてください",
    "header": "ゲームへようこそ、{0}さん!",
    "desc": "これはゲームの説明です。"
}
This example may not cover all of the implementations of drawing templates, but it shows the general idea of how to use drawing templates in GM-I18n.

For more information about the drawing, please read the Direct Drawings section. It may help you to understand how to use drawing templates in your game.

  With these optimizations, the GM-I18n system should be able to handle large projects with extensive localization without sacrificing performance.