From 21fd645fdd43c2d9cf6640d2316e0572903be1f3 Mon Sep 17 00:00:00 2001
From: lcy <1459594991@qq.com>
Date: 星期六, 09 五月 2026 20:59:17 +0800
Subject: [PATCH] 592 多语言适配 提交主干
---
Assets/Editor/UIComponent/ImageLanguageAdapterHelper.cs | 67 ++++
Assets/Editor/UIComponent/ImageLanguageAdapterEditor.cs.meta | 11
Assets/Editor/UIComponent/ImageLanguageAdapterScanTool.cs.meta | 11
Assets/Editor/UIComponent/ImageLanguageAdapterHelper.cs.meta | 11
Assets/Editor/UIComponent/ImageLanguageAdapterEditor.cs | 217 +++++++++++++++
Assets/Editor/UIComponent/ImageLanguageAdapterScanTool.cs | 498 +++++++++++++++++++++++++++++++++++
6 files changed, 815 insertions(+), 0 deletions(-)
diff --git a/Assets/Editor/UIComponent/ImageLanguageAdapterEditor.cs b/Assets/Editor/UIComponent/ImageLanguageAdapterEditor.cs
new file mode 100644
index 0000000..7d02575
--- /dev/null
+++ b/Assets/Editor/UIComponent/ImageLanguageAdapterEditor.cs
@@ -0,0 +1,217 @@
+using System.Collections.Generic;
+using UnityEditor;
+using UnityEngine;
+using UnityEngine.UI;
+
+[CustomEditor(typeof(ImageLanguageAdapter), true)]
+[CanEditMultipleObjects]
+public class ImageLanguageAdapterEditor : Editor
+{
+ private Dictionary<string, bool> m_FoldoutStates = new Dictionary<string, bool>();
+
+ private void OnEnable()
+ {
+ ImageLanguageAdapterHelper.Initialize();
+
+ if (target is ImageLanguageAdapter adapter)
+ {
+ if (!adapter.HasConfig(ImageLanguageAdapter.DefaultLangId))
+ {
+ adapter.ReadCurrentToConfig(ImageLanguageAdapter.DefaultLangId);
+ EditorUtility.SetDirty(adapter);
+ }
+ foreach (var langId in adapter.GetConfiguredLanguages()) m_FoldoutStates.TryAdd(langId, false);
+ }
+ }
+
+ public override void OnInspectorGUI()
+ {
+ var adapter = target as ImageLanguageAdapter;
+
+ DrawBasicInfo(adapter);
+ EditorGUILayout.Space();
+ DrawLanguageConfigs(adapter);
+
+ if (GUI.changed) EditorUtility.SetDirty(target);
+ }
+
+ private void DrawBasicInfo(ImageLanguageAdapter adapter)
+ {
+ using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
+ {
+ EditorGUILayout.LabelField("鍩烘湰淇℃伅", EditorStyles.boldLabel);
+
+ EditorGUI.BeginChangeCheck();
+
+ Component newTarget = (Component)EditorGUILayout.ObjectField("鐩爣缁勪欢", adapter.TargetImageComponent, typeof(Component), true);
+ ImageComponentType newType = (ImageComponentType)EditorGUILayout.EnumPopup("缁勪欢绫诲瀷", adapter.TargetImageType);
+
+ if (EditorGUI.EndChangeCheck())
+ {
+ Undo.RecordObject(adapter, "Update Basic Info");
+
+ if (newTarget != null && newTarget != adapter.TargetImageComponent)
+ {
+ string typeName = newTarget.GetType().Name;
+ if (typeName == "ImageEx") newType = ImageComponentType.ImageEx;
+ else if (newTarget is Image) newType = ImageComponentType.Image;
+ }
+
+ adapter.TargetImageComponent = newTarget;
+ adapter.TargetImageType = newType;
+ }
+
+ EditorGUILayout.HelpBox("璇锋墜鍔ㄦ嫋鎷借閫傞厤鐨勫浘鐗囩粍浠讹紝骞剁‘璁ょ粍浠剁被鍨嬫槸鍚︽纭��", MessageType.Info);
+ }
+ }
+
+ private void DrawLanguageConfigs(ImageLanguageAdapter adapter)
+ {
+ using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
+ {
+ EditorGUILayout.LabelField("璇█鎺掔増閰嶇疆", EditorStyles.boldLabel);
+ EditorGUILayout.HelpBox("default浠呯敤浜庣紪杈戞椂鎭㈠鍒濆鐘舵��,鏃犲搴旇瑷�閰嶇疆鏃剁敤缁勪欢褰撳墠鐘舵��", MessageType.Info);
+
+ foreach (var langId in adapter.GetConfiguredLanguages()) DrawLanguageConfigItem(adapter, langId);
+
+ EditorGUILayout.Space();
+ DrawAddLanguageConfig(adapter);
+ }
+ }
+
+ private void DrawLanguageConfigItem(ImageLanguageAdapter adapter, string langId)
+ {
+ m_FoldoutStates.TryAdd(langId, false);
+
+ int presetIndex = System.Array.IndexOf(ImageLanguageAdapterHelper.PresetLanguageIds, langId);
+ string name = presetIndex >= 0 ? ImageLanguageAdapterHelper.PresetLanguageNames[presetIndex] : ImageLanguageAdapter.GetLanguageShowName(langId);
+ string label = string.IsNullOrEmpty(name) ? langId : $"{langId} ({name})";
+
+ EditorGUI.indentLevel++;
+ if (langId == ImageLanguageAdapter.DefaultLangId) GUI.backgroundColor = new Color(1f, 0.9f, 0.7f);
+
+ m_FoldoutStates[langId] = EditorGUILayout.Foldout(m_FoldoutStates[langId], label, true);
+ GUI.backgroundColor = Color.white;
+
+ if (m_FoldoutStates[langId])
+ {
+ EditorGUI.indentLevel++;
+ var config = adapter.LanguageConfigs.Get(langId);
+ if (config != null)
+ {
+ DrawConfigItem(config);
+ EditorGUILayout.Space();
+ DrawConfigItemActions(adapter, langId);
+ }
+ EditorGUI.indentLevel--;
+ }
+ EditorGUI.indentLevel--;
+ }
+
+ private void DrawConfigItem(ImageLanguageConfigItem cfg)
+ {
+ EditorGUILayout.LabelField("RectTransform 閰嶇疆", EditorStyles.miniBoldLabel);
+ EditorGUI.BeginChangeCheck();
+
+ cfg.anchoredPosition = EditorGUILayout.Vector2Field("Anchored Position", cfg.anchoredPosition);
+ cfg.sizeDelta = EditorGUILayout.Vector2Field("Size Delta", cfg.sizeDelta);
+ cfg.anchorMin = EditorGUILayout.Vector2Field("Anchor Min", cfg.anchorMin);
+ cfg.anchorMax = EditorGUILayout.Vector2Field("Anchor Max", cfg.anchorMax);
+ cfg.pivot = EditorGUILayout.Vector2Field("Pivot", cfg.pivot);
+ cfg.localScale = EditorGUILayout.Vector3Field("Local Scale", cfg.localScale);
+ cfg.localRotation = EditorGUILayout.Vector3Field("Local Rotation", cfg.localRotation);
+
+ EditorGUILayout.Space();
+ EditorGUILayout.LabelField("Image 閰嶇疆", EditorStyles.miniBoldLabel);
+
+ cfg.enabled = EditorGUILayout.Toggle("鏄剧ず", cfg.enabled);
+ cfg.color = EditorGUILayout.ColorField("Color", cfg.color);
+ cfg.type = (Image.Type)EditorGUILayout.EnumPopup("Type", cfg.type);
+
+ if (cfg.type == Image.Type.Sliced || cfg.type == Image.Type.Tiled)
+ {
+ cfg.fillCenter = EditorGUILayout.Toggle("Fill Center", cfg.fillCenter);
+ }
+
+ if (cfg.type == Image.Type.Filled)
+ {
+ cfg.fillMethod = (Image.FillMethod)EditorGUILayout.EnumPopup("Fill Method", cfg.fillMethod);
+ cfg.fillAmount = EditorGUILayout.Slider("Fill Amount", cfg.fillAmount, 0f, 1f);
+ cfg.fillOrigin = EditorGUILayout.IntField("Fill Origin", cfg.fillOrigin);
+ }
+
+ cfg.preserveAspect = EditorGUILayout.Toggle("Preserve Aspect", cfg.preserveAspect);
+ cfg.useSpriteMesh = EditorGUILayout.Toggle("Use Sprite Mesh", cfg.useSpriteMesh);
+ cfg.pixelsPerUnitMultiplier = EditorGUILayout.FloatField("Pixels Per Unit Multiplier", cfg.pixelsPerUnitMultiplier);
+
+ if (EditorGUI.EndChangeCheck()) EditorUtility.SetDirty(target);
+ }
+
+ private void DrawConfigItemActions(ImageLanguageAdapter adapter, string langId)
+ {
+ using (new EditorGUILayout.HorizontalScope())
+ {
+ GUILayout.Space(EditorGUI.indentLevel * 15f);
+
+ if (GUILayout.Button("浠庡綋鍓嶈鍙�", GUILayout.Width(80)))
+ {
+ Undo.RecordObject(adapter, "Read Current Config");
+ adapter.ReadCurrentToConfig(langId);
+ }
+
+ if (GUILayout.Button("搴旂敤", GUILayout.Width(60)))
+ {
+ Undo.RecordObject(adapter, "Apply Config");
+ adapter.ApplyConfig(langId);
+ }
+
+ using (new EditorGUI.DisabledScope(langId == ImageLanguageAdapter.DefaultLangId))
+ {
+ if (GUILayout.Button("鍒犻櫎", GUILayout.Width(50)))
+ {
+ Undo.RecordObject(adapter, "Remove Config");
+ adapter.RemoveConfig(langId);
+ m_FoldoutStates.Remove(langId);
+ }
+ }
+ }
+ }
+
+ private void DrawAddLanguageConfig(ImageLanguageAdapter adapter)
+ {
+ EditorGUILayout.BeginHorizontal();
+ GUILayout.Space(15f);
+ EditorGUILayout.LabelField("娣诲姞棰勮璇█:", GUILayout.Width(100));
+
+ float viewWidth = EditorGUIUtility.currentViewWidth - 30f;
+ float currentWidth = 115f;
+ float buttonWidth = 64f;
+
+ for (int i = 0; i < ImageLanguageAdapterHelper.PresetLanguageIds.Length; i++)
+ {
+ string langId = ImageLanguageAdapterHelper.PresetLanguageIds[i];
+ if (!adapter.HasConfig(langId))
+ {
+ if (currentWidth + buttonWidth > viewWidth)
+ {
+ EditorGUILayout.EndHorizontal();
+ EditorGUILayout.BeginHorizontal();
+ GUILayout.Space(115f);
+ currentWidth = 115f;
+ }
+
+ if (GUILayout.Button(ImageLanguageAdapterHelper.PresetLanguageNames[i], GUILayout.Width(60)))
+ {
+ Undo.RecordObject(adapter, "Add Language Config");
+ var newConfig = adapter.HasConfig(ImageLanguageAdapter.DefaultLangId)
+ ? adapter.LanguageConfigs.Get(ImageLanguageAdapter.DefaultLangId).Clone()
+ : new ImageLanguageConfigItem();
+ adapter.SetConfig(langId, newConfig);
+ m_FoldoutStates[langId] = true;
+ }
+ currentWidth += buttonWidth;
+ }
+ }
+ EditorGUILayout.EndHorizontal();
+ }
+}
\ No newline at end of file
diff --git a/Assets/Editor/UIComponent/ImageLanguageAdapterEditor.cs.meta b/Assets/Editor/UIComponent/ImageLanguageAdapterEditor.cs.meta
new file mode 100644
index 0000000..3695ef5
--- /dev/null
+++ b/Assets/Editor/UIComponent/ImageLanguageAdapterEditor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 6f2905447a660d641abe91b18a29680a
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Editor/UIComponent/ImageLanguageAdapterHelper.cs b/Assets/Editor/UIComponent/ImageLanguageAdapterHelper.cs
new file mode 100644
index 0000000..50dc412
--- /dev/null
+++ b/Assets/Editor/UIComponent/ImageLanguageAdapterHelper.cs
@@ -0,0 +1,67 @@
+using System.Collections.Generic;
+using System.IO;
+using LitJson;
+using UnityEngine;
+
+/// <summary>
+/// 缂栬緫鍣ㄥ浘鐗囪瑷�閰嶇疆璇诲彇鍔╂墜
+/// 璐熻矗缁熶竴瑙f瀽 InitialFunction.txt
+/// </summary>
+public static class ImageLanguageAdapterHelper
+{
+ public static string[] PresetLanguageIds { get; private set; }
+ public static string[] PresetLanguageNames { get; private set; }
+ private static bool s_Initialized;
+
+ public static void Initialize()
+ {
+ if (s_Initialized) return;
+
+ var idList = new List<string> { ImageLanguageAdapter.DefaultLangId };
+ var nameList = new List<string> { "榛樿" };
+
+ string configPath = Path.Combine(Application.dataPath, "ResourcesOut/Config/InitialFunction.txt");
+ if (File.Exists(configPath))
+ {
+ try
+ {
+ string[] lines = File.ReadAllLines(configPath);
+ for (int i = 3; i < lines.Length; i++)
+ {
+ string line = lines[i];
+ if (string.IsNullOrWhiteSpace(line)) continue;
+
+ int index = line.IndexOf('\t');
+ if (index == -1) continue;
+
+ string key = line.Substring(0, index);
+ if (key == "LanguageEx")
+ {
+ string[] fields = line.Split('\t');
+ if (fields.Length > 1 && !string.IsNullOrEmpty(fields[1]))
+ {
+ var dict = JsonMapper.ToObject<Dictionary<string, string>>(fields[1]);
+ if (dict != null)
+ {
+ foreach (var kvp in dict)
+ {
+ idList.Add(kvp.Key);
+ nameList.Add(kvp.Value);
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+ catch (System.Exception ex)
+ {
+ Debug.LogWarning($"[ImageLanguageAdapterHelper] 璇诲彇閰嶇疆澶辫触: {ex.Message}");
+ }
+ }
+
+ PresetLanguageIds = idList.ToArray();
+ PresetLanguageNames = nameList.ToArray();
+ s_Initialized = true;
+ }
+}
\ No newline at end of file
diff --git a/Assets/Editor/UIComponent/ImageLanguageAdapterHelper.cs.meta b/Assets/Editor/UIComponent/ImageLanguageAdapterHelper.cs.meta
new file mode 100644
index 0000000..8556591
--- /dev/null
+++ b/Assets/Editor/UIComponent/ImageLanguageAdapterHelper.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: dd266766e23062e4eb668f7102620a06
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Editor/UIComponent/ImageLanguageAdapterScanTool.cs b/Assets/Editor/UIComponent/ImageLanguageAdapterScanTool.cs
new file mode 100644
index 0000000..fa4b514
--- /dev/null
+++ b/Assets/Editor/UIComponent/ImageLanguageAdapterScanTool.cs
@@ -0,0 +1,498 @@
+using System.Collections.Generic;
+using System.IO;
+using UnityEditor;
+using UnityEditor.IMGUI.Controls;
+using UnityEngine;
+
+// ======================== 鎵弿鏁版嵁妯″瀷 ========================
+
+public class ImageScanResultItem
+{
+ public string PrefabPath { get; }
+ public string GameObjectPath { get; }
+ public List<string> MissingLanguages { get; set; } = new List<string>();
+ public ImageComponentType ComponentType { get; }
+ public string PrefabGUID { get; }
+
+ public ImageScanResultItem(string path, string goPath, ImageComponentType type, string guid)
+ {
+ PrefabPath = path;
+ GameObjectPath = goPath;
+ ComponentType = type;
+ PrefabGUID = guid;
+ }
+
+ public string GetDisplayName() => $"{GameObjectPath} (缂哄皯: {string.Join(", ", MissingLanguages)}) [{ComponentType}]";
+}
+
+public class ImagePrefabScanResult
+{
+ public string PrefabPath { get; }
+ public string PrefabGUID { get; }
+ public List<ImageScanResultItem> Items { get; } = new List<ImageScanResultItem>();
+
+ public ImagePrefabScanResult(string path, string guid)
+ {
+ PrefabPath = path;
+ PrefabGUID = guid;
+ }
+}
+
+public class ImageScanResultSummary
+{
+ public string ScanDirectory { get; }
+ public int TotalPrefabsScanned { get; set; }
+ public int TotalAdaptersFound { get; set; }
+ public int AdaptersWithMissingConfig { get; private set; }
+ public List<ImagePrefabScanResult> PrefabResults { get; } = new List<ImagePrefabScanResult>();
+
+ public ImageScanResultSummary(string dir) => ScanDirectory = dir;
+
+ public void AddResult(ImageScanResultItem item)
+ {
+ var prefabResult = PrefabResults.Find(p => p.PrefabPath == item.PrefabPath);
+ if (prefabResult == null)
+ {
+ prefabResult = new ImagePrefabScanResult(item.PrefabPath, item.PrefabGUID);
+ PrefabResults.Add(prefabResult);
+ }
+ prefabResult.Items.Add(item);
+ AdaptersWithMissingConfig++;
+ }
+}
+
+// ======================== TreeView 瀹炵幇 ========================
+
+public class ImageMetadataTreeViewItem : TreeViewItem
+{
+ public object Metadata { get; }
+ public ImageMetadataTreeViewItem(int id, string name, object meta) : base(id, 0, name) => Metadata = meta;
+}
+
+public class ImageScanResultTreeView : TreeView
+{
+ private ImageScanResultSummary m_Summary;
+
+ public ImageScanResultTreeView(TreeViewState state, ImageScanResultSummary summary) : base(state)
+ {
+ m_Summary = summary;
+ Reload();
+ ExpandAll();
+ }
+
+ protected override TreeViewItem BuildRoot()
+ {
+ var root = new TreeViewItem { id = 0, depth = -1, displayName = "Root" };
+
+ root.children = new List<TreeViewItem>();
+
+ if (m_Summary == null || m_Summary.PrefabResults.Count == 0)
+ return root;
+
+ int itemId = 1;
+ foreach (var prefabResult in m_Summary.PrefabResults)
+ {
+ string prefabName = Path.GetFileNameWithoutExtension(prefabResult.PrefabPath);
+ var prefabItem = new ImageMetadataTreeViewItem(itemId++, $"{prefabName} ({prefabResult.Items.Count}涓棶棰�)", prefabResult);
+
+ foreach (var adapterItem in prefabResult.Items)
+ {
+ var adapterTreeItem = new ImageMetadataTreeViewItem(itemId++, adapterItem.GetDisplayName(), adapterItem);
+ prefabItem.AddChild(adapterTreeItem);
+ }
+ root.AddChild(prefabItem);
+ }
+
+ SetupDepthsFromParentsAndChildren(root);
+
+ return root;
+ }
+
+ protected override void RowGUI(RowGUIArgs args)
+ {
+ var item = args.item as ImageMetadataTreeViewItem;
+ if (item != null && item.Metadata is ImagePrefabScanResult)
+ GUI.Label(args.rowRect, item.displayName, EditorStyles.boldLabel);
+ else if (item != null && item.Metadata is ImageScanResultItem adapterItem)
+ GUI.Label(args.rowRect, $"{adapterItem.GameObjectPath} (缂哄皯: {string.Join(", ", adapterItem.MissingLanguages)})");
+ else
+ base.RowGUI(args);
+ }
+
+ protected override void DoubleClickedItem(int id)
+ {
+ var item = FindItem(id, rootItem) as ImageMetadataTreeViewItem;
+ if (item == null) return;
+
+ if (item.Metadata is ImageScanResultItem adapterItem)
+ PingGameObject(adapterItem.PrefabPath, adapterItem.GameObjectPath);
+ else if (item.Metadata is ImagePrefabScanResult prefabResult)
+ {
+ var obj = AssetDatabase.LoadAssetAtPath<GameObject>(prefabResult.PrefabPath);
+ if (obj != null)
+ {
+ Selection.activeObject = obj;
+ EditorGUIUtility.PingObject(obj);
+ }
+ }
+ }
+
+ private void PingGameObject(string prefabPath, string gameObjectPath)
+ {
+ var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
+ if (prefab == null) return;
+
+ Transform target = prefab.transform.Find(gameObjectPath);
+ if (target != null)
+ {
+ Selection.activeObject = target.gameObject;
+ EditorGUIUtility.PingObject(target.gameObject);
+ }
+ else
+ {
+ Selection.activeObject = prefab;
+ EditorGUIUtility.PingObject(prefab);
+ Debug.LogWarning($"[ImageScanTool] 鎵句笉鍒拌矾寰� '{gameObjectPath}'锛屽凡閫変腑鏁翠釜棰勫埗浣�");
+ }
+ }
+}
+
+// ======================== 鎵弿宸ュ叿涓荤獥鍙� ========================
+
+public class ImageLanguageAdapterScanTool : EditorWindow
+{
+ private string m_ScanDirectory = "Assets";
+ private Vector2 m_ScrollPosition;
+ private ImageScanResultSummary m_ScanResult;
+ private bool m_IsScanning;
+ private float m_ScanProgress;
+ private string m_ScanStatus;
+
+ private ImageScanResultTreeView m_TreeView;
+ private TreeViewState m_TreeViewState;
+
+ private int m_SourceLangIndex = 0;
+ private int m_TargetLangIndex = 0;
+ private bool m_OverwriteExisting = false;
+
+ [MenuItem("绋嬪簭/ImageLanguageAdapter鎵弿涓庣鐞嗗伐鍏�")]
+ public static void ShowWindow()
+ {
+ var window = GetWindow<ImageLanguageAdapterScanTool>("鍥剧墖璇█閫傞厤鍣ㄦ壂鎻�");
+ window.minSize = new Vector2(600f, 500f);
+ }
+
+ private void OnEnable() => ImageLanguageAdapterHelper.Initialize();
+
+ private void OnGUI()
+ {
+ DrawHeader();
+ EditorGUILayout.Space(5f);
+ DrawScanSettings();
+ EditorGUILayout.Space(5f);
+ DrawScanButton();
+ EditorGUILayout.Space(5f);
+ DrawResults();
+ EditorGUILayout.Space(5f);
+ DrawBatchOperations();
+ }
+
+ private void DrawHeader()
+ {
+ using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
+ {
+ EditorGUILayout.LabelField("ImageLanguageAdapter 閰嶇疆缂哄け鎵弿涓庢壒閲忔搷浣滃伐鍏�", EditorStyles.boldLabel);
+ EditorGUILayout.HelpBox("鎵弿鎸囧畾鐩綍涓嬫墍鏈夐鍒朵綋锛屾娴嬬粍浠剁殑璇█閰嶇疆鏄惁瀹屾暣锛屾垨鎵ц鎵归噺璇█閰嶇疆澶嶅埗銆�", MessageType.Info);
+ }
+ }
+
+ private void DrawScanSettings()
+ {
+ using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
+ {
+ EditorGUILayout.LabelField("鎵弿璁剧疆", EditorStyles.boldLabel);
+
+ using (new EditorGUILayout.HorizontalScope())
+ {
+ EditorGUILayout.LabelField("鐩爣鐩綍:", GUILayout.Width(80f));
+ m_ScanDirectory = EditorGUILayout.TextField(m_ScanDirectory);
+ if (GUILayout.Button("閫夋嫨...", GUILayout.Width(70f)))
+ {
+ string path = EditorUtility.OpenFolderPanel("閫夋嫨鎵弿鐩綍", Application.dataPath, "");
+ if (!string.IsNullOrEmpty(path))
+ m_ScanDirectory = path.StartsWith(Application.dataPath) ? "Assets" + path.Substring(Application.dataPath.Length) : path;
+ }
+ }
+
+ EditorGUILayout.Space(5f);
+ EditorGUILayout.LabelField($"棰勮璇█:");
+
+ EditorGUILayout.BeginHorizontal();
+ GUILayout.Space(15f);
+ int displayCount = 0;
+ foreach (var langId in ImageLanguageAdapterHelper.PresetLanguageIds)
+ {
+ if (langId == ImageLanguageAdapter.DefaultLangId) continue;
+ EditorGUILayout.LabelField(langId, GUILayout.Width(50f));
+ if (++displayCount % 8 == 0)
+ {
+ EditorGUILayout.EndHorizontal();
+ EditorGUILayout.BeginHorizontal();
+ GUILayout.Space(15f);
+ }
+ }
+ EditorGUILayout.EndHorizontal();
+ }
+ }
+
+ private void DrawScanButton()
+ {
+ using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
+ {
+ using (new EditorGUILayout.HorizontalScope())
+ {
+ using (new EditorGUI.DisabledScope(m_IsScanning || string.IsNullOrEmpty(m_ScanDirectory)))
+ {
+ if (GUILayout.Button("寮�濮嬫壂鎻�", GUILayout.Height(30f))) StartScan();
+ }
+
+ if (m_IsScanning)
+ {
+ EditorGUILayout.LabelField("鎵弿涓�...", GUILayout.Width(100f));
+ m_ScanProgress = EditorGUILayout.Slider(m_ScanProgress, 0f, 1f);
+ Repaint();
+ }
+ }
+ if (!string.IsNullOrEmpty(m_ScanStatus)) EditorGUILayout.LabelField(m_ScanStatus);
+ }
+ }
+
+ private void DrawResults()
+ {
+ using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
+ {
+ EditorGUILayout.LabelField("鎵弿缁撴灉", EditorStyles.boldLabel);
+
+ if (m_ScanResult != null)
+ {
+ EditorGUILayout.LabelField($"棰勫埗浣撴�绘暟: {m_ScanResult.TotalPrefabsScanned} | Adapter鎬绘暟: {m_ScanResult.TotalAdaptersFound} | 缂哄け閰嶇疆: {m_ScanResult.AdaptersWithMissingConfig}");
+ EditorGUILayout.Space(5f);
+
+ if (m_TreeView != null)
+ {
+ m_ScrollPosition = EditorGUILayout.BeginScrollView(m_ScrollPosition, GUILayout.MinHeight(150f));
+ var rect = EditorGUILayout.GetControlRect(false, m_TreeView.totalHeight);
+ m_TreeView.OnGUI(rect);
+ EditorGUILayout.EndScrollView();
+ }
+
+ EditorGUILayout.Space(5f);
+ using (new EditorGUILayout.HorizontalScope())
+ {
+ if (GUILayout.Button("灞曞紑鍏ㄩ儴")) m_TreeView?.ExpandAll();
+ if (GUILayout.Button("鎶樺彔鍏ㄩ儴")) m_TreeView?.CollapseAll();
+ }
+ }
+ else
+ {
+ EditorGUILayout.HelpBox("鐐瑰嚮銆屽紑濮嬫壂鎻忋�嶆寜閽繘琛屾壂鎻�", MessageType.None);
+ }
+ }
+ }
+
+ private void DrawBatchOperations()
+ {
+ using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
+ {
+ EditorGUILayout.LabelField("鎵归噺鎿嶄綔 (閽堝鐩爣鐩綍涓嬬殑鎵�鏈夐鍒朵綋涓婄殑ImageLanguageAdapter缁勪欢)", EditorStyles.boldLabel);
+
+ if (ImageLanguageAdapterHelper.PresetLanguageIds == null || ImageLanguageAdapterHelper.PresetLanguageIds.Length == 0)
+ return;
+
+ using (new EditorGUILayout.HorizontalScope())
+ {
+ EditorGUILayout.LabelField("鏃ц瑷�:", GUILayout.Width(60f));
+ m_SourceLangIndex = EditorGUILayout.Popup(m_SourceLangIndex, ImageLanguageAdapterHelper.PresetLanguageNames);
+
+ GUILayout.Space(20f);
+
+ EditorGUILayout.LabelField("鏂拌瑷�:", GUILayout.Width(60f));
+ m_TargetLangIndex = EditorGUILayout.Popup(m_TargetLangIndex, ImageLanguageAdapterHelper.PresetLanguageNames);
+ }
+
+ m_OverwriteExisting = EditorGUILayout.Toggle("瑕嗙洊宸插瓨鍦ㄧ殑鐩爣閰嶇疆", m_OverwriteExisting);
+
+ EditorGUILayout.Space(5f);
+
+ bool isSameLanguage = m_SourceLangIndex == m_TargetLangIndex;
+ using (new EditorGUI.DisabledScope(isSameLanguage || m_IsScanning || string.IsNullOrEmpty(m_ScanDirectory)))
+ {
+ if (GUILayout.Button("鎵归噺澶嶅埗閰嶇疆", GUILayout.Height(30f)))
+ {
+ string sourceLang = ImageLanguageAdapterHelper.PresetLanguageIds[m_SourceLangIndex];
+ string targetLang = ImageLanguageAdapterHelper.PresetLanguageIds[m_TargetLangIndex];
+
+ if (EditorUtility.DisplayDialog("楂樺嵄鎿嶄綔纭",
+ $"姝ゆ搷浣滃皢閬嶅巻銆恵m_ScanDirectory}銆戜笅鎵�鏈夐鍒朵綋銆俓n\n" +
+ $"鎶婂畠浠殑 [{sourceLang}] 閰嶇疆澶嶅埗骞跺簲鐢ㄥ埌 [{targetLang}] 閰嶇疆涓娿�俓n\n" +
+ $"姝ゆ搷浣滀笉鍙挙閿�锛佸缓璁彁鍓嶄娇鐢� Git/SVN 鎻愪氦浠g爜銆俓n纭畾瑕佺户缁悧锛�",
+ "纭畾鎵ц", "鍙栨秷"))
+ {
+ ExecuteBatchCopy(sourceLang, targetLang);
+ }
+ }
+ }
+
+ if (isSameLanguage)
+ {
+ EditorGUILayout.HelpBox("鏃ц瑷�涓庢柊璇█涓嶈兘鐩稿悓", MessageType.Warning);
+ }
+ }
+ }
+
+ private void ExecuteBatchCopy(string sourceLang, string targetLang)
+ {
+ string[] prefabGuids = AssetDatabase.FindAssets("t:Prefab", new[] { m_ScanDirectory });
+ if (prefabGuids.Length == 0) return;
+
+ int modifiedPrefabCount = 0;
+ int modifiedAdapterCount = 0;
+
+ try
+ {
+ for (int i = 0; i < prefabGuids.Length; i++)
+ {
+ string path = AssetDatabase.GUIDToAssetPath(prefabGuids[i]);
+ EditorUtility.DisplayProgressBar("鎵归噺澶嶅埗閰嶇疆", $"澶勭悊涓� ({i + 1}/{prefabGuids.Length}): {path}", (float)i / prefabGuids.Length);
+
+ GameObject prefabAsset = AssetDatabase.LoadAssetAtPath<GameObject>(path);
+ bool isModified = false;
+
+ foreach (var adapter in prefabAsset.GetComponentsInChildren<ImageLanguageAdapter>(true))
+ {
+ if (adapter.HasConfig(sourceLang))
+ {
+ if (m_OverwriteExisting || !adapter.HasConfig(targetLang))
+ {
+ Undo.RecordObject(adapter, "Batch Copy Language Config");
+
+ var clonedConfig = adapter.GetConfig(sourceLang).Clone();
+ adapter.SetConfig(targetLang, clonedConfig);
+
+ EditorUtility.SetDirty(adapter);
+ isModified = true;
+ modifiedAdapterCount++;
+ }
+ }
+ }
+
+ if (isModified)
+ {
+ modifiedPrefabCount++;
+ }
+ }
+ }
+ finally
+ {
+ EditorUtility.ClearProgressBar();
+ AssetDatabase.SaveAssets();
+
+ PerformScan();
+
+ EditorUtility.DisplayDialog("鎵归噺鎿嶄綔瀹屾垚",
+ $"鎵归噺澶嶅埗缁撴潫锛乗n\n淇敼鐨勯鍒朵綋鏁伴噺: {modifiedPrefabCount} 涓猏n鏇存柊鐨勯�傞厤鍣ㄩ厤缃暟閲�: {modifiedAdapterCount} 涓�",
+ "纭");
+ }
+ }
+
+ private void StartScan()
+ {
+ m_IsScanning = true;
+ m_ScanProgress = 0f;
+ m_ScanStatus = "鍑嗗鎵弿...";
+
+ EditorApplication.CallbackFunction updateCallback = null;
+ updateCallback = () =>
+ {
+ if (!m_IsScanning)
+ {
+ EditorApplication.update -= updateCallback;
+ return;
+ }
+ PerformScan();
+ m_IsScanning = false;
+ EditorApplication.update -= updateCallback;
+ };
+ EditorApplication.update += updateCallback;
+ }
+
+ private void PerformScan()
+ {
+ m_ScanResult = new ImageScanResultSummary(m_ScanDirectory);
+ string[] prefabGuids = AssetDatabase.FindAssets("t:Prefab", new[] { m_ScanDirectory });
+ m_ScanResult.TotalPrefabsScanned = prefabGuids.Length;
+
+ if (prefabGuids.Length == 0)
+ {
+ m_ScanStatus = "鏈壘鍒颁换浣曢鍒朵綋";
+ return;
+ }
+
+ for (int i = 0; i < prefabGuids.Length; i++)
+ {
+ string path = AssetDatabase.GUIDToAssetPath(prefabGuids[i]);
+ ScanPrefab(path, prefabGuids[i]);
+
+ m_ScanProgress = (float)(i + 1) / prefabGuids.Length;
+ m_ScanStatus = $"姝e湪鎵弿: {Path.GetFileName(path)} ({i + 1}/{prefabGuids.Length})";
+
+ if (i % 10 == 0) Repaint();
+ }
+
+ m_TreeViewState ??= new TreeViewState();
+ m_TreeView = new ImageScanResultTreeView(m_TreeViewState, m_ScanResult);
+ m_ScanStatus = $"鎵弿瀹屾垚! 鍙戠幇 {m_ScanResult.AdaptersWithMissingConfig} 涓己澶遍厤缃�";
+ Repaint();
+ }
+
+ private void ScanPrefab(string path, string guid)
+ {
+ GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(path);
+ if (prefab == null) return;
+
+ var adapters = prefab.GetComponentsInChildren<ImageLanguageAdapter>(true);
+
+ m_ScanResult.TotalAdaptersFound += adapters.Length;
+
+ foreach (var adapter in adapters)
+ {
+ List<string> missing = new List<string>();
+ foreach (var langId in ImageLanguageAdapterHelper.PresetLanguageIds)
+ {
+ if (langId == ImageLanguageAdapter.DefaultLangId) continue;
+ if (!adapter.HasConfig(langId)) missing.Add(langId);
+ }
+
+ if (missing.Count > 0)
+ {
+ var item = new ImageScanResultItem(path, GetGameObjectPath(adapter.gameObject, prefab), adapter.TargetImageType, guid)
+ {
+ MissingLanguages = missing
+ };
+ m_ScanResult.AddResult(item);
+ }
+ }
+ }
+
+ private string GetGameObjectPath(GameObject go, GameObject root)
+ {
+ var parts = new List<string>();
+ Transform curr = go.transform;
+ while (curr != null && curr != root.transform)
+ {
+ parts.Insert(0, curr.name);
+ curr = curr.parent;
+ }
+ return string.Join("/", parts);
+ }
+}
\ No newline at end of file
diff --git a/Assets/Editor/UIComponent/ImageLanguageAdapterScanTool.cs.meta b/Assets/Editor/UIComponent/ImageLanguageAdapterScanTool.cs.meta
new file mode 100644
index 0000000..67cda7e
--- /dev/null
+++ b/Assets/Editor/UIComponent/ImageLanguageAdapterScanTool.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 7d753da3934048046828299f1ee02998
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
--
Gitblit v1.8.0