209 lines
6.4 KiB
C#
209 lines
6.4 KiB
C#
|
|
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
|
|||
|
|
}
|
|||
|
|
}
|