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 IsLoop;
|
||||||
public bool IsRunning;
|
public bool IsRunning;
|
||||||
public bool IsUnscaled;
|
public bool IsUnscaled;
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
|
||||||
public float CreationTime;
|
public float CreationTime;
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal interface ITimerServiceDebugView
|
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);
|
void GetStatistics(out int activeCount, out int poolCapacity, out int peakActiveCount, out int freeCount);
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
|
||||||
int GetStaleOneShotTimers(TimerDebugInfo[] results);
|
int GetStaleOneShotTimers(TimerDebugInfo[] results);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user