581 lines
22 KiB
C#
581 lines
22 KiB
C#
/*
|
||
* @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 = 0,
|
||
ZH = 1, // 简体中文 / Simplified Chinese
|
||
TDZH = 2, // 繁体中文 / Traditional Chinese
|
||
EN = 3, // 英文 / English
|
||
JP = 4, // 日语 / Japanese
|
||
KR = 5, // 韩语 / Korean
|
||
RU = 10, // 俄语 / Russian
|
||
ES = 11, // 西班牙语 / Spanish
|
||
PT = 12, // 葡萄牙语 / Portuguese
|
||
FR = 13, // 法语 / French
|
||
DE = 14, // 德语 / German
|
||
ID = 15, // 印尼语 / Indonesian
|
||
TH = 16, // 泰语 / Thai
|
||
PL = 17, // 波兰语 / Polish
|
||
VI = 18, // 越南语 / Vietnamese
|
||
MS = 19, // 马来语 / Malay
|
||
UK = 20, // 乌克兰语 / Ukrainian
|
||
KZ = 21, // 哈萨克语 / Kazakh
|
||
TR = 22, // 土耳其语 / Turkish
|
||
IT = 23, // 意大利语 / Italian
|
||
NL = 24, // 荷兰语 / Dutch
|
||
FI = 25, // 芬兰语 / Finnish
|
||
SV = 26, // 瑞典语 / Swedish
|
||
NO = 27, // 挪威语 / Norwegian
|
||
CS = 28, // 捷克语 / Czech
|
||
HU = 29, // 匈牙利语 / Hungarian
|
||
EL = 30, // 希腊语 / Greek
|
||
RO = 31, // 罗马尼亚语 / Romanian
|
||
ET = 32, // 爱沙尼亚语 / Estonian
|
||
LT = 33, // 立陶宛语 / Lithuanian
|
||
HR = 34, // 克罗地亚语 / Croatian
|
||
SR = 35, // 塞尔维亚语 / Serbian
|
||
SL = 36, // 斯洛文尼亚语 / Slovenian
|
||
SK = 37, // 斯洛伐克语 / Slovak
|
||
BE = 38, // 白俄罗斯语 / Belarusian
|
||
HE = 39, // 希伯来语 / Hebrew
|
||
BG = 40, // 保加利亚语 / Bulgarian
|
||
UZ = 41, // 乌兹别克语 / Uzbek
|
||
KY = 42, // 吉尔吉斯语 / Kyrgyz
|
||
MN = 43, // 蒙古语 / Mongolian
|
||
AR = 44, // 阿拉伯语 / Arabic
|
||
DA = 45, // 丹麦语 / Danish
|
||
TL = 46, // 菲律宾语 / Tagalog (Filipino)
|
||
Custom = 999, // 自定义语种 / Custom Language
|
||
Max = 1000, // 最大值 / Max value
|
||
}
|
||
|
||
|
||
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 = item.GetStrByType(type);
|
||
// Fallback 链:
|
||
// 1. 5 种主语言(除 EN):自己有就用自己,没有就直接返回空(不 fallback)
|
||
// 2. EN:自己没有 → 兜底到 ZH
|
||
// 3. 其他语言(含 Custom):自己没有 → 兜底到 EN,EN 还没有 → 兜底到 ZH
|
||
// ZH 是终极兜底,自己不 fallback
|
||
if (string.IsNullOrEmpty(ret) && NeedsEnglishFallback(type))
|
||
{
|
||
ret = item.GetStrByType(MultilingualType.EN);
|
||
}
|
||
if (string.IsNullOrEmpty(ret) && NeedsChineseFallback(type))
|
||
{
|
||
ret = item.GetStrByType(MultilingualType.ZH);
|
||
}
|
||
if (string.IsNullOrEmpty(ret)) return ret;
|
||
|
||
ret = ResolveEmbeddedStringsRunning(ret, type);
|
||
return ret;
|
||
}
|
||
|
||
// 是否需要回落英文:仅 5 种主语言(中英日韩繁)和 None/Max 不回落
|
||
private static bool NeedsEnglishFallback(MultilingualType type)
|
||
{
|
||
return type != MultilingualType.None
|
||
&& type != MultilingualType.Max
|
||
&& type != MultilingualType.ZH
|
||
&& type != MultilingualType.TDZH
|
||
&& type != MultilingualType.EN
|
||
&& type != MultilingualType.JP
|
||
&& type != MultilingualType.KR;
|
||
}
|
||
|
||
// 是否需要回落中文:除 ZH 自身外都需要(包括 EN 自己 — EN 没翻译时也兜底中文)
|
||
// ZH/None/Max 不 fallback,避免无意义查询
|
||
private static bool NeedsChineseFallback(MultilingualType type)
|
||
{
|
||
return type != MultilingualType.None
|
||
&& type != MultilingualType.Max
|
||
&& type != MultilingualType.ZH;
|
||
}
|
||
|
||
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,
|
||
MultilingualType.Custom => group.ENFont, // Custom 语种回退到 EN 字体
|
||
_ => 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 = item.GetStrByType(type);
|
||
if (string.IsNullOrEmpty(ret)) return ret;
|
||
|
||
ret = ResolveEmbeddedStrings(ret, type);
|
||
return ret;
|
||
}
|
||
|
||
// 获取字符串中嵌套的 ID 列表 (按对应语种的顺序)
|
||
public List<uint> GetSubStringIdRunning(uint id, MultilingualType type)
|
||
{
|
||
var result = new List<uint>();
|
||
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 $"<sprite name=\"{subItem.Icon}\"><color={hex}>{str}</color>";
|
||
return $"<color={hex}>{str}</color>";
|
||
});
|
||
|
||
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 string Custom; // 自定义语种(仅供 Mod 应用使用)
|
||
public bool IsProperNoun;
|
||
public bool IsDialogue;
|
||
public string DialogueSpeaker;
|
||
public bool IsDeprecated;
|
||
public bool IsCustom;
|
||
public bool IsSpecialTerm;
|
||
// 次要文案:版本说明 / 地理科普等不重要文本,导出时可整体排除以减少送翻量
|
||
public bool IsSecondary;
|
||
// 默认橘色
|
||
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;
|
||
Custom = Custom?.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,
|
||
MultilingualType.Custom => Custom,
|
||
_ => 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),
|
||
MultilingualType.Custom => !string.IsNullOrEmpty(Custom),
|
||
_ => 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;
|
||
}
|
||
} |