Search Unity

Question UI Toolkit support?

Discussion in 'Localization Tools' started by b4gn0, Sep 22, 2020.

  1. b4gn0

    b4gn0

    Joined:
    Jul 26, 2019
    Posts:
    119
    Hello,

    I and my team are trying the new Localisation package, and it looks awesome.
    We use Addressables so it fits nicely in there, too.

    However, we are using UI Toolkit for all of our UI needs, and it would be awesome to assign a StringReference to -say- a label text.

    Is this something we should implement ourselves, or is it getting looked into by Unity devs?

    Thank you!
     
    KevinWardProf likes this.
  2. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,292
    Hey,
    It's certainly on our roadmap but will be some time before we have an official integration.
    We are talking with the UI toolkit team at the moment.
    If you need something in the short term the you should look at adding your own integration, I can't really give any firm dates for an integration yet.
     
  3. andrew-lukasik

    andrew-lukasik

    Joined:
    Jan 31, 2013
    Posts:
    249
    In case sb needs to integrate these two packages I attach my working example you can start with. It handles runtime language changes too (this resets hierarchy tho).

    This class assumes that you designate StringTable keys in label fields (as seen in Label, Button, etc) and start them all with '#' char (so other labels will be left be)
    Example: https://i.imgur.com/H5RUIej.gif
    Code (CSharp):
    1. // void* src = https://gist.github.com/andrew-raphael-lukasik/72a4d3d14dd547a1d61ae9dc4c4513da
    2. using UnityEngine;
    3. using UnityEngine.UIElements;
    4. using UnityEngine.Localization;
    5. using UnityEngine.Localization.Tables;
    6. using UnityEngine.ResourceManagement.AsyncOperations;
    7.  
    8. // NOTE: this class assumes that you designate StringTable keys in label fields (as seen in Label, Button, etc)
    9. // and start them all with '#' char (so other labels will be left be)
    10. // example: https://i.imgur.com/H5RUIej.gif
    11.  
    12. [DisallowMultipleComponent]
    13. [RequireComponent( typeof(UIDocument) )]
    14. public class UIDocumentLocalization : MonoBehaviour
    15. {
    16.  
    17.    [SerializeField] LocalizedStringTable _table = null;
    18.    UIDocument _document;
    19.  
    20.    /// <summary> Executed after hierarchy is cloned fresh and translated. </summary>
    21.    public event System.Action onCompleted = ()=>{};
    22.  
    23.  
    24.    void OnEnable ()
    25.    {
    26.        if( _document==null )
    27.            _document = gameObject.GetComponentInParent<UIDocument>( includeInactive:true );
    28.  
    29.        _table.TableChanged += OnTableChanged;
    30.    }
    31.  
    32.  
    33.    void OnDisable ()
    34.    {
    35.        _table.TableChanged -= OnTableChanged;
    36.    }
    37.  
    38.  
    39.    void OnTableChanged ( StringTable table )
    40.    {
    41.        var root = _document.rootVisualElement;
    42.        root.Clear();
    43.        _document.visualTreeAsset.CloneTree( root );
    44.  
    45.        var op = _table.GetTable();
    46.        op.Completed -= OnTableLoaded;
    47.        op.Completed += OnTableLoaded;
    48.    }
    49.  
    50.    void OnTableLoaded ( AsyncOperationHandle<StringTable> op )
    51.    {
    52.        StringTable table = op.Result;
    53.        var root = _document.rootVisualElement;
    54.  
    55.        LocalizeChildrenRecursively( root , table );
    56.        onCompleted();
    57.  
    58.        root.MarkDirtyRepaint();
    59.    }
    60.  
    61.    void Localize ( VisualElement next , StringTable table )
    62.    {
    63.        if( typeof(TextElement).IsInstanceOfType(next) )
    64.        {
    65.            TextElement textElement = (TextElement) next;
    66.            string key = textElement.text;
    67.            if( !string.IsNullOrEmpty(key) && key[0]=='#' )
    68.            {
    69.                key = key.TrimStart('#');
    70.                StringTableEntry entry = table[ key ];
    71.                if( entry!=null )
    72.                    textElement.text = entry.LocalizedValue;
    73.                else
    74.                    Debug.LogWarning($"No {table.LocaleIdentifier.Code} translation for key: '{key}'");
    75.            }
    76.        }
    77.    }
    78.  
    79.    void LocalizeChildrenRecursively ( VisualElement element , StringTable table )
    80.    {
    81.        VisualElement.Hierarchy elementHierarchy = element.hierarchy;
    82.        int numChildren = elementHierarchy.childCount;
    83.        for( int i=0 ; i<numChildren ; i++ )
    84.        {
    85.            VisualElement child = elementHierarchy.ElementAt( i );
    86.            Localize( child , table );
    87.        }
    88.        for( int i=0 ; i<numChildren ; i++ )
    89.        {
    90.            VisualElement child = elementHierarchy.ElementAt( i );
    91.            VisualElement.Hierarchy childHierarchy = child.hierarchy;
    92.            int numGrandChildren = childHierarchy.childCount;
    93.            if( numGrandChildren!=0 )
    94.                LocalizeChildrenRecursively( child , table );
    95.        }
    96.    }
    97.  
    98. }
    99.  
    gist.github copy

    Note: it will throw errors if addressables aren't built yet (do that).

    It's not as ideal as native-like integration, but it gets job done for now. If you have better idea how to do it - please share it!
     
    Last edited: Mar 14, 2021
    drbatuira, codestage, MTandi and 2 others like this.
  4. MTandi

    MTandi

    Joined:
    Aug 4, 2017
    Posts:
    10
    @andrew-lukasik the translations are applied, but it breaks callbacks registered for buttons.

    I guess because of the tree cloning...

    UPDATE:

    I removed the tree cloning logic and storing the key in the viewDataKey property, to not have it reset every time you change the language.
    Code (CSharp):
    1. ...
    2.             string key = textElement.viewDataKey;
    3.             if( !string.IsNullOrEmpty(key) && key[0]=='_' )
    4.             {
    5.                 key = key.TrimStart('_');
    6. ...
    Though, I'm not sure how to solve another problem...

    Code (CSharp):
    1.     void OnDisable ()
    2.     {
    3.         _table.TableChanged -= OnTableChanged;
    4.     }
    This doesn't seem to be working. When you disable the script, and change the language, the table still tries to run the OnTableChanged handler.
     
    Last edited: Mar 12, 2021
  5. andrew-lukasik

    andrew-lukasik

    Joined:
    Jan 31, 2013
    Posts:
    249
    This problem is addressed in the initial source already - although implicitly.

    Arrange this in YourUiBindingClass.cs :
    Code (CSharp):
    1.  
    2. void OnEnable ()
    3. {
    4.     OnBind();// just in case of race condition
    5.     _UIDocumentLocalization.Completed -= OnBind;
    6.     _UIDocumentLocalization.Completed += OnBind;
    7. }
    8.  
    9. void OnBind ()
    10. {
    11.     VisualElement yourRootVisualElement = _UIDocument.rootVisualElement;
    12.  
    13.     /* button etc. binding code goes here */
    14. }
     
  6. MTandi

    MTandi

    Joined:
    Aug 4, 2017
    Posts:
    10
    Yeah, I removed the cloning logic, so the Completed is not needed anymore - the bindings don't break.

    Is everything OK for you when you toggle the script on/off a few times and then switch the language?

    The list of delegates kept growing for me, as the script doesn't unsubscribe properly.
     
    andrew-lukasik likes this.
  7. andrew-lukasik

    andrew-lukasik

    Joined:
    Jan 31, 2013
    Posts:
    249
    Oh, you're right, didn't noticed that before! I will investigate it sometime soon but I'm surprised this handler isn't being unsubscribed, like, at all.
    I hope it's my silly mistake somewhere and not custom event accessor shenanigans.
     
  8. andrew-lukasik

    andrew-lukasik

    Joined:
    Jan 31, 2013
    Posts:
    249
    Not sure why but events stopped pilling up today and I can't reproduce that behavior anymore. Maybe it was Unity version related? (on 2020.3.0f1 now)

    Other than that I can see an exception when translation event is raised due to m_ChangeHandler being null in com.unity.localization@0.10.0-preview. To fix it just add "if( m_ChangeHandler!=null )" at line 139 in LocalizedTable.cs

    Code (csharp):
    1. NullReferenceException: Object reference not set to an instance of an object
    2. UnityEngine.Localization.LocalizedTable`2[TTable,TEntry].AutomaticLoadingCompleted (UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle`1[TObject] loadOperation) (at Packages/com.unity.localization@0.10.0-preview/Runtime/Localized Reference/LocalizedTable.cs:140)
    3. DelegateList`1[T].Invoke (T res) (at Library/PackageCache/com.unity.addressables@1.16.13/Runtime/ResourceManager/Util/DelegateList.cs:69)
    4. UnityEngine.Debug:LogException(Exception)
    5. DelegateList`1:Invoke(AsyncOperationHandle`1) (at Library/PackageCache/com.unity.addressables@1.16.13/Runtime/ResourceManager/Util/DelegateList.cs:73)
    6. UnityEngine.Localization.LoadTableOperation`2:TableLoaded(AsyncOperationHandle`1)
    7. DelegateList`1:Invoke(AsyncOperationHandle`1) (at Library/PackageCache/com.unity.addressables@1.16.13/Runtime/ResourceManager/Util/DelegateList.cs:69)
    8. UnityEngine.ResourceManagement.Util.DelayedActionManager:LateUpdate() (at Library/PackageCache/com.unity.addressables@1.16.13/Runtime/ResourceManager/Util/DelayedActionManager.cs:159)
     
    MTandi likes this.
  9. drbatuira

    drbatuira

    Joined:
    May 4, 2020
    Posts:
    23
    @andrew-lukasik thanks for the script, I will modify it for my needs (custom, without using localization)
     
    andrew-lukasik likes this.