com.alicizax.unity.framework/Editor/Localization/LocalizationSettingsProvider.cs
2026-04-17 14:21:46 +08:00

527 lines
20 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using AlicizaX.Editor;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
namespace AlicizaX.Localization.Editor
{
internal sealed class LocalizationSettingsProvider : EditorWindow
{
private const string WindowTitle = "Localization Settings";
private const string MenuPath = "AlicizaX/Localization/Open Localization Settings";
private SerializedObject _serializedObject;
private SerializedProperty _languageTypes;
private SerializedProperty _genLangaugeTypePath;
private SerializedProperty _generateScriptCodeFirstConfig;
private SerializedProperty _generateLanguageTypesNamespace;
private SerializedProperty _generateLanguageTypesTemplate;
private ReorderableList _languageList;
private readonly List<string> _languagePopupOptions = new();
private readonly List<string> _originalLanguages = new();
private readonly List<GameLocaizationTable> _localizationTables = new();
private Vector2 _scrollPosition;
private bool _hasUnsavedChanges;
[MenuItem(MenuPath)]
private static void Open()
{
LocalizationSettingsProvider window = GetWindow<LocalizationSettingsProvider>();
window.titleContent = new GUIContent(WindowTitle);
window.minSize = new Vector2(760f, 520f);
window.Show();
}
private void OnEnable()
{
InitGUI();
RefreshLocalizationTables();
}
private void OnFocus()
{
RefreshLocalizationTables();
}
private void OnDisable()
{
_serializedObject?.Dispose();
_serializedObject = null;
LocalizationConfiguration.Save();
}
private void InitGUI()
{
LocalizationConfiguration setting = LocalizationConfiguration.Instance;
_serializedObject?.Dispose();
_serializedObject = new SerializedObject(setting);
_languageTypes = _serializedObject.FindProperty("LanguageTypes");
_genLangaugeTypePath = _serializedObject.FindProperty("_genLangaugeTypePath");
_generateScriptCodeFirstConfig = _serializedObject.FindProperty("generateScriptCodeFirstConfig");
_generateLanguageTypesNamespace = _serializedObject.FindProperty("generateLanguageTypesNamespace");
_generateLanguageTypesTemplate = _serializedObject.FindProperty("generateLanguageTypesTemplate");
CaptureOriginalLanguages();
BuildLanguageList();
RefreshLanguagePopupOptions();
}
private void CaptureOriginalLanguages()
{
_originalLanguages.Clear();
if (_languageTypes == null)
{
return;
}
for (int i = 0; i < _languageTypes.arraySize; i++)
{
_originalLanguages.Add(_languageTypes.GetArrayElementAtIndex(i).stringValue);
}
}
private void BuildLanguageList()
{
_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) =>
{
SerializedProperty element = _languageTypes.GetArrayElementAtIndex(index);
rect.y += 2f;
bool isBuiltInLanguage = index < 2;
using (new EditorGUI.DisabledGroupScope(isBuiltInLanguage))
{
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 => list.index >= 2;
_languageList.onAddCallback = list =>
{
int newIndex = _languageTypes.arraySize;
_languageTypes.InsertArrayElementAtIndex(newIndex);
SerializedProperty newElement = _languageTypes.GetArrayElementAtIndex(newIndex);
newElement.stringValue = "NewLanguage";
_hasUnsavedChanges = true;
};
_languageList.onRemoveCallback = list =>
{
if (list.index < 2)
{
EditorUtility.DisplayDialog("Cannot Remove", "The first two languages (ChineseSimplified and English) cannot be removed.", "OK");
return;
}
_languageTypes.DeleteArrayElementAtIndex(list.index);
_hasUnsavedChanges = true;
};
}
private void RefreshLanguagePopupOptions()
{
_languagePopupOptions.Clear();
IReadOnlyList<string> languageNames = LocalizationConfiguration.Instance.LanguageTypeNames;
for (int i = 0; i < languageNames.Count; i++)
{
string name = languageNames[i].Or("Unknown");
_languagePopupOptions.Add(name);
}
}
private void RefreshLocalizationTables()
{
_localizationTables.Clear();
string[] guids = AssetDatabase.FindAssets("t:GameLocaizationTable");
for (int i = 0; i < guids.Length; i++)
{
string assetPath = AssetDatabase.GUIDToAssetPath(guids[i]);
GameLocaizationTable table = AssetDatabase.LoadAssetAtPath<GameLocaizationTable>(assetPath);
if (table != null)
{
_localizationTables.Add(table);
}
}
}
private void OnGUI()
{
if (_serializedObject == null || !_serializedObject.targetObject)
{
InitGUI();
}
_serializedObject.Update();
RefreshLanguagePopupOptions();
EditorGUILayout.Space();
_scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition);
DrawLanguageTypesSection();
EditorGUILayout.Space(8f);
DrawLanguageTypesGenerateSection();
EditorGUILayout.Space(8f);
DrawLocalizationTablesSection();
EditorGUILayout.EndScrollView();
if (_serializedObject.ApplyModifiedProperties())
{
LocalizationConfiguration.Save();
}
}
private void DrawLanguageTypesSection()
{
using (new EditorDrawing.BorderBoxScope(new GUIContent("Language Types"), roundedBox: false))
{
_languageList.DoLayoutList();
EditorGUILayout.Space(10f);
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
using (new EditorGUI.DisabledGroupScope(!_hasUnsavedChanges))
{
if (GUILayout.Button("Save Language Changes", GUILayout.Width(200f), GUILayout.Height(30f)))
{
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 DrawLanguageTypesGenerateSection()
{
using (new EditorDrawing.BorderBoxScope(new GUIContent("Generate LanguageTypes"), roundedBox: false))
{
EditorGUILayout.PropertyField(_genLangaugeTypePath, new GUIContent("File Path"));
EditorGUILayout.PropertyField(_generateLanguageTypesNamespace, new GUIContent("Namespace"));
EditorDrawing.DrawStringSelectPopup(
new GUIContent("生成多语言Key索引文件时的注释首选语言"),
new GUIContent("None"),
_languagePopupOptions.ToArray(),
_generateScriptCodeFirstConfig.stringValue,
selected =>
{
_generateScriptCodeFirstConfig.stringValue = selected;
_serializedObject.ApplyModifiedProperties();
LocalizationConfiguration.Save();
});
EditorGUILayout.LabelField("Template", EditorStyles.boldLabel);
EditorGUILayout.HelpBox("Use placeholders: {NAMESPACE_START}, {NAMESPACE_END}, {LANGUAGE_CONSTANTS}, {LANGUAGE_LIST}", MessageType.None);
EditorGUI.BeginChangeCheck();
string template = EditorGUILayout.TextArea(_generateLanguageTypesTemplate.stringValue, GUILayout.MinHeight(240f));
if (EditorGUI.EndChangeCheck())
{
_generateLanguageTypesTemplate.stringValue = template;
}
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
if (GUILayout.Button("Reset Template", GUILayout.Width(140f)))
{
_generateLanguageTypesTemplate.stringValue = LocalizationConfiguration.DefaultTemplate;
}
if (GUILayout.Button("Generate Language Types", GUILayout.Width(180f)))
{
RegenerateLanguageTypes();
}
EditorGUILayout.EndHorizontal();
}
}
private void DrawLocalizationTablesSection()
{
using (new EditorDrawing.BorderBoxScope(new GUIContent("LocalizationTable"), roundedBox: false))
{
if (GUILayout.Button("Refresh LocalizationTable List", GUILayout.Width(220f)))
{
RefreshLocalizationTables();
}
EditorGUILayout.Space(4f);
if (_localizationTables.Count == 0)
{
EditorGUILayout.HelpBox("No LocalizationTable assets found in the project.", MessageType.Info);
return;
}
for (int i = 0; i < _localizationTables.Count; i++)
{
DrawLocalizationTableEntry(_localizationTables[i], i);
if (i < _localizationTables.Count - 1)
{
EditorGUILayout.Space(6f);
}
}
}
}
private void DrawLocalizationTableEntry(GameLocaizationTable table, int index)
{
SerializedObject tableSerializedObject = new SerializedObject(table);
SerializedProperty pathProperty = tableSerializedObject.FindProperty("GenerateScriptCodePath");
SerializedProperty namespaceProperty = tableSerializedObject.FindProperty("GenerateScriptCodeNamespace");
using (new EditorDrawing.BorderBoxScope(new GUIContent($"LocalizationTable {index + 1}"), roundedBox: false))
{
using (new EditorGUI.DisabledGroupScope(true))
{
EditorGUILayout.ObjectField("Table", table, typeof(GameLocaizationTable), false);
}
tableSerializedObject.Update();
EditorGUILayout.PropertyField(pathProperty, new GUIContent("Gen Code Path"));
EditorGUILayout.PropertyField(namespaceProperty, new GUIContent("Namespace"));
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
if (GUILayout.Button("Gen Code", GUILayout.Width(120f)))
{
tableSerializedObject.ApplyModifiedProperties();
EditorUtility.SetDirty(table);
LocalizationWindowUtility.GenerateCode(table);
}
EditorGUILayout.EndHorizontal();
if (tableSerializedObject.ApplyModifiedProperties())
{
EditorUtility.SetDirty(table);
AssetDatabase.SaveAssets();
}
}
}
private void RegenerateLanguageTypes()
{
string filePath = _genLangaugeTypePath.stringValue;
if (string.IsNullOrWhiteSpace(filePath))
{
EditorUtility.DisplayDialog("Invalid Path", "LanguageTypes output path cannot be empty.", "OK");
return;
}
string directory = Path.GetDirectoryName(filePath);
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
string template = string.IsNullOrEmpty(_generateLanguageTypesTemplate.stringValue)
? LocalizationConfiguration.DefaultTemplate
: _generateLanguageTypesTemplate.stringValue;
string generatedCode = BuildLanguageTypesCode(template, _generateLanguageTypesNamespace.stringValue, LocalizationConfiguration.Instance.LanguageTypeNames);
File.WriteAllText(filePath, generatedCode, Encoding.UTF8);
AssetDatabase.Refresh();
}
private static string BuildLanguageTypesCode(string template, string namespaceName, IReadOnlyList<string> languages)
{
StringBuilder constantsBuilder = new StringBuilder();
StringBuilder listBuilder = new StringBuilder();
for (int i = 0; i < languages.Count; i++)
{
string languageName = languages[i];
constantsBuilder.AppendLine($" public const string {languageName} = \"{languageName}\";");
listBuilder.AppendLine($" \"{languageName}\",");
}
string namespaceStart = string.IsNullOrWhiteSpace(namespaceName)
? string.Empty
: $"namespace {namespaceName}{Environment.NewLine}{{{Environment.NewLine}";
string namespaceEnd = string.IsNullOrWhiteSpace(namespaceName)
? string.Empty
: $"{Environment.NewLine}}}";
return template
.Replace("{NAMESPACE_START}", namespaceStart)
.Replace("{NAMESPACE_END}", namespaceEnd)
.Replace("{LANGUAGE_CONSTANTS}", constantsBuilder.ToString().TrimEnd())
.Replace("{LANGUAGE_LIST}", listBuilder.ToString().TrimEnd());
}
private void ApplyLanguageChanges()
{
List<string> currentLanguages = new List<string>();
for (int i = 0; i < _languageTypes.arraySize; i++)
{
currentLanguages.Add(_languageTypes.GetArrayElementAtIndex(i).stringValue);
}
List<string> addedLanguages = new List<string>();
List<string> removedLanguages = new List<string>();
Dictionary<string, string> renamedLanguages = new Dictionary<string, string>();
foreach (string lang in currentLanguages)
{
if (!_originalLanguages.Contains(lang))
{
addedLanguages.Add(lang);
}
}
foreach (string lang in _originalLanguages)
{
if (!currentLanguages.Contains(lang))
{
removedLanguages.Add(lang);
}
}
for (int i = 0; i < Mathf.Min(_originalLanguages.Count, currentLanguages.Count); i++)
{
if (_originalLanguages[i] != currentLanguages[i])
{
if (!addedLanguages.Contains(currentLanguages[i]) && !removedLanguages.Contains(_originalLanguages[i]))
{
renamedLanguages[_originalLanguages[i]] = currentLanguages[i];
}
}
}
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;
foreach (KeyValuePair<string, string> 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;
}
}
foreach (string newLang in addedLanguages)
{
if (table.Languages.Exists(lang => lang.LanguageName == newLang))
{
continue;
}
LocalizationLanguage newLanguage = ScriptableObject.CreateInstance<LocalizationLanguage>();
newLanguage.name = newLang;
newLanguage.LanguageName = newLang;
newLanguage.Strings = new List<LocalizationLanguage.LocalizationString>();
foreach (GameLocaizationTable.TableData section in table.TableSheet)
{
foreach (GameLocaizationTable.SheetItem item in section.SectionSheet)
{
string sectionKey = section.SectionName.Replace(" ", string.Empty);
string itemKey = item.Key.Replace(" ", string.Empty);
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;
}
foreach (string 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();
_originalLanguages.Clear();
_originalLanguages.AddRange(currentLanguages);
_hasUnsavedChanges = false;
RefreshLocalizationTables();
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");
}
}
}