What’s the recommended way to localize a UI Dropdown component. I have a Settings screen with a Graphics Quality Dropdown with High, Medium and Low options. I’d like to use the StringTable for each of these options. I haven’t been able to find any examples of how to do this.
I’ve been thinking about writing a LocalizeDropdown component similar to the built-in LocalizeString component. But before I attempt that, I wanted to see if anyone has crossed this bridge before or has suggestions for me.
Probably not the best code, but it allows you to add localized string and texture options in the Unity inspector. These then get used to populate the dropdown and to update all options if the locale changes
Currently (with my limited testing), the only bug I have right now is that the initial CaptionSprite does not show after initial population of the dropdown.
I’ll probably improve on this as my needs change (and there is no official solution yet).
Any feedback is also welcome (be it on style or the logic itself)
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using TMPro;
using UnityEngine;
using UnityEngine.Localization;
using UnityEngine.Localization.Settings;
namespace Utilities.Localization
{
[RequireComponent(typeof(TMP_Dropdown))]
[AddComponentMenu("Localization/Localize dropdown")]
public class LocalizeDropdown : MonoBehaviour
{
// Fields
// =======
public List<LocalizedDropdownOption> options;
public int selectedOptionIndex = 0;
// Properties
// ===========
private TMP_Dropdown Dropdown => GetComponent<TMP_Dropdown>();
// Methods
// ========
private IEnumerator Start()
{
yield return PopulateDropdown();
}
private void OnEnable() => LocalizationSettings.SelectedLocaleChanged += UpdateDropdownOptions;
private void OnDisable() => LocalizationSettings.SelectedLocaleChanged -= UpdateDropdownOptions;
private void OnDestroy() => LocalizationSettings.SelectedLocaleChanged -= UpdateDropdownOptions;
private IEnumerator PopulateDropdown()
{
// Clear any options that might be present
Dropdown.ClearOptions();
Dropdown.onValueChanged.RemoveListener(UpdateSelectedOptionIndex);
for (var i = 0; i < options.Count; ++i)
{
var option = options[i];
var localizedText = string.Empty;
Sprite localizedSprite = null;
// If the option has text, fetch the localized version
if (!option.text.IsEmpty)
{
var localizedTextHandle = option.text.GetLocalizedString();
yield return localizedTextHandle;
localizedText = localizedTextHandle.Result;
// If this is the selected item, also update the caption text
if (i == selectedOptionIndex)
{
UpdateSelectedText(localizedText);
}
}
// If the option has a sprite, fetch the localized version
if (!option.sprite.IsEmpty)
{
var localizedSpriteHandle = option.sprite.LoadAssetAsync();
yield return localizedSpriteHandle;
localizedSprite = localizedSpriteHandle.Result;
// If this is the selected item, also update the caption text
if (i == selectedOptionIndex)
{
UpdateSelectedSprite(localizedSprite);
}
}
// Finally add the option with the localized content
Dropdown.options.Add(new TMP_Dropdown.OptionData(localizedText, localizedSprite));
}
// Update selected option, to make sure the correct option can be displayed in the caption
Dropdown.value = selectedOptionIndex;
Dropdown.onValueChanged.AddListener(UpdateSelectedOptionIndex);
}
private void UpdateDropdownOptions(Locale locale)
{
// Updating all options in the dropdown
// Assumes that this list is the same as the options passed on in the inspector window
for (var i = 0; i < Dropdown.options.Count; ++i)
{
var optionI = i;
var option = options[i];
// Update the text
if (!option.text.IsEmpty)
{
var localizedTextHandle = option.text.GetLocalizedString(locale);
localizedTextHandle.Completed += (handle) =>
{
Dropdown.options[optionI].text = handle.Result;
// If this is the selected item, also update the caption text
if (optionI == selectedOptionIndex)
{
UpdateSelectedText(handle.Result);
}
};
}
// Update the sprite
if (!option.sprite.IsEmpty)
{
var localizedSpriteHandle = option.sprite.LoadAssetAsync();
localizedSpriteHandle.Completed += (handle) =>
{
Dropdown.options[optionI].image = localizedSpriteHandle.Result;
// If this is the selected item, also update the caption sprite
if (optionI == selectedOptionIndex)
{
UpdateSelectedSprite(localizedSpriteHandle.Result);
}
};
}
}
}
private void UpdateSelectedOptionIndex(int index) => selectedOptionIndex = index;
private void UpdateSelectedText(string text)
{
if (Dropdown.captionText != null)
{
Dropdown.captionText.text = text;
}
}
private void UpdateSelectedSprite(Sprite sprite)
{
if (Dropdown.captionImage != null)
{
Dropdown.captionImage.sprite = sprite;
}
}
}
[Serializable]
public class LocalizedDropdownOption
{
public LocalizedString text;
public LocalizedSprite sprite;
}
}
EDIT: Updated script (Changed the code style a bit, applied some feedback and fixed a bug)
Didn’t know that was possible (still exploring the package), so I’ll change that
I have not, but will try. I used the Texture because the table entry and my assets are Texture2D
I think it’s a bug in my own code (the intention is to have it show up immediatly), but haven’t had the time yet do take a decent look at it.
I’ll (probably) be doing that this weekend
There is a small issue in your wonderful code. The selectedOptionIndex is set to 0 which prevents from setting the dropdown value from Prefs at start.
So I changed it like that and it works perfectly now:
EDIT: I also changed OnEnable so that a reenabled dropdown would update if the locale has been changed while it was disabled.
// https://discussions.unity.com/t/792432
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using TMPro;
using UnityEngine;
using UnityEngine.Localization;
using UnityEngine.Localization.Settings;
namespace Utilities.Localization
{
[RequireComponent(typeof(TMP_Dropdown))]
[AddComponentMenu("Localization/Localize Dropdown")]
public class LocalizeDropdown : MonoBehaviour
{
// Fields
// =======
public List<LocalizedDropdownOption> options;
public int selectedOptionIndex = 0;
private Locale currentLocale = null;
// Properties
// ===========
private TMP_Dropdown Dropdown => GetComponent<TMP_Dropdown>();
// Methods
// ========
private IEnumerator Start()
{
yield return PopulateDropdown();
}
private void OnEnable()
{
var locale = LocalizationSettings.SelectedLocale;
if (currentLocale != null && locale != currentLocale)
{
UpdateDropdownOptions(locale);
currentLocale = locale;
}
LocalizationSettings.SelectedLocaleChanged += UpdateDropdownOptions;
}
private void OnDisable() => LocalizationSettings.SelectedLocaleChanged -= UpdateDropdownOptions;
private void OnDestroy() => LocalizationSettings.SelectedLocaleChanged -= UpdateDropdownOptions;
private IEnumerator PopulateDropdown()
{
// Clear any options that might be present
selectedOptionIndex = Dropdown.value;
Dropdown.ClearOptions();
Dropdown.onValueChanged.RemoveListener(UpdateSelectedOptionIndex);
for (var i = 0; i < options.Count; ++i)
{
var option = options[i];
var localizedText = string.Empty;
Sprite localizedSprite = null;
// If the option has text, fetch the localized version
if (!option.text.IsEmpty)
{
var localizedTextHandle = option.text.GetLocalizedString();
yield return localizedTextHandle;
localizedText = localizedTextHandle.Result;
// If this is the selected item, also update the caption text
if (i == selectedOptionIndex)
{
UpdateSelectedText(localizedText);
}
}
// If the option has a sprite, fetch the localized version
if (!option.sprite.IsEmpty)
{
var localizedSpriteHandle = option.sprite.LoadAssetAsync();
yield return localizedSpriteHandle;
localizedSprite = localizedSpriteHandle.Result;
// If this is the selected item, also update the caption text
if (i == selectedOptionIndex)
{
UpdateSelectedSprite(localizedSprite);
}
}
// Finally add the option with the localized content
Dropdown.options.Add(new TMP_Dropdown.OptionData(localizedText, localizedSprite));
}
// Update selected option, to make sure the correct option can be displayed in the caption
Dropdown.value = selectedOptionIndex;
Dropdown.onValueChanged.AddListener(UpdateSelectedOptionIndex);
currentLocale = LocalizationSettings.SelectedLocale;
}
private void UpdateDropdownOptions(Locale locale)
{
// Updating all options in the dropdown
// Assumes that this list is the same as the options passed on in the inspector window
for (var i = 0; i < Dropdown.options.Count; ++i)
{
var optionI = i;
var option = options[i];
// Update the text
if (!option.text.IsEmpty)
{
var localizedTextHandle = option.text.GetLocalizedString(locale);
localizedTextHandle.Completed += (handle) =>
{
Dropdown.options[optionI].text = handle.Result;
// If this is the selected item, also update the caption text
if (optionI == selectedOptionIndex)
{
UpdateSelectedText(handle.Result);
}
};
}
// Update the sprite
if (!option.sprite.IsEmpty)
{
var localizedSpriteHandle = option.sprite.LoadAssetAsync();
localizedSpriteHandle.Completed += (handle) =>
{
Dropdown.options[optionI].image = localizedSpriteHandle.Result;
// If this is the selected item, also update the caption sprite
if (optionI == selectedOptionIndex)
{
UpdateSelectedSprite(localizedSpriteHandle.Result);
}
};
}
}
}
private void UpdateSelectedOptionIndex(int index) => selectedOptionIndex = index;
private void UpdateSelectedText(string text)
{
if (Dropdown.captionText != null)
{
Dropdown.captionText.text = text;
}
}
private void UpdateSelectedSprite(Sprite sprite)
{
if (Dropdown.captionImage != null)
{
Dropdown.captionImage.sprite = sprite;
}
}
}
[Serializable]
public class LocalizedDropdownOption
{
public LocalizedString text;
public LocalizedSprite sprite;
}
}
...
Error CS1061 ‘string’ does not contain a definition for ‘Result’ and no accessible extension method ‘Result’ accepting a first argument of type ‘string’ could be found (are you missing a using directive or an assembly reference?)
For some reasons I got an empty DropDown. Is it possible to fix it somehow?
Unity 2020.3.24f1
ERROR
A table with the same key menu already exists. Something went wrong during preloading.
UnityEngine.Localization.PreloadDatabaseOperation2<UnityEngine.Localization.Tables.StringTable, UnityEngine.Localization.Tables.StringTableEntry>:LoadTableContents (UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle1<System.Collections.Generic.IList1<UnityEngine.Localization.Tables.StringTable>>) DelegateList1<UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle1<System.Collections.Generic.IList1<UnityEngine.Localization.Tables.StringTable>>>:Invoke (UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle1<System.Collections.Generic.IList1<UnityEngine.Localization.Tables.StringTable>>) (at Library/PackageCache/com.unity.addressables@1.19.9/Runtime/ResourceManager/Util/DelegateList.cs:69)
UnityEngine.ResourceManagement.ChainOperationTypelessDepedency1<System.Collections.Generic.IList1<UnityEngine.Localization.Tables.StringTable>>:OnWrappedCompleted (UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle1<System.Collections.Generic.IList1<UnityEngine.Localization.Tables.StringTable>>)
private IEnumerator PopulateDropdown()
{
// Clear any options that might be present
selectedOptionIndex = Dropdown.value;
Dropdown.ClearOptions();
Dropdown.onValueChanged.RemoveListener(UpdateSelectedOptionIndex);
for (var i = 0; i < options.Count; ++i)
{
//THIS CODE EXECTUES ONLY ONCE
}
I found that inside the FOR cycle the iterations runs only once, even if you have like 10 “options” added.
I created my own Dropdown Localizer.
This is as addiction of the TMP_Dropdown component. Instead of options in the Dropdown component you can add the LocalizedString options in this script.
Any feedback is welcome!
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Localization;
using UnityEngine.Localization.Settings;
using TMPro;
public class LocalizeDropdown : MonoBehaviour
{
[SerializeField] private List<LocalizedString> dropdownOptions;
private TMP_Dropdown tmpDropdown;
private void Awake()
{
List<TMP_Dropdown.OptionData> tmpDropdownOptions = new List<TMP_Dropdown.OptionData>();
for (int i = 0; i < dropdownOptions.Count; i++)
{
tmpDropdownOptions.Add(new TMP_Dropdown.OptionData(dropdownOptions[i].GetLocalizedString()));
}
if (!tmpDropdown) tmpDropdown = GetComponent<TMP_Dropdown>();
tmpDropdown.options = tmpDropdownOptions;
}
Locale currentLocale;
private void ChangedLocale(Locale newLocale)
{
if (currentLocale == newLocale) return;
currentLocale = newLocale;
List<TMP_Dropdown.OptionData> tmpDropdownOptions = new List<TMP_Dropdown.OptionData>();
for (int i = 0; i < dropdownOptions.Count; i++)
{
tmpDropdownOptions.Add(new TMP_Dropdown.OptionData(dropdownOptions[i].GetLocalizedString()));
}
tmpDropdown.options = tmpDropdownOptions;
}
private void Update()
{
LocalizationSettings.SelectedLocaleChanged += ChangedLocale;
}
}
Did you know how to hide the Default Dropdown Localizer which on top right in Game window?
It a dropdown button to change localized language,and I want to undisplay that.
Very thanks.