/* * @Author: 白哉 * @Description: * @Date: 2025年05月26日 星期一 11:05:13 * @Modify: */ using System; using System.Collections.Generic; using System.Text.RegularExpressions; using Logic.CrashSight; using TMPro; using Unity.VisualScripting; using UnityEngine; using ColorUtility = UnityEngine.ColorUtility; namespace Logic.Multilingual { public enum MultilingualType { None, ZH, // 简中 TDZH, // 繁中 EN, // 英文 JP, // 日语 KR, // 韩语 RU, // 俄语 ES, // 西班牙语 PT, // 葡萄牙语 FR, // 法语 Max, } public class MultilingualData : ScriptableObject { public List FontGroups = new List(); public List Items = new List(); public List TargetTypes = new List(); private Dictionary _itemDict; public Dictionary ItemDict => _itemDict; public string GetMultilingualStr(uint id, MultilingualType type) { if (_itemDict == null) RefreshDict(); if (_itemDict == null) return string.Empty; if (!_itemDict.TryGetValue(id, out var item)) return string.Empty; var ret = item.GetStrByType(type); if (string.IsNullOrEmpty(ret)) return ret; ret = ResolveEmbeddedStringsRunning(ret, type); return ret; } public TMP_FontAsset GetMultilingualFont(uint fontId, MultilingualType type) { if (fontId == 0) return null; foreach (var group in FontGroups) { if (group.FontID != fontId) continue; return type switch { MultilingualType.ZH => group.ZHFont, MultilingualType.TDZH => group.TDZHFont, MultilingualType.EN => group.ENFont, MultilingualType.JP => group.JPFont, MultilingualType.KR => group.KRFont, MultilingualType.RU => group.RUFont, MultilingualType.ES => group.ESFont, MultilingualType.PT => group.PTFont, MultilingualType.FR => group.FRFont, _ => null, }; } return null; } public uint GetFontGroupID(TMP_FontAsset font) { foreach (var group in FontGroups) { if (group.ZHFont == font) return group.FontID; } return 0; } public void RefreshDict() { if (_itemDict == null) _itemDict = new Dictionary(); if (_itemDict.Count == Items.Count) return; _itemDict.Clear(); foreach (var item in Items) _itemDict[item.ID] = item; } public MultilingualType GetSystemLanguageTargetMultilingual(MultilingualType type) { var index = (int)type - 1; if (index >= TargetTypes.Count) return type; return TargetTypes[index]; } public string GetMultilingualStrEditor(uint id, MultilingualType type) { if (_itemDict == null) RefreshDict(); if (_itemDict == null) return string.Empty; if (!_itemDict.TryGetValue(id, out var item)) return string.Empty; var ret = item.GetStrByType(type); if (string.IsNullOrEmpty(ret)) return ret; ret = ResolveEmbeddedStrings(ret, type); return ret; } // 获取字符串中嵌套的 ID 列表 (按对应语种的顺序) public List GetSubStringIdRunning(uint id, MultilingualType type) { var result = new List(); if (_itemDict == null) RefreshDict(); if (_itemDict == null) return result; if (!_itemDict.TryGetValue(id, out var item)) return result; var origin = item.GetStrByType(type); if (string.IsNullOrEmpty(origin)) return result; var regex = new Regex(@"\*\*<(?:!\[(\d+)\])?(\d+)>\*\*"); var matches = regex.Matches(origin); foreach (Match m in matches) { if (uint.TryParse(m.Groups[2].Value, out var subId)) { result.Add(subId); } } return result; } // ID 转 String (运行时) public string ResolveEmbeddedStringsRunning(string origin, MultilingualType type) { if (string.IsNullOrEmpty(origin)) return string.Empty; var regex = new Regex(@"\*\*<(?:!\[(\d+)\])?(\d+)>\*\*"); var result = regex.Replace(origin, m => { var prefixGroup = m.Groups[1]; // [n] 中的 n var idStr = m.Groups[2].Value; // id RefreshDict(); if (!uint.TryParse(idStr, out var subId)) return m.Value; if (_itemDict == null || !_itemDict.TryGetValue(subId, out var subItem)) return m.Value; var str = subItem.GetStrByType(type); // 检测嵌套:展开后的字符串不应再包含 **<...>** if (Regex.IsMatch(str, @"\*\*<.+?>\*\*")) { LogSystem.LogError($"ResolveEmbeddedStrings: 检测到嵌套引用,ID: {subId},内容: {str}"); return m.Value; } var hex = subItem.Color; if (string.IsNullOrEmpty(hex)) { var color = new Color(1f, 0.647f, 0f); hex = ColorUtility.ToHtmlStringRGB(color); } if (!hex.StartsWith("#")) hex = "#" + hex; if (!string.IsNullOrEmpty(subItem.Icon)) return $"{str}"; return $"{str}"; }); return result; } // ID 转 String public string ResolveEmbeddedStrings(string origin, MultilingualType type) { if (string.IsNullOrEmpty(origin)) return string.Empty; var regex = new Regex(@"\*\*<(?:!\[(\d+)\])?(\d+)>\*\*"); var result = regex.Replace(origin, m => { var prefixGroup = m.Groups[1]; // [n] 中的 n var idStr = m.Groups[2].Value; // id RefreshDict(); if (!uint.TryParse(idStr, out var subId)) return m.Value; if (_itemDict == null || !_itemDict.TryGetValue(subId, out var subItem)) return m.Value; var str = subItem.GetStrByType(type); // 检测嵌套:展开后的字符串不应再包含 **<...>** if (Regex.IsMatch(str, @"\*\*<.+?>\*\*")) { LogSystem.LogError($"ResolveEmbeddedStrings: 检测到嵌套引用,ID: {subId},内容: {str}"); return m.Value; } if (prefixGroup.Success) { return $"**<[{prefixGroup.Value}]{str}>**"; } else { return $"**<{str}>**"; } }); return result; } // String 转 ID public string UnResolveEmbeddedStrings(string origin, MultilingualType type) { if (string.IsNullOrEmpty(origin)) return string.Empty; var regex = new Regex(@"\*\*<(?:!\[(\d+)\])?(.+?)>\*\*"); var result = regex.Replace(origin, m => { var prefixGroup = m.Groups[1]; // [n] 中的 n var str = m.Groups[2].Value; // 内部字符串 // 已经是 id 则跳过 if (uint.TryParse(str, out _)) return m.Value; // 检测嵌套:内部字符串不应再包含 **<...>** if (Regex.IsMatch(str, @"\*\*<.+?>\*\*")) { LogSystem.LogError($"UnResolveEmbeddedStrings: 检测到嵌套引用,内容: {str}"); return m.Value; } RefreshDict(); // 在 _itemDict 中反查 id uint id = 0; bool found = false; foreach (var kv in _itemDict) { if (kv.Value.GetStrByType(type) == str) { id = kv.Key; found = true; break; } } if (!found) { LogSystem.LogError($"UnResolveEmbeddedStrings: 找不到字符串对应的ID,字符串: origin:{origin} str:{str}"); return m.Value; } if (prefixGroup.Success) { return $"**<[{prefixGroup.Value}]{id}>**"; } else { return $"**<{id}>**"; } }); return result; } /// /// 将其他语言的嵌入字符串对齐到中文已转换好的ID格式 /// zhResolved: 中文已经通过 UnResolveEmbeddedStrings 转换后的字符串(包含 **** 和 **<[n]id>**) /// otherOrigin: 其他语言的原始字符串(包含 **** 和 **<[n]str>**) /// type: 当前语言类型(用于日志) /// itemId: 当前item的ID(用于日志) /// public string AlignEmbeddedStringsToZH(string zhResolved, string otherOrigin, MultilingualType type, uint itemId) { if (string.IsNullOrEmpty(otherOrigin)) return otherOrigin; if (string.IsNullOrEmpty(zhResolved)) return otherOrigin; // 解析中文已转换的嵌入引用 var zhRegex = new Regex(@"\*\*<(?:!\[(\d+)\])?(\d+)>\*\*"); var zhMatches = zhRegex.Matches(zhResolved); // 构建中文的 n->完整匹配 字典(带前缀n的)和 无前缀的有序列表 var zhPrefixDict = new Dictionary(); // n -> **<[n]id>** var zhNoPrefixList = new List(); // **** 有序列表 foreach (Match m in zhMatches) { var prefixGroup = m.Groups[1]; var idStr = m.Groups[2].Value; if (prefixGroup.Success) { zhPrefixDict[prefixGroup.Value] = m.Value; // n -> **<[n]id>** } else { zhNoPrefixList.Add(m.Value); // **** } } // 解析其他语言的嵌入引用 var otherRegex = new Regex(@"\*\*<(?:!\[(\d+)\])?(.+?)>\*\*"); var otherMatches = otherRegex.Matches(otherOrigin); // 分类其他语言的匹配:带前缀n的 和 不带前缀的 var otherPrefixMatches = new List(); var otherNoPrefixMatches = new List(); foreach (Match m in otherMatches) { var prefixGroup = m.Groups[1]; var content = m.Groups[2].Value; // 如果内容已经是纯数字(id),跳过 if (uint.TryParse(content, out _)) continue; if (prefixGroup.Success) { otherPrefixMatches.Add(m); } else { otherNoPrefixMatches.Add(m); } } // 从后往前替换,避免索引偏移 // 收集所有需要替换的操作 var replacements = new List<(int index, int length, string replacement)>(); // 1. 处理带前缀 **<[n]str>** 的情况:使用中文对应n的 **<[n]id>** foreach (var m in otherPrefixMatches) { var n = m.Groups[1].Value; if (zhPrefixDict.TryGetValue(n, out var zhReplacement)) { replacements.Add((m.Index, m.Length, zhReplacement)); } else { LogSystem.LogError( $"AlignEmbeddedStringsToZH: 语言{type} ID:{itemId} 中文找不到前缀[{n}]对应的引用,删除此项: {m.Value}"); replacements.Add((m.Index, m.Length, string.Empty)); } } // 2. 处理不带前缀 **** 的情况:按顺序对齐中文的 **** int zhCount = zhNoPrefixList.Count; int otherCount = otherNoPrefixMatches.Count; if (zhCount != otherCount) { LogSystem.LogError( $"AlignEmbeddedStringsToZH: 语言{type} ID:{itemId} ****数量不匹配,中文:{zhCount} 当前语言:{otherCount}"); } for (int i = 0; i < otherNoPrefixMatches.Count; i++) { var m = otherNoPrefixMatches[i]; if (i < zhCount) { // 对齐中文的 **** replacements.Add((m.Index, m.Length, zhNoPrefixList[i])); } else { // 中文少,删除其他语言多余的 **** replacements.Add((m.Index, m.Length, string.Empty)); } } // 按索引从后往前排序并替换 replacements.Sort((a, b) => b.index.CompareTo(a.index)); var result = otherOrigin; foreach (var (index, length, replacement) in replacements) { result = result.Substring(0, index) + replacement + result.Substring(index + length); } return result; } } [Serializable] public class MultilingualItem { public uint ID; public string ZH; public string TDZH; public string EN; public string JP; public string KR; public string RU; public string ES; public string PT; public string FR; public bool IsProperNoun; public bool IsDialogue; public string DialogueSpeaker; public bool IsDeprecated; public bool IsCustom; public bool IsSpecialTerm; // 默认橘色 public string Color; public string Icon; [NonSerialized] public string Desc; public MultilingualItem() { IsCustom = false; } public void Refresh() { ZH = ZH?.Replace("\r\n", "\n") ?? string.Empty; TDZH = TDZH?.Replace("\r\n", "\n") ?? string.Empty; EN = EN?.Replace("\r\n", "\n") ?? string.Empty; JP = JP?.Replace("\r\n", "\n") ?? string.Empty; KR = KR?.Replace("\r\n", "\n") ?? string.Empty; RU = RU?.Replace("\r\n", "\n") ?? string.Empty; ES = ES?.Replace("\r\n", "\n") ?? string.Empty; PT = PT?.Replace("\r\n", "\n") ?? string.Empty; FR = FR?.Replace("\r\n", "\n") ?? string.Empty; DialogueSpeaker = DialogueSpeaker?.Replace("\r\n", "\n") ?? string.Empty; Desc = Desc?.Replace("\r\n", "\n") ?? string.Empty; } public string GetStrByType(MultilingualType type) { return type switch { MultilingualType.ZH => ZH, MultilingualType.TDZH => TDZH, MultilingualType.EN => EN, MultilingualType.JP => JP, MultilingualType.KR => KR, MultilingualType.RU => RU, MultilingualType.ES => ES, MultilingualType.PT => PT, MultilingualType.FR => FR, _ => string.Empty, }; } public bool IsTranslate(MultilingualType type) { if (type == MultilingualType.ZH) return true; if (type == MultilingualType.None) { return !string.IsNullOrEmpty(TDZH) && !string.IsNullOrEmpty(EN) && !string.IsNullOrEmpty(JP) && !string.IsNullOrEmpty(KR) && !string.IsNullOrEmpty(RU) && !string.IsNullOrEmpty(ES) && !string.IsNullOrEmpty(PT) && !string.IsNullOrEmpty(FR); } return type switch { MultilingualType.TDZH => !string.IsNullOrEmpty(TDZH), MultilingualType.EN => !string.IsNullOrEmpty(EN), MultilingualType.JP => !string.IsNullOrEmpty(JP), MultilingualType.KR => !string.IsNullOrEmpty(KR), MultilingualType.RU => !string.IsNullOrEmpty(RU), MultilingualType.ES => !string.IsNullOrEmpty(ES), MultilingualType.PT => !string.IsNullOrEmpty(PT), MultilingualType.FR => !string.IsNullOrEmpty(FR), _ => false, }; } // 不分大小写将 True 或者 False 字符串转换为布尔值 public static bool ParseBoolStr(string str) { return string.Equals(str, "True", StringComparison.OrdinalIgnoreCase); } } [Serializable] public class MultilingualFontGroup { public uint FontID; public TMP_FontAsset ZHFont; public TMP_FontAsset TDZHFont; public TMP_FontAsset ENFont; public TMP_FontAsset JPFont; public TMP_FontAsset KRFont; public TMP_FontAsset RUFont; public TMP_FontAsset ESFont; public TMP_FontAsset PTFont; public TMP_FontAsset FRFont; } }