com.alicizax.unity.framework/Editor/Localization/LocalizationTableWindow/LocalizationTableWindow.cs

601 lines
21 KiB
C#
Raw Normal View History

2025-09-23 12:04:39 +08:00
using System;
using System.IO;
using System.Collections.Generic;
using AlicizaX.Editor;
using Sirenix.OdinInspector;
using UnityEditor.IMGUI.Controls;
using UnityEditor;
using UnityEngine;
namespace AlicizaX.Localization.Editor
{
public class LocalizationTableWindow : EditorWindow
{
private List<GameLocaizationTable> allTables = new List<GameLocaizationTable>(); // 存储所有找到的GameLocaizationTable
private string[] tableDisplayNames; // 用于下拉框显示的名称数组
private int selectedTableIndex = 0; // 当前选中的索引
private string selectString = string.Empty;
private GameLocaizationTable currentTable; // 当前选中的GameLocaizationTable
2025-09-25 11:11:19 +08:00
// 原本是 const现在改成可调的字段
[SerializeField] private float languagesWidth = 200f;
2025-10-14 19:38:28 +08:00
[SerializeField] private float tableSheetWidth = 300f;
2025-09-25 11:11:19 +08:00
private const float SplitterWidth = 3f; // 拖拽条的宽度
private bool isResizingLeft, isResizingMiddle;
2025-09-23 12:04:39 +08:00
private float Spacing => EditorGUIUtility.standardVerticalSpacing * 2;
private GUIStyle miniLabelButton => new GUIStyle(EditorStyles.miniButton)
{
font = EditorStyles.miniBoldLabel.font,
fontSize = EditorStyles.miniBoldLabel.fontSize
};
public class WindowSelection
{
public TreeViewItem TreeViewItem;
}
public sealed class LanguageSelect : WindowSelection
{
public TempLanguageData Language;
}
public sealed class SectionSelect : WindowSelection
{
public SheetSectionTreeView Section;
}
public sealed class ItemSelect : WindowSelection
{
public SheetItemTreeView Item;
}
private LocalizationWindowData windowData;
private SearchField searchField;
private string searchString;
private Vector2 scrollPosition;
[SerializeField] private TreeViewState languagesTreeViewState;
private LanguagesTreeView languagesTreeView;
[SerializeField] private TreeViewState tableSheetTreeViewState;
private TableSheetTreeView tableSheetTreeView;
private WindowSelection selection = null;
private bool globalExpanded = false;
private void CreateGUI()
{
searchField = new SearchField();
}
private void OnDestroy()
{
SaveSelection();
}
private void OnEnable()
{
RefreshTableList();
}
private void OnDisable()
{
SaveSelection();
}
private void RefreshTableList()
{
allTables.Clear();
string[] guids = AssetDatabase.FindAssets("t:GameLocaizationTable");
foreach (string guid in guids)
{
string assetPath = AssetDatabase.GUIDToAssetPath(guid);
GameLocaizationTable table = AssetDatabase.LoadAssetAtPath<GameLocaizationTable>(assetPath);
if (table != null)
{
allTables.Add(table);
}
}
tableDisplayNames = new string[allTables.Count];
for (int i = 0; i < allTables.Count; i++)
{
tableDisplayNames[i] = allTables[i].name;
}
if (allTables.Count > 0 && selectedTableIndex >= allTables.Count)
{
selectedTableIndex = 0;
}
var selectIndex = selectedTableIndex;
var lastSelect = EditorPrefs.GetString("LastSelectedGameLocaizationTable", string.Empty);
if (!string.IsNullOrEmpty(lastSelect))
{
for (int i = 0; i < allTables.Count; i++)
{
var path = AssetDatabase.GetAssetPath(allTables[i]);
if (path.Equals(lastSelect))
{
selectIndex = i;
}
}
}
if (selectIndex <= tableDisplayNames.Length - 1 && selectIndex >= 0)
{
selectString = tableDisplayNames[selectIndex];
}
UpdateCurrentTable();
}
private void UpdateCurrentTable()
{
if (allTables.Count > 0 && selectedTableIndex >= 0 && selectedTableIndex < allTables.Count)
{
currentTable = allTables[selectedTableIndex];
SaveSelection();
}
else
{
currentTable = null;
}
InitializeTreeView();
}
private void SaveSelection()
{
if (currentTable != null)
{
string path = AssetDatabase.GetAssetPath(currentTable);
EditorPrefs.SetString("LastSelectedGameLocaizationTable", path);
}
}
private void InitializeTreeView()
{
if (!currentTable) return;
LocalizationWindowUtility.BuildWindowData(currentTable, out windowData);
foreach (var section in windowData.TableSheet)
{
section.IsExpanded = globalExpanded;
}
languagesTreeViewState = new TreeViewState();
languagesTreeView = new(languagesTreeViewState, windowData, currentTable)
{
OnLanguageSelect = (s) => selection = s
};
tableSheetTreeViewState = new TreeViewState();
tableSheetTreeView = new(tableSheetTreeViewState, windowData)
{
OnTableSheetSelect = (s) => selection = s
};
}
private void OnGUI()
{
2025-09-25 11:11:19 +08:00
// 顶部工具栏
2025-09-23 12:04:39 +08:00
Rect toolbarRect = new(0, 0, position.width, 20f);
GUI.Box(toolbarRect, GUIContent.none, EditorStyles.toolbar);
float buttonWidth = 100f;
float spacing = 5f;
Rect leftTitle = new(toolbarRect.xMin, 0, 40, 20f);
Rect leftPop = new(leftTitle.xMin + 40, 0, 200, 20f);
Rect saveBtn = new(toolbarRect.xMax - buttonWidth - spacing, 0, buttonWidth, 20f);
Rect genBtn = new(saveBtn.xMin - buttonWidth - spacing, 0, buttonWidth, 20f);
Rect importBtn = new(genBtn.xMin - buttonWidth - spacing, 0, buttonWidth, 20f);
Rect exportBtn = new(importBtn.xMin - buttonWidth - spacing, 0, buttonWidth, 20f);
EditorGUI.LabelField(leftTitle, "Table", EditorStyles.boldLabel);
EditorDrawing.DrawStringSelectPopup(leftPop, tableDisplayNames, selectString, (e) =>
{
selectString = e;
selectedTableIndex = allTables.FindIndex(table => table.name == e);
UpdateCurrentTable();
});
if (currentTable == null) return;
if (GUI.Button(exportBtn, "Export CSV", EditorStyles.toolbarButton))
{
string path = EditorUtility.SaveFilePanel("Export CSV", "", "Localization", "csv");
LocalizationExporter.ExportLocalizationToCSV(windowData, path);
}
if (GUI.Button(importBtn, "Import CSV", EditorStyles.toolbarButton))
{
string path = EditorUtility.OpenFilePanel("Export CSV", "", "csv");
LocalizationExporter.ImportLocalizationFromCSV(windowData, path);
}
if (GUI.Button(genBtn, "Gen Code", EditorStyles.toolbarButton))
{
LocalizationWindowUtility.GenerateCode(currentTable);
}
if (GUI.Button(saveBtn, "Save Asset", EditorStyles.toolbarButton))
{
BuildLocalizationTable();
EditorUtility.SetDirty(currentTable);
AssetDatabase.SaveAssets();
}
2025-09-25 11:11:19 +08:00
// ---------------- 这里开始是 SplitView ----------------
Rect fullRect = new Rect(0, 20f, position.width, position.height - 20f);
2025-09-23 12:04:39 +08:00
2025-09-25 11:11:19 +08:00
// 左侧 Languages
Rect languagesRect = new Rect(fullRect.x, fullRect.y, languagesWidth, fullRect.height);
languagesTreeView?.OnGUI(languagesRect);
2025-09-23 12:04:39 +08:00
2025-09-25 11:11:19 +08:00
// 左侧拖拽条
Rect leftSplitter = new Rect(languagesRect.xMax, fullRect.y, SplitterWidth, fullRect.height);
EditorGUIUtility.AddCursorRect(leftSplitter, MouseCursor.ResizeHorizontal);
HandleResize(ref languagesWidth, leftSplitter, ref isResizingLeft);
2025-09-23 12:04:39 +08:00
2025-09-25 11:11:19 +08:00
// 中间 TableSheet
Rect tableSheetRect = new Rect(leftSplitter.xMax, fullRect.y, tableSheetWidth, fullRect.height);
tableSheetTreeView?.OnGUI(tableSheetRect);
2025-09-23 12:04:39 +08:00
2025-09-25 11:11:19 +08:00
// 中间拖拽条
Rect midSplitter = new Rect(tableSheetRect.xMax, fullRect.y, SplitterWidth, fullRect.height);
EditorGUIUtility.AddCursorRect(midSplitter, MouseCursor.ResizeHorizontal);
HandleResize(ref tableSheetWidth, midSplitter, ref isResizingMiddle);
// 右侧 Inspector剩余空间
float inspectorWidth = fullRect.width - (languagesWidth + tableSheetWidth + SplitterWidth * 2);
Rect inspectorRect = new Rect(midSplitter.xMax, fullRect.y, inspectorWidth, fullRect.height);
2025-09-23 12:04:39 +08:00
if (selection != null)
{
2025-09-25 11:11:19 +08:00
GUILayout.BeginArea(inspectorRect);
2025-09-23 12:04:39 +08:00
{
2025-09-25 11:11:19 +08:00
if (selection is LanguageSelect lang) OnDrawLanguageInspector(lang);
else if (selection is SectionSelect sec) OnDrawSectionInspector(sec);
else if (selection is ItemSelect item) OnDrawSectionItemInspector(item);
2025-09-23 12:04:39 +08:00
}
2025-09-25 11:11:19 +08:00
GUILayout.EndArea();
}
}
private void HandleResize(ref float targetWidth, Rect splitterRect, ref bool isResizing)
{
Event e = Event.current;
switch (e.type)
{
case EventType.MouseDown:
if (splitterRect.Contains(e.mousePosition))
{
isResizing = true;
e.Use();
}
break;
case EventType.MouseDrag:
if (isResizing)
{
targetWidth += e.delta.x;
targetWidth = Mathf.Max(100f, targetWidth); // 设置最小宽度
e.Use();
Repaint();
}
break;
case EventType.MouseUp:
if (isResizing)
{
isResizing = false;
e.Use();
}
break;
2025-09-23 12:04:39 +08:00
}
}
private void OnDrawSectionInspector(SectionSelect section)
{
// section name change
EditorGUI.BeginChangeCheck();
{
section.Section.Name = EditorGUILayout.TextField("Name", section.Section.Name);
}
if (EditorGUI.EndChangeCheck())
{
section.TreeViewItem.displayName = section.Section.Name;
}
using (new EditorGUI.DisabledGroupScope(true))
{
int childerCount = section.TreeViewItem.children?.Count ?? 0;
EditorGUILayout.IntField(new GUIContent("Keys"), childerCount);
}
EditorGUILayout.Space(2);
EditorDrawing.Separator();
EditorGUILayout.Space(1);
using (new EditorGUI.DisabledGroupScope(true))
{
EditorGUILayout.LabelField("Id: " + section.Section.Id, EditorStyles.miniBoldLabel);
}
}
2025-10-14 19:38:28 +08:00
private void OnDrawSectionItemInspector(ItemSelect itemSelect)
2025-09-23 12:04:39 +08:00
{
2025-10-14 19:38:28 +08:00
var item = itemSelect.Item;
EditorGUILayout.LabelField("Editing Key Across Languages", EditorStyles.boldLabel);
// 显示 Key 和 ID只读
using (new EditorGUI.DisabledGroupScope(true))
2025-09-23 12:04:39 +08:00
{
2025-10-14 19:38:28 +08:00
EditorGUILayout.TextField("Key", item.Key);
EditorGUILayout.LabelField("Id: " + item.Id);
2025-09-23 12:04:39 +08:00
}
2025-10-14 19:38:28 +08:00
EditorGUILayout.Space(4);
2025-09-23 12:04:39 +08:00
EditorDrawing.Separator();
2025-10-14 19:38:28 +08:00
EditorGUILayout.Space(4);
2025-09-23 12:04:39 +08:00
2025-10-14 19:38:28 +08:00
// 遍历所有语言
foreach (var lang in windowData.Languages)
2025-09-23 12:04:39 +08:00
{
2025-10-14 19:38:28 +08:00
if (lang.Entry.Asset == null)
continue;
var languageName = lang.Entry.LanguageName;
TempSheetItem targetItem = null;
// 在语言的 TableSheet 里找到对应的 Item
foreach (var section in lang.TableSheet)
{
targetItem = section.Items.Find(i => i.Id == item.Id);
if (targetItem != null)
break;
}
if (targetItem == null)
{
EditorGUILayout.HelpBox($"Language [{languageName}] does not contain this key.", MessageType.Info);
continue;
}
// 绘制语言名标题
EditorGUILayout.LabelField(languageName, EditorStyles.miniBoldLabel);
// 多行文本框
EditorGUI.BeginChangeCheck();
targetItem.Value = EditorGUILayout.TextArea(targetItem.Value, GUILayout.MinHeight(40));
if (EditorGUI.EndChangeCheck())
{
// 标记已修改
EditorUtility.SetDirty(lang.Entry.Asset);
}
EditorGUILayout.Space(6);
2025-09-23 12:04:39 +08:00
}
}
2025-10-14 19:38:28 +08:00
2025-09-23 12:04:39 +08:00
private void OnDrawLanguageInspector(LanguageSelect selection)
{
var language = selection.Language;
var entry = language.Entry;
var treeView = selection.TreeViewItem;
using (new EditorGUI.DisabledGroupScope(entry.Asset == null))
{
// Draw search field
EditorGUILayout.Space();
GUIContent expandText = new GUIContent("Expand");
float expandWidth = miniLabelButton.CalcSize(expandText).x;
var searchRect = EditorGUILayout.GetControlRect();
searchRect.xMax -= (expandWidth + 2f);
searchString = searchField.OnGUI(searchRect, searchString);
Rect expandRect = new Rect(searchRect.xMax + 2f, searchRect.y, expandWidth, searchRect.height);
expandRect.y -= 1f;
using (new EditorDrawing.BackgroundColorScope("#F7E987"))
{
if (GUI.Button(expandRect, expandText, miniLabelButton))
{
globalExpanded = !globalExpanded;
foreach (var section in language.TableSheet)
{
section.Reference.IsExpanded = globalExpanded;
}
}
}
if (entry.Asset != null)
{
// Draw localization data
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
{
foreach (var section in GetSearchResult(language, searchString))
{
DrawLocalizationKey(section);
}
}
EditorGUILayout.EndScrollView();
}
else
{
EditorGUILayout.HelpBox("To begin editing localization data, you must first assign a localization asset.", MessageType.Warning);
}
}
}
private void DrawLocalizationKey(TempSheetSection section)
{
if (section.Items == null || section.Items.Count == 0)
return;
using (new EditorDrawing.BorderBoxScope(false))
{
string sectionName = section.Name.Replace(" ", "");
section.Reference.IsExpanded = EditorGUILayout.Foldout(section.Reference.IsExpanded, new GUIContent(sectionName), true, EditorDrawing.Styles.miniBoldLabelFoldout);
// Show section keys when expanded
if (section.Reference.IsExpanded)
{
foreach (var item in section.Items)
{
string keyName = item.Key.Replace(" ", "");
string key = sectionName + "." + keyName;
if (IsMultiline(item.Value))
key += " (Multiline)";
using (new EditorGUILayout.VerticalScope(GUI.skin.box))
{
// Display the expandable toggle
using (new EditorGUILayout.HorizontalScope(GUI.skin.box))
{
item.IsExpanded = EditorGUILayout.Foldout(item.IsExpanded, new GUIContent(key), true, EditorDrawing.Styles.miniBoldLabelFoldout);
}
if (item.IsExpanded)
{
// Show TextArea when expanded
float height = (EditorGUIUtility.standardVerticalSpacing + EditorGUIUtility.singleLineHeight) * 3;
height += EditorGUIUtility.standardVerticalSpacing;
item.Scroll = EditorGUILayout.BeginScrollView(item.Scroll, GUILayout.Height(height));
item.Value = EditorGUILayout.TextArea(item.Value, GUILayout.ExpandHeight(true));
EditorGUILayout.EndScrollView();
}
else
{
// Show TextField when collapsed
item.Value = EditorGUILayout.TextField(item.Value);
}
}
}
}
}
EditorGUILayout.Space(1f);
}
private IEnumerable<TempSheetSection> GetSearchResult(TempLanguageData languageData, string search)
{
if (!string.IsNullOrEmpty(search))
{
List<TempSheetSection> searchResult = new();
foreach (var section in languageData.TableSheet)
{
List<TempSheetItem> sectionItems = new();
string sectionName = section.Name.Replace(" ", "");
foreach (var item in section.Items)
{
string keyName = item.Key.Replace(" ", "");
string key = sectionName + "." + keyName;
if (key.Contains(search))
sectionItems.Add(item);
}
searchResult.Add(new TempSheetSection()
{
Items = sectionItems,
Reference = section.Reference
});
}
return searchResult;
}
return languageData.TableSheet;
}
private bool IsMultiline(string text)
{
return text.Contains("\n") || text.Contains("\r");
}
private void BuildLocalizationTable()
{
// 1. build table sheet
currentTable.TableSheet = new();
foreach (var section in windowData.TableSheet)
{
GameLocaizationTable.TableData tableData = new GameLocaizationTable.TableData(section.Name, section.Id);
foreach (var item in section.Items)
{
GameLocaizationTable.SheetItem sheetItem = new GameLocaizationTable.SheetItem(item.Key, item.Id, item.isGen);
tableData.SectionSheet.Add(sheetItem);
}
currentTable.TableSheet.Add(tableData);
}
// 2. build table sheet for each language
IList<LocalizationLanguage> languages = new List<LocalizationLanguage>();
foreach (var language in windowData.Languages)
{
if (language.Entry.Asset == null)
continue;
LocalizationLanguage asset = language.Entry.Asset;
IList<LocalizationLanguage.LocalizationString> strings = new List<LocalizationLanguage.LocalizationString>();
foreach (var section in language.TableSheet)
{
string sectionKey = section.Name.Replace(" ", "");
foreach (var item in section.Items)
{
string itemKey = item.Key.Replace(" ", "");
string key = sectionKey + "." + itemKey;
strings.Add(new()
{
SectionId = section.Id,
EntryId = item.Id,
Key = key,
Value = item.Value
});
}
}
asset.LanguageName = language.Entry.LanguageName;
asset.Strings = new(strings);
languages.Add(asset);
EditorUtility.SetDirty(asset);
}
currentTable.Languages = new(languages);
}
}
}