UI系统组件
This commit is contained in:
@@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using BaseGames.Localization;
|
||||
@@ -11,11 +9,12 @@ namespace BaseGames.Editor.Localization
|
||||
/// <summary>
|
||||
/// 本地化 CSV 导入/导出工具。
|
||||
///
|
||||
/// 每个表导出为独立 CSV,列顺序:key, ChineseSimplified, English, Japanese, Korean。
|
||||
/// 每个表导出为独立 CSV(含 UTF-8 BOM,Excel 中文不乱码),列顺序:key, ChineseSimplified, English, Japanese, Korean。
|
||||
/// 文件存放路径:Assets/_Game/Localization/Export/{TableName}.csv
|
||||
///
|
||||
/// 导入:读取 CSV → 回写 Resources/Localization/{Language}/{TableName}.json
|
||||
/// 导入:读取 CSV → 合并回写 Assets/_Game/Data/Localization/{Language}/{TableName}.json(经 LocalizationFileIO)
|
||||
///
|
||||
/// 注:日常单表编辑请用「BaseGames / Localization / 表格编辑器」,此窗口用于批量多表导入导出。
|
||||
/// 菜单:BaseGames / Localization / CSV 导入导出工具
|
||||
/// </summary>
|
||||
public class LocalizationCsvTool : EditorWindow
|
||||
@@ -29,9 +28,6 @@ namespace BaseGames.Editor.Localization
|
||||
|
||||
// ── 状态 ─────────────────────────────────────────────────────────────
|
||||
|
||||
private static readonly Language[] s_allLanguages =
|
||||
(Language[])Enum.GetValues(typeof(Language));
|
||||
|
||||
private static readonly string[] s_allTables =
|
||||
{
|
||||
LocalizationTable.UI,
|
||||
@@ -45,6 +41,7 @@ namespace BaseGames.Editor.Localization
|
||||
};
|
||||
|
||||
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];
|
||||
@@ -61,7 +58,7 @@ namespace BaseGames.Editor.Localization
|
||||
EditorGUILayout.Space(6);
|
||||
GUILayout.Label("📤 导出 → CSV", EditorStyles.boldLabel);
|
||||
EditorGUILayout.HelpBox(
|
||||
$"将 Resources/Localization/ 中的 JSON 表导出为 CSV 文件。\n" +
|
||||
$"将 {DataDir}/ 中的 JSON 表导出为 CSV 文件(UTF-8 BOM,Excel 可直接打开)。\n" +
|
||||
$"目标目录:{ExportDir}/",
|
||||
MessageType.Info);
|
||||
|
||||
@@ -84,7 +81,7 @@ namespace BaseGames.Editor.Localization
|
||||
|
||||
GUILayout.Label("📥 导入 ← CSV", EditorStyles.boldLabel);
|
||||
EditorGUILayout.HelpBox(
|
||||
$"读取 {ExportDir}/ 中的 CSV,回写至 Resources/Localization/ JSON 文件。\n" +
|
||||
$"读取 {ExportDir}/ 中的 CSV,合并回写至 {DataDir}/ JSON 文件。\n" +
|
||||
"已有 Key 覆盖,新增 Key 追加,不删除多余 Key。",
|
||||
MessageType.Info);
|
||||
|
||||
@@ -114,7 +111,7 @@ namespace BaseGames.Editor.Localization
|
||||
|
||||
private void RunExport()
|
||||
{
|
||||
EnsureDirectory(ExportDir);
|
||||
LocalizationManager.ClearEditorPreviewCache();
|
||||
int exported = 0;
|
||||
|
||||
try
|
||||
@@ -126,50 +123,23 @@ namespace BaseGames.Editor.Localization
|
||||
|
||||
EditorUtility.DisplayProgressBar("导出 CSV", $"导出 {tableName}…", (float)ti / s_allTables.Length);
|
||||
|
||||
// 收集所有语言的字典(以第一个语言的 Key 集合为主键集)
|
||||
// 经统一门面读取各语言字典(正确路径),交给 LocalizationCsv 写出(含 BOM)
|
||||
var langDicts = new Dictionary<Language, Dictionary<string, string>>();
|
||||
var allKeys = new SortedSet<string>(StringComparer.Ordinal);
|
||||
|
||||
foreach (var lang in s_allLanguages)
|
||||
bool any = false;
|
||||
foreach (var lang in LocalizationFileIO.AllLanguages)
|
||||
{
|
||||
LocalizationManager.ClearEditorPreviewCache();
|
||||
var dict = LocalizationManager.GetEditorTable(lang, tableName);
|
||||
if (dict != null)
|
||||
{
|
||||
langDicts[lang] = dict;
|
||||
foreach (var k in dict.Keys) allKeys.Add(k);
|
||||
}
|
||||
var dict = LocalizationFileIO.Read(lang, tableName);
|
||||
langDicts[lang] = dict;
|
||||
if (dict.Count > 0) any = true;
|
||||
}
|
||||
|
||||
if (allKeys.Count == 0)
|
||||
if (!any)
|
||||
{
|
||||
Debug.LogWarning($"[CsvTool] 表「{tableName}」无数据,跳过导出。");
|
||||
continue;
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
// 表头
|
||||
sb.Append("key");
|
||||
foreach (var lang in s_allLanguages)
|
||||
sb.Append(',').Append(lang);
|
||||
sb.AppendLine();
|
||||
|
||||
// 数据行
|
||||
foreach (var key in allKeys)
|
||||
{
|
||||
sb.Append(CsvEscape(key));
|
||||
foreach (var lang in s_allLanguages)
|
||||
{
|
||||
string val = "";
|
||||
if (langDicts.TryGetValue(lang, out var d))
|
||||
d.TryGetValue(key, out val);
|
||||
sb.Append(',').Append(CsvEscape(val ?? ""));
|
||||
}
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
string csvPath = $"{ExportDir}/{tableName}.csv";
|
||||
File.WriteAllText(csvPath, sb.ToString(), Encoding.UTF8);
|
||||
LocalizationCsv.ExportToFile(tableName, langDicts);
|
||||
exported++;
|
||||
}
|
||||
}
|
||||
@@ -195,61 +165,18 @@ namespace BaseGames.Editor.Localization
|
||||
{
|
||||
if (!_importSelected[ti]) continue;
|
||||
string tableName = s_allTables[ti];
|
||||
string csvPath = Path.Combine(Path.GetDirectoryName(Application.dataPath)!, $"{ExportDir}/{tableName}.csv");
|
||||
|
||||
EditorUtility.DisplayProgressBar("导入 CSV", $"导入 {tableName}…", (float)ti / s_allTables.Length);
|
||||
|
||||
if (!File.Exists(csvPath))
|
||||
{
|
||||
Debug.LogWarning($"[CsvTool] CSV 文件不存在,跳过:{ExportDir}/{tableName}.csv");
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var rows = ParseCsv(File.ReadAllText(csvPath, Encoding.UTF8));
|
||||
if (rows.Count < 2) continue;
|
||||
|
||||
// 表头行解析出列对应的语言
|
||||
var header = rows[0];
|
||||
var langCols = new List<(int col, Language lang)>();
|
||||
for (int col = 1; col < header.Count; col++)
|
||||
// 合并语义 + 正确格式/目录/Addressable 注册,全部由统一门面处理
|
||||
int affected = LocalizationCsv.ImportFileToJson(tableName);
|
||||
if (affected == 0)
|
||||
{
|
||||
if (Enum.TryParse<Language>(header[col].Trim(), out var lang))
|
||||
langCols.Add((col, lang));
|
||||
Debug.LogWarning($"[CsvTool] CSV 文件不存在或为空,跳过:{ExportDir}/{tableName}.csv");
|
||||
continue;
|
||||
}
|
||||
|
||||
// 为每个语言准备合并后的字典
|
||||
var mergedDicts = new Dictionary<Language, Dictionary<string, string>>();
|
||||
foreach (var (_, lang) in langCols)
|
||||
{
|
||||
LocalizationManager.ClearEditorPreviewCache();
|
||||
var existing = LocalizationManager.GetEditorTable(lang, tableName)
|
||||
?? new Dictionary<string, string>(StringComparer.Ordinal);
|
||||
mergedDicts[lang] = new Dictionary<string, string>(existing, StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
// 用 CSV 数据覆盖/追加
|
||||
for (int row = 1; row < rows.Count; row++)
|
||||
{
|
||||
var cells = rows[row];
|
||||
if (cells.Count == 0 || string.IsNullOrWhiteSpace(cells[0])) continue;
|
||||
string key = cells[0];
|
||||
foreach (var (col, lang) in langCols)
|
||||
{
|
||||
string val = col < cells.Count ? cells[col] : "";
|
||||
mergedDicts[lang][key] = val;
|
||||
}
|
||||
}
|
||||
|
||||
// 写回 JSON
|
||||
foreach (var (lang, dict) in mergedDicts)
|
||||
{
|
||||
string jsonPath = GetJsonPath(lang, tableName);
|
||||
EnsureDirectory(Path.GetDirectoryName(jsonPath));
|
||||
File.WriteAllText(jsonPath, DictToJson(dict), Encoding.UTF8);
|
||||
}
|
||||
|
||||
imported++;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -275,104 +202,6 @@ namespace BaseGames.Editor.Localization
|
||||
|
||||
// ── 工具函数 ─────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>将 value 按 RFC 4180 规范包裹(含逗号/双引号/换行时用双引号包裹,内部双引号转义为 "")。</summary>
|
||||
private static string CsvEscape(string value)
|
||||
{
|
||||
if (value == null) return "";
|
||||
bool needsQuote = value.Contains(',') || value.Contains('"') || value.Contains('\n') || value.Contains('\r');
|
||||
if (!needsQuote) return value;
|
||||
return "\"" + value.Replace("\"", "\"\"") + "\"";
|
||||
}
|
||||
|
||||
/// <summary>简单 CSV 解析器,支持 RFC 4180 带引号字段(含换行)。</summary>
|
||||
private static List<List<string>> ParseCsv(string text)
|
||||
{
|
||||
var rows = new List<List<string>>();
|
||||
var row = new List<string>();
|
||||
var cell = new StringBuilder();
|
||||
bool inQuote = false;
|
||||
int i = 0;
|
||||
|
||||
while (i < text.Length)
|
||||
{
|
||||
char c = text[i];
|
||||
if (inQuote)
|
||||
{
|
||||
if (c == '"')
|
||||
{
|
||||
if (i + 1 < text.Length && text[i + 1] == '"')
|
||||
{ cell.Append('"'); i += 2; }
|
||||
else
|
||||
{ inQuote = false; i++; }
|
||||
}
|
||||
else
|
||||
{ cell.Append(c); i++; }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (c == '"')
|
||||
{ inQuote = true; i++; }
|
||||
else if (c == ',')
|
||||
{ row.Add(cell.ToString()); cell.Clear(); i++; }
|
||||
else if (c == '\r')
|
||||
{
|
||||
row.Add(cell.ToString()); cell.Clear();
|
||||
rows.Add(row); row = new List<string>();
|
||||
if (i + 1 < text.Length && text[i + 1] == '\n') i++;
|
||||
i++;
|
||||
}
|
||||
else if (c == '\n')
|
||||
{ row.Add(cell.ToString()); cell.Clear(); rows.Add(row); row = new List<string>(); i++; }
|
||||
else
|
||||
{ cell.Append(c); i++; }
|
||||
}
|
||||
}
|
||||
|
||||
if (cell.Length > 0 || row.Count > 0)
|
||||
{
|
||||
row.Add(cell.ToString());
|
||||
rows.Add(row);
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
/// <summary>将字符串字典序列化为最小 JSON 对象(UTF-8,适合直接写入 Resources JSON)。</summary>
|
||||
private static string DictToJson(Dictionary<string, string> dict)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("{");
|
||||
int written = 0;
|
||||
foreach (var kv in dict)
|
||||
{
|
||||
sb.Append(" ").Append(JsonString(kv.Key)).Append(": ").Append(JsonString(kv.Value));
|
||||
if (++written < dict.Count) sb.Append(',');
|
||||
sb.AppendLine();
|
||||
}
|
||||
sb.Append('}');
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string JsonString(string s)
|
||||
{
|
||||
if (s == null) return "\"\"";
|
||||
s = s.Replace("\\", "\\\\")
|
||||
.Replace("\"", "\\\"")
|
||||
.Replace("\n", "\\n")
|
||||
.Replace("\r", "\\r")
|
||||
.Replace("\t", "\\t");
|
||||
return $"\"{s}\"";
|
||||
}
|
||||
|
||||
private static string GetJsonPath(Language lang, string tableName)
|
||||
=> $"Assets/Resources/Localization/{lang}/{tableName}.json";
|
||||
|
||||
private static void EnsureDirectory(string dir)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
|
||||
private static void SetAll(bool[] arr, bool v)
|
||||
{
|
||||
for (int i = 0; i < arr.Length; i++) arr[i] = v;
|
||||
|
||||
Reference in New Issue
Block a user