Hi. I found that Unity created localization system for classic UGUI, https://docs.unity3d.com/Packages/com.unity.localization@0.6/manual/index.html So is there any chance for Unity-made localization system for UI Toolkit?
I haven't used Unity's localisation; But I am making my own localisation tool so I'm interested in this topic. What I think you mean to ask is if the localisation package will work with UI elements, with labels and buttons and what not. I'm sure it would be possible to work with it using C# to grab the required text, and change the text on a element to that acquired text. It looks like Localization's are stored as Assets in the project (much like my own). It would be fabulous if Unity's UI Toolkit could like assets into UXML (other than resource loading, I guess). I am facing the dilemma now of if I should extend all of Unity's Visual Elements for my own plugin; it feels like I should, but no plan how as of yet.
Localization in my experience depends just as much on how you receive and want to process the files from the person/people/agency you hire to do your localization for you, how you want to set up the localization table used in memory, and when/where/how you want the "current language" to be tracked and changed by the user, as it does on what you're applying that data to. In this case (UI Elements), you can query through all labels and get whatever properties you like from them, so you can identify them by name, or make a new tag for "localization-name" or something in order to work as an identifier. You then just replace whatever text is in that label with your localized version. Add-ons that extend VisualElement/Label to automate this would simply be wiring into your own localization table (probably a dictionary of strings, dictionary of audio files, dictionary of images), which is really based on how you use assets in your project. I can't imagine a "one size fits all" solution to this- though ScriptableObjects could probably create some kind of a standard, like localization for UGUI does, I probably wouldn't even bother using it since ScriptableObjects wouldn't be how I'd be getting the translated assets from the company I hire for localizing the game. *shrugs*
If they develop something it should be extensible enough so that if I want to provide the data I can implement it my way. For example: get the data from one or several *.json, *.csv, ...
I saw that at 1:13:26 in this webinar from December 2020 the Localization team says they don't have an eta on UI Builder Localization support but that they are working on it with the localization team. Does anyone have an update on this? Thank you!
Short answer is its on the roadmap of things we need to do for localization but we've not finalized a timeline for its support. Longer answer is our current objectives are to get the Localization package stable and a 1.0 version released. Once that has been concluded we will pivot to determine what the next requirements for localization are and then meet those needs (UIToolkit being one of the higher priorities).
Thank you so much, Phil! If you need testers to try and get the packages working together, I'd be happy to try and be of assistance!
Our team uses the UI toolkit and we would like to use the Localization package, it looks very good. Any update on when we can expect the UI toolkit integration? Thanks.
My team is also super keen on using UI Toolkit, but having to create a custom solution for Localization is not that ideal. Looking forward to hearing more about Unity's Localization support for this.
2 years after the first post there is still no localization solution inside UIToolkit or even a road map with an approximative date?
Hello there, we're also looking for a clean way to localize strings and assets in the UI builder. Strings are first thing, but localizing assets like background images could also help us a lot. Has the Unity team made progress on the UI Builder integration? If not, do you have any suggestions on ways to achieve this with the UI builder? I'm worried using a MonoBehaviour might cause flickering between non-localized and localized strings when the UI is being loaded.
Hey all, thought I'd share my interim solution (fingers crossed for proper integration from Unity soon). Notes: - My code uses a different code style to that which Unity does. Season to taste - Set Windows > Asset Management > Localization Scene Controls > Active Locale so you can see resolved text in-editor - You must change Localization Smart Format error actions to NOT throw exceptions, otherwise UI Builder may fail to load your UXML files or... EXPLOSIONS. e.g. here's what I've set them to: Code (CSharp): using UnityEngine.Localization; using UnityEngine.Localization.Settings; using UnityEngine.UIElements; namespace FrankenStorm.UI { public class LocalisedLabel : TextElement { public new class UxmlFactory : UxmlFactory<LocalisedLabel, UxmlTraits> { } public new class UxmlTraits : VisualElement.UxmlTraits { readonly UxmlStringAttributeDescription _table = new() { name = "table" }; readonly UxmlStringAttributeDescription _tableEntry = new() { name = "table-entry" }; public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc) { base.Init(ve, bag, cc); var label = (LocalisedLabel)ve; label.Table = _table.GetValueFromBag(bag, cc); label.TableEntry = _tableEntry.GetValueFromBag(bag, cc); label.Refresh(); } } const string USSClassName = "fs-localised-label"; public string Table { get; set; } public string TableEntry { get; set; } public LocalizedString LocalisedString { get; private set; } public LocalisedLabel() : this(string.Empty, string.Empty) { } public LocalisedLabel(string table, string tableEntry) { AddToClassList(USSClassName); LocalisedString = new LocalizedString(); LocalisedString.StringChanged += HandleStringChanged; Table = table; TableEntry = tableEntry; Refresh(); } public void Refresh() { LocalisedString.TableReference = string.IsNullOrEmpty(Table) ? LocalizationSettings.StringDatabase.DefaultTable : Table; LocalisedString.TableEntryReference = TableEntry; } void HandleStringChanged(string val) => text = val; } } Note that I've got this class in a namespace. You don't need to do that, but if you do, then you do need to add an assembly attribute to make UI Builder aware of your namespace. e.g. I've got this in my UI assembly's AssemblyInfo.cs: Code (CSharp): #if UNITY_EDITOR using UnityEditor.UIElements; [assembly: UxmlNamespacePrefix("FrankenStorm.UI", "fs")] #endif This works fine with smart strings, just access the LocalisedString directly and set variables as you desire, e.g.: Code (CSharp): using UnityEngine; using UnityEngine.Localization.SmartFormat.PersistentVariables; using UnityEngine.UIElements; namespace FrankenStorm.UI { [RequireComponent(typeof(UIDocument))] public class UIController : MonoBehaviour { UIDocument _doc; LocalisedLabel _test; public void Awake() { _doc = GetComponent<UIDocument>(); _test = _doc.rootVisualElement.Q<LocalisedLabel>("test"); } public void Update() { _test.LocalisedString.Remove("SecondsIn"); _test.LocalisedString.Add("SecondsIn", new IntVariable { Value = (int)Time.time }); _test.LocalisedString.RefreshString(); } } } I've not tested this much. If anyone sees any red flags, please share with me! EDIT: AssemblyInfo.cs UnityEditor.UIElements needed preprocessor conditions.
Try this instead of recreating the variable each time: Code (csharp): [RequireComponent(typeof(UIDocument))] public class UIController : MonoBehaviour { UIDocument _doc; LocalisedLabel _test; IntVariable m_Time; public void Awake() { _doc = GetComponent<UIDocument>(); _test = _doc.rootVisualElement.Q<LocalisedLabel>("test"); } public void Update() { if (m_Time == null) { m_Time = new IntVariable(); _test.LocalisedString.Add("SecondsIn", m_Time); } m_Time.Value = (int)Time.time; // Will trigger the string to update } }
Ah great tip, thanks Karl! Didn't cross my mind to re-use to reference EDIT from the future: Even with this approach, it still seems required to call RefreshString at least once, once the variable is added to the string the first time. Otherwise the string seems to remain in an erroneous state.
I had already posted my code here but using StringChanged and making the LocalizedString public are two nice improvements: Code (csharp): using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using UnityEngine; using UnityEngine.Localization; using UnityEngine.Localization.Settings; using UnityEngine.Localization.Tables; using UnityEngine.UIElements; namespace UnityEngine.UIElements { internal class LocalizedLabel : TextElement { public new class UxmlFactory : UxmlFactory<LocalizedLabel, UxmlTraits> { } public new class UxmlTraits : TextElement.UxmlTraits { UxmlStringAttributeDescription m_LocaleKey = new UxmlStringAttributeDescription { name = "locale-key" }; public override IEnumerable<UxmlChildElementDescription> uxmlChildElementsDescription { get { yield break; } } public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc) { base.Init(ve, bag, cc); var ll = ve as LocalizedLabel; ll.LocaleKey = m_LocaleKey.GetValueFromBag(bag, cc); ll.initLabel(); } } public string LocaleKey { get; set; } public LocalizedString LocalizedString { get; private set; } public new static readonly string ussClassName = "localized-label"; public LocalizedLabel() { AddToClassList(ussClassName); LocalizedString = new LocalizedString(); } public LocalizedLabel(string localeKey) : this() { this.LocaleKey = localeKey; initLabel(); } private void initLabel() { var p = LocaleKey.IndexOf('/'); if (p > 0 && p != (LocaleKey.Length - 1) && LocaleKey.Length > 2) { LocalizedString.TableReference = LocaleKey.Substring(0, p); LocalizedString.TableEntryReference = LocaleKey.Substring(p + 1); RegisterCallback<GeometryChangedEvent>(initChange) ; } } private void initChange(GeometryChangedEvent evt) { LocalizedString.StringChanged += stringChanged; UnregisterCallback<GeometryChangedEvent>(initChange); } private void stringChanged(string value) { text = value; } } }
I've fixed the warning: "SendMessage cannot be called during Awake, CheckConsistency, or OnValidate" by using a GeometryChangedEvent callback.
Another option would be to use Asset Localization instead of String Localization. You can use Localized Property Variants (Unity 2020.3 or later) or Component Localizers to change the Tree View Asset of the UIDocument component at runtime, and have one Tree View Asset for each locale. This is useful for situations in which the layout is altered with the language change or you want a more fine control of the visual elements of the document like animations. I would suggest the latter unless you have a lot of different UI Documents to deal with in a lot of languages. For Component Localizers, it only requires 2 lines of code and adding the LocalizeUIDocumentEvent component to the UIDocument you want to localize: Code (CSharp): public class LocalizeUIDocumentEvent : LocalizedAssetEvent<VisualTreeAsset, LocalizedVisualTreeAsset, UnityEvent<VisualTreeAsset>>{} (It might be necessary to replace the derived class to LocalizedAssetEvent<VisualTreeAsset> depending on the version of your Localization package and editor, the documentation is a bit outdated) Code (CSharp): [Serializable] public class LocalizedVisualTreeAsset: LocalizedAsset<VisualTreeAsset> {} Note that you mustn't set the Visual Tree Asset directly. Instead, make a setter function that does the cleanup for the previous Visual Tree, and the initialization for the new one. Plus, you can combine this with the LocalizedLabel from above.
We do have a prototype based on a new bindings feature for UI Toolkit. Unfortunately, it will require Unity 2023 and above as that's the version the feature should land in.
Any update on this? No support in 1.4. Am I looking in the right place? https://docs.unity3d.com/Packages/com.unity.localization@1.4/changelog/CHANGELOG.html
Still in the roadmap as "Planned" : https://portal.productboard.com/rcc...age?utm_medium=social&utm_source=portal_share
Hi, The work is still ongoing with an estimate of landing during the 2023 cycle. We are currently waiting for the UI Toolkit binding improvements to land, once that is completed then we need to apply some changes to the package. Edit(June 1): The bindings changes landed in 2023.2! We are now working on the integration, expect to see something later in the year
The work is done now, just waiting on some reviews and then we will prepare the 1.5 release. The LocalizedString class is now a Custom binding so it should be very simple to use. E.g Code (csharp): label.SetBinding("text", localizedString); The uxml would look like: Code (csharp): <ui:UXML xmlns:ui="UnityEngine.UIElements"> <ui:Label text="Label"> <Bindings> <UnityEngine.Localization.LocalizedString property="text" table="General Test Data" entry="My Entry"> <variables> <UnityEngine.Localization.LocalVariable name="counter"> <UnityEngine.Localization.SmartFormat.PersistentVariables.IntVariable value="2" /> </UnityEngine.Localization.LocalVariable> </variables> </UnityEngine.Localization.LocalizedString> </Bindings> </ui:Label> </ui:UXML>
Smart! Thank you, Karl. That's precisely what I was after. Not quite as fancy shmancy as .NET's resx but good enough for what I need.
Hello, I'd like to share my custom solution, which is very easy to work with: custom styles! It's not perfect but works for simple cases. Like this in uxml: <ui:Label text="Leader Board" style="--localize-text: 'text-leader-board'" /> <ui:Button text="Play" style="--localize-text:'text-play'" /> 'text-leader-board' and 'text-play' is your localization keys. Use a C# script to get all TextElement in a UIDocument and poll custom styles, if there is a style for '--localize-text', replace the text with a localization table lookup. Example C# code, might need some changes to work for you: Code (CSharp): using UnityEngine; using UnityEngine.Localization.Settings; using UnityEngine.UIElements; public class UILocalizationStyle:MonoBehaviour { [SerializeField] private UIDocument _document; private static readonly CustomStyleProperty<string> CustomStyleProperty = new CustomStyleProperty<string>("--localize-text"); private void OnEnable() { foreach (var textElement in _document.rootVisualElement.Query<TextElement>().ToList()) { LocalizeTextElement(textElement); textElement.RegisterCallback<CustomStyleResolvedEvent>(OnStyleResolved); } } private void OnStyleResolved(CustomStyleResolvedEvent evt) { LocalizeTextElement(evt.target as TextElement); } private void LocalizeTextElement(TextElement textElement) { Debug.Assert(textElement != null); if (textElement.customStyle.TryGetValue(CustomStyleProperty, out var key)) { if (!string.IsNullOrEmpty(key)) { string text = LocalizationSettings.StringDatabase.GetLocalizedString(key); if (!string.IsNullOrEmpty(text)) { textElement.text = text; } } } } } Also, Unity has just provided an official solution. Update: Updated the script at Dec. 28 2023