Search Unity

Scriptable Object Localization

Discussion in 'Localization Tools' started by Goty-Metal, Aug 10, 2020.

  1. Goty-Metal

    Goty-Metal

    Joined:
    Apr 4, 2020
    Posts:
    168
    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!
     
  2. Alexis-Dev

    Alexis-Dev

    Joined:
    Apr 16, 2019
    Posts:
    121
    Hi,

    You could use LocalizeString in Scriptable Objects.
    Localize the TextMeshPro Text (by adding LocalizeStringEvent component).

    And use:
    Instead of:
    Best,
    Alexis
     
    Strom_CL, tzdevil, Xesk and 5 others like this.
  3. Goty-Metal

    Goty-Metal

    Joined:
    Apr 4, 2020
    Posts:
    168
    Thanks Alexis, TMP are working fine, but i can't figure how to use the LocalizedString, i have a list like this:
    Code (CSharp):
    1. List<LocalizedString> phrases
    And BEFORE i just did:
    Code (CSharp):
    1. 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.
     
  4. Alexis-Dev

    Alexis-Dev

    Joined:
    Apr 16, 2019
    Posts:
    121
    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
     
  5. Goty-Metal

    Goty-Metal

    Joined:
    Apr 4, 2020
    Posts:
    168
    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.
     
  6. Alexis-Dev

    Alexis-Dev

    Joined:
    Apr 16, 2019
    Posts:
    121
    Ok!

    you need to change:
    by:
    LocalizeStringEvent is the added component when you localize TMP_Text component in editor.

    Best,
    Alexis
     
    karl_jones likes this.
  7. Goty-Metal

    Goty-Metal

    Joined:
    Apr 4, 2020
    Posts:
    168
    Thanks Alexis, finally i came up with the solution:

    Code (CSharp):
    1. UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle<string> localizedText = _conversacion[_indexConversacion].GetLocalizedString();
    2.  
    3. if (localizedText.IsDone)
    4.     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!
     
  8. Goty-Metal

    Goty-Metal

    Joined:
    Apr 4, 2020
    Posts:
    168
    Ok so i'm currently calling this method from my MonoBehaviour "Awake()":
    Code (CSharp):
    1. private async Task preloadConversation() {
    2.         for (int i = 0; i < npcScriptableObject.localizedStringsConversation.Count; i++) {
    3.             UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle<string> localizedText = npcScriptableObject.localizedStringsConversation[i].GetLocalizedString();
    4.  
    5.             while (!localizedText.IsDone)
    6.                 await Task.Delay(10);
    7.  
    8.             conversation.Add(localizedText.Result);
    9.         }
    10. }
    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()":

    Code (CSharp):
    1. private IEnumerator preloadConversation() {
    2.         for (int i = 0; i < npcScriptableObject.localizedStringsConversation.Count; i++) {
    3.             UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle<string> localizedText = npcScriptableObject.localizedStringsConversation[i].GetLocalizedString();
    4.             yield return new WaitUntil(() => localizedText.IsDone);
    5.             conversation.Add(localizedText.Result);
    6.         }
    7.  
    8. }
     
  9. Alexis-Dev

    Alexis-Dev

    Joined:
    Apr 16, 2019
    Posts:
    121
    Hi,

    hum try this:
    Code (CSharp):
    1.        
    2. private async Task preloadConversation() {
    3.     for (int i = 0; i < npcScriptableObject.localizedStringsConversation.Count; i++)
    4.     {
    5.         string localizedText =
    6.             await npcScriptableObject.localizedStringsConversation[i].GetLocalizedString().Task;
    7.  
    8.         conversation.Add(localizedText);
    9.     }
    10. }
    11.  
    I'm not a coroutine expert, so I will investigate and edit this post if I find something.

    Best,
    Alexis
     
  10. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,300
    What do you mean it doesnt work?
    I have had mixed results using Tasks. I find Coroutines to be more reliable
    Code (csharp):
    1.  
    2.         private IEnumerator PreloadConversation()
    3.         {
    4.             for (int i = 0; i < npcScriptableObject.localizedStringsConversation.Count; i++)
    5.             {
    6.                 var localizedText = npcScriptableObject.localizedStringsConversation[i].GetLocalizedString();
    7.                 yield return localizedText;
    8.                 conversation.Add(localizedText.Result);
    9.             }
    10.         }
    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
     
  11. Goty-Metal

    Goty-Metal

    Joined:
    Apr 4, 2020
    Posts:
    168
    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 <fb001e01371b4adca20013e0ac763896>:0)
    System.ThrowHelper.ThrowArgumentOutOfRangeException () (at <fb001e01371b4adca20013e0ac763896>:0)
    System.Collections.Generic.List`1[T].get_Item (System.Int32 index) (at <fb001e01371b4adca20013e0ac763896>: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:
    Code (CSharp):
    1.  
    2. void Awake() {
    3.         if (npcScriptableObject != null)
    4.             PreloadConversation();
    5. }
    6.  
    7. private async Task PreloadConversation() {
    8.         for (int i = 0; i < npcScriptableObject.localizedStringsConversation.Count; i++) {
    9.             UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle<string> localizedText = npcScriptableObject.localizedStringsConversation[i].GetLocalizedString();
    10.  
    11.             while (!localizedText.IsDone)
    12.                 await Task.Delay(10);
    13.  
    14.             conversation.Add(localizedText.Result);
    15.         }
    16. }

    But this DOESN'T:
    Code (CSharp):
    1.  
    2. void Awake() {
    3.         if (npcScriptableObject != null)
    4.            StartCoroutine(PreloadConversation());
    5. }
    6. private IEnumerator PreloadConversation() {
    7.     for (int i = 0; i < npcScriptableObject.localizedStringsConversation.Count; i++) {
    8.         var localizedText = npcScriptableObject.localizedStringsConversation[i].GetLocalizedString();
    9.         yield return localizedText;
    10.         conversation.Add(localizedText.Result);
    11.     }
    12. }
     
  12. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,300
    What does the full script look like?
     
  13. Goty-Metal

    Goty-Metal

    Joined:
    Apr 4, 2020
    Posts:
    168
    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:

    Code (CSharp):
    1.  
    2. private async Task PreloadConversation() {
    3.         for (int i = 0; i < npcScriptableObject.localizedStringsConversation.Count; i++) {
    4.             UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle<string> localizedText = npcScriptableObject.localizedStringsConversation[i].GetLocalizedString();
    5.             Debug.Log("Preloading = " + i);
    6.             while (!localizedText.IsDone)
    7.                 await Task.Delay(10);
    8.  
    9.             Debug.Log("Preloaded correctly = " + localizedText.Result);
    10.             conversation.Add(localizedText.Result);
    11.         }
    12. }
    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:

    Code (CSharp):
    1. private IEnumerator PreloadConversation() {
    2.     for (int i = 0; i < npcScriptableObject.localizedStringsConversation.Count; i++) {
    3.         var localizedText = npcScriptableObject.localizedStringsConversation[i].GetLocalizedString();
    4.         Debug.Log("Preloading = " + i);
    5.         yield return localizedText;
    6.         conversation.Add(localizedText.Result);
    7.         Debug.Log("Preloaded correctly = " + localizedText.Result);
    8.     }
    9. }
    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.
     
  14. Goty-Metal

    Goty-Metal

    Joined:
    Apr 4, 2020
    Posts:
    168
    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!
     
  15. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,300
    Did you start the function with StartCouroutine?
    Try StartCoroutine(PreloadConversation());
     
  16. Goty-Metal

    Goty-Metal

    Joined:
    Apr 4, 2020
    Posts:
    168
    I did it on awake()

    Code (CSharp):
    1. void Awake() {
    2.     StartCoroutine(PreloadConversation());
    3. }
     
  17. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,300
    Does that work?
     
  18. Goty-Metal

    Goty-Metal

    Joined:
    Apr 4, 2020
    Posts:
    168
    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?
     
  19. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,300
    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.
     
  20. Goty-Metal

    Goty-Metal

    Joined:
    Apr 4, 2020
    Posts:
    168
    It's assigned in the inspector, so i pick every scriptable object localizedStrings from inspector 1 by 1.
     
  21. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,300
    Are you waiting for the preloading coroutine to finish before you try and use the localized strings? I think you may be trying to access them in the same frame. You need to wait for them all, then maybe set a flag to indicate the loading is finished.
     
  22. Goty-Metal

    Goty-Metal

    Joined:
    Apr 4, 2020
    Posts:
    168
    I can wait an entire minute if you want, they never load like with the task method, really i don't know why, maybe because it's inside a loop, don't worry, if the task one works is OK =D
     
  23. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,300
    Hmm one thing it could be is that they are already done. Try checking the IsDone flag and only yield if it is false.
     
  24. Goty-Metal

    Goty-Metal

    Joined:
    Apr 4, 2020
    Posts:
    168
    Again index error, it's not loading with corutines.
    By the way just found that you can preload tables by ticking the "Preload Table" or "Preload All Tables" checkbox, but i'm doing some testings and some entries are loaded and others are NOT, this is really weird, i'm starting to think that 0.7.1 is bugged someway...
     
  25. Goty-Metal

    Goty-Metal

    Joined:
    Apr 4, 2020
    Posts:
    168
    So i've updated to 0.8.0 preview and preloading tables works (with the checkbox), so it was a bug on 0.7.1, i'm goign to use that method so i don't have to preload the strings manually, thanks for the help!
     
    karl_jones likes this.
  26. adrianfrancisco

    adrianfrancisco

    Joined:
    Jul 29, 2021
    Posts:
    14
    Goty-Metal could you please explain how did you make it work finally? I have a scriptable object with just a string that I want to localize. When I add LocalizedString I can not edit the text and save the text in the scriptable object because there's no window on the inspector nor I can access a string inside this LocalizedString. I'm reading the thread several times but I'm still lost... Thank you!!

    PS: I really think unity should make a tutorial on how to localize scriptable objects
     
  27. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,300
    You should have an editor for the localized string in the inspector. What does yours look like?
     
    adrianfrancisco likes this.
  28. adrianfrancisco

    adrianfrancisco

    Joined:
    Jul 29, 2021
    Posts:
    14
    Thank you! the problem was that I was trying to format it with [Space(10)][TextArea(30, 30)] (Because the text is quite long) so that made it look empty and saying: use "TextAreaDrawer with string". I removed that and a menu appeared. Until now I have been Localizing using the Localization Scene Controls which has made it super easy (Except for being quite buggy when messing with RectTransforms of UI elements). But now I am using the Lozalization Tables editor and I think it will work out. Thank you everybody!
     
  29. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,300
    Oh yes, TextArea won't be applied to the LocalizedString property drawer. Can you file a bug report about those RectTransforms issues?
     
  30. adrianfrancisco

    adrianfrancisco

    Joined:
    Jul 29, 2021
    Posts:
    14
    This actually didn't work for me
    For me it was (This part is the same)
    Code (CSharp):
    1. public class ItemObject : ScriptableObject
    2. {
    3.  public LocalizedString nameKey;
    4. }
    With this a panel in the inspector would open where you can add a new table entry with the translations.
    And then, referencing the TextMeshPro and the ScriptableObject without having to add the LocalizeStringEvent component, just by:
    Code (CSharp):
    1. [SerializeField] private ItemObject itemObject;
    2. [SerializeField] private TMP_Text text;
    3.  
    4. public void Start()
    5. {
    6.  text.text = itemObject.nameKey.GetLocalizedString();
    7. }
    This worked for me, it is just what I was looking for, and it is very simple.

    EDIT: Sure karl_jones, will do! (But they happen any time I touch the RectTransform with the Tracking Changes On, so I thought it would be a well known bug... I'm using Unity 2020.3.14f1)
     
    karl_jones likes this.
  31. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,300
    No Im not aware of any bugs like this.