refactor(localization): 迁移本地化为 CSV 驱动并支持多语言角色名
This commit is contained in:
@@ -36,8 +36,6 @@ namespace AibisDream.Utility
|
||||
|
||||
public const string UITextTable = "UIText";
|
||||
|
||||
public const string ActorNameTable = "ActorName";
|
||||
|
||||
#endregion
|
||||
|
||||
#region 资产
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.Framework;
|
||||
|
||||
namespace AibisDream.Kit
|
||||
{
|
||||
@@ -6,6 +6,8 @@ namespace AibisDream.Kit
|
||||
{
|
||||
public string Key { get; set; }
|
||||
public string Cn { get; set; }
|
||||
public string En { get; set; }
|
||||
public string Ja { get; set; }
|
||||
public string NameColor { get; set; }
|
||||
public string HeadPicPath { get; set; }
|
||||
public string VoicePath { get; set; }
|
||||
@@ -22,6 +24,8 @@ namespace AibisDream.Kit
|
||||
CharacterVo vo;
|
||||
vo.key = Key;
|
||||
vo.cn = Cn;
|
||||
vo.en = En;
|
||||
vo.ja = Ja;
|
||||
vo.nameColor = NameColor;
|
||||
vo.headPicPath = HeadPicPath;
|
||||
vo.voicePath = VoicePath;
|
||||
@@ -37,6 +41,8 @@ namespace AibisDream.Kit
|
||||
{
|
||||
public string key;
|
||||
public string cn;
|
||||
public string en;
|
||||
public string ja;
|
||||
public string nameColor;
|
||||
public string headPicPath;
|
||||
public string voicePath;
|
||||
@@ -58,14 +64,27 @@ namespace AibisDream.Kit
|
||||
}
|
||||
else
|
||||
{
|
||||
// 人名能找到
|
||||
var entry = LocalizationKit.ActorTable[key];
|
||||
rawName = entry == null ? key : entry.LocalizedValue;
|
||||
rawName = GetLocalizedNameByKind(LocalizationKit.CurrentLocaleKind);
|
||||
}
|
||||
|
||||
return string.IsNullOrEmpty(nameColor) ? rawName : $"<color={nameColor}>{rawName}</color>";
|
||||
}
|
||||
|
||||
private string GetLocalizedNameByKind(LocaleKind kind)
|
||||
{
|
||||
switch (kind)
|
||||
{
|
||||
case LocaleKind.En:
|
||||
return string.IsNullOrWhiteSpace(en) ? key : en;
|
||||
case LocaleKind.Ja:
|
||||
if (!string.IsNullOrWhiteSpace(ja)) return ja;
|
||||
return string.IsNullOrWhiteSpace(cn) ? key : cn;
|
||||
case LocaleKind.Cn:
|
||||
default:
|
||||
return string.IsNullOrWhiteSpace(cn) ? key : cn;
|
||||
}
|
||||
}
|
||||
|
||||
public string VoicePath
|
||||
{
|
||||
get
|
||||
|
||||
@@ -13,8 +13,10 @@ namespace AibisDream.Kit
|
||||
public class ConfigUtil : SingletonBase<ConfigUtil>
|
||||
{
|
||||
private const string CharacterConfigPath = "/Config/character.csv";
|
||||
private const string ParamConfigPath = "/Config/params.csv";
|
||||
|
||||
private Dictionary<string, Character> _characters;
|
||||
private Dictionary<string, Param> _params;
|
||||
|
||||
/// <summary>
|
||||
/// 用于单例模式
|
||||
@@ -39,12 +41,23 @@ namespace AibisDream.Kit
|
||||
public override void OnSingletonInit()
|
||||
{
|
||||
InitCharacterConfig();
|
||||
InitParamConfig();
|
||||
}
|
||||
|
||||
private void InitCharacterConfig()
|
||||
{
|
||||
var characterList = CsvUtil.ReadAsBean<Character>(Application.streamingAssetsPath + CharacterConfigPath);
|
||||
_characters = characterList.ToDictionary(item => item.Key, item => item);
|
||||
_characters = characterList
|
||||
.Where(item => !string.IsNullOrWhiteSpace(item.Key))
|
||||
.ToDictionary(item => item.Key, item => item);
|
||||
}
|
||||
|
||||
private void InitParamConfig()
|
||||
{
|
||||
var paramList = CsvUtil.ReadAsBean<Param>(Application.streamingAssetsPath + ParamConfigPath);
|
||||
_params = paramList
|
||||
.Where(item => !string.IsNullOrWhiteSpace(item.Key))
|
||||
.ToDictionary(item => item.Key, item => item);
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +72,17 @@ namespace AibisDream.Kit
|
||||
return _characters.TryGetValue(key, out character);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按key获取参数本地化配置
|
||||
/// </summary>
|
||||
/// <param name="key">key</param>
|
||||
/// <param name="param">参数配置</param>
|
||||
/// <returns>是否能获取</returns>
|
||||
public bool TryGetParam(string key, out Param param)
|
||||
{
|
||||
return _params.TryGetValue(key, out param);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按类型读取Json配置
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
using AibisDream.Framework;
|
||||
|
||||
namespace AibisDream.Kit
|
||||
{
|
||||
public class Param
|
||||
{
|
||||
public string Key { get; set; }
|
||||
public string Cn { get; set; }
|
||||
public string En { get; set; }
|
||||
public string Ja { get; set; }
|
||||
|
||||
public string LocalizeByKind(LocaleKind kind)
|
||||
{
|
||||
switch (kind)
|
||||
{
|
||||
case LocaleKind.En:
|
||||
return string.IsNullOrWhiteSpace(En) ? Key : En;
|
||||
case LocaleKind.Ja:
|
||||
if (!string.IsNullOrWhiteSpace(Ja)) return Ja;
|
||||
return string.IsNullOrWhiteSpace(Cn) ? Key : Cn;
|
||||
case LocaleKind.Cn:
|
||||
default:
|
||||
return string.IsNullOrWhiteSpace(Cn) ? Key : Cn;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c12e0d2c486f4ea6a5d4f70878b6e7c1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,59 +1,83 @@
|
||||
using UnityEngine;
|
||||
using AibisDream.Kit;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Localization;
|
||||
using UnityEngine.Localization.Settings;
|
||||
using UnityEngine.Localization.Tables;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace AibisDream.Framework
|
||||
{
|
||||
/// <summary>
|
||||
/// csv 本地化列类别。集中声明"哪些 Locale 归到哪一列",csv 数据类按此枚举取字段。
|
||||
/// </summary>
|
||||
public enum LocaleKind
|
||||
{
|
||||
Cn,
|
||||
En,
|
||||
Ja
|
||||
}
|
||||
|
||||
public static class LocalizationKit
|
||||
{
|
||||
private const string LocalizationPrefix = "l10n.";
|
||||
private const string ActorTableName = "ActorName";
|
||||
private const string ParamTableName = "Params";
|
||||
|
||||
private static LocalizationTable _paramTable;
|
||||
|
||||
public static LocalizationTable ActorTable { get; private set; }
|
||||
|
||||
public static string CurrentLanguageCode { get; private set; } = "zh-Hans";
|
||||
public static Locale CurrentLocale => LocalizationSettings.SelectedLocale;
|
||||
|
||||
/// <summary>
|
||||
/// 把 LocaleCode 映射到 csv 列类别。集中在此一处,便于扩展(加新语言时修改这里 + LocaleKind 枚举 + 数据类字段)。
|
||||
/// </summary>
|
||||
public static LocaleKind GetLocaleKind(string localeCode)
|
||||
{
|
||||
if (string.IsNullOrEmpty(localeCode)) return LocaleKind.Cn;
|
||||
if (localeCode.StartsWith("en")) return LocaleKind.En;
|
||||
if (localeCode.StartsWith("ja")) return LocaleKind.Ja;
|
||||
return LocaleKind.Cn;
|
||||
}
|
||||
|
||||
public static LocaleKind CurrentLocaleKind => GetLocaleKind(CurrentLanguageCode);
|
||||
|
||||
static LocalizationKit()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
EditorApplication.playModeStateChanged += OnPlayModeChanged;
|
||||
#else
|
||||
_paramTable = new LocalizationTable(ParamTableName);
|
||||
ActorTable = new LocalizationTable(ActorTableName);
|
||||
LocalizationSettings.SelectedLocaleChanged += OnLocaleChanged;
|
||||
RefreshCurrentLanguageCodeAsync();
|
||||
#endif
|
||||
}
|
||||
|
||||
private static void OnLocaleChanged(Locale locale)
|
||||
{
|
||||
UpdateCurrentLanguageCode(locale);
|
||||
EnumEventSystem.Global.Send(SettingChangeEvent.LocaleChanged, locale);
|
||||
}
|
||||
|
||||
private static void UpdateCurrentLanguageCode(Locale locale)
|
||||
{
|
||||
if (locale != null)
|
||||
{
|
||||
CurrentLanguageCode = locale.Identifier.Code;
|
||||
}
|
||||
}
|
||||
|
||||
private static async void RefreshCurrentLanguageCodeAsync()
|
||||
{
|
||||
await LocalizationSettings.InitializationOperation.Task;
|
||||
UpdateCurrentLanguageCode(LocalizationSettings.SelectedLocale);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private static void OnPlayModeChanged(PlayModeStateChange playMode)
|
||||
{
|
||||
if (playMode == PlayModeStateChange.EnteredPlayMode)
|
||||
{
|
||||
// 进入游戏模式的时候
|
||||
_paramTable?.Dispose();
|
||||
ActorTable?.Dispose();
|
||||
_paramTable = new LocalizationTable(ParamTableName);
|
||||
ActorTable = new LocalizationTable(ActorTableName);
|
||||
LocalizationSettings.SelectedLocaleChanged += OnLocaleChanged;
|
||||
RefreshCurrentLanguageCodeAsync();
|
||||
}
|
||||
else if (playMode == PlayModeStateChange.ExitingPlayMode)
|
||||
{
|
||||
_paramTable?.Dispose();
|
||||
ActorTable?.Dispose();
|
||||
_paramTable = null;
|
||||
ActorTable = null;
|
||||
LocalizationSettings.SelectedLocaleChanged -= OnLocaleChanged;
|
||||
}
|
||||
}
|
||||
@@ -81,33 +105,22 @@ namespace AibisDream.Framework
|
||||
/// 本地化参数
|
||||
/// </summary>
|
||||
/// <param name="param">参数名</param>
|
||||
/// <param name="table">本地化表</param>
|
||||
/// <returns></returns>
|
||||
public static string LocalizeParam(string param, LocalizationTable table)
|
||||
{
|
||||
// 非本地化参数直接返回
|
||||
if (!param.StartsWith(LocalizationPrefix)) return param;
|
||||
|
||||
var localizationKey = param[LocalizationPrefix.Length..];
|
||||
// 获取本地化值
|
||||
if (table.TryGetValue(localizationKey, out var value))
|
||||
{
|
||||
return value.LocalizedValue;
|
||||
}
|
||||
|
||||
// 如果找不到,则使用原始值
|
||||
Debug.LogWarning($"Variable Key '{localizationKey}' not found in localization table.");
|
||||
return localizationKey;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 本地化参数
|
||||
/// </summary>
|
||||
/// <param name="param"></param>
|
||||
/// <returns></returns>
|
||||
public static string LocalizeParam(string param)
|
||||
{
|
||||
return LocalizeParam(param, _paramTable);
|
||||
// 非本地化参数直接返回
|
||||
if (string.IsNullOrEmpty(param)) return param;
|
||||
if (!param.StartsWith(LocalizationPrefix)) return param;
|
||||
|
||||
var localizationKey = param[LocalizationPrefix.Length..];
|
||||
if (ConfigUtil.Instance.TryGetParam(localizationKey, out var value))
|
||||
{
|
||||
return value.LocalizeByKind(CurrentLocaleKind);
|
||||
}
|
||||
|
||||
// 如果找不到,则使用原始值
|
||||
Debug.LogWarning($"Variable Key '{localizationKey}' not found in params csv.");
|
||||
return localizationKey;
|
||||
}
|
||||
|
||||
public static string GetL10NParamKey(string key)
|
||||
@@ -125,64 +138,4 @@ namespace AibisDream.Framework
|
||||
await LocalizationSettings.InitializationOperation.Task;
|
||||
}
|
||||
}
|
||||
|
||||
public class LocalizationTable
|
||||
{
|
||||
public string TableName { get; }
|
||||
|
||||
private StringTable _stringTable;
|
||||
private bool _isInitialized;
|
||||
|
||||
public LocalizationTable(string tableName)
|
||||
{
|
||||
TableName = tableName;
|
||||
Debug.Log($"开始初始化表格{tableName}");
|
||||
InitializeAsync();
|
||||
}
|
||||
|
||||
private async void InitializeAsync()
|
||||
{
|
||||
// 等待本地化系统初始化完成
|
||||
await LocalizationSettings.InitializationOperation.Task;
|
||||
|
||||
_stringTable = LocalizationSettings.StringDatabase.GetTable(TableName);
|
||||
LocalizationSettings.SelectedLocaleChanged += OnLocaleChanged;
|
||||
_isInitialized = true;
|
||||
|
||||
Debug.Log($"表格{TableName}已成功加载");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isInitialized)
|
||||
{
|
||||
LocalizationSettings.SelectedLocaleChanged -= OnLocaleChanged;
|
||||
}
|
||||
_stringTable = null;
|
||||
_isInitialized = false;
|
||||
}
|
||||
|
||||
private void OnLocaleChanged(Locale locale)
|
||||
{
|
||||
if (_isInitialized)
|
||||
{
|
||||
_stringTable = LocalizationSettings.StringDatabase.GetTable(TableName);
|
||||
}
|
||||
}
|
||||
|
||||
public StringTableEntry this[string key] => _isInitialized ? _stringTable?[key] : null;
|
||||
|
||||
public bool TryGetValue(string key, out StringTableEntry value)
|
||||
{
|
||||
if (_isInitialized && _stringTable != null)
|
||||
{
|
||||
value = _stringTable.GetEntry(key);
|
||||
return value != null;
|
||||
}
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public LocaleIdentifier? LocaleIdentifier => _isInitialized ? _stringTable?.LocaleIdentifier : null;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
通过key索引配置,中文名,,"显示位置,Default使用默认,Screen插线屏幕,OldBox基本对话框",人物角色(Player/MainActor/Aside),名称显示时的颜色,头像Pic路径,语音资源路径,气泡序号(默认0)
|
||||
Key,Cn,,DialogViewType,Role,NameColor,HeadPicPath,VoicePath,BubbleIdx
|
||||
通过key索引配置,中文名,英文名,"显示位置,Default使用默认,Screen插线屏幕,OldBox基本对话框",人物角色(Player/MainActor/Aside),名称显示时的颜色,头像Pic路径,语音资源路径,气泡序号(默认0)
|
||||
Key,Cn,En,DialogViewType,Role,NameColor,HeadPicPath,VoicePath,BubbleIdx
|
||||
jg,警告,Warning,Default,MainActor,#7ae622,,,
|
||||
门,门,Door,Default,MainActor,#7ae622,,,
|
||||
矩形,黑暗,Darkness,Default,MainActor,#7ae622,,,
|
||||
@@ -10,45 +10,45 @@ peipei,佩佩,Peipei,Default,MainActor,#00FFFF,peipei_head,VO/voice_PP,
|
||||
女孩,女孩,Girl,Default,MainActor,,,VO/voice_JJ,
|
||||
pp,佩佩,Peipei,Default,MainActor,#00FFFF,peipei_head,VO/voice_PP,
|
||||
ppm,佩佩,Peipei,Default,Aside,#00FFFF,peipei_head,VO/voice_PP,
|
||||
me,路斯,Me,Default,Player,,me_head,VO/voice_ME,
|
||||
bb,BB机,beeper,Default,Player,,me_head,,
|
||||
me,路斯,Luth,Default,Player,,me_head,VO/voice_ME,
|
||||
bb,BB机,Beeper,Default,Player,,me_head,,
|
||||
jj,姐姐,Sister,Default,MainActor,,me_head,VO/voice_JJ,
|
||||
jj?,柔和女声,???,Default,MainActor,,me_head,VO/voice_JJ,
|
||||
hs,火山,Huoshan,Default,MainActor,#FFA800,peipei_head,VO/voice_HS,
|
||||
hs,火山,Cal,Default,MainActor,#FFA800,peipei_head,VO/voice_HS,
|
||||
hs?,沙哑男声,???,Default,MainActor,#FFA800,peipei_head,VO/voice_HS,
|
||||
yl,英理,Yingli,Default,MainActor,#4000FF,peipei_head,VO/voice_YL,
|
||||
yl,英理,Ingrid,Default,MainActor,#4000FF,peipei_head,VO/voice_YL,
|
||||
ql?,02号调酒机体,Bartender,Default,MainActor,#00FFFF,peipei_head,VO/voice_YL,
|
||||
ql,奇琳,Kirin,Default,MainActor,#00FFFF,peipei_head,VO/voice_YL,
|
||||
ql,琪琳,Kilin,Default,MainActor,#00FFFF,peipei_head,VO/voice_YL,
|
||||
peipei_novoice,胆怯女声,Timid female voice,Default,Aside,#00FFFF,,VO/voice_PP,2
|
||||
yingli_novoice,英里,Stern female voice,Default,Aside,#FFCC00,,VO/voice_YL,1
|
||||
tutorial,教学,Teaching,Default,Aside,,,,
|
||||
yingli_novoice,严厉女声,Stern female voice,Default,Aside,#FFCC00,,VO/voice_YL,1
|
||||
tutorial,教学,Tutorial,Default,Aside,,,,
|
||||
docter,儿科医生,Pediatrician,Default,Aside,#FFCC00,,,
|
||||
peipei_child,童年佩佩,Childhood Peipei,Default,Aside,#00FFFF,,VO/voice_PP,
|
||||
unknown,???,???,Default,Aside,#FFCC00,,,
|
||||
shitou,石头,Stone,Default,MainActor,,,VO/voice_shitou,
|
||||
Descent,Descent,Descent,Default,Aside,#FFCC00,,VO/voice_me,
|
||||
e-assistant,电子助手,Electronic assistant,Default,Aside,#FFCC00,,VO/voice_me,
|
||||
e-assistant,电子助手,Electronic Assistant,Default,Aside,#FFCC00,,VO/voice_me,
|
||||
Gotashi_unknow,戴纯金雕塑头的男人,Man with a pure gold sculpture head,Default,Aside,#FFCC00,,,
|
||||
Linking,连接中……,Connecting...,Screen,Aside,#E6A23C,,,
|
||||
Analysing,分析中,Analyzing...,Screen,Aside,#E6A23C,,,
|
||||
Error,错误警告,Error warning,Screen,Aside,#E6A23C,,,
|
||||
UfModule,UnstableFusion<br>图像生成模块,UnstableFusion image generation module,Screen,Aside,#F56C6C,,,
|
||||
EyeModule,HS9型<br>视觉模块,HS9 type visual module,Screen,Aside,#F56C6C,,,
|
||||
EmoModule,通用型<br>情绪发生模块,Universal emotion generation module,Screen,Aside,#F56C6C,,,
|
||||
MemModule,通用型<br>记忆模块,Universal memory module,Screen,Aside,#F56C6C,,,
|
||||
LogicModule,MK1型<br>逻辑处理模块,MK1 type logic processing module,Screen,Aside,#F56C6C,,,
|
||||
SpeakerModule,MK1型<br>表达模块,MK1 type expression module,Screen,Aside,#F56C6C,,,
|
||||
UfModule,Unstable Fusion图像生成模块,Unstable Fusion Module,Screen,Aside,#F56C6C,,,
|
||||
EyeModule,HS9型视觉模块,HS9 Visual Module,Screen,Aside,#F56C6C,,,
|
||||
EmoModule,通用型情绪发生模块,Universal Emotion Generation Module,Screen,Aside,#F56C6C,,,
|
||||
MemModule,通用型记忆模块,Universal Memory Module,Screen,Aside,#F56C6C,,,
|
||||
LogicModule,MK1型逻辑处理模块,MK1 logic Processing Module,Screen,Aside,#F56C6C,,,
|
||||
SpeakerModule,MK1型表达模块,MK1 Expression Module,Screen,Aside,#F56C6C,,,
|
||||
SaleModule,销售型<br>销售语言处理模块,Sales type sales language processing module,Screen,Aside,#F56C6C,,,
|
||||
ChipModule,4R7型<br>色觉晶圆训练槽,4R7 type color perception wafer training slot,Screen,Aside,#F56C6C,,,
|
||||
ChipModule,4R7型色觉晶圆训练槽,4R7 Color Perception Wafer Training Slot,Screen,Aside,#F56C6C,,,
|
||||
HivemindModule,未知型号<br>Hivemind通讯终端,Unknown type Hivemind communication terminal,Screen,Aside,#F56C6C,,,
|
||||
HeartModule,C03UR型特配<br>涡轮增压动力模块,C03UR special turbocharged power module,Screen,Aside,#E6A23C,,,
|
||||
LungModule,MK1型<br>气体处理模块,MK1 type gas processing module,Screen,Aside,#E6A23C,,,
|
||||
LiverModule,MK1型<br>冷却液循环模块,MK1 type coolant circulation module,Screen,Aside,#E6A23C,,,
|
||||
DigestModule,MK1型<br>物料处理模块,MK1 type material processing module,Screen,Aside,#E6A23C,,,
|
||||
HeartModule,C03UR型特配涡轮增压动力模块,C03UR Special Turbocharged Power Module,Screen,Aside,#E6A23C,,,
|
||||
LungModule,MK1型气体处理模块,MK1 Gas Processing Module,Screen,Aside,#E6A23C,,,
|
||||
LiverModule,MK1型冷却液循环模块,MK1 Coolant Circulation Module,Screen,Aside,#E6A23C,,,
|
||||
DigestModule,MK1型物料处理模块,MK1 Material Processing Module,Screen,Aside,#E6A23C,,,
|
||||
CenterText,,,CenterText,Aside,,,,
|
||||
CenterOption,,,CenterOption,Aside,,,,
|
||||
Task,,,Task,Aside,,,,
|
||||
TempText,,,Default,Aside,,,,
|
||||
gotash?,,,Default,MainActor,,,VO/voice_GTS_broken,
|
||||
classmate,,,Default,Aside,,,,
|
||||
gotash?,???,???,Default,MainActor,,,VO/voice_GTS_broken,
|
||||
classmate,同学,Classmate,Default,Aside,,,,
|
||||
jb,酒保,Bartender,Default,MainActor,,,,
|
||||
|
||||
|
@@ -0,0 +1,9 @@
|
||||
键,中文,英文,日文
|
||||
Key,Cn,En,Ja
|
||||
天空,天空,Sky,
|
||||
海报,海报,Poster,
|
||||
植物,植物,Plant,
|
||||
朋友,朋友,Friend,
|
||||
情绪,情绪,Emo,
|
||||
记忆,记忆,Memory,
|
||||
逻辑,逻辑,Logic,
|
||||
|
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e721272cd05718a4d8872d0f3e3f5b8c
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user