Files
ichni_Official/Assets/Scripts/Localization/LocalizedTMPText.cs

209 lines
6.4 KiB
C#
Raw Normal View History

2026-07-25 13:27:53 -04:00
using TMPro;
using UnityEngine;
using UnityEngine.Localization;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.Localization;
using UnityEngine.Localization.Settings;
using UnityEngine.Localization.Tables;
#endif
namespace Ichni.Localization
{
/// <summary>
/// 将静态 <see cref="TMP_Text"/> 与 Unity Localization 的
/// <see cref="LocalizedString"/> 绑定。
/// <para>
/// 组件启用时订阅 <see cref="LocalizedString.StringChanged"/>。因此切换 Locale
/// 或在 Play Mode 中修改 Inspector 里的 String Reference 后TMP 文本会自动更新。
/// </para>
/// <para>
/// Inspector 自带的 String Reference 可预览各 Locale 的文本。Edit Mode 中变更引用时,
/// 本组件只将项目默认 Locale 的文本同步到 TMP_Text便于直接预览不保存回退文本
/// 也不提供额外的编辑器自检功能。
/// </para>
/// </summary>
[DisallowMultipleComponent]
[RequireComponent(typeof(TMP_Text))]
[AddComponentMenu("Localization/Localized TMP Text")]
public sealed class LocalizedTMPText : MonoBehaviour
{
[SerializeField]
[Tooltip("要更新的 TMP 文本。留空时会自动使用当前 GameObject 上的 TMP_Text。")]
private TMP_Text targetText;
[SerializeField]
[Tooltip("在 Inspector 中选择 String Table Collection 和对应的 Entry。")]
private LocalizedString stringReference = new LocalizedString();
private bool _isListening;
/// <summary>当前绑定的 TMP 文本。</summary>
public TMP_Text TargetText => targetText;
/// <summary>当前使用的 Unity Localization 字符串引用。</summary>
public LocalizedString StringReference => stringReference;
/// <summary>
/// 在运行时替换当前文本使用的 String Reference。
/// <para>
/// 该接口主要供动态生成的 UI 使用,例如 Dropdown 的选项 Item。替换前会先解除旧引用的
/// <see cref="LocalizedString.StringChanged"/> 订阅,再为新引用建立订阅,因此不会残留
/// 旧语言表的回调,也不会重复监听。
/// </para>
/// </summary>
public void SetStringReference(LocalizedString reference)
{
StopListening();
stringReference = reference ?? new LocalizedString();
EnsureTargetText();
if (isActiveAndEnabled)
{
StartListening();
}
#if UNITY_EDITOR
if (!Application.isPlaying)
{
ApplyEditorPreviewText();
}
#endif
}
private void Awake()
{
EnsureTargetText();
}
private void OnValidate()
{
EnsureTargetText();
if (Application.isPlaying)
{
// Play Mode 中通过 Inspector 更换 Table 或 Entry 后立即刷新。
if (_isListening)
{
stringReference?.RefreshString();
}
return;
}
#if UNITY_EDITOR
ApplyEditorPreviewText();
#endif
}
private void OnEnable()
{
EnsureTargetText();
StartListening();
}
private void OnDisable()
{
StopListening();
}
private void OnDestroy()
{
StopListening();
}
/// <summary>
/// 主动请求重新解析当前 String Reference。
/// 通常仅在运行时修改 Smart String 参数后调用;普通语言切换会自动刷新。
/// </summary>
public void RefreshText()
{
if (_isListening)
{
stringReference.RefreshString();
}
}
private void StopListening()
{
if (!_isListening || stringReference == null)
{
return;
}
stringReference.StringChanged -= ApplyText;
_isListening = false;
}
/// <summary>
/// 为当前 String Reference 建立语言变化监听。
/// Unity Localization 会在首次订阅时请求当前 Locale 的文本;这里不使用
/// <c>WaitForCompletion</c>,以避免阻塞主线程或干扰 Addressables 队列。
/// </summary>
private void StartListening()
{
if (_isListening || stringReference == null || stringReference.IsEmpty)
{
return;
}
stringReference.StringChanged += ApplyText;
_isListening = true;
}
private void EnsureTargetText()
{
if (targetText == null)
{
targetText = GetComponent<TMP_Text>();
}
}
private void ApplyText(string localizedText)
{
EnsureTargetText();
if (targetText != null)
{
targetText.text = localizedText ?? string.Empty;
}
}
#if UNITY_EDITOR
/// <summary>
/// 在编辑器中将项目默认 Locale 的已导入文本同步到 TMP_Text。
/// 这里直接读取 String Table 资产,不调用异步 Localization API因而不会加载 Addressables。
/// </summary>
private void ApplyEditorPreviewText()
{
if (targetText == null || stringReference == null || stringReference.IsEmpty)
{
return;
}
Locale projectLocale = LocalizationSettings.ProjectLocale;
StringTableCollection tableCollection =
LocalizationEditorSettings.GetStringTableCollection(stringReference.TableReference);
StringTable table = projectLocale == null
? null
: tableCollection?.GetTable(projectLocale.Identifier) as StringTable;
StringTableEntry entry = table?.GetEntryFromReference(stringReference.TableEntryReference);
if (entry == null)
{
return;
}
string previewText = entry.LocalizedValue ?? string.Empty;
if (targetText.text == previewText)
{
return;
}
targetText.text = previewText;
EditorUtility.SetDirty(targetText);
}
#endif
}
}