Search Unity

How to update LocalizeString at runtime

Discussion in 'Localization Tools' started by default_team, Sep 13, 2020.

  1. default_team

    default_team

    Joined:
    Dec 1, 2018
    Posts:
    21
    I'm updating the string reference of a LocalizeString at runtime. But the UI.Text won't update unless I reset the LocalizationSettings.SelectedLocale.

    Code (CSharp):
    1. setLocalizedStringReference(LocalizedStringReference ref) {
    2.      localizedString.StringReference = ref;
    3.      LocalizationSettings.SelectedLocale = differentLocale;
    4. }
    As shown in the example, I had to set SelectedLocale to a different locale, for the UI.Text to be updated.
    But I want to change the StringReference without having to switch the locale.
     
  2. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,300
    Hey. Could you file a bug report please?
     
  3. default_team

    default_team

    Joined:
    Dec 1, 2018
    Posts:
    21
    Will do. BTW when we change LocalizedString.StringReference does it supposed to update the corresponding UI.Text as well?

    I'm using package 0.3.1 as I'm using Unity 2018.3
     
  4. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,300
    Yes, it should update the text. Don't use 0.3.1!!!!
    It's a year old and not at all compatible with the current version.
    You won't be able to upgrade in the future, things have changed a lot.
    Unfortunately, the Localization system just does not support 2018.3. This is because we rely heavily on the 2019.3 SerializeReference feature.
     
    default_team likes this.
  5. viv5552

    viv5552

    Joined:
    Dec 21, 2020
    Posts:
    7
    Same problem on Unity 2019.4.16f1 localization package 0.9
    Also how to localize Dropdown - TextMeshPro options?
     
  6. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,300
    What does your code look like?
    You can use a script to localise drop-downmenu options. You will also be able to do it using the localized properties when they are available later in the year
     
  7. viv5552

    viv5552

    Joined:
    Dec 21, 2020
    Posts:
    7
    My code is very simple. It's a clicker game based on one of the official unity learn tutorials. There's a score int variable being calculated among other stuff inside GameManager.cs attached to Game Manager empty game object. Score is displayed on canvas as Text-TextMeshPro. Before implementing localization displaying the score was also handled inside GameManager.cs like this:
    Code (CSharp):
    1.     public TextMeshProUGUI scoreText;
    2.     public int score;
    3. ...
    4.     public void UpdateScore (int scoreToAdd)
    5.     {
    6.    
    7.         score += scoreToAdd;
    8.         scoreText.text = "Score: " + score;
    9.     }
    10. ...
    11.  
    UpdateScore method is called from this method inside GameManager.cs which is responsible for spawning targets adding 5 pts at each spawn
    Code (CSharp):
    1.     IEnumerator SpawnTarget()
    2.     {
    3.         while (isGameActive)
    4.         {
    5.             UpdateScore(5);
    6.             yield return new WaitForSeconds(spawnRate);
    7.             int index = Random.Range(0, targets.Count);
    8.             Instantiate(targets[index]);
    9.          
    10.         }
    11.     }
    12.  
    And also from this method inside Target.cs which takes cost from a script attached to each target prefab and uses it as an argument to GameManager.UpdateScore()
    Code (CSharp):
    1.     private void OnMouseDown()
    2.     {
    3.         if (gameManager.isGameActive)
    4.         {
    5.             Destroy(gameObject);
    6.             gameManager.UpdateScore(GetComponent<Cost>().cost);
    7.             Instantiate(explosionParticle, transform.position, explosionParticle.transform.rotation);
    8.         }
    9.     }
    10.  
    For implementing localization for the score text i used this video as a guide:


    I commented out all TextMeshProUGUI related stuff from GameManager.cs, attached Localize String Event component to TextMeshPro gameobject, created this script:
    Code (CSharp):
    1. public class LocalizationSmartFields : MonoBehaviour
    2. {
    3.     public string scorestring;
    4.     private GameManager gameManager;
    5.  
    6.     void Start()
    7.     {
    8.          gameManager = GameObject.Find("Game Manager").GetComponent<GameManager>();
    9.  
    10.     }
    11.  
    12.     void Update()
    13.     {
    14.         scorestring = gameManager.score.ToString();
    15.     }
    16. }
    17.  
    and dragged it into the Format Arguments field of Localize String Event .
    As a result score only updates when i switch a language during playing using package's dropdown in top right corner of the game window.
    Maybe i should use something from those examples you mentioned in my unity answers thread but my noobish skills aren't enough to figure out which one of those string related examples responsible for what.
    I had no problem implementing localization to strings that aren't being changed during play though (apart from localizing a dropdowns which i still have no idea how to approach)
     
  8. viv5552

    viv5552

    Joined:
    Dec 21, 2020
    Posts:
    7
    Also after implementing localization this error always being displayed in console after pressing play. It doesn't affect playability in any way though.

    UnityException: get_isUpdating is not allowed to be called during serialization, call it from OnEnable instead. Called from ScriptableObject 'StringTableCollection'.
    See "Script Serialization" page in the Unity Manual for further details.
    UnityEditor.AddressableAssets.AddressableAssetSettingsDefaultObject.get_Settings () (at Library/PackageCache/com.unity.addressables@1.16.6/Editor/AddressableAssetSettingsDefaultObject.cs:109)
    UnityEditor.AddressableAssets.AddressableAssetSettingsDefaultObject.GetSettings (System.Boolean create) (at Library/PackageCache/com.unity.addressables@1.16.6/Editor/AddressableAssetSettingsDefaultObject.cs:169)
    UnityEditor.Localization.LocalizationEditorSettings.GetAddressableAssetSettings (System.Boolean create) (at Library/PackageCache/com.unity.localization@0.9.0-preview/Editor/Settings/LocalizationEditorSettings.cs:384)
    UnityEditor.Localization.LocalizationEditorSettings.GetLocalesInternal () (at Library/PackageCache/com.unity.localization@0.9.0-preview/Editor/Settings/LocalizationEditorSettings.cs:547)
    UnityEditor.Localization.LocalizationEditorSettings.GetLocales () (at Library/PackageCache/com.unity.localization@0.9.0-preview/Editor/Settings/LocalizationEditorSettings.cs:104)
    UnityEditor.Localization.Plugins.CSV.Columns.ColumnMapping.AddLocaleMappings (System.Collections.Generic.IList`1[T] cells, System.Boolean includeComments) (at Library/PackageCache/com.unity.localization@0.9.0-preview/Editor/Plugins/CSV/Columns/ColumnMapping.cs:19)
    UnityEditor.Localization.Plugins.CSV.Columns.ColumnMapping.CreateDefaultMapping (System.Boolean includeComments) (at Library/PackageCache/com.unity.localization@0.9.0-preview/Editor/Plugins/CSV/Columns/ColumnMapping.cs:13)
    UnityEditor.Localization.Plugins.CSV.CsvExtension..ctor () (at Library/PackageCache/com.unity.localization@0.9.0-preview/Editor/Plugins/CSV/CsvExtension.cs:13)
     
  9. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,300
    Ok try this
    Code (csharp):
    1.  
    2. using TMPro;
    3. using UnityEngine;
    4. using UnityEngine.Localization;
    5.  
    6. public class ScoreScript : MonoBehaviour
    7. {
    8.     public LocalizedString localizedScore; // "Score: {0}"
    9.     public TextMeshProUGUI scoreText;
    10.     public int score;
    11.  
    12.     void OnEnable()
    13.     {
    14.         localizedScore.Arguments = new object[]{ score }; // 1 argument which is the score
    15.         localizedScore.StringChanged += UpdateScoreText;
    16.     }
    17.  
    18.     void OnDisable()
    19.     {
    20.         localizedScore.StringChanged -= UpdateScoreText;
    21.     }
    22.  
    23.     void RefreshString()
    24.     {
    25.         localizedScore.Arguments[0] = score; // Update the score argument in the string
    26.         localizedScore.RefreshString();
    27.     }
    28.  
    29.     public void UpdateScore(int scoreToAdd)
    30.     {
    31.         score += scoreToAdd;
    32.         RefreshString();
    33.     }
    34.  
    35.     void UpdateScoreText(string text)
    36.     {
    37.         scoreText.text = text;
    38.     }
    39. }
    40.  
     
  10. viv5552

    viv5552

    Joined:
    Dec 21, 2020
    Posts:
    7
    I'm not sure how am i supposed to integrate it into my existing project.
    I created script copypasting your code, then i renamed int score to int scorestring just because that's how it was named in my localization table (i mean for the score there's "Score: {scorestring}" in english locale, "Очки: {scorestring}" in russian locale etc.)
    Then i attached this in inspector to Score Text game object, chose table entry for it and deactivated Localized String Event and my old Localization Smart Fields components. After that i declared ScoreScript scoreScript in GameManager, defined it inside void Start{} using GetComponent<>() and tried to call scoreScript.UpdateScore() from GameManager. But in result it always gives an error
    NullReferenceException: Object reference not set to an instance of an object
    GameManager.UpdateScore (System.Int32 scoreToAdd) (at Assets/Scripts/GameManager.cs:44)
    GameManager.StartGame () (at Assets/Scripts/GameManager.cs:64)
    GameManager.Start () (at Assets/Scripts/GameManager.cs:20)
    44th line in gamemanager is the one where i try to call scoreScript.UpdateScore()
    I've tried making everything inside ScoreScript.cs public, tried reactivating Localized String event and dragging ScoreScript into it's Format Arguments field but none of this helped. Here's how full scripts look like now

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using TMPro;
    5. using UnityEngine.SceneManagement;
    6. using UnityEngine.UI;
    7.  
    8. public class GameManager : MonoBehaviour
    9. {
    10.     public List<GameObject> targets;
    11.     private float spawnRate = 1.0f;
    12.     public TextMeshProUGUI scoreText;
    13.     public GameObject gameOverMenu;
    14.     public int score;
    15.     public bool isGameActive;
    16.     public ScoreScript scoreScript;
    17.     public static float difficulty { get; set; }
    18.     void Start()
    19.     {
    20.         StartGame();
    21.         scoreScript = scoreText.GetComponent<ScoreScript>();
    22.     }
    23.  
    24.     void Update()
    25.     {
    26.  
    27.     }
    28.     IEnumerator SpawnTarget()
    29.     {
    30.         while (isGameActive)
    31.         {
    32.             UpdateScore(5);
    33.             yield return new WaitForSeconds(spawnRate);
    34.             int index = Random.Range(0, targets.Count);
    35.             Instantiate(targets[index]);
    36.            
    37.         }
    38.     }
    39.  
    40.     public void UpdateScore (int scoreToAdd)
    41.     {
    42.         //scoreText.text = "Score: " + score;
    43.         //score += scoreToAdd;
    44.         scoreScript.UpdateScore(scoreToAdd);
    45.     }
    46.  
    47.     public void GameOver()
    48.     {
    49.         isGameActive = false;
    50.         gameOverMenu.SetActive(true);
    51.     }
    52.  
    53.     public void RestartGame()
    54.     {
    55.         SceneManager.LoadScene(SceneManager.GetActiveScene().name);
    56.     }
    57.  
    58.     public void StartGame()
    59.     {
    60.         isGameActive = true;
    61.        
    62.         score = 0;
    63.         spawnRate /= difficulty;
    64.         UpdateScore(0);
    65.         StartCoroutine(SpawnTarget());
    66.     }
    67.  
    68. }
    69.  
    scoreText is assigned in Inspector so it's not the cause of the problem

    Code (CSharp):
    1. using TMPro;
    2. using UnityEngine;
    3. using UnityEngine.Localization;
    4.  
    5. public class ScoreScript : MonoBehaviour
    6. {
    7.     public LocalizedString localizedScore; // "Score: {0}"
    8.     public TextMeshProUGUI scoreText;
    9.     public int scorestring;
    10.  
    11.     public void OnEnable()
    12.     {
    13.         localizedScore.Arguments = new object[] { scorestring }; // 1 argument which is the score
    14.         localizedScore.StringChanged += UpdateScoreText;
    15.     }
    16.  
    17.     public void OnDisable()
    18.     {
    19.         localizedScore.StringChanged -= UpdateScoreText;
    20.     }
    21.  
    22.     public void RefreshString()
    23.     {
    24.         localizedScore.Arguments[0] = scorestring; // Update the score argument in the string
    25.         localizedScore.RefreshString();
    26.     }
    27.  
    28.     public void UpdateScore(int scoreToAdd)
    29.     {
    30.         scorestring += scoreToAdd;
    31.         RefreshString();
    32.     }
    33.  
    34.     public void UpdateScoreText(string text)
    35.     {
    36.         scoreText.text = text;
    37.     }
    38. }
    Seems like i dived into too advanced topic for a complete beginner but i'm still interested in what am i doing wrong.
     
  11. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,300
    Sounds like scoreScript has not been assigned. There are many ways to solve this, here is one :)
    Just drag the script into the slot
    fix.gif
     
    RandomGuy403 likes this.
  12. Jontac

    Jontac

    Joined:
    Feb 6, 2019
    Posts:
    33
    Hi
    I want to change a local variable via script. I don't however want to do it by creating a public variable that I have to drag and drop in the editor as Dapper Dino does in the clip above. I just want to reference the localized string event and assign a variable. How do I do this?

    Thanks in advance!
     
  13. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,300
    Do you mean a persistent variable? A variable that is visible in the inspector for the LocalizedString?
    You can get the variables like this https://docs.unity3d.com/Packages/c...n_SmartFormat_PersistentVariables_IVariable__

    If its something else can you give me an example of what code you currently have?
     
  14. Jontac

    Jontac

    Joined:
    Feb 6, 2019
    Posts:
    33
    I think so. I'm still a bit unclear as to the definition of persistent variable. I have a "Localize String Event" attached to a TMP text. I've set the string regerence to a line "Ready in {time_ready} {time_unit}. Where I want to connect time_ready to a int variable in script for counting down; and the time_unit as a nested localization depending on it being minutes or seconds to go for the count down.

    But I can't figure out how to reference the 2 local variables.

    Thanks for your super speedy respons and help!

    Best regards,
    Jonatan
     
  15. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,300
    time_ready and time_ready need to be setup.
    There are multiple ways to do this, such as;
    • Variable names in a class that can be extracted with reflection.
    • Keys in a dictionary
    • Persistent variables
    If you made them persistent variables it could work like this:

    Code (csharp):
    1. public class Example : MonoBehaviour
    2. {
    3.     public LocalizeStringEvent stringEvent;
    4.  
    5.     IntVariable m_TimeReady;
    6.     FloatVariable m_TimeUnit;
    7.  
    8.     private void Start()
    9.     {
    10.         // Create and add the variables
    11.         m_TimeReady = new IntVariable {  Value = 123 };
    12.         m_TimeUnit = new FloatVariable { Value = 123.321f };
    13.         stringEvent.StringReference.Add("time_ready", m_TimeReady);
    14.         stringEvent.StringReference.Add("time_unit", m_TimeUnit);
    15.         stringEvent.StringReference.RefreshString();
    16.     }
    17.  
    18.     private void Update()
    19.     {
    20.         // Update the variable - this will trigger RefreshString
    21.         m_TimeUnit.Value -= Time.deltaTime * 10.0f;
    22.     }
    23. }
    You can also add and setup the variables in the inspector. If you did that then you could fetch them in script like this:

    Code (csharp):
    1. public class Example : MonoBehaviour
    2. {
    3.     public LocalizeStringEvent stringEvent;
    4.  
    5.     IntVariable m_TimeReady;
    6.     FloatVariable m_TimeUnit;
    7.  
    8.     private void Start()
    9.     {
    10.         // Create and add the variables
    11.         m_TimeUnit = new FloatVariable { Value = 123.321f };
    12.  
    13.         m_TimeReady = stringEvent.StringReference["time_ready"] as IntVariable;
    14.  
    15.         // OR
    16.  
    17.         if (stringEvent.StringReference.TryGetValue("time_unit", out var time))
    18.             m_TimeUnit = time as FloatVariable;
    19.     }
    20.  
    21.     private void Update()
    22.     {
    23.         // Update the variable - this will trigger RefreshString
    24.         m_TimeUnit.Value -= Time.deltaTime * 10.0f;
    25.     }
    26. }
    You could use a class like this:

    Code (csharp):
    1. public class Example : MonoBehaviour
    2. {
    3.     public LocalizeStringEvent stringEvent;
    4.  
    5.     class MyValues
    6.     {
    7.         public int time_ready;
    8.         public float time_unit;
    9.     }
    10.     MyValues m_Values;
    11.  
    12.     private void Start()
    13.     {
    14.         m_Values = new MyValues();
    15.  
    16.         // Add a reference to the instance
    17.         stringEvent.StringReference.Arguments = new object[] { m_Values };
    18.         stringEvent.StringReference.RefreshString();
    19.     }
    20.  
    21.     private void Update()
    22.     {
    23.         // Update the values
    24.         m_Values.time_ready = Random.Range(0, 100);
    25.         m_Values.time_unit = Random.value;
    26.  
    27.         // Force an update
    28.         stringEvent.StringReference.RefreshString();
    29.     }
    30. }
     
    Jontac likes this.
  16. Jontac

    Jontac

    Joined:
    Feb 6, 2019
    Posts:
    33
    Thank you so much for your amazing and quick help! I especially liked how you provided different alternatives.

    I have 2 follow up questions.
    1) Are there clear differences in senarios when you would choose one of the following exampels?

    Code 1:
    Code (CSharp):
    1.         m_TimeUnit = new FloatVariable { Value = 123.321f };
    2.         stringEvent.StringReference.Add("time_unit", m_TimeUnit);
    3.         stringEvent.StringReference.RefreshString();
    Code 2:
    Code (CSharp):
    1.         m_TimeReady = stringEvent.StringReference["time_ready"] as IntVariable;
    Code 3:
    Code (CSharp):
    1.         m_TimeUnit = new FloatVariable { Value = 123.321f };
    2.         if (stringEvent.StringReference.TryGetValue("time_unit", out var time))
    3.             m_TimeUnit = time as FloatVariable;
    2) I have {time_unit} as a nested Localized String. And I have these values premade for different languages. It can take on e.g. "minutes" (english) "minutos" (spanish) etc. or "hours" (english) "horas" (spanish) etc. or secunds and so on. At the moment my text is stuck on "minutes" but I want to switch "minutes" to "seconds" as it comes to 59 seconds.

    The end result would go from "Ready in 1 minute" to "Ready in 59 seconds" if I had it set to english.

    Thanks for your amazing help!

    Best regards,
    Jonatan
     
  17. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,300
    1) They are all doing something different so I'm not sure. Number 1 adds a local variable. Number 2 fetches the variable and number 3 fetches a variable but checks if the variable exists. Although number 3 should probably add that variable if it's not found or it won't work.

    2) There's a few ways. You could use some logic in the string to pick the correct value.
    https://docs.unity3d.com/Packages/com.unity.localization@1.4/manual/Smart/Conditional-Formatter.html

    Alternatively you may want to create a custom formatter and do all the logic in that. https://docs.unity3d.com/Packages/c...manual/Smart/Creating-a-Custom-Formatter.html
     
    Jontac likes this.
  18. Jontac

    Jontac

    Joined:
    Feb 6, 2019
    Posts:
    33
    But there has to be a easier way to change/update what one has pre-assigned as a nested variable in the editor?

    If in my example I have a Table Collection called "TimeTable" with:
    1) A Smart string entry called "s_YouAre???AwayFromVictory" with an english string "You are {time_unit} away from victory!"
    2) Regular string entry called "s_day" with a corresponding string "days"
    3) Regular string entry called "s_hour" with a corresponding string "hours"
    4) Regular string entry called "s_min" with a corresponding string "minutes"
    5) Regular string entry called "s_sec" with a corresponding string "seconds"

    At the start i might have set it in the editor for "time_unit" to be set as "s_day". But as time progress I would like to reference "time_unit" in the script for updates.

    Now, I can replace "You are {time_unit} away from victory!" with "hours" by writing something like:
    Code (CSharp):
    1.     ...StringReference.SetReference("TimeTable", "s_hour");
    2.  
    What I want to do is keep the entry as is but just updated the nested. I'm thinking that if I can access the first level of entry or so to speak, I should be able to enter the nested as well?

    Thanks again for all your help!

    Best regards,
    Jonatan
     
  19. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,300
    Oh you want to access a nested LocalizedString?
    This can be done in the same way as a variable.
    Code (csharp):
    1. var nested = stringEvent.StringReference["nested-value"] as LocalizedString;
    Is that what you are looking for?
     
    Jontac likes this.
  20. Jontac

    Jontac

    Joined:
    Feb 6, 2019
    Posts:
    33
    upload_2023-3-4_12-32-3.png


    upload_2023-3-4_12-37-11.png

    Yes, more or less. What I then want to do is update it. So if I access my nested variable "time_unit", how do I then change it's key from "s_day" to "s_hour"?

    Thanks in advance!

    Best regards,
    Jonatan
     

    Attached Files:

  21. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,300
    You should be able to do something like this

    Code (csharp):
    1. var nested = stringEvent.StringReference["nested-value"] as LocalizedString;
    2. nested.TableEntryReference = "s_hour";
    3. stringReference.RefreshString();
     
    Jontac likes this.
  22. Jontac

    Jontac

    Joined:
    Feb 6, 2019
    Posts:
    33
    You're one of a kind my friend! Thank you very much for your amazing and freakishly quick respones! :D
     
    karl_jones likes this.