com.alicizax.unity.framework/Editor/Localization/LocalizationSettingsProvider.cs
2025-12-26 18:08:15 +08:00

399 lines
15 KiB
C#

using System.Collections.Generic;
using System.Text;
using AlicizaX.Editor;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using UnityEditorInternal;
namespace AlicizaX.Localization.Editor
{
internal class LocalizationSettingsProvider : UnityEditor.SettingsProvider
{
private SerializedObject _serializedObject;
private SerializedProperty _languageTypes;
private ReorderableList _languageList;
private SerializedProperty _genLangaugeTypePath;
private SerializedProperty generateScriptCodeFirstConfig;
private List<string> popConfig = new List<string>();
// Track original state for comparison
private List<string> _originalLanguages = new List<string>();
private bool _hasUnsavedChanges = false;
public LocalizationSettingsProvider() : base("Project/Localization Settings", SettingsScope.Project)
{
}
public override void OnActivate(string searchContext, VisualElement rootElement)
{
InitGUI();
}
private void InitGUI()
{
var setting = LocalizationConfiguration.Instance;
_serializedObject?.Dispose();
_serializedObject = new SerializedObject(setting);
_languageTypes = _serializedObject.FindProperty("LanguageTypes");
_genLangaugeTypePath = _serializedObject.FindProperty("_genLangaugeTypePath");
generateScriptCodeFirstConfig = _serializedObject.FindProperty("generateScriptCodeFirstConfig");
// Store original language list
_originalLanguages.Clear();
for (int i = 0; i < _languageTypes.arraySize; i++)
{
_originalLanguages.Add(_languageTypes.GetArrayElementAtIndex(i).stringValue);
}
// 自定义 ReorderableList
_languageList = new ReorderableList(_serializedObject, _languageTypes,
draggable: false,
displayHeader: true,
displayAddButton: true,
displayRemoveButton: true);
_languageList.drawHeaderCallback = rect => { EditorGUI.LabelField(rect, "Language Types"); };
_languageList.drawElementCallback = (rect, index, isActive, isFocused) =>
{
var element = _languageTypes.GetArrayElementAtIndex(index);
rect.y += 2;
if (index < 2) // 前两个(中文、英文)禁止修改
{
EditorGUI.BeginDisabledGroup(true);
EditorGUI.TextField(
new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight),
element.stringValue);
EditorGUI.EndDisabledGroup();
}
else
{
EditorGUI.BeginChangeCheck();
string newValue = EditorGUI.TextField(
new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight),
element.stringValue);
if (EditorGUI.EndChangeCheck())
{
element.stringValue = newValue;
_hasUnsavedChanges = true;
}
}
};
// 禁止删除前两个
_languageList.onCanRemoveCallback = list =>
{
return list.index >= 2; // 只有索引 >=2 的项才能删除
};
// Hook for when a language is added
_languageList.onAddCallback = list =>
{
int newIndex = _languageTypes.arraySize;
_languageTypes.InsertArrayElementAtIndex(newIndex);
var newElement = _languageTypes.GetArrayElementAtIndex(newIndex);
newElement.stringValue = "NewLanguage";
_hasUnsavedChanges = true;
};
// Hook for when a language is removed
_languageList.onRemoveCallback = list =>
{
if (list.index < 2)
{
EditorUtility.DisplayDialog("Cannot Remove", "The first two languages (ChineseSimplified and English) cannot be removed.", "OK");
return;
}
// Just remove from the list, no immediate sync
_languageTypes.DeleteArrayElementAtIndex(list.index);
_hasUnsavedChanges = true;
};
popConfig.Clear();
if (setting.LanguageTypeNames.Count > 0)
{
foreach (var lang in setting.LanguageTypeNames)
{
string name = lang.Or("Unknown");
popConfig.Add(name);
}
}
}
public override void OnGUI(string searchContext)
{
if (_serializedObject == null || !_serializedObject.targetObject)
{
InitGUI();
}
_serializedObject.Update();
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(_genLangaugeTypePath);
EditorDrawing.DrawStringSelectPopup(new GUIContent("Gen Lang"), new GUIContent("None"), popConfig.ToArray(), generateScriptCodeFirstConfig.stringValue,
(e) =>
{
generateScriptCodeFirstConfig.stringValue = e;
_serializedObject.ApplyModifiedProperties();
LocalizationConfiguration.Save();
});
if (GUILayout.Button("Generate Language Types"))
{
RegenerateLanguageTypes();
}
_languageList.DoLayoutList();
if (EditorGUI.EndChangeCheck())
{
_serializedObject.ApplyModifiedProperties();
LocalizationConfiguration.Save();
}
// Add Save button
EditorGUILayout.Space(10);
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
using (new EditorGUI.DisabledGroupScope(!_hasUnsavedChanges))
{
if (GUILayout.Button("Save Language Changes", GUILayout.Width(200), GUILayout.Height(30)))
{
ApplyLanguageChanges();
}
}
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
if (_hasUnsavedChanges)
{
EditorGUILayout.HelpBox("You have unsaved language changes. Click 'Save Language Changes' to apply them to all GameLocalizationTable assets.", MessageType.Warning);
}
}
private void RegenerateLanguageTypes()
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("using System.Collections.Generic;");
sb.AppendLine("");
sb.AppendLine("/// <summary>");
sb.AppendLine("/// AutoGenerate");
sb.AppendLine("/// </summary>");
sb.AppendLine("public static class LanguageTypes");
sb.AppendLine("{");
for (int i = 0; i < LocalizationConfiguration.Instance.LanguageTypeNames.Count; i++)
{
sb.AppendLine($"\tpublic const string {LocalizationConfiguration.Instance.LanguageTypeNames[i]} = \"{LocalizationConfiguration.Instance.LanguageTypeNames[i]}\";");
}
sb.AppendLine("");
sb.AppendLine("\tpublic static readonly IReadOnlyList<string> Languages = new List<string>");
sb.AppendLine("\t{");
for (int i = 0; i < LocalizationConfiguration.Instance.LanguageTypeNames.Count; i++)
{
sb.AppendLine($"\t\t\"{LocalizationConfiguration.Instance.LanguageTypeNames[i]}\",");
}
sb.AppendLine("\t};");
sb.AppendLine("");
sb.AppendLine("\tpublic static string IndexToString(int index)");
sb.AppendLine("\t{");
sb.AppendLine("\t\tif (index < 0 || index >= Languages.Count) return \"Unknown\";");
sb.AppendLine("\t\treturn Languages[index];");
sb.AppendLine("\t}");
sb.AppendLine("");
sb.AppendLine("\tpublic static int StringToIndex(string s)");
sb.AppendLine("\t{");
sb.AppendLine("\t\tint index = -1;");
sb.AppendLine("\t\tfor (int i = 0; i < Languages.Count; i++)");
sb.AppendLine("\t\t{");
sb.AppendLine("\t\t\tif (Languages[i] == s)");
sb.AppendLine("\t\t\t{");
sb.AppendLine("\t\t\t\tindex = i;");
sb.AppendLine("\t\t\t\tbreak;");
sb.AppendLine("\t\t\t}");
sb.AppendLine("\t\t}");
sb.AppendLine("");
sb.AppendLine("\t\treturn index;");
sb.AppendLine("\t}");
sb.AppendLine("");
sb.AppendLine("}");
System.IO.File.WriteAllText(_genLangaugeTypePath.stringValue, sb.ToString());
AssetDatabase.Refresh();
}
public override void OnDeactivate()
{
base.OnDeactivate();
LocalizationConfiguration.Save();
}
private void ApplyLanguageChanges()
{
// Get current language list
List<string> currentLanguages = new List<string>();
for (int i = 0; i < _languageTypes.arraySize; i++)
{
currentLanguages.Add(_languageTypes.GetArrayElementAtIndex(i).stringValue);
}
// Detect changes
List<string> addedLanguages = new List<string>();
List<string> removedLanguages = new List<string>();
Dictionary<string, string> renamedLanguages = new Dictionary<string, string>(); // old -> new
// Find added languages
foreach (var lang in currentLanguages)
{
if (!_originalLanguages.Contains(lang))
{
addedLanguages.Add(lang);
}
}
// Find removed languages
foreach (var lang in _originalLanguages)
{
if (!currentLanguages.Contains(lang))
{
removedLanguages.Add(lang);
}
}
// Detect renames (same index, different name)
for (int i = 0; i < Mathf.Min(_originalLanguages.Count, currentLanguages.Count); i++)
{
if (_originalLanguages[i] != currentLanguages[i])
{
// Check if this is a rename (not an add/remove)
if (!addedLanguages.Contains(currentLanguages[i]) && !removedLanguages.Contains(_originalLanguages[i]))
{
renamedLanguages[_originalLanguages[i]] = currentLanguages[i];
}
}
}
// Apply changes to all tables
string[] guids = AssetDatabase.FindAssets("t:GameLocaizationTable");
int tablesUpdated = 0;
foreach (string guid in guids)
{
string assetPath = AssetDatabase.GUIDToAssetPath(guid);
GameLocaizationTable table = AssetDatabase.LoadAssetAtPath<GameLocaizationTable>(assetPath);
if (table == null)
continue;
bool tableModified = false;
// Handle renames first
foreach (var rename in renamedLanguages)
{
LocalizationLanguage language = table.Languages.Find(lang => lang.LanguageName == rename.Key);
if (language != null)
{
language.LanguageName = rename.Value;
language.name = rename.Value;
tableModified = true;
}
}
// Handle additions
foreach (var newLang in addedLanguages)
{
// Skip if already exists
if (table.Languages.Exists(lang => lang.LanguageName == newLang))
continue;
// Create new LocalizationLanguage asset
LocalizationLanguage newLanguage = ScriptableObject.CreateInstance<LocalizationLanguage>();
newLanguage.name = newLang;
newLanguage.LanguageName = newLang;
newLanguage.Strings = new List<LocalizationLanguage.LocalizationString>();
// Synchronize keys from existing table structure
foreach (var section in table.TableSheet)
{
foreach (var item in section.SectionSheet)
{
string sectionKey = section.SectionName.Replace(" ", "");
string itemKey = item.Key.Replace(" ", "");
string fullKey = sectionKey + "." + itemKey;
newLanguage.Strings.Add(new LocalizationLanguage.LocalizationString
{
SectionId = section.Id,
EntryId = item.Id,
Key = fullKey,
Value = string.Empty
});
}
}
AssetDatabase.AddObjectToAsset(newLanguage, table);
table.Languages.Add(newLanguage);
tableModified = true;
}
// Handle removals
foreach (var removedLang in removedLanguages)
{
LocalizationLanguage languageToDelete = table.Languages.Find(lang => lang.LanguageName == removedLang);
if (languageToDelete != null)
{
table.Languages.Remove(languageToDelete);
UnityEngine.Object.DestroyImmediate(languageToDelete, true);
tableModified = true;
}
}
if (tableModified)
{
EditorUtility.SetDirty(table);
tablesUpdated++;
}
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
// Update original languages list
_originalLanguages.Clear();
_originalLanguages.AddRange(currentLanguages);
_hasUnsavedChanges = false;
// Log results
if (addedLanguages.Count > 0)
Debug.Log($"Added {addedLanguages.Count} language(s) to {tablesUpdated} table(s): {string.Join(", ", addedLanguages)}");
if (removedLanguages.Count > 0)
Debug.Log($"Removed {removedLanguages.Count} language(s) from {tablesUpdated} table(s): {string.Join(", ", removedLanguages)}");
if (renamedLanguages.Count > 0)
Debug.Log($"Renamed {renamedLanguages.Count} language(s) in {tablesUpdated} table(s)");
EditorUtility.DisplayDialog("Success", $"Language changes applied to {tablesUpdated} GameLocalizationTable(s).", "OK");
}
static LocalizationSettingsProvider s_provider;
[SettingsProvider]
public static SettingsProvider CreateMyCustomSettingsProvider()
{
if (s_provider == null)
{
s_provider = new LocalizationSettingsProvider();
}
return s_provider;
}
}
}