601 lines
21 KiB
C#
601 lines
21 KiB
C#
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
|
||
|
||
|
||
// 原本是 const,现在改成可调的字段
|
||
[SerializeField] private float languagesWidth = 200f;
|
||
[SerializeField] private float tableSheetWidth = 300f;
|
||
|
||
private const float SplitterWidth = 3f; // 拖拽条的宽度
|
||
private bool isResizingLeft, isResizingMiddle;
|
||
|
||
|
||
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()
|
||
{
|
||
// 顶部工具栏
|
||
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();
|
||
}
|
||
|
||
// ---------------- 这里开始是 SplitView ----------------
|
||
Rect fullRect = new Rect(0, 20f, position.width, position.height - 20f);
|
||
|
||
// 左侧 Languages
|
||
Rect languagesRect = new Rect(fullRect.x, fullRect.y, languagesWidth, fullRect.height);
|
||
languagesTreeView?.OnGUI(languagesRect);
|
||
|
||
// 左侧拖拽条
|
||
Rect leftSplitter = new Rect(languagesRect.xMax, fullRect.y, SplitterWidth, fullRect.height);
|
||
EditorGUIUtility.AddCursorRect(leftSplitter, MouseCursor.ResizeHorizontal);
|
||
HandleResize(ref languagesWidth, leftSplitter, ref isResizingLeft);
|
||
|
||
// 中间 TableSheet
|
||
Rect tableSheetRect = new Rect(leftSplitter.xMax, fullRect.y, tableSheetWidth, fullRect.height);
|
||
tableSheetTreeView?.OnGUI(tableSheetRect);
|
||
|
||
// 中间拖拽条
|
||
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);
|
||
|
||
if (selection != null)
|
||
{
|
||
GUILayout.BeginArea(inspectorRect);
|
||
{
|
||
if (selection is LanguageSelect lang) OnDrawLanguageInspector(lang);
|
||
else if (selection is SectionSelect sec) OnDrawSectionInspector(sec);
|
||
else if (selection is ItemSelect item) OnDrawSectionItemInspector(item);
|
||
}
|
||
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;
|
||
}
|
||
}
|
||
|
||
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);
|
||
}
|
||
}
|
||
|
||
private void OnDrawSectionItemInspector(ItemSelect itemSelect)
|
||
{
|
||
var item = itemSelect.Item;
|
||
|
||
EditorGUILayout.LabelField("Editing Key Across Languages", EditorStyles.boldLabel);
|
||
|
||
// 显示 Key 和 ID(只读)
|
||
using (new EditorGUI.DisabledGroupScope(true))
|
||
{
|
||
EditorGUILayout.TextField("Key", item.Key);
|
||
EditorGUILayout.LabelField("Id: " + item.Id);
|
||
}
|
||
|
||
EditorGUILayout.Space(4);
|
||
EditorDrawing.Separator();
|
||
EditorGUILayout.Space(4);
|
||
|
||
// 遍历所有语言
|
||
foreach (var lang in windowData.Languages)
|
||
{
|
||
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);
|
||
}
|
||
}
|
||
|
||
|
||
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);
|
||
}
|
||
}
|
||
}
|