Pluralization

Pluralization is a process of handling the plural form of a word. It's also known as plural handling or plural form in other localization system. It's also a powerful feature that makes any internationalization system complete!

Let's say, you have a message that says "You have {plural_value} apple". It's correct if you have only one apple. But if you have more than one apple, it should be "You have {plural_value} apples", right? That's where pluralization comes in handy!


Static Pluralization

GM-I18n provides a simple pluralization feature that can handle the plural form of a word/phrase in the message.

Format: form0 | form1 | ...
Access: <index> or {plural: <index>}
Used In: i18n_get_messages(), i18n_create_ref_message(), i18n_draw_message()

For the static pluralization, the simplest way, you need to provide 2 forms of the word/phrase in the message. Generally, the first form is for singular, and the second form is for plural. And then you can pass the index of the form you want to use in the data.

Create Event
// assume the system is initialized on global variable
// i18n_get_messages(key, [data], [locale], [i18n])
// i18n_create_ref_message(var_name, key, [data], [i18n])

// static messages, static pluralization
msg1 = i18n_get_messages("have_apple");             // "You don't have any apple | You have apple(s)", raw message

msg2 = [                                            // in an array
    i18n_get_messages("have_apple", 0)              // "You don't have any apple", 0 = directly use the first form
];  

msg3 = {                                                        // in a struct
    my_msg : i18n_get_messages("have_apple", 1, "id");          // "Kamu punya apel", 1 = directly use the second form
}; 

msg4 = i18n_get_messages("have_apple_cnt", {plural: 1});        // "I have 1 apple", {plural: 1} = use the second form through the `plural` key
msg5 = i18n_get_messages("have_apple_cnt", {                    // "I have 10 apples", {plural: 2} = use the third form, {count: 10} = replace the {plural_value} placeholder
    plural : 2,                                                 // need to set the index value of the `plural` key manually
    plural_value : 10                                           // interpolate the {plural_value} placeholder
});

// dynamic messages, static pluralization, no difference with static messages 
global.ref_msg1 = i18n_create_ref_message("global.ref_msg1", "have_apple");

global.ref_msg2 = [
    i18n_create_ref_message("g.ref_msg2.0", "have_apple", 0)
];

ref_msg3 = {
    my_msg : i18n_create_ref_message("ref_msg3.my_msg", "have_apple", 1, "id")
}

global.ref_msg4 = i18n_create_ref_message("global.ref_msg4", "have_apple_cnt", {plural: 1});
ref_msg5 = i18n_create_ref_message("ref_msg5", "have_apple_cnt", {
    plural : 2, 
    plural_value : 10
});
en.json
{
    "have_apple": "You don't have any apple | You have apple(s)",
    "have_apple_cnt": "I don't have any apple | I have 1 apple | I have {plural_value} apples"
}
id.json
{
    "have_apple": "Kamu tidak punya apel | Kamu punya apel",
    "have_apple_cnt": "Aku tidak punya apel | Aku punya 1 apel | Aku punya {plural_value} apel"
}
You can use the <index> or {plural: <index>} to access the plural form. Both are valid. But it's recommended to use the {plural: <index>} because it's more readable and flexible. You can see the example in the next section.

The plural form is static. So, if you change the value of the plural key after creating the message, it won't affect the plural form in the message.

You can't use the pluralization feature with the indexed data. You need to use the named data instead. See the Interpolation section for more information.

Dynamic Pluralization

Dynamic pluralization use the same format as static pluralization, but the forms are dynamic. So, you can change the plural form in the message, even after creating the message.

Format: form0 | form1 | ...
Access: <index> or {plural: <index>} or {plural: <function>, plural_value: <value>}
Used In: i18n_get_messages(), i18n_create_ref_message(), i18n_draw_message()

You can make your own pluralization rule in the plural key in the named data. The function will be called with the plural_value as the parameter, and then it will return the index of the plural form you want to use. It's great for complex pluralization rule!

In Static Messages

This method is quite easy to implement the dynamic pluralization. You just need to update the message every step, and the pluralization will be updated automatically based on your pluralization rule.

Pros:

  • Quite easy to implement.
  • No need to create the message reference.
  • Can be used in anywhere in the game, just like a static message.

Cons:

  • Need to update the message every step (may cause performance issue).
  • Only support with direct pluralization (by passing <index> to the data parameter in the message).
  • Not suitable for complex pluralization rule.
  • No natural support for interpolated, linked, or nested message.
Create Event
// assume the system is initialized on global variable

// the value to be passed to the pluralization function
apple_count = 0;
plural_index = 0;

// a simple method for pluralization rule
plural_rule = function() {      
    // modify the plural_index based on the apple_count value
    plural_index = !apple_count ? 0 : 1;
}


// static messages in instance variable
msg1 = i18n_get_messages("have_apple_cnt", 0, "en");        // "I don't have any apple", will updated in Step Event

msg2 = [
    ""                                                      // empty string, will updated in Step Event
];

msg3 = {
    my_msg : i18n_get_messages("confirm", 0, "en")          // "No", will updated in Step Event
};

// static messages in global variable
global.my_msg = i18n_get_messages("have_apple_cnt", {
    plural : 0,                                             // "Aku tidak punya apel", will updated in Step Event
    plural_value : apple_count
}, "id");   

global.my_arr = [
    i18n_get_messages("shop.buy", plural_index, "en")       // "Buy 1 {name}? | Buy {plural_value} {name}{suffix}?", will updated in Step Event
];

global.my_struct = {
    my_msg : i18n_get_messages("confirm", 1, "en")          // "Yes", will updated in Step Event
};
Step Event
// update the message every step, so the pluralization will be updated automatically
// only applicable for static messages

msg1 = i18n_get_messages("have_apple_cnt",                  // simple pluralization rule
    (!apple_count ? 0 : (apple_count == 1 ? 1 : 2)),        // if apple_count is 0, use the first form (index 0). if apple_count is 1, use the second form (index 1). otherwise, use the third form (index 2)
"en");                                                      // will change to "I don't have any apple", "I have 1 apple", or "I have {plural_value} apples" based on the apple_count value

msg2[0] = i18n_get_messages("shop.buy",
    (apple_count == 1 ? 0 : 1),                             // if apple_count is 1, use the first form. otherwise, use the second form
"en");                                                      // will change to "Buy 1 {name}?" or "Buy {plural_value} {name}{suffix}?" based on the apple_count value

msg3.my_msg = i18n_get_messages("confirm",
    (apple_count >= 10 ? 0 : 1),                            // if apple_count is 10 or more, use the first form. otherwise, use the second form
"en");                                                      // will change to "No" or "Yes" based on the apple_count value

global.my_msg = i18n_get_messages("have_apple_cnt", {       // passing a struct as the data in event that update every step is DISCOURAGED!
    plural : (apple_count == 0 ? 0 : 1),                    // if apple_count is 0, use the first form. otherwise, use the second form
    plural_value : apple_count                              // interpolate the {plural_value} placeholder
}, "id");                                                   // will change to "Aku tidak punya apel" or "Aku punya {plural_value} apel" (interpolated) based on the apple_count value

global.my_arr[0] = i18n_get_messages("shop.buy", plural_index, "en");

global.my_struct.my_msg = i18n_get_messages("confirm",
    (apple_count >= 10 ? 0 : 1),
"en");
Key Pressed - Up
// increase the apple count
apple_count++;

// call the pluralization rule
plural_rule();
Key Pressed - Down
// decrease the apple count
apple_count--;

// call the pluralization rule
plural_rule();
en.json
{
    "have_apple_cnt": "I don't have any apple | I have 1 apple | I have {plural_value} apples",
    "confirm": "No | Yes",
    "shop": {
        "template_1": "Your {resc} is",
        "buy": "Buy 1 {name}? | Buy {plural_value} {name}{suffix}?",
        "confirm": "[[shop.template_1]]n't enough to buy {name} | [[shop.template_1]] enough to buy 1 {name} | [[shop.template_1]] enough to buy {plural_value} {name}{suffix}"
    }
}
id.json
{
    "have_apple_cnt": "Aku tidak punya apel | Aku punya {plural_value} apel",
    "confirm": "Tidak | Ya",
    "shop": {
        "template_1": "{resc} kamu",
        "buy": "Beli 1 {name}? | Beli {plural_value} {name}?",
        "confirm": "[[shop.template_1]] tidak cukup untuk membeli {name} | [[shop.template_1]] cukup untuk membeli {plural_value} {name}"
    }

}
The pluralization function (ternary operator in the example above) is called every time you update the message. So, it's better to make the function as simple as possible to avoid performance issue.

You can also store a value in a variable (such as plural_index), and then pass the variable to the data parameter.
The static pluralization is not suitable for complex pluralization rule. Each language has its own pluralization rule, and it's impossible to cover all of them with a simple function.

Using Direct Drawing

This method is the simplest and quickest way to implement the dynamic pluralization. You just need to pass the plural index (or a simple function that return the index) and the key message to the i18n_draw_message() function.

Pros:

  • Super easy and quick to implement. Good choice if you only need to draw the message.
  • No need to create the message reference, not even create a variable for storing the message.
  • Can draw a static or dynamic message, with static or dynamic pluralization.

Cons:

  • Only used for drawing the message. The Draw Event or Draw GUI Event is the only place you can use this method.
  • May cause a performance issue if you have a lot of message keys in that locale (like, 1000+ keys in a locale).
  • Only support with direct pluralization (by passing <index> to the data parameter in the message).
  • Not suitable for complex pluralization rule.
  • No natural support for interpolated, linked, or nested message.
Create Event
// assume the system is initialized on global variable

// the value to be passed to the pluralization function
apple_count = 0;
plural_index = 0;

// a simple method for pluralization rule
plural_rule = function() {      
    // modify the plural_index based on the apple_count value
    plural_index = !apple_count ? 0 : 1;
}
Draw Event
// i18n_draw_message(x, y, text, [data], [locale], [i18n])

// static message and pluralization
i18n_draw_message(100, 50, "@:have_apple_cnt", 0, "en");        // "I don't have any apple", will always use the first form (index 0)

// static message and dynamic pluralization
i18n_draw_message(100, 50, "@:confirm", plural_index, "id");    // "Tidak" if plural_index is 0, or "Ya" if plural_index is 1

i18n_draw_message(100, 50, "@:have_apple_cnt",
    (!apple_count ? 0 : (apple_count == 1 ? 1 : 2)),            // "I don't have any apple" if apple_count is 0, "I have 1 apple" if apple_count is 1, "I have {plural_value} apples" otherwise (not interpolated)
"en");

i18n_draw_message(100, 50, "@:have_apple_cnt", {                // passing a struct as the data in event that update every step is DISCOURAGED!
    plural : (apple_count == 0 ? 0 : 1),                        // "Aku tidak punya apel" if apple_count is 0, "Aku punya {plural_value} apel" otherwise (interpolated)
    plural_value : apple_count
}, "id");

// dynamic message and pluralization
i18n_draw_message(100, 50, "@:confirm", (plural_index == 1));   // "No" / "Tidak" if plural_index is 0, or "Yes" / "Ya" if plural_index is 1

i18n_draw_message(100, 50, "@:shop.buy", 
    (apple_count == 1 ? 0 : 1)                                  // if apple_count is 1, use the first form. otherwise, use the second form
);

i18n_draw_message(100, 50, "@:have_apple_cnt", 
    (i18n_get_locale() == "en"                                  // a bit complex pluralization rule
        ? (!apple_count ? 0 : (apple_count == 1 ? 1 : 2))       // rule for en locale: "I don't have any apple" if apple_count is 0, "I have 1 apple" if apple_count is 1, "I have {plural_value} apples" otherwise (not interpolated)
        : (apple_count >= 1 ? 1 : 0))                           // rule for other locale (id): "Aku punya {plural_value} apel" if apple_count is 1 or more (not interpolated)
);
Key Pressed - Up
// increase the apple count
apple_count++;

// call the pluralization rule
plural_rule();
Key Pressed - Down
// decrease the apple count
apple_count--;
  
// call the pluralization rule
plural_rule();
en.json
{
    "have_apple_cnt": "I don't have any apple | I have 1 apple | I have {plural_value} apples",
    "confirm": "No | Yes",
    "shop": {
        "template_1": "Your {resc} is",
        "buy": "Buy 1 {name}? | Buy {plural_value} {name}{suffix}?",
        "confirm": "[[shop.template_1]]n't enough to buy {name} | [[shop.template_1]] enough to buy 1 {name} | [[shop.template_1]] enough to buy {plural_value} {name}{suffix}"
    }
}
id.json
{
    "have_apple_cnt": "Aku tidak punya apel | Aku punya {plural_value} apel",
    "confirm": "Tidak | Ya",
    "shop": {
        "template_1": "{resc} kamu",
        "buy": "Beli 1 {name}? | Beli {plural_value} {name}?",
        "confirm": "[[shop.template_1]] tidak cukup untuk membeli {name} | [[shop.template_1]] cukup untuk membeli {plural_value} {name}"
    }

}
You can add @: prefix to the text parameter in the i18n_draw_message() function to use the message key instead of the actual text. It's useful if you want to use the same message key in different locale (so it become a dynamic message). See the example above.

The pluralization function (ternary operator in the example above) is called every time you draw the message. So, it's better to make the function as simple as possible to avoid performance issue.

You can also store a value in a variable (such as plural_index), and then pass the variable to the data parameter.
The direct drawing is not suitable for complex pluralization rule. Each language has its own pluralization rule, and it's impossible to cover all of them with a simple function.

The dynamic message in this method come with a performance cost. GM-I18n system will lookup the message from the locale data every time you draw the message. So, use it wisely.

Using Message Reference

This method is the most flexible and powerful way to implement the dynamic pluralization. It's recommended for you to implement the dynamic pluralization with this method.

Pros:

  • A true dynamic message.
  • Really flexible and powerful to implement the dynamic pluralization.
  • Suitable for complex pluralization rule.
  • Can apply interpolation, linked message, and nested message.
  • Really fast performance. The message is only lookup once, and then it will be updated if the locale is changed.
  • For the pluralization, you don't need to call the update function every time you want to update the message. Just update the data (plural_value), and the message will be updated automatically.

Cons:

  • Take more effort to implement.
  • The pluralization rule may be a bit complex if you want to support multiple locale.
  • You need to manage the message reference yourself (such as updating the data).
Create
// assume the system is initialized on global variable

// the value to be passed to the pluralization function
apple_count = 0;
plural_index = 0;

// dynamic messages and pluralization
// in instance variable
ref_msg1 = i18n_create_ref_message("simple_ref", "confirm", {
    plural: function(plural_value) {                            // the function for simple pluralization rule
        return plural_value >= 10 ? 0 : 1;                      // if plural_value is 10 or more, use the first form (index 0). otherwise, use the second form (index 1)
    },
    plural_value: apple_count                                   // the value to be passed to the pluralization function
});

// in global variable
global.ref_msg2 = i18n_create_ref_message("global.ref_msg1", "have_apple_cnt", {
    plural: function(plural_value) {                                            // the function for complex pluralization rule, you need to make 1 parameter to pass the `plural_value` 
        if (i18n_get_locale() == "en") {                                        // en locale has 3 forms
            return !plural_value ? 0 : (plural_value == 1 ? 1 : 2);             // if plural_value is 0, use the first form (index 0). if plural_value is 1, use the second form (index 1). otherwise, use the third form (index 2)
        } else if (i18n_get_locale() == "id") {                                 // id locale has only 2 forms
            return !plural_value ? 0 : 1;                                       // if plural_value is 0, use the first form (index 0). otherwise, use the second form (index 1)
        }
    },
    plural_value: apple_count                                                   // the value to be passed to the pluralization function
});

// in an array
ref_msg3 = [
    i18n_create_ref_message("ref_msg3.0", "shop.buy", {
        plural: function(plural_value) {
            return plural_value == 1 ? 0 : 1;               // if plural_value is 1, use the first form (index 0). otherwise, use the second form (index 1)
        },
        plural_value: apple_count,                          // because we are using named data, the interpolation will work
        name: "apple",                                      // if plural_value is 0, the message is "Buy 1 apple?" or "Beli 1 apple?" (the "apple" still in english!)
        suffix: "s"                                         // otherwise, the message is "Buy {plural_value} apples?" or "Beli {plural_value} apple?" (the {plural_value} is interpolated, but the "apple" still in english!})
    })
];

// in a struct
global.ref_msg4 = {
    my_msg : i18n_create_ref_message("g.ref_msg4.my_msg", "confirm", {
        plural: function(plural_value) {
            return plural_value >= 10 ? 0 : 1;
        },
        plural_value: apple_count
    }),
    my_arr : [
        i18n_create_ref_message("global.ref_msg4.my_arr.0", "shop.confirm", {
            plural: function(plural_value) {
                if (i18n_get_locale() == "en") {                                        // en locale has 3 forms
                    return !plural_value ? 0 : (plural_value == 1 ? 1 : 2);             // if plural_value is 0, use the first form (index 0). if plural_value is 1, use the second form (index 1). otherwise, use the third form (index 2)
                } else if (i18n_get_locale() == "id") {                                 // id locale has only 2 forms
                    return !plural_value ? 0 : 1;                                       // if plural_value is 0, use the first form (index 0). otherwise, use the second form (index 1)
                }
            },
            plural_value: apple_count,
            name: "apple",
            resc: "copper coin",
            suffix: "s"
        })
    ]
};

// see the key pressed event for the update function
Key Pressed - Up
// increase the apple count
apple_count++;

// i18n_update_plurals(var_name, plural_value, [update_refs], [i18n])
// update the plural value in the message reference
i18n_update_plurals("ref_msg1", apple_count);                   // don't pass the `true` here 

// i18n_update_plurals("global.ref_msg2", apple_count);         // in case you don't want to update the reference,
                                                                // just don't call the function
                                                                // for optimization, you should pass the `true` to update the reference, on the LAST message reference that you want to update
i18n_update_plurals("ref_msg3.0", apple_count);                 // don't pass the `true` here
i18n_update_plurals("g.ref_msg4.my_msg", apple_count);          // don't pass the `true` here too
i18n_update_plurals("g.ref_msg4.my_arr.0", apple_count, true);  // pass the `true` here, because this is the LAST message reference that we want to update
Key Pressed - Down
// decrease the apple count
apple_count--;

// update the plural value in the message reference
i18n_update_plurals("global.ref_msg2", apple_count, true);      // an example if you only want to update plural value on 1 reference
Key Pressed - Space
// change the locale
// i18n_set_locale(code, [update_refs], [i18n])
i18n_set_locale((i18n_get_locale() == "en" ? "id" : "en"));     // a simple locale switcher
en.json
{
    "have_apple_cnt": "I don't have any apple | I have 1 apple | I have {plural_value} apples",
    "confirm": "No | Yes",
    "shop": {
        "template_1": "Your {resc} is",
        "buy": "Buy 1 {name}? | Buy {plural_value} {name}{suffix}?",
        "confirm": "[[shop.template_1]]n't enough to buy {name} | [[shop.template_1]] enough to buy 1 {name} | [[shop.template_1]] enough to buy {plural_value} {name}{suffix}"
    }
}
id.json
{
    "have_apple_cnt": "Aku tidak punya apel | Aku punya {plural_value} apel",
    "confirm": "Tidak | Ya",
    "shop": {
        "template_1": "{resc} kamu",
        "buy": "Beli 1 {name}? | Beli {plural_value} {name}?",
        "confirm": "[[shop.template_1]] tidak cukup untuk membeli {name} | [[shop.template_1]] cukup untuk membeli {plural_value} {name}"
    }

}
Remember that the plural in the data is the rule that return the index of the plural form you want to use. You need to make sure that the function have 1 parameter, and return the correct index.

The plural_value is not limited to number type. You can use any data type that you want. For example, you can use string, array, struct, etc. But of course, the simplest and most common type is a number type.

Though the plural_value is reserved for pluralization, you can still use it for interpolation as well.

You can get a dynamic interpolation by updating the plural_value in the message reference using i18n_update_plurals() function.

If you're using both pluralization and interpolation in the message, the system will process the pluralization first, and then the interpolation.
The plural and plural_value in the data is reserved for pluralization. You can't use it for other purpose.

You need to pass the true to the update_refs parameter in the i18n_update_plurals() function after you change the plural_value in the message reference. Otherwise, the message reference won't be updated. See the example in the Key Pressed - Up and Key Pressed - Down event.

For optimization, you should only pass the true to the update_refs parameter in the i18n_update_plurals() function on the LAST message reference that you want to update. Passing true on every i18n_update_plurals() function may cause performance issue. See the correct usage in the Key Pressed - Up and Key Pressed - Down event.

Don't calli18n_update_plurals()in any event that updated every step (such as Step Event, Draw Event, etc.)! It may cause a performance issue, and it's even worse than updating the message every step.

Summary

There are 3 ways to implement the dynamic pluralization in GM-I18n. Each have their own pros and cons. You need to choose the one that best fit your needs, or just mix and match them!

  1. Updating the Message Every Step
    • Pros: Quite easy to implement. No need to create the message reference.
    • Cons: Not suitable for complex pluralization rule. No natural support for interpolated, linked, or nested message.
  2. Direct Drawing
    • Pros: Super easy and quick to implement. Good choice if you only need to draw the message.
    • Cons: Only used for drawing the message. Not suitable for complex pluralization rule. No natural support for interpolated, linked, or nested message.
  3. Using Message Reference
    • Pros: The most flexible and powerful way to implement the pluralization. Suitable for complex pluralization rule. Have natural support for interpolated, linked, or nested message.
    • Cons: A bit more complex to implement. Need to create the message reference.

You can't use the pluralization feature with the indexed data. You need to use the named data instead. See the Interpolation section for more information.

You need to return the index of the plural form in the plural function explicitly (such as 0 or 1). If you don't the system won't know which plural form you want to use. See the example above which return the explicit index in the plural function using ternary operator.

You can customize the starting pluralization index (default: 0) and the delimiter (default: |) in the i18n_create() function.

  With pluralization, you can create a more natural and flexible message that can adapt to the user's input or game state. It's a powerful feature that can enhance the user experience in your game!