feat: 本地化字体兼容

This commit is contained in:
2026-08-02 01:22:40 +08:00
parent 3bff294130
commit 3adb04ebb3
56 changed files with 2233 additions and 99 deletions
@@ -1,5 +1,6 @@
using System.Collections;
using System;
using System.Threading;
using System.Threading.Tasks;
using AibisDream.Utility;
using UnityEngine;
@@ -11,22 +12,14 @@ using UnityEditor;
namespace AibisDream.Framework
{
/// <summary>
/// csv 本地化列类别。集中声明"哪些 Locale 归到哪一列",csv 数据类按此枚举取字段。
/// </summary>
public enum LocaleKind
{
Cn,
En,
Ja,
Ru,
Es,
PtBr
}
public static class LocalizationKit
{
private const string LocalizationPrefix = "l10n.";
private static Task<bool> currentLanguageSwitchTask;
private static int languageSwitchVersion;
private static bool managedLocaleChange;
private static bool typographyLoaderConfigured;
private static bool localeChangedSubscribed;
public static string CurrentLanguageCode { get; private set; } = "zh-Hans";
public static Locale CurrentLocale => LocalizationSettings.SelectedLocale;
@@ -78,8 +71,14 @@ namespace AibisDream.Framework
{
#if UNITY_EDITOR
EditorApplication.playModeStateChanged += OnPlayModeChanged;
if (EditorApplication.isPlayingOrWillChangePlaymode)
{
EnsureTypographyLoaderConfigured();
SubscribeLocaleChanged();
RefreshCurrentLanguageCodeAsync();
}
#else
LocalizationSettings.SelectedLocaleChanged += OnLocaleChanged;
SubscribeLocaleChanged();
RefreshCurrentLanguageCodeAsync();
#endif
}
@@ -88,6 +87,14 @@ namespace AibisDream.Framework
{
UpdateCurrentLanguageCode(locale);
EnumEventSystem.Global.Send(SettingChangeEvent.LocaleChanged, locale);
if (!managedLocaleChange && locale != null)
{
EnsureTypographyLoaderConfigured();
LocaleKind localeKind = GetLocaleKind(locale.Identifier.Code);
if (!TypographyService.IsActiveFor(localeKind))
currentLanguageSwitchTask = TypographyService.LoadAndActivateAsync(localeKind);
}
}
private static void UpdateCurrentLanguageCode(Locale locale)
@@ -109,32 +116,95 @@ namespace AibisDream.Framework
{
if (playMode == PlayModeStateChange.EnteredPlayMode)
{
LocalizationSettings.SelectedLocaleChanged += OnLocaleChanged;
EnsureTypographyLoaderConfigured();
SubscribeLocaleChanged();
RefreshCurrentLanguageCodeAsync();
}
else if (playMode == PlayModeStateChange.ExitingPlayMode)
{
LocalizationSettings.SelectedLocaleChanged -= OnLocaleChanged;
UnsubscribeLocaleChanged();
TypographyService.ResetForTests();
typographyLoaderConfigured = false;
currentLanguageSwitchTask = null;
}
}
#endif
public static async void SwitchLanguage(string localeCode)
public static void SwitchLanguage(string localeCode)
{
_ = SwitchLanguageAsync(localeCode);
}
public static Task<bool> SwitchLanguageAsync(string localeCode)
{
int requestVersion = Interlocked.Increment(ref languageSwitchVersion);
currentLanguageSwitchTask = SwitchLanguageCoreAsync(localeCode, requestVersion);
return currentLanguageSwitchTask;
}
private static async Task<bool> SwitchLanguageCoreAsync(string localeCode, int requestVersion)
{
// 等待本地化系统初始化完成
await LocalizationSettings.InitializationOperation.Task;
var selectedLocale =
LocalizationSettings.AvailableLocales.Locales.Find(locale => locale.Identifier.Code == localeCode);
if (!selectedLocale)
{
Debug.LogWarning($"Locale with code {localeCode} not found.");
return false;
}
if (selectedLocale)
EnsureTypographyLoaderConfigured();
bool typographyReady = await TypographyService.LoadAndActivateAsync(GetLocaleKind(localeCode));
if (requestVersion != languageSwitchVersion)
return false;
managedLocaleChange = true;
try
{
LocalizationSettings.SelectedLocale = selectedLocale;
}
else
finally
{
Debug.LogWarning($"Locale with code {localeCode} not found.");
managedLocaleChange = false;
}
await Task.Yield();
Canvas.ForceUpdateCanvases();
if (!typographyReady)
{
Debug.LogWarning(
$"[LocalizationKit] Locale 已切换为 {localeCode},但字体 Profile 加载失败,保留上一套字体。");
}
return true;
}
private static void EnsureTypographyLoaderConfigured()
{
if (typographyLoaderConfigured)
return;
TypographyService.ConfigureLoader(new ResourceSystemTypographyProfileLoader());
typographyLoaderConfigured = true;
}
private static void SubscribeLocaleChanged()
{
if (localeChangedSubscribed)
return;
LocalizationSettings.SelectedLocaleChanged += OnLocaleChanged;
localeChangedSubscribed = true;
}
private static void UnsubscribeLocaleChanged()
{
if (!localeChangedSubscribed)
return;
LocalizationSettings.SelectedLocaleChanged -= OnLocaleChanged;
localeChangedSubscribed = false;
}
/// <summary>
@@ -250,6 +320,24 @@ namespace AibisDream.Framework
public static IEnumerator WaitUntilReady()
{
yield return LocalizationSettings.InitializationOperation;
Task<bool> languageTask;
do
{
languageTask = currentLanguageSwitchTask;
while (languageTask != null && !languageTask.IsCompleted)
yield return null;
} while (languageTask != currentLanguageSwitchTask);
EnsureTypographyLoaderConfigured();
if (!TypographyService.IsReady)
{
Task<bool> typographyTask = TypographyService.LoadAndActivateAsync(
GetLocaleKind(LocalizationSettings.SelectedLocale?.Identifier.Code));
while (!typographyTask.IsCompleted)
yield return null;
}
yield return null;
}
@@ -0,0 +1,33 @@
using System.Threading.Tasks;
using UnityEngine.ResourceManagement.AsyncOperations;
namespace AibisDream.Framework
{
internal sealed class ResourceSystemTypographyProfileLoader : ITypographyProfileLoader
{
public async Task<TypographyProfile> LoadAsync(string key)
{
AsyncOperationHandle<TypographyProfile> handle =
ResourceSystem.LoadPersistentAsync<TypographyProfile>(key);
try
{
await handle.Task;
if (handle.Status == AsyncOperationStatus.Succeeded && handle.Result != null)
return handle.Result;
ResourceSystem.EarlyReleasePersistent(key);
return null;
}
catch
{
ResourceSystem.EarlyReleasePersistent(key);
throw;
}
}
public void Release(string key)
{
ResourceSystem.EarlyReleasePersistent(key);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c9dd51810ef0cc94398175f3597a978e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -140,6 +140,15 @@ namespace AibisDream.Framework
public static void EarlyRelease<T>(AsyncOperationHandle<T> handle)
=> ActiveLoader.EarlyRelease(handle);
/// <summary>
/// 提前释放持久化 Loader 中指定 Key 的资源。
/// </summary>
public static void EarlyReleasePersistent(string key)
{
if (_persistentLoader != null)
_persistentLoader.EarlyRelease(key);
}
#endregion
}
}