#if ENABLE_PROPERTY_VARIANTS || PACKAGE_DOCS_GENERATION using System; using System.Collections; using System.Collections.Generic; using UnityEngine.Localization.PropertyVariants.TrackedObjects; using UnityEngine.Localization.PropertyVariants.TrackedProperties; using UnityEngine.Localization.Settings; using UnityEngine.Pool; using UnityEngine.ResourceManagement.AsyncOperations; namespace UnityEngine.Localization.PropertyVariants { /// /// The GameObject Localizer component is responsible for storing and applying all Localized Property Variants Configurations /// for the [GameObject](https://docs.unity3d.com/ScriptReference/GameObject.html) it is attached to. /// /// /// This shows how to configure a to apply changes to a [Text](https://docs.unity3d.com/Packages/com.unity.ugui@latest/index.html?subfolder=/api/UnityEngine.UI.Text.html) /// component for the Font, Font Size and Text properties. /// /// /// /// This shows how to configure a to apply changes to a [TextMeshProUGUI](https://docs.unity3d.com/Packages/com.unity.textmeshpro@latest/index.html?subfolder=/api/TMPro.TextMeshProUGUI.html) /// component for the Font, Font Size and Text properties. /// /// /// /// This shows how to configure a to apply changes to a [Dropdown](https://docs.unity3d.com/Packages/com.unity.ugui@latest/index.html?subfolder=/api/UnityEngine.UI.Dropdown.html) /// for the options values. /// /// /// /// This shows how to configure a to apply changes to a [TMP_Dropdown](https://docs.unity3d.com/Packages/com.unity.textmeshpro@latest/index.html?subfolder=/api/TMPro.TMP_Dropdown.html) /// for the options values. /// /// /// /// This shows how to configure a to apply changes to a for the x, y and width properties. /// This can be useful when you need to make adjustments due to changes in text length for a particular . /// /// /// /// This shows how to configure a to apply changes to a custom script. /// /// [ExecuteAlways] [DisallowMultipleComponent] public class GameObjectLocalizer : MonoBehaviour { [SerializeReference] List m_TrackedObjects = new List(); Locale m_CurrentLocale; LocalizedString.ChangeHandler m_LocalizedStringChanged; bool m_IgnoreChange; internal AsyncOperationHandle CurrentOperation { get; private set; } void OnEnable() { LocalizationSettings.SelectedLocaleChanged += SelectedLocaleChanged; RegisterChanges(); // Update when reenabled (LOC-579) if (m_CurrentLocale != null) { var locale = LocalizationSettings.SelectedLocale; if (!ReferenceEquals(m_CurrentLocale, locale)) { SelectedLocaleChanged(locale); } } } void OnDisable() { UnregisterChanges(); AddressablesInterface.SafeRelease(CurrentOperation); CurrentOperation = default; LocalizationSettings.SelectedLocaleChanged -= SelectedLocaleChanged; } IEnumerator Start() { m_CurrentLocale = null; var localeOp = LocalizationSettings.SelectedLocaleAsync; if (!localeOp.IsDone) yield return localeOp; SelectedLocaleChanged(localeOp.Result); } void SelectedLocaleChanged(Locale locale) { m_CurrentLocale = locale; // Ignore null, this will reset the driven properties back to their defaults. if (locale == null) return; ApplyLocaleVariant(locale); } /// /// The objects that are being tracked by this Localizer. /// public List TrackedObjects => m_TrackedObjects; /// /// Returns the for the target component or creates a new instance if is set to . /// /// The Type of TrackedObject that should be found or added. /// The Target Object to track. /// Creates a new Tracked Object if one can not be found. /// public T GetTrackedObject(Object target, bool create = true) where T : TrackedObject, new() { var trackedObject = GetTrackedObject(target); if (trackedObject != null) return (T)trackedObject; if (!create) return default; var component = target as Component; if (component == null) return null; if (component.gameObject != gameObject) { throw new Exception("Tracked Objects must share the same GameObject as the GameObjectLocalizer. " + $"The Component {component} is attached to the GameObject {component.gameObject}" + $" but the GameObjectLocalizer is attached to {gameObject}."); } var newTrackedObject = new T {Target = target}; TrackedObjects.Add(newTrackedObject); return newTrackedObject; } /// /// Returns the for the target component or if one does not exist. /// See for a version that will create a new TrackedObject if one does not already exist. /// /// The target object to search for. /// The or if one could not be found. public TrackedObject GetTrackedObject(Object target) { if (target == null) throw new ArgumentNullException(nameof(target)); foreach (var t in TrackedObjects) { if (t?.Target == target) return t; } return null; } /// /// Apply all variants for the selected to this GameObject. /// /// The to apply to the GameObject. /// A handle to any loading operations or if the operation was immediate. public AsyncOperationHandle ApplyLocaleVariant(Locale locale) => ApplyLocaleVariant(locale, LocalizationSettings.ProjectLocale); /// /// Apply all variants for the selected to this GameObject. /// When a value cannot be found, the value is used. /// /// The to apply to the GameObject. /// The fallback to use when a value does not exist for . /// A handle to any loading operations or if the operation was immediate. public AsyncOperationHandle ApplyLocaleVariant(Locale locale, Locale fallback) { if (CurrentOperation.IsValid()) { if (!CurrentOperation.IsDone) Debug.LogWarning("Attempting to Apply Variant when the previous operation has not yet completed.", this); AddressablesInterface.Release(CurrentOperation); CurrentOperation = default; } var asyncOperations = ListPool.Get(); foreach (var to in TrackedObjects) { if (to == null) continue; var operation = to.ApplyLocale(locale, fallback); if (!operation.IsDone) asyncOperations.Add(operation); } if (asyncOperations.Count == 1) { AddressablesInterface.Acquire(asyncOperations[0]); CurrentOperation = asyncOperations[0]; ListPool.Release(asyncOperations); return CurrentOperation; } if (asyncOperations.Count > 1) { CurrentOperation = AddressablesInterface.CreateGroupOperation(asyncOperations); return CurrentOperation; } ListPool.Release(asyncOperations); return default; } void RegisterChanges() { if (m_LocalizedStringChanged == null) m_LocalizedStringChanged = _ => RequestUpdate(); try { // Ignore any changes sent whilst we register m_IgnoreChange = true; foreach (var trackedObject in m_TrackedObjects) { foreach (var property in trackedObject.TrackedProperties) { if (property is LocalizedStringProperty localizedStringProperty) { localizedStringProperty.LocalizedString.StringChanged += m_LocalizedStringChanged; } } } } finally { m_IgnoreChange = false; } } void UnregisterChanges() { if (m_LocalizedStringChanged == null) return; foreach (var trackedObject in m_TrackedObjects) { foreach (var property in trackedObject.TrackedProperties) { if (property is LocalizedStringProperty localizedStringProperty) { localizedStringProperty.LocalizedString.StringChanged -= m_LocalizedStringChanged; } } } } void RequestUpdate() { // Ignore the change, it will be handled by the selected locale change event. if (m_IgnoreChange || LocalizationSettings.Instance.IsChangingSelectedLocale || (CurrentOperation.IsValid() && !CurrentOperation.IsDone)) return; ApplyLocaleVariant(m_CurrentLocale); } } } #endif