1
This commit is contained in:
parent
fcfa284f2c
commit
9acea7f00c
@ -1,485 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using AlicizaX.Localization.Runtime;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AlicizaX.Localization.Editor
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class LocalizationAiWriteRequest
|
||||
{
|
||||
public string TableAssetPath;
|
||||
public string SectionName = "AI";
|
||||
public string Key;
|
||||
public bool AllowUpdate = true;
|
||||
public bool AutoCreateSection = true;
|
||||
public bool AutoCreateLanguageAsset = true;
|
||||
public bool FillMissingLanguagesWithEmpty = true;
|
||||
public bool SaveAssets = true;
|
||||
public bool HotReloadWhenPlaying = true;
|
||||
public List<LocalizationAiTranslation> Translations = new();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class LocalizationAiTranslation
|
||||
{
|
||||
public string Language;
|
||||
public string Text;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class LocalizationAiWriteResult
|
||||
{
|
||||
public bool Success;
|
||||
public string TableAssetPath;
|
||||
public string SectionName;
|
||||
public string Key;
|
||||
public string RuntimeKey;
|
||||
public bool CreatedSection;
|
||||
public bool CreatedEntry;
|
||||
public bool HotReloaded;
|
||||
public List<string> UpdatedLanguages = new();
|
||||
public List<string> Warnings = new();
|
||||
public string Message;
|
||||
}
|
||||
|
||||
public static class LocalizationAiWriteTool
|
||||
{
|
||||
private const string DefaultSectionName = "AI";
|
||||
private const string LastSelectedTableKey = "LastSelectedGameLocaizationTable";
|
||||
|
||||
public static LocalizationAiWriteResult Execute(LocalizationAiWriteRequest request)
|
||||
{
|
||||
LocalizationAiWriteResult result = new();
|
||||
if (request == null)
|
||||
{
|
||||
result.Message = "Request is null.";
|
||||
return result;
|
||||
}
|
||||
|
||||
string tablePath = request.TableAssetPath;
|
||||
if (string.IsNullOrWhiteSpace(tablePath))
|
||||
{
|
||||
tablePath = EditorPrefs.GetString(LastSelectedTableKey, string.Empty);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(tablePath))
|
||||
{
|
||||
result.Message = "TableAssetPath is empty.";
|
||||
return result;
|
||||
}
|
||||
|
||||
GameLocaizationTable table = AssetDatabase.LoadAssetAtPath<GameLocaizationTable>(tablePath);
|
||||
if (table == null)
|
||||
{
|
||||
result.Message = $"Can not load GameLocaizationTable from path: {tablePath}";
|
||||
result.TableAssetPath = tablePath;
|
||||
return result;
|
||||
}
|
||||
|
||||
result.TableAssetPath = tablePath;
|
||||
|
||||
string sectionName = request.SectionName;
|
||||
string itemKey = request.Key;
|
||||
ParseAndNormalizeKey(ref sectionName, ref itemKey);
|
||||
|
||||
if (string.IsNullOrEmpty(sectionName))
|
||||
{
|
||||
sectionName = DefaultSectionName;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(itemKey))
|
||||
{
|
||||
result.Message = "Key is empty after normalization.";
|
||||
return result;
|
||||
}
|
||||
|
||||
if (table.TableSheet == null)
|
||||
{
|
||||
table.TableSheet = new List<GameLocaizationTable.TableData>();
|
||||
}
|
||||
|
||||
if (table.Languages == null)
|
||||
{
|
||||
table.Languages = new List<LocalizationLanguage>();
|
||||
}
|
||||
|
||||
int sectionIndex = FindSectionIndex(table, sectionName);
|
||||
if (sectionIndex < 0)
|
||||
{
|
||||
if (!request.AutoCreateSection)
|
||||
{
|
||||
result.Message = $"Section '{sectionName}' does not exist.";
|
||||
return result;
|
||||
}
|
||||
|
||||
GameLocaizationTable.TableData newSection = new(sectionName, GenerateUniqueSectionId(table));
|
||||
table.TableSheet.Add(newSection);
|
||||
sectionIndex = table.TableSheet.Count - 1;
|
||||
result.CreatedSection = true;
|
||||
}
|
||||
|
||||
GameLocaizationTable.TableData section = table.TableSheet[sectionIndex];
|
||||
if (section.SectionSheet == null)
|
||||
{
|
||||
section.SectionSheet = new List<GameLocaizationTable.SheetItem>();
|
||||
}
|
||||
|
||||
int entryIndex = FindEntryIndex(section, itemKey);
|
||||
bool createdEntry = false;
|
||||
if (entryIndex < 0)
|
||||
{
|
||||
GameLocaizationTable.SheetItem newItem = new(itemKey, GenerateUniqueEntryId(table), true);
|
||||
section.SectionSheet.Add(newItem);
|
||||
entryIndex = section.SectionSheet.Count - 1;
|
||||
createdEntry = true;
|
||||
result.CreatedEntry = true;
|
||||
}
|
||||
else if (!request.AllowUpdate)
|
||||
{
|
||||
result.Message = $"Key '{itemKey}' already exists in section '{sectionName}'.";
|
||||
return result;
|
||||
}
|
||||
|
||||
GameLocaizationTable.SheetItem entry = section.SectionSheet[entryIndex];
|
||||
string runtimeKey = BuildRuntimeKey(section.SectionName, entry.Key);
|
||||
|
||||
Dictionary<string, string> translationMap = BuildTranslationMap(request.Translations, result.Warnings);
|
||||
EnsureEntryExistsForAllLanguages(table, section, entry, runtimeKey, translationMap, request, result);
|
||||
|
||||
table.TableSheet[sectionIndex] = section;
|
||||
|
||||
table.InvalidateLanguageLookup();
|
||||
EditorUtility.SetDirty(table);
|
||||
if (request.SaveAssets)
|
||||
{
|
||||
AssetDatabase.SaveAssets();
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
|
||||
if (request.HotReloadWhenPlaying &&
|
||||
Application.isPlaying &&
|
||||
AppServices.TryGet<ILocalizationService>(out var localizationService))
|
||||
{
|
||||
localizationService.ReloadLocalizationConfig(table);
|
||||
result.HotReloaded = true;
|
||||
}
|
||||
|
||||
result.Success = true;
|
||||
result.SectionName = section.SectionName;
|
||||
result.Key = entry.Key;
|
||||
result.RuntimeKey = runtimeKey;
|
||||
result.Message = createdEntry
|
||||
? $"Added localization entry '{runtimeKey}'."
|
||||
: $"Updated localization entry '{runtimeKey}'.";
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string ExecuteJson(string json)
|
||||
{
|
||||
LocalizationAiWriteRequest request = Utility.Json.ToObject<LocalizationAiWriteRequest>(json);
|
||||
LocalizationAiWriteResult result = Execute(request);
|
||||
return Utility.Json.ToJson(result);
|
||||
}
|
||||
|
||||
private static void EnsureEntryExistsForAllLanguages(
|
||||
GameLocaizationTable table,
|
||||
GameLocaizationTable.TableData section,
|
||||
GameLocaizationTable.SheetItem entry,
|
||||
string runtimeKey,
|
||||
Dictionary<string, string> translationMap,
|
||||
LocalizationAiWriteRequest request,
|
||||
LocalizationAiWriteResult result)
|
||||
{
|
||||
for (int i = 0; i < table.Languages.Count; i++)
|
||||
{
|
||||
LocalizationLanguage language = table.Languages[i];
|
||||
if (language == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
UpsertLanguageEntry(
|
||||
language,
|
||||
section.Id,
|
||||
entry.Id,
|
||||
runtimeKey,
|
||||
translationMap.TryGetValue(language.LanguageName, out string text) ? text : string.Empty,
|
||||
translationMap.ContainsKey(language.LanguageName) || request.FillMissingLanguagesWithEmpty);
|
||||
|
||||
EditorUtility.SetDirty(language);
|
||||
result.UpdatedLanguages.Add(language.LanguageName);
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<string, string> pair in translationMap)
|
||||
{
|
||||
if (HasLanguage(table, pair.Key))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!request.AutoCreateLanguageAsset)
|
||||
{
|
||||
result.Warnings.Add($"Language asset '{pair.Key}' does not exist in table '{table.name}'.");
|
||||
continue;
|
||||
}
|
||||
|
||||
LocalizationLanguage language = CreateLanguageAsset(table, pair.Key);
|
||||
UpsertLanguageEntry(language, section.Id, entry.Id, runtimeKey, pair.Value, true);
|
||||
EditorUtility.SetDirty(language);
|
||||
result.UpdatedLanguages.Add(language.LanguageName);
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpsertLanguageEntry(
|
||||
LocalizationLanguage language,
|
||||
int sectionId,
|
||||
int entryId,
|
||||
string runtimeKey,
|
||||
string value,
|
||||
bool shouldWrite)
|
||||
{
|
||||
if (language.Strings == null)
|
||||
{
|
||||
language.Strings = new List<LocalizationLanguage.LocalizationString>();
|
||||
}
|
||||
|
||||
int index = FindLanguageStringIndex(language, sectionId, entryId);
|
||||
if (index >= 0)
|
||||
{
|
||||
LocalizationLanguage.LocalizationString item = language.Strings[index];
|
||||
item.Key = runtimeKey;
|
||||
if (shouldWrite)
|
||||
{
|
||||
item.Value = value ?? string.Empty;
|
||||
}
|
||||
|
||||
language.Strings[index] = item;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!shouldWrite)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
language.Strings.Add(new LocalizationLanguage.LocalizationString
|
||||
{
|
||||
SectionId = sectionId,
|
||||
EntryId = entryId,
|
||||
Key = runtimeKey,
|
||||
Value = value ?? string.Empty
|
||||
});
|
||||
}
|
||||
|
||||
private static LocalizationLanguage CreateLanguageAsset(GameLocaizationTable table, string languageName)
|
||||
{
|
||||
LocalizationLanguage language = ScriptableObject.CreateInstance<LocalizationLanguage>();
|
||||
language.name = languageName;
|
||||
language.LanguageName = languageName;
|
||||
language.Strings = new List<LocalizationLanguage.LocalizationString>();
|
||||
AssetDatabase.AddObjectToAsset(language, table);
|
||||
table.Languages.Add(language);
|
||||
table.InvalidateLanguageLookup();
|
||||
EditorUtility.SetDirty(table);
|
||||
return language;
|
||||
}
|
||||
|
||||
private static Dictionary<string, string> BuildTranslationMap(
|
||||
List<LocalizationAiTranslation> translations,
|
||||
List<string> warnings)
|
||||
{
|
||||
Dictionary<string, string> map = new(StringComparer.Ordinal);
|
||||
if (translations == null)
|
||||
{
|
||||
return map;
|
||||
}
|
||||
|
||||
for (int i = 0; i < translations.Count; i++)
|
||||
{
|
||||
LocalizationAiTranslation translation = translations[i];
|
||||
if (translation == null || string.IsNullOrWhiteSpace(translation.Language))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string normalizedLanguage = NormalizePart(translation.Language);
|
||||
if (string.IsNullOrEmpty(normalizedLanguage))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (map.ContainsKey(normalizedLanguage))
|
||||
{
|
||||
warnings.Add($"Duplicate language '{normalizedLanguage}' found. Last value wins.");
|
||||
}
|
||||
|
||||
map[normalizedLanguage] = translation.Text ?? string.Empty;
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private static bool HasLanguage(GameLocaizationTable table, string languageName)
|
||||
{
|
||||
for (int i = 0; i < table.Languages.Count; i++)
|
||||
{
|
||||
LocalizationLanguage language = table.Languages[i];
|
||||
if (language != null && string.Equals(language.LanguageName, languageName, StringComparison.Ordinal))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int FindSectionIndex(GameLocaizationTable table, string sectionName)
|
||||
{
|
||||
for (int i = 0; i < table.TableSheet.Count; i++)
|
||||
{
|
||||
if (string.Equals(table.TableSheet[i].SectionName, sectionName, StringComparison.Ordinal))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static int FindEntryIndex(GameLocaizationTable.TableData section, string itemKey)
|
||||
{
|
||||
for (int i = 0; i < section.SectionSheet.Count; i++)
|
||||
{
|
||||
if (string.Equals(section.SectionSheet[i].Key, itemKey, StringComparison.Ordinal))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static int FindLanguageStringIndex(LocalizationLanguage language, int sectionId, int entryId)
|
||||
{
|
||||
for (int i = 0; i < language.Strings.Count; i++)
|
||||
{
|
||||
LocalizationLanguage.LocalizationString item = language.Strings[i];
|
||||
if (item.SectionId == sectionId && item.EntryId == entryId)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static int GenerateUniqueSectionId(GameLocaizationTable table)
|
||||
{
|
||||
return GenerateUniqueId(table, true);
|
||||
}
|
||||
|
||||
private static int GenerateUniqueEntryId(GameLocaizationTable table)
|
||||
{
|
||||
return GenerateUniqueId(table, false);
|
||||
}
|
||||
|
||||
private static int GenerateUniqueId(GameLocaizationTable table, bool checkSectionIds)
|
||||
{
|
||||
int candidate = Mathf.Abs(Guid.NewGuid().GetHashCode());
|
||||
while (candidate == 0 || ContainsId(table, candidate, checkSectionIds))
|
||||
{
|
||||
candidate = Mathf.Abs(Guid.NewGuid().GetHashCode());
|
||||
}
|
||||
|
||||
return candidate;
|
||||
}
|
||||
|
||||
private static bool ContainsId(GameLocaizationTable table, int id, bool checkSectionIds)
|
||||
{
|
||||
for (int i = 0; i < table.TableSheet.Count; i++)
|
||||
{
|
||||
GameLocaizationTable.TableData section = table.TableSheet[i];
|
||||
if (checkSectionIds && section.Id == id)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (section.SectionSheet == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int j = 0; j < section.SectionSheet.Count; j++)
|
||||
{
|
||||
if (!checkSectionIds && section.SectionSheet[j].Id == id)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string BuildRuntimeKey(string sectionName, string itemKey)
|
||||
{
|
||||
return string.Concat(sectionName, ".", itemKey);
|
||||
}
|
||||
|
||||
private static void ParseAndNormalizeKey(ref string sectionName, ref string itemKey)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(itemKey))
|
||||
{
|
||||
int separatorIndex = itemKey.IndexOf('.');
|
||||
if (separatorIndex > 0 && separatorIndex < itemKey.Length - 1)
|
||||
{
|
||||
sectionName = itemKey.Substring(0, separatorIndex);
|
||||
itemKey = itemKey.Substring(separatorIndex + 1);
|
||||
}
|
||||
}
|
||||
|
||||
sectionName = NormalizePart(sectionName);
|
||||
itemKey = NormalizePart(itemKey);
|
||||
}
|
||||
|
||||
private static string NormalizePart(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
StringBuilder builder = new(value.Length);
|
||||
for (int i = 0; i < value.Length; i++)
|
||||
{
|
||||
char current = value[i];
|
||||
if (char.IsLetterOrDigit(current) || current == '_')
|
||||
{
|
||||
builder.Append(current);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char.IsWhiteSpace(current) || current == '-' || current == '/')
|
||||
{
|
||||
builder.Append('_');
|
||||
}
|
||||
}
|
||||
|
||||
while (builder.Length > 0 && builder[0] == '_')
|
||||
{
|
||||
builder.Remove(0, 1);
|
||||
}
|
||||
|
||||
while (builder.Length > 0 && builder[builder.Length - 1] == '_')
|
||||
{
|
||||
builder.Remove(builder.Length - 1, 1);
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 511aa2d0442ccf746aa6dd266d5197d6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -8,10 +8,7 @@ namespace AlicizaX.Timer.Runtime
|
||||
public bool IsLoop;
|
||||
public bool IsRunning;
|
||||
public bool IsUnscaled;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public float CreationTime;
|
||||
#endif
|
||||
}
|
||||
|
||||
internal interface ITimerServiceDebugView
|
||||
@ -20,8 +17,6 @@ namespace AlicizaX.Timer.Runtime
|
||||
|
||||
void GetStatistics(out int activeCount, out int poolCapacity, out int peakActiveCount, out int freeCount);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
int GetStaleOneShotTimers(TimerDebugInfo[] results);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user