using System; using System.Collections.Generic; using UnityEditor; using UnityEngine; using BaseGames.Localization; namespace BaseGames.Editor.Localization { /// /// 本地化 CSV 导入/导出工具。 /// /// 每个表导出为独立 CSV(含 UTF-8 BOM,Excel 中文不乱码),列顺序:key, ChineseSimplified, English, Japanese, Korean。 /// 文件存放路径:Assets/_Game/Localization/Export/{TableName}.csv /// /// 导入:读取 CSV → 合并回写 Assets/_Game/Data/Localization/{Language}/{TableName}.json(经 LocalizationFileIO) /// /// 注:日常单表编辑请用「BaseGames / Localization / 表格编辑器」,此窗口用于批量多表导入导出。 /// 菜单:BaseGames / Localization / CSV 导入导出工具 /// public class LocalizationCsvTool : EditorWindow { [MenuItem("BaseGames/Localization/CSV 导入导出工具")] private static void Open() { var win = GetWindow("本地化 CSV 工具"); win.minSize = new Vector2(480, 360); } // ── 状态 ───────────────────────────────────────────────────────────── private static readonly string[] s_allTables = { LocalizationTable.UI, LocalizationTable.Dialogue, LocalizationTable.Quest, LocalizationTable.Spells, LocalizationTable.Skills, LocalizationTable.Items, LocalizationTable.Character, LocalizationTable.Tutorial, }; private const string ExportDir = "Assets/_Game/Localization/Export"; private const string DataDir = LocalizationPaths.DataRoot; private readonly bool[] _exportSelected = new bool[s_allTables.Length]; private readonly bool[] _importSelected = new bool[s_allTables.Length]; private string _statusMessage = ""; private MessageType _statusType = MessageType.None; private Vector2 _scroll; // ── GUI ─────────────────────────────────────────────────────────────── private void OnGUI() { _scroll = EditorGUILayout.BeginScrollView(_scroll); EditorGUILayout.Space(6); GUILayout.Label("📤 导出 → CSV", EditorStyles.boldLabel); EditorGUILayout.HelpBox( $"将 {DataDir}/ 中的 JSON 表导出为 CSV 文件(UTF-8 BOM,Excel 可直接打开)。\n" + $"目标目录:{ExportDir}/", MessageType.Info); EditorGUI.indentLevel++; for (int i = 0; i < s_allTables.Length; i++) _exportSelected[i] = EditorGUILayout.Toggle(s_allTables[i], _exportSelected[i]); EditorGUI.indentLevel--; EditorGUILayout.BeginHorizontal(); if (GUILayout.Button("全选")) SetAll(_exportSelected, true); if (GUILayout.Button("全不选")) SetAll(_exportSelected, false); EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(4); if (GUILayout.Button("▶ 导出选中表", GUILayout.Height(30))) RunExport(); EditorGUILayout.Space(16); EditorGUILayout.LabelField("", GUI.skin.horizontalSlider); GUILayout.Label("📥 导入 ← CSV", EditorStyles.boldLabel); EditorGUILayout.HelpBox( $"读取 {ExportDir}/ 中的 CSV,合并回写至 {DataDir}/ JSON 文件。\n" + "已有 Key 覆盖,新增 Key 追加,不删除多余 Key。", MessageType.Info); EditorGUI.indentLevel++; for (int i = 0; i < s_allTables.Length; i++) _importSelected[i] = EditorGUILayout.Toggle(s_allTables[i], _importSelected[i]); EditorGUI.indentLevel--; EditorGUILayout.BeginHorizontal(); if (GUILayout.Button("全选")) SetAll(_importSelected, true); if (GUILayout.Button("全不选")) SetAll(_importSelected, false); EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(4); if (GUILayout.Button("▶ 导入选中表", GUILayout.Height(30))) RunImport(); EditorGUILayout.Space(8); if (!string.IsNullOrEmpty(_statusMessage)) EditorGUILayout.HelpBox(_statusMessage, _statusType); EditorGUILayout.Space(6); EditorGUILayout.EndScrollView(); } // ── 导出 ───────────────────────────────────────────────────────────── private void RunExport() { LocalizationManager.ClearEditorPreviewCache(); int exported = 0; try { for (int ti = 0; ti < s_allTables.Length; ti++) { if (!_exportSelected[ti]) continue; string tableName = s_allTables[ti]; EditorUtility.DisplayProgressBar("导出 CSV", $"导出 {tableName}…", (float)ti / s_allTables.Length); // 经统一门面读取各语言字典(正确路径),交给 LocalizationCsv 写出(含 BOM) var langDicts = new Dictionary>(); bool any = false; foreach (var lang in LocalizationFileIO.AllLanguages) { var dict = LocalizationFileIO.Read(lang, tableName); langDicts[lang] = dict; if (dict.Count > 0) any = true; } if (!any) { Debug.LogWarning($"[CsvTool] 表「{tableName}」无数据,跳过导出。"); continue; } LocalizationCsv.ExportToFile(tableName, langDicts); exported++; } } finally { EditorUtility.ClearProgressBar(); } AssetDatabase.Refresh(); SetStatus($"✅ 成功导出 {exported} 个表到 {ExportDir}/", MessageType.Info); } // ── 导入 ───────────────────────────────────────────────────────────── private void RunImport() { int imported = 0; int errors = 0; try { for (int ti = 0; ti < s_allTables.Length; ti++) { if (!_importSelected[ti]) continue; string tableName = s_allTables[ti]; EditorUtility.DisplayProgressBar("导入 CSV", $"导入 {tableName}…", (float)ti / s_allTables.Length); try { // 合并语义 + 正确格式/目录/Addressable 注册,全部由统一门面处理 int affected = LocalizationCsv.ImportFileToJson(tableName); if (affected == 0) { Debug.LogWarning($"[CsvTool] CSV 文件不存在或为空,跳过:{ExportDir}/{tableName}.csv"); continue; } imported++; } catch (Exception ex) { Debug.LogError($"[CsvTool] 导入「{tableName}」失败:{ex.Message}"); errors++; } } } finally { EditorUtility.ClearProgressBar(); } AssetDatabase.Refresh(); LocalizationManager.ClearEditorPreviewCache(); string msg = errors == 0 ? $"✅ 成功导入 {imported} 个表。" : $"⚠ 导入 {imported} 个表,{errors} 个失败(见控制台)。"; SetStatus(msg, errors == 0 ? MessageType.Info : MessageType.Warning); } // ── 工具函数 ───────────────────────────────────────────────────────── private static void SetAll(bool[] arr, bool v) { for (int i = 0; i < arr.Length; i++) arr[i] = v; } private void SetStatus(string msg, MessageType type) { _statusMessage = msg; _statusType = type; Repaint(); } } }