Localization With Save&Load System

There was a point where I got stuck in the localization system again. My game has an Inbox system. As the player progresses in the game, he receives various messages, and all of them arrive dynamically. I learned to translate dynamic text, but now I have a problem: I save these texts and reload them after the scene changes or the game is restored. What method can I follow in this case? Because there will be different data in each message, it would be a bit ridiculous to keep them one by one. What can I do about this?

using System;
using UnityEngine;

[System.Serializable]
public class MessageData
{
    public string title;
    public string content;
    public string sender;
    public DateTime timestamp;
    public CalendarManager dateTime;
    public bool isRead;
    public Sprite parchment;
}

I keep the messages as above, I keep the title and content in string form.

public string ResultText(string clanName, BattleResult result, string date, string playerScore, string enemyScore)
{
    return SetResultContent(result) +
        "<b>Outcome: </b>" + result.ToString() + "\r\n" +
        "<b>Date: </b>" + date + "\r\n" +
        "<b>Enemy Clan: </b>" + clanName + "\r\n" +
        "<b>Score: </b>" + playerScore + " - " + enemyScore;
}

I posted a sample message here, I put the returned string value into the content variable in MessageData and save it.
I’m using ScriptableObject and JSON for the logging system.

Are you using the localization package or is this a general localization question?

I would not recommend the concat approach you are using in ResultText, it would be better to smart strings or string.format to give the translator more context and handle any differences for a particular language.

It sounds like you would need to store the data you are passing into the messages. You could convert the data into strings and just store them that way:
E.G public string[ ] arguments;

I gave some incomplete information. Yes, I’m using Unity’s localization system and the current ResultText is the normal text I used to use. I will translate these, but first I need to solve the problem I mentioned.

For example, I translated it as follows:

Outcome: {0}
Date: {1}
Enemy Clan:
{2} Score: {3} - {4}

Now you have that you can convert your arguments into strings and store them with the message data.
Is MessageData what you store per message that you want translated?
You could add the arguments into it:

E.G

[System.Serializable]
public class MessageData
{
    public string title;
    public string content;
    public string sender;
    public DateTime timestamp;
    public CalendarManager dateTime;
    public bool isRead;
    public Sprite parchment;
    public string[] arguments;
}

Then you do something like this:

public string GetMessage(MessageData md)
{
    return LocalizationSettings.StringDatabase.GetLocalizedString("MyTable", "My Message", md.arguments.Select(s => (object)s).ToArray());
}

While I was thinking about it later, a similar method came to my mind. I will put it into practice today and if it works I will report it here. Thanks for the answer.

1 Like

Hello @karl_jones
I was working on this problem and I tried to develop such a way.
I created a function of type LocalizedString in the MessageReferences class and returned the Key and arguments I wanted.

public class MessageReferences : MonoBehaviour
{
    [SerializeField] PlayerInformations playerInfo;

    public LocalizedString WelcomeTitleLocalize()
    {
        LocalizedString localizedString = new LocalizedString();
        localizedString.TableEntryReference = "WelcomeTitleKey";
        localizedString.Arguments = new object[] { playerInfo.playerClan.clanName, playerInfo.playerName };
        localizedString.Arguments[0] = playerInfo.playerClan.clanName;
        localizedString.Arguments[1] = playerInfo.playerName;
        return localizedString;
    }
}

I keep the LocalizedString variable in the MessageData class.

public class MessageData
{
    public string[] titleArguments;
    public string[] contentArguments;

    public LocalizedString titleLS;
    public LocalizedString contentLS;
}

In the InboxManager class, I assign the Local Table I created in the MessageReferences class to MessageData. Then I create the object and activate the function in the object.

private void Start()
{
    
        AddAndCreateMessage(messageReferences.WelcomeTitleLocalize(), messageReferences.WelcomeContent());
}
void AddAndCreateMessage(LocalizedString localizedString, string content)
{
    MessageData newMessage = new MessageData();
    newMessage.titleLS = localizedString;
    newMessage.timestamp = calendarSO.dateTime;
    newMessage.isRead = false;
    newMessage.parchment = closeParchment;
    inboxMessages.Add(newMessage);
    messageDataSO.AddMessage(newMessage);
    if (SceneManager.GetActiveScene().buildIndex == 1 && !isFirstMessages) CreateTitle(newMessage, localizedString);
}

void CreateTitle(MessageData message)
{
    GameObject title = Instantiate(titlePrefab, titleTransform);
    titleObjects.Add(title);
    title.transform.SetAsFirstSibling();

    string[] arguments = new string[message.titleLS.Arguments.Count];
    for (int i = 0; i < message.titleLS.Arguments.Count; i++)
    {
        arguments[i] = message.titleLS.Arguments[i].ToString();
    }

    MessageLocalize messageLocalize = title.GetComponent<MessageLocalize>();
    messageLocalize.EnterTitleText(message.titleLS.TableEntryReference.Key, arguments);
    //TextMeshProUGUI titleText = title.GetComponentInChildren<TextMeshProUGUI>();
    //titleText.text = message.title;

}

MessageLocalize is in the title object. Here I take the Key and Arguments from outside and assign them to the Localize Table in the title object, then I refresh it.

public class MessageLocalize : MonoBehaviour
{
    [SerializeField] LocalizedString titleLocalString;
    [SerializeField] TextMeshProUGUI titleText;

    private void OnEnable()
    {
        titleLocalString.StringChanged += UpdateText;
    }

    private void OnDisable()
    {
        titleLocalString.StringChanged -= UpdateText;
    }

    void UpdateText(string value)
    {
        titleText.text = value;
    }

    public void EnterTitleText(string value, string[] arguments)
    {
        titleLocalString.TableEntryReference = value;
        titleLocalString.Arguments = arguments;
        titleLocalString.RefreshString();
    }
}

I have done something similar to this elsewhere and it works but not in the title in the message box. When I check it, it throws the arguments and key properly, but when I change the language there is no change. I have marked the values as “smart” on the table, what could be the problem?

Im not sure, could you put together a simple project to show the issue?

I fixed the problem, thank you. If I have any other questions about this, I’ll come back to ask. You’ve saved me a couple of times, thank you very much. :slight_smile:

1 Like

@karl_jones Sorry to bother you again, I fixed the title part of the messages but I encountered an error like this in the content part.

FormattingException: Error parsing format string: Could not evaluate the selector “1” at 36
Sayın {0} Klanına, yeni Han olarak {1} seçildi.

Meesage reference part;

public LocalizedString WelcomeContentLocalize()
{
    LocalizedString localizedString = new LocalizedString();
    localizedString.TableEntryReference = "WelcomeContentKey";
    localizedString.Arguments = new object[] { playerInfo.playerClan.clanName, playerInfo.playerName };
    localizedString.Arguments[0] = playerInfo.playerClan.clanName;
    localizedString.Arguments[1] = playerInfo.playerName;
    return localizedString;
}

The content creation part where I create the messages.

public void OnMessageClick(MessageData messageData)
{
        string[] contentArguments = GetArguments(messageData.contentLS);
        contentLocalized.EnterTitleText(messageData.contentLS.TableEntryReference.Key, contentArguments);
}

string[] GetArguments(LocalizedString localizedString)
{
    string[] arguments = new string[localizedString.Arguments.Count];
    for (int i = 0; i < localizedString.Arguments.Count; i++)
    {
        arguments[i] = localizedString.Arguments[i].ToString();
    }
    return arguments;
}

Localize the string part:

public void EnterTitleText(string value, string[] arguments)
{
    titleLocalString.TableEntryReference = value;
    titleLocalString.Arguments = arguments;
    titleLocalString.RefreshString();
}

Localization table;

I did the same steps for title, but it runs it without any problems, but it gives the above error when creating content. When I delete the {1} part, the error is fixed and it works properly. What could be the cause of this, actually the arguments are the same as in title.
Extra information: I create the title later in the game but it stays in a fixed place in the Content Canvas. Both keys are in the same table.

9703001--1385495--upload_2024-3-15_15-30-26.png

9703001--1385486--upload_2024-3-15_15-27-33.png

localizedString.Arguments = new object[] { playerInfo.playerClan.clanName, playerInfo.playerName };
localizedString.Arguments[0] = playerInfo.playerClan.clanName;
localizedString.Arguments[1] = playerInfo.playerName;

You dont need to do this twice.

This is enough:

localizedString.Arguments = new object[] { playerInfo.playerClan.clanName, playerInfo.playerName };

The error indicates that there is no argument {1}. It could be that its trying to format before you have assigned the Arguments?

Thanks for the information, somewhere I looked, he did it this way, but I think I understand the reason now.

Yes, I think it was a related problem, now I closed the object from the scene and opened it when I was going to assign a content value to it and the problem was solved. Thank you very much.YYin There is still a part that is stuck in my mind, when I debug it, I saw that the arguments were thrown, I couldn’t fully understand the problem.

What do you mean the arguments were thrown?

Sorry, I wrote it wrong, argument values were assigned during debugging, but it still gave an error. Maybe I missed something.

After the problem was solved, something like this happened: It no longer gave an error, but even though the value {1} was assigned on the work computer, it did not appear in the translation. When I went home and tried it on my own computer without doing anything, the {1} argument appeared in the translation. Does this differ from computer to computer? When I go back to my work computer, I’ll show it visually if the problem still exists.

Are you able to reproduce this in a simple example project?

If the same problem occurs, let me show you. Thanks for your help, you are truly an amazing person.

1 Like

Hi @karl_jones
I encountered an interesting problem. Currently the system is working properly. Let me briefly explain what I did.
In MessageData, I keep two LocalizedString type variables for the title and content, and I created string type variables for the keys and string[ ] type variables for the arguments. When saving the game, I save it in these string variables, and when loading it, I create the LocalizedString variable again from the key and argument.

So far everything is working normally and properly. When I close and reopen the game, there is no problem with the save system, but when I change the scene, it gives an error.
There are three scenes in my game, the start scene, the game scene and the battle scene. The system I mentioned above works on the game screen, I keep the messages in a list and when I return to the game scene from the battle scene, I re-create the messages in the list.

When I say come back after the war is over, it gives an error while creating the message, the error is:
FormattingException: Error parsing format string: Could not evaluate the selector “1” at 13

9707663--1386662--upload_2024-3-18_11-2-10.jpeg

When I debug and check, I see that the arguments are assigned properly.

After receiving this error, when I close the game and open it again, it loads the message without any problems, that is, it can load again, but it gives such an error when returning from the battle scene. An error occurs without entering the function I mentioned below.

public void EnterTitleText(string value, string[] arguments)
{
    titleLocalString.TableEntryReference = value;
    titleLocalString.Arguments = arguments;
    titleLocalString.RefreshString();
}

Here, value and arguments appear as they should, but
titleLocalString.TableEntryReference = value;
When assigning in this section, it gives the error I mentioned above.

Do you have any idea about the reason?

It sounds like it may be initializing before you have applied the arguments? Is this part of a LocalizeStringEvent component? Try starting with the component disabled and then enable it once you have assigned the Arguments.

1 Like

Yes, I solved the problem this way. Thanks!

1 Like

Hi @karl_jones
When localizing the game, some texts may not fit into the text box. If I use GameObjectLocalizer for this situation and only change the font for that language, will this cause a conflict?
There is a LocalizeStringEvent component on the text and I change the texts from there. If I add GameObjectLocalizer to the same Text and only change the font of the Text, will this cause any problems or is there another way?

I don’t want to use auto size.

No that should be fine to do.

1 Like