Tutorial: Making a Special Order Compatible with Other Languages (i18n)

From Stardew Modding Wiki
Jump to navigation Jump to search

In this tutorial, we will format the example from the tutorial on Adding a Special Order to make it compatible with other languages, using i18n formatting. We have a tutorial as well as documentation from Content Patcher (CP) explaining what i18n is.

To make our special order compatible with other languages, we will create an i18n folder and default.json file and create i18n keys/values for those strings that will be used in the content.json and the default.json.

If you use the game's translation {Item:TextPlural} system, then you'll also be interested in the section on adding the strings in a patch (thank you to atravita for pointing this out!). Note that this tutorial doesn't contain an example of how to use that, but you can unpack your game files and check out the SpecialOrderStrings.json to see it in action.

We are still using CP for this. I will explain a file/folder structure and i18n key/value nomenclature similar to the one used in Townies Need Food (partly adapted from Lumisteria Special Orders), and I will note where you could make different choices in the i18n key/value nomenclature, depending on your preference.

Assumptions we're working with

This tutorial assumes you are familiar with the basics of how to create a special order (tutorial here) and are only here to learn about how to format the special order to make it compatible with other languages.

Set up i18n folder and default.json file

First, in addition to the content.json file and manifest.json file you needed for the mod (tutorial on this here), we will need to create an i18n folder, and a default.json file to go in that folder. A default.json file is created the same way as a content.json / manifest.json file. The default.json file will contain the string texts to show, and we will edit the contents later. Your mod folders should look like this:

Mod Folder Structure

Original completed example reference

For reference, this is the completed example from the tutorial on Adding a Special Order:

{
        "LogName": "Abigail Crunchy Treats",
        "Action": "EditData",
        "Target": "Data/SpecialOrders",
        "Entries": {
          "ExampleMod_Abigail_CrunchyTreats": {
            "Name": "Crunchy Treats", // This is the special order name that will show on the orders board and in the player journal
            "Requester": "Abigail",
            "Duration": "Week",
            "Repeatable": "True",
            "RequiredTags": "!season_spring, !season_summer",
            "OrderType": "",
            "SpecialRule": "",
            "ItemToRemoveOnEnd": null,
            "MailToRemoveOnEnd": null,
            "RandomizedElements": null,
            "Text": "I'm craving some crunchy treats! Could someone bring me 10 quartz and 10 amethyst?", // The text shown on the special order board and the journal,
            "Objectives": [
                {
                  "Type": "Deliver",
                  "Text": "Deliver 10 Quartz to Abigail", // The text for the objective in the journal
                  "RequiredCount": "10",
                  "Data": {
                    "AcceptedContextTags": "item_quartz",
                    "Message": "Quartz really sparkles, doesn't it? Thanks @!$h", // What the NPC says when you deliver the item
                    "TargetName": "Abigail" // Which NPC to deliver the item to
                  }
                },
                {
                  "Type": "Deliver",
                  "Text": "Deliver 10 Amethyst to Abigail",
                  "RequiredCount": "10",
                  "Data": {
                    "AcceptedContextTags": "item_amethyst",
                    "Message": "It definitely tastes of purple!$h",
                    "TargetName": "Abigail"
                  }
                }
              ],
              "Rewards": [
                {
                  "Type": "Money",
                  "Data": {
                    "Amount": "4026"
                  }
                },
                {
                  "Type": "Friendship",
                  "Data": {}
                }
              ]
          }
        }
  }

Create i18n keys and values

Next, we will use the order ID we established, ExampleMod_Abigail_CrunchyTreats, the first key in Entries, to create the tags for the relevant text fields. These will be used in the content.json as well as in the default.json. Specifically, these are:

String name in the file Key for i18n Value for i18n Actual string to go in default.json file
Name in Entries ExampleMod_Abigail_CrunchyTreats_Name {{i18n:ExampleMod_Abigail_CrunchyTreats_Name}} Crunchy Treats
Text in Entries ExampleMod_Abigail_CrunchyTreats_Text {{i18n:ExampleMod_Abigail_CrunchyTreats_Text}} I'm craving some crunchy treats! Could someone bring me 10 quartz and 10 amethyst?
Text in Entries > Objectives (from 1st objective) ExampleMod_Abigail_CrunchyTreats_Objective_Text_0 {{i18n:ExampleMod_Abigail_CrunchyTreats_Objective_Text_0}} Deliver 10 Quartz to Abigail
Message in Entries > Objectives > Data (from 1st objective) ExampleMod_Abigail_CrunchyTreats_Message_0 {{i18n:ExampleMod_Abigail_CrunchyTreats_Message_0}} Quartz really sparkles, doesn't it? Thanks @!$h
Text in Entries > Objectives (from 2nd objective) ExampleMod_Abigail_CrunchyTreats_Objective_Text_1 {{i18n:ExampleMod_Abigail_CrunchyTreats_Objective_Text_1}} Deliver 10 Amethyst to Abigail
Message in Entries > Objectives > Data (from 2nd objective) ExampleMod_Abigail_CrunchyTreats_Message_1 {{i18n:ExampleMod_Abigail_CrunchyTreats_Message_1}} It definitely tastes of purple!$h

Note that you could have chosen to format the keys and values a little differently, based on personal preference. For instance, the keys and values for the first row, Name, could have easily been ExampleMod_Abigail_CrunchyTreats_Order_Name and {{i18n:ExampleMod_Abigail_CrunchyTreats_Order_Name}}. It's up to you to decide how specific you want to get.

Since we have multiple objectives, we have to denote the strings for each with something. In this example, it's _X at the end of the key and value, starting with 0 and then increasing by 1. Since Stardew Valley starts counting from 0, it seemed natural to follow that convention. You could potentially use something else if you prefer; this is just what I've tested.

Add patch for strings in content.json file (only needed when using the game's translation {Item:TextPlural} system)

Using a patch to add the strings in the content.json file is only needed when using the game's translation {Item:TextPlural} system, which will look like this, using the keys and values from the table above (thank you to atravita for pointing this out!):

{
        "LogName": "Strings Abigail Crunchy Treats",
        "Action": "EditData",
        "Target": "Strings/SpecialOrderStrings",
        "Entries": {
          "ExampleMod_Abigail_CrunchyTreats_Name": "{{i18n:ExampleMod_Abigail_CrunchyTreats_Name}}",
          "ExampleMod_Abigail_CrunchyTreats_Text": "{{i18n:ExampleMod_Abigail_CrunchyTreats_Text}}",
          "ExampleMod_Abigail_CrunchyTreats_Objective_Text_0": "{{i18n:ExampleMod_Abigail_CrunchyTreats_Objective_Text_0}}",
          "ExampleMod_Abigail_CrunchyTreats_Message_0": "{{i18n:ExampleMod_Abigail_CrunchyTreats_Message_0}}"
        }
      }

Add i18n keys and strings in default.json file

Then we will go to the default.json file, and add the i18n keys and the actual strings text, so the default.json file should look like this:

{
  "ExampleMod_Abigail_CrunchyTreats_Name": "Crunchy Treats",
  "ExampleMod_Abigail_CrunchyTreats_Text": "I'm craving some crunchy treats! Could someone bring me 10 quartz and 10 amethyst?",
  "ExampleMod_Abigail_CrunchyTreats_Objective_Text_0": "Deliver 10 Quartz to Abigail",
  "ExampleMod_Abigail_CrunchyTreats_Message_0": "Quartz really sparkles, doesn't it? Thanks @!$h",
  "ExampleMod_Abigail_CrunchyTreats_Objective_Text_1": "Deliver 10 Amethyst to Abigail",
  "ExampleMod_Abigail_CrunchyTreats_Message_1": "It definitely tastes of purple!$h",
}

Add i18n keys in the special order in content.json file (didn't use the game's translation {Item:TextPlural} system)

Lastly, now that we have the strings in the default.json file, we can edit the original special order so that it lists the i18n keys from the table above in place of the string text. Our special order in the content.json should now look like this:

{
      "LogName": "Abigail Crunchy Treats",
      "Action": "EditData",
      "Target": "Data/SpecialOrders",
      "Entries": {
        "ExampleMod_Abigail_CrunchyTreats": {
          "Name": "{{i18n:ExampleMod_Abigail_CrunchyTreats_Name}}",
          "Requester": "Abigail",
          "Duration": "Week",
          "Repeatable": "True",
          "RequiredTags": "!season_spring, !season_summer",
          "OrderType": "",
          "SpecialRule": "",
          "Text": "{{i18n:ExampleMod_Abigail_CrunchyTreats_Text}}",
          "ItemToRemoveOnEnd": null,
          "MailToRemoveOnEnd": null,
          "RandomizedElements": null,
          "Objectives": [
            {
              "Type": "Deliver",
              "Text": "{{i18n:ExampleMod_Abigail_CrunchyTreats_Objective_Text_0}}",
              "RequiredCount": "10",
              "Data": {
                "AcceptedContextTags": "item_quartz",
                "Message": "{{i18n:ExampleMod_Abigail_CrunchyTreats_Message_0}}",
                "TargetName": "Abigail"
              }
            },
            {
              "Type": "Deliver",
              "Text": "{{i18n:ExampleMod_Abigail_CrunchyTreats_Objective_Text_1}}",
              "RequiredCount": "10",
              "Data": {
                "AcceptedContextTags": "item_amethyst",
                "Message": "{{i18n:ExampleMod_Abigail_CrunchyTreats_Message_1}}",
                "TargetName": "Abigail"
              }
            }
          ],
          "Rewards": [
            {
              "Type": "Money",
              "Data": {
                "Amount": "4026"
              }
            },
            {
              "Type": "Friendship",
              "Data": {}
            }
          ]
        }
      }
    }

Add i18n keys in the special order in content.json file (did use the game's translation {Item:TextPlural} system)

Lastly, now that we have the patch to add the strings, and the strings are in the default.json file, we can edit the original special order so that it lists the i18n keys from the table above in place of the string text. Our special order in the content.json should now look like this (thank you to atravita for pointing this out!):

{
      "LogName": "Abigail Crunchy Treats",
      "Action": "EditData",
      "Target": "Data/SpecialOrders",
      "Entries": {
        "ExampleMod_Abigail_CrunchyTreats": {
          "Name": "[ExampleMod_Abigail_CrunchyTreats_Name]",
          "Requester": "Abigail",
          "Duration": "Week",
          "Repeatable": "True",
          "RequiredTags": "!season_spring, !season_summer",
          "OrderType": "",
          "SpecialRule": "",
          "Text": "[ExampleMod_Abigail_CrunchyTreats_Text]",
          "ItemToRemoveOnEnd": null,
          "MailToRemoveOnEnd": null,
          "RandomizedElements": null,
          "Objectives": [
            {
              "Type": "Deliver",
              "Text": "[ExampleMod_Abigail_CrunchyTreats_Objective_Text_0]",
              "RequiredCount": "10",
              "Data": {
                "AcceptedContextTags": "item_quartz",
                "Message": "[ExampleMod_Abigail_CrunchyTreats_Message_0]",
                "TargetName": "Abigail"
              }
            },
            {
              "Type": "Deliver",
              "Text": "[ExampleMod_Abigail_CrunchyTreats_Objective_Text_1]",
              "RequiredCount": "10",
              "Data": {
                "AcceptedContextTags": "item_amethyst",
                "Message": "[ExampleMod_Abigail_CrunchyTreats_Message_1]",
                "TargetName": "Abigail"
              }
            }
          ],
          "Rewards": [
            {
              "Type": "Money",
              "Data": {
                "Amount": "4026"
              }
            },
            {
              "Type": "Friendship",
              "Data": {}
            }
          ]
        }
      }
    }

Test the special order

If you made it this far, congratulations, you have made your special order compatible with other languages! Don't forget to test your order, to make sure all the strings display correctly.

Set up the special order for i18n from the start

Now that you know how to convert an existing special order to i18n format, you may be wondering how you can start your special order with i18n in mind.

My approach is to write the order per the step Add i18n keys in the special order in content.json file (step 4 and 7 bundled together), then write the strings patch in the content.json file (step 5), and then lastly write the actual strings in the default.json file (step 6). If you find it easier to write the mod in a different order than that, that's fine too! As long as you cover all 4 steps, you're set.

See also