diff --git a/Assets/Editor/Localization.meta b/Assets/Editor/Localization.meta new file mode 100644 index 000000000..6f91e2d37 --- /dev/null +++ b/Assets/Editor/Localization.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d3a91a90ebc2fd742bd10a66893940b8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Editor/Localization/LocalizedObjSpriteValidator.cs b/Assets/Editor/Localization/LocalizedObjSpriteValidator.cs new file mode 100644 index 000000000..1bdc75c65 --- /dev/null +++ b/Assets/Editor/Localization/LocalizedObjSpriteValidator.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using AibisDream.UI; +using UnityEditor; +using UnityEditor.Build; +using UnityEditor.Build.Reporting; +using UnityEngine; + +namespace AibisDream.EditorTools.Localization +{ + public readonly struct LocalizedObjSpriteValidationIssue + { + public LocalizedObjSpriteValidationIssue(string assetPath, string message) + { + AssetPath = assetPath; + Message = message; + } + + public string AssetPath { get; } + public string Message { get; } + + public override string ToString() + { + return $"{AssetPath}: {Message}"; + } + } + + /// + /// Validates localized Obj sprite groups without depending on Yarn references or + /// Unity Localization asset tables. A group becomes localized as soon as one + /// supported non-Chinese suffix is present. + /// + public static class LocalizedObjSpriteValidator + { + internal const string DefaultObjSpriteRoot = "Assets/RawResources/Art/Obj"; + private const string MenuPath = + "Tools/AIBIS/Localization/Validate Localized Obj Sprites"; + + [MenuItem(MenuPath)] + public static void ValidateFromMenu() + { + IReadOnlyList issues = Validate(); + LogIssues(issues); + if (issues.Count == 0) + { + Debug.Log( + "[LocalizedObjSpriteValidator] Validation passed. " + + "All detected localized Obj sprite groups are complete."); + } + } + + public static IReadOnlyList Validate( + string rootPath = DefaultObjSpriteRoot) + { + var issues = new List(); + if (!Directory.Exists(rootPath)) + { + issues.Add(new LocalizedObjSpriteValidationIssue( + ToAssetPath(rootPath), + "Obj sprite root does not exist.")); + return issues; + } + + var localizedGroups = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (string filePath in Directory.EnumerateFiles( + rootPath, + "*.png", + SearchOption.AllDirectories)) + { + string fileName = Path.GetFileNameWithoutExtension(filePath); + if (!LocalizedObjSpriteNaming.TryParseLocalizedVariant( + fileName, + out string logicalName, + out _)) + { + continue; + } + + if (string.IsNullOrWhiteSpace(logicalName)) + { + issues.Add(new LocalizedObjSpriteValidationIssue( + ToAssetPath(filePath), + "Localized variant has an empty logical name.")); + continue; + } + + string directory = Path.GetDirectoryName(filePath) ?? rootPath; + localizedGroups.Add(Path.Combine(directory, logicalName)); + } + + foreach (string groupPath in localizedGroups.OrderBy( + path => path, + StringComparer.OrdinalIgnoreCase)) + { + ValidateRequiredFile( + $"{groupPath}.png", + LocalizedObjSpriteNaming.DefaultLocaleCode, + issues); + + foreach (string localeCode in + LocalizedObjSpriteNaming.NonDefaultLocaleCodes) + { + ValidateRequiredFile( + $"{groupPath}-{localeCode}.png", + localeCode, + issues); + } + } + + return issues; + } + + internal static void LogIssues( + IReadOnlyList issues) + { + foreach (LocalizedObjSpriteValidationIssue issue in issues) + Debug.LogError($"[LocalizedObjSpriteValidator] {issue}"); + } + + private static void ValidateRequiredFile( + string filePath, + string localeCode, + ICollection issues) + { + if (File.Exists(filePath)) + return; + + issues.Add(new LocalizedObjSpriteValidationIssue( + ToAssetPath(filePath), + $"Missing required '{localeCode}' sprite variant.")); + } + + private static string ToAssetPath(string path) + { + return path.Replace('\\', '/'); + } + } + + public sealed class LocalizedObjSpriteBuildValidator : IPreprocessBuildWithReport + { + // Run before build preprocessors that mutate version information. + public int callbackOrder => -1000; + + public void OnPreprocessBuild(BuildReport report) + { + IReadOnlyList issues = + LocalizedObjSpriteValidator.Validate(); + if (issues.Count == 0) + return; + + LocalizedObjSpriteValidator.LogIssues(issues); + string summary = string.Join( + Environment.NewLine, + issues.Take(20).Select(issue => issue.ToString())); + if (issues.Count > 20) + { + summary += Environment.NewLine + + $"... and {issues.Count - 20} more issue(s)."; + } + + throw new BuildFailedException( + "Localized Obj sprite validation failed:" + + Environment.NewLine + summary); + } + } +} diff --git a/Assets/Editor/Localization/LocalizedObjSpriteValidator.cs.meta b/Assets/Editor/Localization/LocalizedObjSpriteValidator.cs.meta new file mode 100644 index 000000000..17fcfcaba --- /dev/null +++ b/Assets/Editor/Localization/LocalizedObjSpriteValidator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 686ca5acbb6647d4c802ecd2a8ba043d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Editor/Tests/LocalizedObjSpriteTests.cs b/Assets/Editor/Tests/LocalizedObjSpriteTests.cs new file mode 100644 index 000000000..521dbbfe2 --- /dev/null +++ b/Assets/Editor/Tests/LocalizedObjSpriteTests.cs @@ -0,0 +1,97 @@ +using System; +using System.IO; +using System.Linq; +using AibisDream.EditorTools.Localization; +using AibisDream.UI; +using NUnit.Framework; + +namespace AibisDream.SystemEditor.Tests +{ + public sealed class LocalizedObjSpriteTests + { + private string temporaryRoot; + + [SetUp] + public void SetUp() + { + temporaryRoot = Path.Combine( + Path.GetTempPath(), + "AibisDreamLocalizedObjSpriteTests", + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(temporaryRoot); + } + + [TearDown] + public void TearDown() + { + if (!string.IsNullOrEmpty(temporaryRoot) && Directory.Exists(temporaryRoot)) + Directory.Delete(temporaryRoot, true); + } + + [TestCase("zh-Hans", "病历")] + [TestCase("zh-CN", "病历")] + [TestCase("en", "病历-en")] + [TestCase("en-US", "病历-en")] + [TestCase("ja-JP", "病历-ja-JP")] + [TestCase("ru-RU", "病历-ru")] + [TestCase("es-ES", "病历-es")] + [TestCase("pt-BR", "病历-pt-BR")] + public void GetPicName_UsesCanonicalLocaleSuffix( + string localeCode, + string expected) + { + Assert.That( + LocalizedObjSpriteNaming.GetPicName("病历.png", localeCode), + Is.EqualTo(expected)); + } + + [Test] + public void CandidateNames_FallBackToUnsuffixedChineseWithoutDuplicates() + { + Assert.That( + LocalizedObjSpriteNaming.GetCandidatePicNames("病历.png", "en"), + Is.EqualTo(new[] { "病历-en", "病历" })); + Assert.That( + LocalizedObjSpriteNaming.GetCandidatePicNames("病历.png", "zh-Hans"), + Is.EqualTo(new[] { "病历" })); + } + + [Test] + public void Validator_IgnoresUnsuffixedOnlySprites() + { + WriteSprite("普通图片.png"); + + Assert.That( + LocalizedObjSpriteValidator.Validate(temporaryRoot), + Is.Empty); + } + + [Test] + public void Validator_RequiresEveryVariantAfterAGroupIsDetected() + { + WriteCompleteGroup("病历"); + Assert.That( + LocalizedObjSpriteValidator.Validate(temporaryRoot), + Is.Empty); + + File.Delete(Path.Combine(temporaryRoot, "病历-ru.png")); + var issues = LocalizedObjSpriteValidator.Validate(temporaryRoot); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().AssetPath, Does.EndWith("病历-ru.png")); + Assert.That(issues.Single().Message, Does.Contain("'ru'")); + } + + private void WriteCompleteGroup(string logicalName) + { + WriteSprite($"{logicalName}.png"); + foreach (string localeCode in LocalizedObjSpriteNaming.NonDefaultLocaleCodes) + WriteSprite($"{logicalName}-{localeCode}.png"); + } + + private void WriteSprite(string fileName) + { + File.WriteAllBytes(Path.Combine(temporaryRoot, fileName), Array.Empty()); + } + } +} diff --git a/Assets/Editor/Tests/LocalizedObjSpriteTests.cs.meta b/Assets/Editor/Tests/LocalizedObjSpriteTests.cs.meta new file mode 100644 index 000000000..2f13e56a9 --- /dev/null +++ b/Assets/Editor/Tests/LocalizedObjSpriteTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 212d27ffec81bcb48a886db924f43e1a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Editor/Tests/SaveSnapshotContractTests.cs b/Assets/Editor/Tests/SaveSnapshotContractTests.cs index e3b7b87a7..99473a8c8 100644 --- a/Assets/Editor/Tests/SaveSnapshotContractTests.cs +++ b/Assets/Editor/Tests/SaveSnapshotContractTests.cs @@ -36,7 +36,11 @@ namespace AibisDream.SystemEditor.Tests source.sections[SnapshotProviderIds.PlayTool] = new PlayToolSnapshotDto { isObjVisible = true, - objPicName = "证物" + objPicName = "证物", + objIsLocalized = true, + isFullScreenVisible = true, + fullScreenPicName = "病历", + fullScreenIsLocalized = true }; var restored = JsonConvert.DeserializeObject( @@ -50,6 +54,14 @@ namespace AibisDream.SystemEditor.Tests Assert.That(showcase.displayMode, Is.EqualTo("Large")); Assert.That(showcase.picName, Is.EqualTo("D2S星图")); Assert.That(playTool.objPicName, Is.EqualTo("证物")); + Assert.That(playTool.objIsLocalized, Is.True); + Assert.That(playTool.fullScreenPicName, Is.EqualTo("病历")); + Assert.That(playTool.fullScreenIsLocalized, Is.True); + + var legacyPlayTool = JsonConvert.DeserializeObject( + "{\"isObjVisible\":true,\"objPicName\":\"legacy\"}"); + Assert.That(legacyPlayTool.objIsLocalized, Is.False); + Assert.That(legacyPlayTool.fullScreenIsLocalized, Is.False); var legacy = JsonConvert.DeserializeObject( "{\"schemaVersion\":1,\"sections\":{}}"); diff --git a/Assets/GameContent/Feature_MainUI/Prefabs/TerminalChapterPanel.prefab b/Assets/GameContent/Feature_MainUI/Prefabs/TerminalChapterPanel.prefab index 3a5b2aec0..e04d1eae1 100644 --- a/Assets/GameContent/Feature_MainUI/Prefabs/TerminalChapterPanel.prefab +++ b/Assets/GameContent/Feature_MainUI/Prefabs/TerminalChapterPanel.prefab @@ -692,7 +692,7 @@ MonoBehaviour: m_EditorClassIdentifier: canvasGroup: {fileID: 4638119140970039337} sweep: {fileID: 0} - visibleAlpha: 0.6 + visibleAlpha: 0.8 fadeDuration: 0.06 showEase: 1 hideEase: 1 diff --git a/Assets/GameContent/Feature_MainUI/Prefabs/TerminalSettingPanelMini.prefab b/Assets/GameContent/Feature_MainUI/Prefabs/TerminalSettingPanelMini.prefab index e15662a1f..f933e317d 100644 --- a/Assets/GameContent/Feature_MainUI/Prefabs/TerminalSettingPanelMini.prefab +++ b/Assets/GameContent/Feature_MainUI/Prefabs/TerminalSettingPanelMini.prefab @@ -67,7 +67,7 @@ MonoBehaviour: m_EditorClassIdentifier: canvasGroup: {fileID: 2695752103100139348} sweep: {fileID: 0} - visibleAlpha: 0.6 + visibleAlpha: 0.8 fadeDuration: 0.06 showEase: 1 hideEase: 1 @@ -385,7 +385,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 27177156846382951, guid: e45f5fd66e7512444a9a27161cf51b7f, type: 3} propertyPath: m_SizeDelta.x - value: 240 + value: 350 objectReference: {fileID: 0} - target: {fileID: 27177156846382951, guid: e45f5fd66e7512444a9a27161cf51b7f, type: 3} propertyPath: m_AnchoredPosition.x @@ -650,7 +650,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 27177156846382951, guid: e45f5fd66e7512444a9a27161cf51b7f, type: 3} propertyPath: m_SizeDelta.x - value: 240 + value: 350 objectReference: {fileID: 0} - target: {fileID: 27177156846382951, guid: e45f5fd66e7512444a9a27161cf51b7f, type: 3} propertyPath: m_AnchoredPosition.x @@ -915,7 +915,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 27177156846382951, guid: e45f5fd66e7512444a9a27161cf51b7f, type: 3} propertyPath: m_SizeDelta.x - value: 240 + value: 350 objectReference: {fileID: 0} - target: {fileID: 27177156846382951, guid: e45f5fd66e7512444a9a27161cf51b7f, type: 3} propertyPath: m_AnchoredPosition.x diff --git a/Assets/GameContent/Feature_MainUI/Prefabs/TerminalStartPanel.prefab b/Assets/GameContent/Feature_MainUI/Prefabs/TerminalStartPanel.prefab index 144f8fbb8..16045e048 100644 --- a/Assets/GameContent/Feature_MainUI/Prefabs/TerminalStartPanel.prefab +++ b/Assets/GameContent/Feature_MainUI/Prefabs/TerminalStartPanel.prefab @@ -66,7 +66,7 @@ MonoBehaviour: m_EditorClassIdentifier: canvasGroup: {fileID: 5054907813288028710} sweep: {fileID: 0} - visibleAlpha: 0.6 + visibleAlpha: 0.8 fadeDuration: 0.06 showEase: 1 hideEase: 1 diff --git a/Assets/Scenes/Persistence.unity b/Assets/Scenes/Persistence.unity index 221b7cbec..0aa155a11 100644 --- a/Assets/Scenes/Persistence.unity +++ b/Assets/Scenes/Persistence.unity @@ -8704,10 +8704,6 @@ PrefabInstance: serializedVersion: 3 m_TransformParent: {fileID: 834154072} m_Modifications: - - target: {fileID: 36740575257710488, guid: 9514dab519fa1f24dacc2903b8f331c3, type: 3} - propertyPath: m_SizeDelta.x - value: 350 - objectReference: {fileID: 0} - target: {fileID: 270038765062520215, guid: 9514dab519fa1f24dacc2903b8f331c3, type: 3} propertyPath: m_Pivot.x value: 0.5 @@ -8788,14 +8784,6 @@ PrefabInstance: propertyPath: m_LocalEulerAnglesHint.z value: 0 objectReference: {fileID: 0} - - target: {fileID: 497019700501100578, guid: 9514dab519fa1f24dacc2903b8f331c3, type: 3} - propertyPath: m_SizeDelta.x - value: 350 - objectReference: {fileID: 0} - - target: {fileID: 827752375999759650, guid: 9514dab519fa1f24dacc2903b8f331c3, type: 3} - propertyPath: m_SizeDelta.x - value: 350 - objectReference: {fileID: 0} - target: {fileID: 1011642959047981851, guid: 9514dab519fa1f24dacc2903b8f331c3, type: 3} propertyPath: m_Name value: TerminalSettingPanel diff --git a/Assets/Scripts/Dialog System/BaseYarnCommand.cs b/Assets/Scripts/Dialog System/BaseYarnCommand.cs index cc29f9ed4..5ca1c728a 100644 --- a/Assets/Scripts/Dialog System/BaseYarnCommand.cs +++ b/Assets/Scripts/Dialog System/BaseYarnCommand.cs @@ -402,6 +402,13 @@ namespace AibisDream return panel.ShowObj(picName, duration); } + [YarnCommand("show_l10n_obj")] + public static IEnumerator ShowL10nObj(string picName, float duration = 1) + { + var panel = UIManager.Instance.GetPanel(); + return panel.ShowL10nObj(picName, duration); + } + [YarnCommand("hide_obj")] public static IEnumerator HideObj(float duration = 1) { @@ -470,6 +477,13 @@ namespace AibisDream return panel.ShowFullScreen(picName, duration); } + [YarnCommand("show_l10n_full_screen")] + public static IEnumerator ShowL10nFullScreen(string picName, float duration = 1) + { + var panel = UIManager.Instance.GetPanel(); + return panel.ShowL10nFullScreen(picName, duration); + } + [YarnCommand("hide_full_screen")] public static IEnumerator HideFullScreen(float duration = 1) { diff --git a/Assets/Scripts/SaveSystem/SaveSnapshot.cs b/Assets/Scripts/SaveSystem/SaveSnapshot.cs index a9961d27f..f2ba024da 100644 --- a/Assets/Scripts/SaveSystem/SaveSnapshot.cs +++ b/Assets/Scripts/SaveSystem/SaveSnapshot.cs @@ -197,8 +197,10 @@ namespace AibisDream.SaveSystem { public bool isObjVisible; public string objPicName; + public bool objIsLocalized; public bool isFullScreenVisible; public string fullScreenPicName; + public bool fullScreenIsLocalized; } public enum ShowcaseDisplayMode diff --git a/Assets/Scripts/UI/Panel/PlayTool/LocalizedObjSpriteNaming.cs b/Assets/Scripts/UI/Panel/PlayTool/LocalizedObjSpriteNaming.cs new file mode 100644 index 000000000..fff0e1486 --- /dev/null +++ b/Assets/Scripts/UI/Panel/PlayTool/LocalizedObjSpriteNaming.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using AibisDream.Framework; + +namespace AibisDream.UI +{ + /// + /// Defines the file-name convention used by localized Obj sprites. + /// Chinese uses the unsuffixed source asset; every other supported locale uses + /// "{logicalName}-{localeCode}". + /// + internal static class LocalizedObjSpriteNaming + { + internal const string DefaultLocaleCode = "zh-Hans"; + + private static readonly string[] NonDefaultLocaleCodeValues = + { + "en", + "ja-JP", + "ru", + "es", + "pt-BR" + }; + + internal static IReadOnlyList NonDefaultLocaleCodes => + NonDefaultLocaleCodeValues; + + internal static string NormalizeLogicalName(string picName) + { + if (string.IsNullOrEmpty(picName)) return picName; + return picName.EndsWith(".png", StringComparison.OrdinalIgnoreCase) + ? picName.Substring(0, picName.Length - 4) + : picName; + } + + internal static string GetAssetLocaleCode(string localeCode) + { + return LocalizationKit.GetLocaleKind(localeCode) switch + { + LocaleKind.En => "en", + LocaleKind.Ja => "ja-JP", + LocaleKind.Ru => "ru", + LocaleKind.Es => "es", + LocaleKind.PtBr => "pt-BR", + _ => DefaultLocaleCode + }; + } + + internal static string GetPicName(string logicalName, string localeCode) + { + string normalizedName = NormalizeLogicalName(logicalName); + string assetLocaleCode = GetAssetLocaleCode(localeCode); + return string.Equals( + assetLocaleCode, + DefaultLocaleCode, + StringComparison.Ordinal) + ? normalizedName + : $"{normalizedName}-{assetLocaleCode}"; + } + + internal static IReadOnlyList GetCandidatePicNames( + string logicalName, + string localeCode) + { + string normalizedName = NormalizeLogicalName(logicalName); + string localizedName = GetPicName(normalizedName, localeCode); + if (string.Equals(localizedName, normalizedName, StringComparison.Ordinal)) + return new[] { normalizedName }; + + return new[] { localizedName, normalizedName }; + } + + internal static bool TryParseLocalizedVariant( + string fileNameWithoutExtension, + out string logicalName, + out string localeCode) + { + foreach (string candidateCode in NonDefaultLocaleCodeValues) + { + string suffix = $"-{candidateCode}"; + if (!fileNameWithoutExtension.EndsWith( + suffix, + StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + logicalName = fileNameWithoutExtension.Substring( + 0, + fileNameWithoutExtension.Length - suffix.Length); + localeCode = candidateCode; + return true; + } + + logicalName = null; + localeCode = null; + return false; + } + } +} diff --git a/Assets/Scripts/UI/Panel/PlayTool/LocalizedObjSpriteNaming.cs.meta b/Assets/Scripts/UI/Panel/PlayTool/LocalizedObjSpriteNaming.cs.meta new file mode 100644 index 000000000..b8cf38f51 --- /dev/null +++ b/Assets/Scripts/UI/Panel/PlayTool/LocalizedObjSpriteNaming.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 81844af51d08a8e40adc1a25247f2337 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/UI/Panel/PlayToolPanel.cs b/Assets/Scripts/UI/Panel/PlayToolPanel.cs index f4792e90c..5e6d83cc6 100644 --- a/Assets/Scripts/UI/Panel/PlayToolPanel.cs +++ b/Assets/Scripts/UI/Panel/PlayToolPanel.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Collections.Generic; using AibisDream.Framework; using AibisDream.Kit; using AibisDream.SaveSystem; @@ -7,6 +8,7 @@ using AibisDream.Utility; using DG.Tweening; using RainbowArt.CleanFlatUI; using UnityEngine; +using UnityEngine.Localization.Settings; using UnityEngine.ResourceManagement.AsyncOperations; using UnityEngine.UI; @@ -41,6 +43,8 @@ namespace AibisDream.UI private int _fullScreenOperationVersion; private string _currentObjPicName; private string _currentFullScreenPicName; + private bool _currentObjIsLocalized; + private bool _currentFullScreenIsLocalized; private bool _isObjVisible; private bool _isFullScreenVisible; @@ -64,10 +68,7 @@ namespace AibisDream.UI private static string NormalizeObjPicName(string picName) { - if (string.IsNullOrEmpty(picName)) return picName; - return picName.EndsWith(".png", StringComparison.OrdinalIgnoreCase) - ? picName.Substring(0, picName.Length - 4) - : picName; + return LocalizedObjSpriteNaming.NormalizeLogicalName(picName); } private IEnumerator LoadObjSprite( @@ -91,6 +92,62 @@ namespace AibisDream.UI completed?.Invoke(null); } + private IEnumerator LoadLocalizedObjSprite( + string logicalName, + Action completed, + Action warning = null) + { + yield return LocalizationKit.WaitUntilReady(); + + string localeCode = LocalizationSettings.SelectedLocale?.Identifier.Code; + IReadOnlyList candidates = + LocalizedObjSpriteNaming.GetCandidatePicNames(logicalName, localeCode); + + for (int index = 0; index < candidates.Count; index++) + { + Sprite sprite = null; + Action loadWarning = index == candidates.Count - 1 + ? warning + : null; + yield return LoadObjSprite( + candidates[index], + value => sprite = value, + loadWarning); + if (sprite == null) + continue; + + if (index > 0) + { + string message = + $"[PlayToolPanel] Localized sprite is missing for locale " + + $"'{LocalizedObjSpriteNaming.GetAssetLocaleCode(localeCode)}': " + + $"{NormalizeObjPicName(logicalName)}. Falling back to zh-Hans."; + Debug.LogWarning(message); + warning?.Invoke(message); + } + + completed?.Invoke(sprite); + yield break; + } + + completed?.Invoke(null); + } + + private IEnumerator LoadObjSprite( + string picName, + bool isLocalized, + Action completed, + Action warning = null) + { + if (isLocalized) + { + yield return LoadLocalizedObjSprite(picName, completed, warning); + yield break; + } + + yield return LoadObjSprite(picName, completed, warning); + } + private void ApplyObjSprite(Sprite sprite) { var imageAspect = (float)sprite.texture.width / sprite.texture.height; @@ -113,6 +170,7 @@ namespace AibisDream.UI _showObjImage.SetAlpha(0f); _showObjPanel.SetActive(false); _currentObjPicName = null; + _currentObjIsLocalized = false; _isObjVisible = false; } @@ -123,6 +181,7 @@ namespace AibisDream.UI _fullScreenImage.SetAlpha(0f); _fullScreenImage.gameObject.SetActive(false); _currentFullScreenPicName = null; + _currentFullScreenIsLocalized = false; _isFullScreenVisible = false; } @@ -131,12 +190,28 @@ namespace AibisDream.UI /// /// 协程 public IEnumerator ShowObj(string picName, float duration = 1) + { + yield return ShowObjInternal(picName, duration, false); + } + + public IEnumerator ShowL10nObj(string picName, float duration = 1) + { + yield return ShowObjInternal(picName, duration, true); + } + + private IEnumerator ShowObjInternal( + string picName, + float duration, + bool isLocalized) { var version = ++_objOperationVersion; _showObjImage.DOKill(); Sprite newSprite = null; - yield return LoadObjSprite(picName, value => newSprite = value); + yield return LoadObjSprite( + picName, + isLocalized, + value => newSprite = value); if (version != _objOperationVersion) { yield break; @@ -155,6 +230,7 @@ namespace AibisDream.UI if (version != _objOperationVersion) yield break; _currentObjPicName = NormalizeObjPicName(picName); + _currentObjIsLocalized = isLocalized; _isObjVisible = true; } @@ -162,6 +238,7 @@ namespace AibisDream.UI { var version = ++_objOperationVersion; _currentObjPicName = null; + _currentObjIsLocalized = false; _isObjVisible = false; _showObjImage.DOKill(); yield return _showObjImage.FadeOutAsync(duration); @@ -170,13 +247,29 @@ namespace AibisDream.UI } public IEnumerator ShowFullScreen(string picName, float duration = 1) + { + yield return ShowFullScreenInternal(picName, duration, false); + } + + public IEnumerator ShowL10nFullScreen(string picName, float duration = 1) + { + yield return ShowFullScreenInternal(picName, duration, true); + } + + private IEnumerator ShowFullScreenInternal( + string picName, + float duration, + bool isLocalized) { var version = ++_fullScreenOperationVersion; _fullScreenImage.DOKill(); _fullScreenImage.gameObject.SetActive(true); Sprite newSprite = null; - yield return LoadObjSprite(picName, value => newSprite = value); + yield return LoadObjSprite( + picName, + isLocalized, + value => newSprite = value); if (version != _fullScreenOperationVersion) { yield break; @@ -194,6 +287,7 @@ namespace AibisDream.UI if (version != _fullScreenOperationVersion) yield break; _currentFullScreenPicName = NormalizeObjPicName(picName); + _currentFullScreenIsLocalized = isLocalized; _isFullScreenVisible = true; } @@ -201,6 +295,7 @@ namespace AibisDream.UI { var version = ++_fullScreenOperationVersion; _currentFullScreenPicName = null; + _currentFullScreenIsLocalized = false; _isFullScreenVisible = false; _fullScreenImage.DOKill(); yield return _fullScreenImage.FadeOutAsync(duration); @@ -214,8 +309,10 @@ namespace AibisDream.UI { isObjVisible = _isObjVisible, objPicName = _currentObjPicName, + objIsLocalized = _currentObjIsLocalized, isFullScreenVisible = _isFullScreenVisible, - fullScreenPicName = _currentFullScreenPicName + fullScreenPicName = _currentFullScreenPicName, + fullScreenIsLocalized = _currentFullScreenIsLocalized }; } @@ -229,13 +326,18 @@ namespace AibisDream.UI { var version = ++_objOperationVersion; Sprite sprite = null; - yield return LoadObjSprite(snapshot.objPicName, value => sprite = value, warning); + yield return LoadObjSprite( + snapshot.objPicName, + snapshot.objIsLocalized, + value => sprite = value, + warning); if (version == _objOperationVersion && sprite != null) { ApplyObjSprite(sprite); _showObjPanel.SetActive(true); _showObjImage.SetAlpha(1f); _currentObjPicName = NormalizeObjPicName(snapshot.objPicName); + _currentObjIsLocalized = snapshot.objIsLocalized; _isObjVisible = true; } } @@ -248,13 +350,18 @@ namespace AibisDream.UI { var version = ++_fullScreenOperationVersion; Sprite sprite = null; - yield return LoadObjSprite(snapshot.fullScreenPicName, value => sprite = value, warning); + yield return LoadObjSprite( + snapshot.fullScreenPicName, + snapshot.fullScreenIsLocalized, + value => sprite = value, + warning); if (version == _fullScreenOperationVersion && sprite != null) { _fullScreenImage.sprite = sprite; _fullScreenImage.gameObject.SetActive(true); _fullScreenImage.SetAlpha(1f); _currentFullScreenPicName = NormalizeObjPicName(snapshot.fullScreenPicName); + _currentFullScreenIsLocalized = snapshot.fullScreenIsLocalized; _isFullScreenVisible = true; } }