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); } } }