Files
ADOFAIRenderer/UI/LocalizedText.cs
T
2026-10-15 18:29:22 +09:00

147 lines
6.0 KiB
C#

using System;
using ADOFAIRenderer.Localization;
using TMPro;
using UnityEngine;
namespace ADOFAIRenderer.UI;
/// <summary>
/// Component that binds a TextMeshProUGUI component to a localization key or dynamic string provider,
/// automatically updating the displayed text whenever the active language changes.
/// <para>
/// The text is set when the binding is made (<see cref="Bind(TextMeshProUGUI, string)"/>), when the component is enabled,
/// and on every <see cref="LocalizationManager.OnLanguageChanged"/> event. A key and a dynamic provider are mutually
/// exclusive: setting one clears the other. With neither set, <see cref="Refresh"/> leaves the text unchanged.
/// Runs on the main Unity thread.
/// </para>
/// </summary>
internal class LocalizedText : MonoBehaviour
{
/// <summary>Text component that receives the string; looked up on the same GameObject if not assigned by Bind.</summary>
private TextMeshProUGUI? _textComponent;
/// <summary>Localization key resolved through <see cref="LocalizationManager.Get(string)"/>; <c>null</c> when a provider is used.</summary>
private string? _key;
/// <summary>Function that returns the full text (already localized/formatted); <c>null</c> when a key is used.</summary>
private Func<string>? _dynamicProvider;
/// <summary>
/// Gets or sets the localization key. Setting this will clear any dynamic provider and refresh the text immediately.
/// A null or empty key leaves the current text as it is.
/// </summary>
public string? Key
{
get => _key;
set
{
_key = value;
_dynamicProvider = null;
Refresh();
}
}
/// <summary>
/// Sets a dynamic string provider function, clears the key and refreshes the text immediately.
/// The provider is called on every refresh (enable and language change), so it can combine several keys or
/// runtime values, e.g. a label that depends on UI state.
/// </summary>
/// <param name="provider">Function returning the text to display; called on the main Unity thread.</param>
public void SetDynamic(Func<string> provider)
{
_dynamicProvider = provider;
_key = null;
Refresh();
}
/// <summary>
/// Finds the text component and subscribes to language changes. The subscription lasts until <see cref="OnDestroy"/>,
/// also while the component is disabled (a refresh of a disabled object only changes its text).
/// </summary>
private void Awake()
{
EnsureComponent();
LocalizationManager.OnLanguageChanged += HandleLanguageChanged;
}
/// <summary>Refreshes the text each time the component is enabled.</summary>
private void OnEnable()
{
Refresh();
}
/// <summary>Unsubscribes from <see cref="LocalizationManager.OnLanguageChanged"/>.</summary>
private void OnDestroy()
{
LocalizationManager.OnLanguageChanged -= HandleLanguageChanged;
}
/// <summary>Language change handler; the new language is read by <see cref="LocalizationManager.Get(string)"/>.</summary>
private void HandleLanguageChanged(SystemLanguage language)
{
Refresh();
}
/// <summary>Assigns <see cref="_textComponent"/> from the same GameObject if it is not set yet.</summary>
private void EnsureComponent()
{
if (_textComponent == null)
{
_textComponent = GetComponent<TextMeshProUGUI>();
}
}
/// <summary>
/// Refreshes the text using either the dynamic provider or the static localization key.
/// The provider takes precedence; with neither set, or without a text component, nothing changes.
/// A key that is missing in every locale is displayed as the key itself (see <see cref="LocalizationManager.Get(string)"/>).
/// </summary>
public void Refresh()
{
EnsureComponent();
if (_textComponent == null) return;
if (_dynamicProvider != null)
{
_textComponent.text = _dynamicProvider();
}
else if (!string.IsNullOrEmpty(_key))
{
_textComponent.text = LocalizationManager.Get(_key);
}
}
/// <summary>
/// Binds a TextMeshProUGUI component to a localization key.
/// Reuses the <see cref="LocalizedText"/> already on the target's GameObject or adds one, then sets the key,
/// which replaces any earlier binding and updates the text immediately.
/// </summary>
/// <param name="target">Text to localize.</param>
/// <param name="key">Localization key, e.g. <c>"ui.render_config.codec"</c>.</param>
/// <returns>The binding component.</returns>
/// <exception cref="ArgumentNullException"><paramref name="target"/> is null.</exception>
public static LocalizedText Bind(TextMeshProUGUI target, string key)
{
if (target == null) throw new ArgumentNullException(nameof(target));
var component = target.GetComponent<LocalizedText>() ?? target.gameObject.AddComponent<LocalizedText>();
component._textComponent = target;
component.Key = key;
return component;
}
/// <summary>
/// Binds a TextMeshProUGUI component to a dynamic string provider function.
/// Reuses or adds the component like <see cref="Bind(TextMeshProUGUI, string)"/> and updates the text immediately.
/// </summary>
/// <param name="target">Text to localize.</param>
/// <param name="provider">Function returning the text; called on every refresh.</param>
/// <returns>The binding component.</returns>
/// <exception cref="ArgumentNullException"><paramref name="target"/> is null.</exception>
public static LocalizedText Bind(TextMeshProUGUI target, Func<string> provider)
{
if (target == null) throw new ArgumentNullException(nameof(target));
var component = target.GetComponent<LocalizedText>() ?? target.gameObject.AddComponent<LocalizedText>();
component._textComponent = target;
component.SetDynamic(provider);
return component;
}
}