2026-03-19 15:50:14 +08:00

496 lines
17 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* @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;
namespace Logic.Multilingual
{
public enum MultilingualType
{
None,
ZH, // 简中
TDZH, // 繁中
EN, // 英文
JP, // 日语
KR, // 韩语
RU, // 俄语
ES, // 西班牙语
PT, // 葡萄牙语
FR, // 法语
Max,
}
public class MultilingualData : ScriptableObject
{
public List<MultilingualFontGroup> FontGroups = new List<MultilingualFontGroup>();
public List<MultilingualItem> Items = new List<MultilingualItem>();
public List<MultilingualType> TargetTypes = new List<MultilingualType>();
private Dictionary<uint, MultilingualItem> _itemDict;
public Dictionary<uint, MultilingualItem> 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 = type switch
{
MultilingualType.ZH => item.ZH,
MultilingualType.TDZH => item.TDZH,
MultilingualType.EN => item.EN,
MultilingualType.JP => item.JP,
MultilingualType.KR => item.KR,
MultilingualType.RU => item.RU,
MultilingualType.ES => item.ES,
MultilingualType.PT => item.PT,
MultilingualType.FR => item.FR,
_ => string.Empty,
};
// TODO 临时换色
ret = ret.Replace("<color=yellow>", "<color=orange>");
// TODO 临时转义换色
ret = ret.Replace("**<", "<color=orange>");
ret = ret.Replace(">**", "</color>");
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<uint, MultilingualItem>();
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 = type switch
{
MultilingualType.ZH => item.ZH,
MultilingualType.TDZH => item.TDZH,
MultilingualType.EN => item.EN,
MultilingualType.JP => item.JP,
MultilingualType.KR => item.KR,
MultilingualType.RU => item.RU,
MultilingualType.ES => item.ES,
MultilingualType.PT => item.PT,
MultilingualType.FR => item.FR,
_ => string.Empty,
};
ret = ResolveEmbeddedStrings(ret, type);
return ret;
}
// 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;
}
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;
}
/// <summary>
/// 将其他语言的嵌入字符串对齐到中文已转换好的ID格式
/// zhResolved: 中文已经通过 UnResolveEmbeddedStrings 转换后的字符串(包含 [[id]] 和 [[[n]id]]
/// otherOrigin: 其他语言的原始字符串(包含 [[str]] 和 [[[n]str]]
/// type: 当前语言类型(用于日志)
/// itemId: 当前item的ID用于日志
/// </summary>
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<string, string>(); // n -> [[[n]id]]
var zhNoPrefixList = new List<string>(); // [[id]] 有序列表
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); // [[id]]
}
}
// 解析其他语言的嵌入引用
var otherRegex = new Regex(@"\[\[(?:\[(\d+)\])?(.+?)\]\]");
var otherMatches = otherRegex.Matches(otherOrigin);
// 分类其他语言的匹配带前缀n的 和 不带前缀的
var otherPrefixMatches = new List<Match>();
var otherNoPrefixMatches = new List<Match>();
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. 处理不带前缀 [[str]] 的情况:按顺序对齐中文的 [[id]]
int zhCount = zhNoPrefixList.Count;
int otherCount = otherNoPrefixMatches.Count;
if (zhCount != otherCount)
{
LogSystem.LogError(
$"AlignEmbeddedStringsToZH: 语言{type} ID:{itemId} [[str]]数量不匹配,中文:{zhCount} 当前语言:{otherCount}");
}
for (int i = 0; i < otherNoPrefixMatches.Count; i++)
{
var m = otherNoPrefixMatches[i];
if (i < zhCount)
{
// 对齐中文的 [[id]]
replacements.Add((m.Index, m.Length, zhNoPrefixList[i]));
}
else
{
// 中文少,删除其他语言多余的 [[str]]
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;
[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;
}
}