Scriptable Object Localization

So i’m new with the Localization Package (which is super useful by the way), added a dropdown to pick different languages, textmeshpro works like a charm with that… perfect.
The problem is that i have a ton of NPCs with different Scriptable Objects which contains the strings of dialog, how can i “Localize” them?

Thanks in advance!

Hi,

You could use LocalizeString in Scriptable Objects.

Localize the TextMeshPro Text (by adding LocalizeStringEvent component).

And use:

Instead of:

Best,
Alexis

8 Likes

Thanks Alexis, TMP are working fine, but i can’t figure how to use the LocalizedString, i have a list like this:

List<LocalizedString> phrases

And BEFORE i just did:

String phrase = phrases[i];

But as LocalizedString is not a string anymore i’m not sure how to work with them, can’t find any example in the localization package documents.

Hello,

It’s depend of your goal.

  • If you want to localize a string with the current language and don’t localize it when language change, you can do something like this:

I work with task to control the asynchronous function but you can work with coroutine.
- If you want to localize a string with the current language and localize it when language change, it’s more complex…
I advise you to use TMP_Text and don’t use string…
I hope this answer your question!
Best,
Alexis

This is for a dialog system, so each npc has a scriptable object with a list of phrases (strings), i changed that to a list of LocalizedString, the problem comes when i’m reading the phrases to print them in the UI via TMP, i just want to get the current localization string from the LocalizedString key, but to change the TMP text depending on the dialog index:
TMP.text = phrases[1];
(presses next)
TMP.text = phrases[2];
(presses next)
TMP.text = phrases[3];

I can no longer do that cause “phrases” now contains LocalizedString instead of string.

Ok!

you need to change:

by:

LocalizeStringEvent is the added component when you localize TMP_Text component in editor.

Best,
Alexis

1 Like

Thanks Alexis, finally i came up with the solution:

UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle<string> localizedText = _conversacion[_indexConversacion].GetLocalizedString();

if (localizedText.IsDone)
    this.dialogTextTMP.text = localizedText.Result;

The only problem is that “isDone” takes a bit of time so i have to re-call the method, i think i should use an IEnumerator to wait for the text to be ready but i’m not 100% sure how, i’m doing some testings… any ideas are welcome!

Ok so i’m currently calling this method from my MonoBehaviour “Awake()”:

private async Task preloadConversation() {
        for (int i = 0; i < npcScriptableObject.localizedStringsConversation.Count; i++) {
            UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle<string> localizedText = npcScriptableObject.localizedStringsConversation[i].GetLocalizedString();

            while (!localizedText.IsDone)
                await Task.Delay(10);

            conversation.Add(localizedText.Result);
        }
}

Not sure if this is the best method… i’m bassically loading the strings into a list while the scene loads.

I’ve tried this instead but didn’t work, don’t know why, this one with “StartCorutine()”:

private IEnumerator preloadConversation() {
        for (int i = 0; i < npcScriptableObject.localizedStringsConversation.Count; i++) {
            UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle<string> localizedText = npcScriptableObject.localizedStringsConversation[i].GetLocalizedString();
            yield return new WaitUntil(() => localizedText.IsDone);
            conversation.Add(localizedText.Result);
        }

}

Hi,

hum try this:

private async Task preloadConversation() {
    for (int i = 0; i < npcScriptableObject.localizedStringsConversation.Count; i++)
    {
        string localizedText =
            await npcScriptableObject.localizedStringsConversation[i].GetLocalizedString().Task;

        conversation.Add(localizedText);
    }
}

I’m not a coroutine expert, so I will investigate and edit this post if I find something.

Best,
Alexis

What do you mean it doesnt work?
I have had mixed results using Tasks. I find Coroutines to be more reliable

        private IEnumerator PreloadConversation()
        {
            for (int i = 0; i < npcScriptableObject.localizedStringsConversation.Count; i++)
            {
                var localizedText = npcScriptableObject.localizedStringsConversation[i].GetLocalizedString();
                yield return localizedText;
                conversation.Add(localizedText.Result);
            }
        }

To make the above faster you could call GetLocalizedString on all strings and then yield on them. if you check the IsDone flag then this should also help as you wont need to yield an extra frame when the string is already loaded

Thanks or your reply, unfortunately i’m getting this:

ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index
System.ThrowHelper.ThrowArgumentOutOfRangeException (System.ExceptionArgument argument, System.ExceptionResource resource) (at :0)
System.ThrowHelper.ThrowArgumentOutOfRangeException () (at :0)
System.Collections.Generic.List`1[T].get_Item (System.Int32 index) (at :0)
DialogManager_SC.MostrarFraseConversacion () (at Assets/Scripts…

When trying to retrieve the string from the list, in other words, is not preloading the strings.

So THIS works:

void Awake() {
        if (npcScriptableObject != null)
            PreloadConversation();
}

private async Task PreloadConversation() {
        for (int i = 0; i < npcScriptableObject.localizedStringsConversation.Count; i++) {
            UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle<string> localizedText = npcScriptableObject.localizedStringsConversation[i].GetLocalizedString();

            while (!localizedText.IsDone)
                await Task.Delay(10);

            conversation.Add(localizedText.Result);
        }
}

But this DOESN’T:

void Awake() {
        if (npcScriptableObject != null)
           StartCoroutine(PreloadConversation());
}
private IEnumerator PreloadConversation() {
    for (int i = 0; i < npcScriptableObject.localizedStringsConversation.Count; i++) {
        var localizedText = npcScriptableObject.localizedStringsConversation[i].GetLocalizedString();
        yield return localizedText;
        conversation.Add(localizedText.Result);
    }
}

What does the full script look like?

It’s a bit complex, it’s a dialog system, so the basics are:
Awake → Preload
On collision enter 2d → launch dialog, which starts on index 0
Hit interact button → show next index

With the “IEnumerator” method the list is empty, is not preloading anything, so when you try to access any of the indexes it throws that error.

I did a bit of debug:

private async Task PreloadConversation() {
        for (int i = 0; i < npcScriptableObject.localizedStringsConversation.Count; i++) {
            UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle<string> localizedText = npcScriptableObject.localizedStringsConversation[i].GetLocalizedString();
            Debug.Log("Preloading = " + i);
            while (!localizedText.IsDone)
                await Task.Delay(10);

            Debug.Log("Preloaded correctly = " + localizedText.Result);
            conversation.Add(localizedText.Result);
        }
}

And got in console:

  • Preloading = 0
  • Preloaded correctly = Hi my name is Mike…
  • Preloading = 1
  • Preloaded correctly = …

So it’s loading the strings as i already know because the NPC dialogs are working and beight shown.

But with this:

private IEnumerator PreloadConversation() {
    for (int i = 0; i < npcScriptableObject.localizedStringsConversation.Count; i++) {
        var localizedText = npcScriptableObject.localizedStringsConversation[i].GetLocalizedString();
        Debug.Log("Preloading = " + i);
        yield return localizedText;
        conversation.Add(localizedText.Result);
        Debug.Log("Preloaded correctly = " + localizedText.Result);
    }
}

I only got:

  • Preloading = 0 (one per scriptable object).

I think it could be because it’s inside a loop? don’t know, but it never reaches the part in which i get the result.

It’s just a way to preload ALL strings from ALL tables in the game start so i can access them instantly while in runtime? thanks!

Did you start the function with StartCouroutine?
Try StartCoroutine(PreloadConversation());

I did it on awake()

void Awake() {
    StartCoroutine(PreloadConversation());
}

Does that work?

Nope, that’s how i did it the first time (comment #11) it only shows “Preloading = 0 (one per scriptable object).” and it never loads the string.

Isn’t just a method to preload all tables/strings so i can access them instantly in runtime?

What’s the code that sets up localizedStringsConversation look like?
There is no immediate preloading because we need to wait for the addressable asset system to load the asset bundles and this updates in LateUpdate so will be at least 1 frame.

It’s assigned in the inspector, so i pick every scriptable object localizedStrings from inspector 1 by 1.