This commit is contained in:
陈思海 2026-04-17 13:01:42 +08:00
parent 0a5ec37d23
commit e54d2144df
6 changed files with 367 additions and 160 deletions

View File

@ -8,7 +8,47 @@ namespace AlicizaX.Localization.Editor
[AlicizaX.Editor.Setting.FilePath("ProjectSettings/LocalizationConfiguration.asset")] [AlicizaX.Editor.Setting.FilePath("ProjectSettings/LocalizationConfiguration.asset")]
public class LocalizationConfiguration : AlicizaX.Editor.Setting.ScriptableSingleton<LocalizationConfiguration> public class LocalizationConfiguration : AlicizaX.Editor.Setting.ScriptableSingleton<LocalizationConfiguration>
{ {
private const string DefaultLanguageTypesTemplate =
@"using System.Collections.Generic;
{NAMESPACE_START}/// <summary>
/// AutoGenerate
/// </summary>
public static class LanguageTypes
{
{LANGUAGE_CONSTANTS}
public static readonly IReadOnlyList<string> Languages = new List<string>
{
{LANGUAGE_LIST}
};
public static string IndexToString(int index)
{
if (index < 0 || index >= Languages.Count) return ""Unknown"";
return Languages[index];
}
public static int StringToIndex(string s)
{
int index = -1;
for (int i = 0; i < Languages.Count; i++)
{
if (Languages[i] == s)
{
index = i;
break;
}
}
return index;
}
}
{NAMESPACE_END}";
[SerializeField] internal string generateScriptCodeFirstConfig; [SerializeField] internal string generateScriptCodeFirstConfig;
[SerializeField] internal string generateLanguageTypesNamespace = string.Empty;
[SerializeField] internal string generateLanguageTypesTemplate = DefaultLanguageTypesTemplate;
[SerializeField] private List<string> LanguageTypes = new List<string>() [SerializeField] private List<string> LanguageTypes = new List<string>()
{ {
@ -37,5 +77,9 @@ namespace AlicizaX.Localization.Editor
public IReadOnlyList<string> LanguageTypeNames => LanguageTypes; public IReadOnlyList<string> LanguageTypeNames => LanguageTypes;
public string GenerateScriptCodeFirstConfig => generateScriptCodeFirstConfig; public string GenerateScriptCodeFirstConfig => generateScriptCodeFirstConfig;
public string GenerateLanguageTypesNamespace => generateLanguageTypesNamespace;
public string GenerateLanguageTypesTemplate => string.IsNullOrEmpty(generateLanguageTypesTemplate) ? DefaultLanguageTypesTemplate : generateLanguageTypesTemplate;
public static string DefaultTemplate => DefaultLanguageTypesTemplate;
} }
} }

View File

@ -1,52 +1,92 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Text; using System.Text;
using AlicizaX.Editor; using AlicizaX.Editor;
using UnityEditor; using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using UnityEditorInternal; using UnityEditorInternal;
using UnityEngine;
namespace AlicizaX.Localization.Editor namespace AlicizaX.Localization.Editor
{ {
internal class LocalizationSettingsProvider : UnityEditor.SettingsProvider internal sealed class LocalizationSettingsProvider : EditorWindow
{ {
private const string WindowTitle = "Localization Settings";
private const string MenuPath = "Tools/AlicizaX/Localization/Open Localization Settings";
private SerializedObject _serializedObject; private SerializedObject _serializedObject;
private SerializedProperty _languageTypes; private SerializedProperty _languageTypes;
private ReorderableList _languageList;
private SerializedProperty _genLangaugeTypePath; private SerializedProperty _genLangaugeTypePath;
private SerializedProperty generateScriptCodeFirstConfig; private SerializedProperty _generateScriptCodeFirstConfig;
private List<string> popConfig = new List<string>(); 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();
// Track original state for comparison private Vector2 _scrollPosition;
private List<string> _originalLanguages = new List<string>(); private bool _hasUnsavedChanges;
private bool _hasUnsavedChanges = false;
public LocalizationSettingsProvider() : base("Project/Localization Settings", SettingsScope.Project) [MenuItem(MenuPath)]
private static void Open()
{ {
LocalizationSettingsProvider window = GetWindow<LocalizationSettingsProvider>();
window.titleContent = new GUIContent(WindowTitle);
window.minSize = new Vector2(760f, 520f);
window.Show();
} }
public override void OnActivate(string searchContext, VisualElement rootElement) private void OnEnable()
{ {
InitGUI(); InitGUI();
RefreshLocalizationTables();
}
private void OnFocus()
{
RefreshLocalizationTables();
}
private void OnDisable()
{
_serializedObject?.Dispose();
_serializedObject = null;
LocalizationConfiguration.Save();
} }
private void InitGUI() private void InitGUI()
{ {
var setting = LocalizationConfiguration.Instance; LocalizationConfiguration setting = LocalizationConfiguration.Instance;
_serializedObject?.Dispose(); _serializedObject?.Dispose();
_serializedObject = new SerializedObject(setting); _serializedObject = new SerializedObject(setting);
_languageTypes = _serializedObject.FindProperty("LanguageTypes"); _languageTypes = _serializedObject.FindProperty("LanguageTypes");
_genLangaugeTypePath = _serializedObject.FindProperty("_genLangaugeTypePath"); _genLangaugeTypePath = _serializedObject.FindProperty("_genLangaugeTypePath");
generateScriptCodeFirstConfig = _serializedObject.FindProperty("generateScriptCodeFirstConfig"); _generateScriptCodeFirstConfig = _serializedObject.FindProperty("generateScriptCodeFirstConfig");
_generateLanguageTypesNamespace = _serializedObject.FindProperty("generateLanguageTypesNamespace");
_generateLanguageTypesTemplate = _serializedObject.FindProperty("generateLanguageTypesTemplate");
// Store original language list CaptureOriginalLanguages();
BuildLanguageList();
RefreshLanguagePopupOptions();
}
private void CaptureOriginalLanguages()
{
_originalLanguages.Clear(); _originalLanguages.Clear();
if (_languageTypes == null)
{
return;
}
for (int i = 0; i < _languageTypes.arraySize; i++) for (int i = 0; i < _languageTypes.arraySize; i++)
{ {
_originalLanguages.Add(_languageTypes.GetArrayElementAtIndex(i).stringValue); _originalLanguages.Add(_languageTypes.GetArrayElementAtIndex(i).stringValue);
} }
}
// 自定义 ReorderableList private void BuildLanguageList()
{
_languageList = new ReorderableList(_serializedObject, _languageTypes, _languageList = new ReorderableList(_serializedObject, _languageTypes,
draggable: false, draggable: false,
displayHeader: true, displayHeader: true,
@ -57,18 +97,11 @@ namespace AlicizaX.Localization.Editor
_languageList.drawElementCallback = (rect, index, isActive, isFocused) => _languageList.drawElementCallback = (rect, index, isActive, isFocused) =>
{ {
var element = _languageTypes.GetArrayElementAtIndex(index); SerializedProperty element = _languageTypes.GetArrayElementAtIndex(index);
rect.y += 2; rect.y += 2f;
if (index < 2) // 前两个(中文、英文)禁止修改 bool isBuiltInLanguage = index < 2;
{ using (new EditorGUI.DisabledGroupScope(isBuiltInLanguage))
EditorGUI.BeginDisabledGroup(true);
EditorGUI.TextField(
new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight),
element.stringValue);
EditorGUI.EndDisabledGroup();
}
else
{ {
EditorGUI.BeginChangeCheck(); EditorGUI.BeginChangeCheck();
string newValue = EditorGUI.TextField( string newValue = EditorGUI.TextField(
@ -82,23 +115,17 @@ namespace AlicizaX.Localization.Editor
} }
}; };
// 禁止删除前两个 _languageList.onCanRemoveCallback = list => list.index >= 2;
_languageList.onCanRemoveCallback = list =>
{
return list.index >= 2; // 只有索引 >=2 的项才能删除
};
// Hook for when a language is added
_languageList.onAddCallback = list => _languageList.onAddCallback = list =>
{ {
int newIndex = _languageTypes.arraySize; int newIndex = _languageTypes.arraySize;
_languageTypes.InsertArrayElementAtIndex(newIndex); _languageTypes.InsertArrayElementAtIndex(newIndex);
var newElement = _languageTypes.GetArrayElementAtIndex(newIndex); SerializedProperty newElement = _languageTypes.GetArrayElementAtIndex(newIndex);
newElement.stringValue = "NewLanguage"; newElement.stringValue = "NewLanguage";
_hasUnsavedChanges = true; _hasUnsavedChanges = true;
}; };
// Hook for when a language is removed
_languageList.onRemoveCallback = list => _languageList.onRemoveCallback = list =>
{ {
if (list.index < 2) if (list.index < 2)
@ -107,23 +134,38 @@ namespace AlicizaX.Localization.Editor
return; return;
} }
// Just remove from the list, no immediate sync
_languageTypes.DeleteArrayElementAtIndex(list.index); _languageTypes.DeleteArrayElementAtIndex(list.index);
_hasUnsavedChanges = true; _hasUnsavedChanges = true;
}; };
}
popConfig.Clear(); private void RefreshLanguagePopupOptions()
if (setting.LanguageTypeNames.Count > 0)
{ {
foreach (var lang in setting.LanguageTypeNames) _languagePopupOptions.Clear();
IReadOnlyList<string> languageNames = LocalizationConfiguration.Instance.LanguageTypeNames;
for (int i = 0; i < languageNames.Count; i++)
{ {
string name = lang.Or("Unknown"); string name = languageNames[i].Or("Unknown");
popConfig.Add(name); _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);
} }
} }
} }
public override void OnGUI(string searchContext) private void OnGUI()
{ {
if (_serializedObject == null || !_serializedObject.targetObject) if (_serializedObject == null || !_serializedObject.targetObject)
{ {
@ -131,37 +173,38 @@ namespace AlicizaX.Localization.Editor
} }
_serializedObject.Update(); _serializedObject.Update();
EditorGUI.BeginChangeCheck(); RefreshLanguagePopupOptions();
EditorGUILayout.PropertyField(_genLangaugeTypePath); EditorGUILayout.Space();
EditorDrawing.DrawStringSelectPopup(new GUIContent("Gen Lang"), new GUIContent("None"), popConfig.ToArray(), generateScriptCodeFirstConfig.stringValue, _scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition);
(e) =>
DrawLanguageTypesSection();
EditorGUILayout.Space(8f);
DrawLanguageTypesGenerateSection();
EditorGUILayout.Space(8f);
DrawLocalizationTablesSection();
EditorGUILayout.EndScrollView();
if (_serializedObject.ApplyModifiedProperties())
{ {
generateScriptCodeFirstConfig.stringValue = e;
_serializedObject.ApplyModifiedProperties();
LocalizationConfiguration.Save(); LocalizationConfiguration.Save();
}); }
if (GUILayout.Button("Generate Language Types"))
{
RegenerateLanguageTypes();
} }
private void DrawLanguageTypesSection()
{
using (new EditorDrawing.BorderBoxScope(new GUIContent("Language Types"), roundedBox: false))
{
_languageList.DoLayoutList(); _languageList.DoLayoutList();
if (EditorGUI.EndChangeCheck()) EditorGUILayout.Space(10f);
{
_serializedObject.ApplyModifiedProperties();
LocalizationConfiguration.Save();
}
// Add Save button
EditorGUILayout.Space(10);
EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace(); GUILayout.FlexibleSpace();
using (new EditorGUI.DisabledGroupScope(!_hasUnsavedChanges)) using (new EditorGUI.DisabledGroupScope(!_hasUnsavedChanges))
{ {
if (GUILayout.Button("Save Language Changes", GUILayout.Width(200), GUILayout.Height(30))) if (GUILayout.Button("Save Language Changes", GUILayout.Width(200f), GUILayout.Height(30f)))
{ {
ApplyLanguageChanges(); ApplyLanguageChanges();
} }
@ -175,83 +218,180 @@ namespace AlicizaX.Localization.Editor
EditorGUILayout.HelpBox("You have unsaved language changes. Click 'Save Language Changes' to apply them to all GameLocalizationTable assets.", MessageType.Warning); 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() private void RegenerateLanguageTypes()
{ {
StringBuilder sb = new StringBuilder(); string filePath = _genLangaugeTypePath.stringValue;
if (string.IsNullOrWhiteSpace(filePath))
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]}\";"); EditorUtility.DisplayDialog("Invalid Path", "LanguageTypes output path cannot be empty.", "OK");
return;
} }
sb.AppendLine(""); string directory = Path.GetDirectoryName(filePath);
sb.AppendLine("\tpublic static readonly IReadOnlyList<string> Languages = new List<string>"); if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
sb.AppendLine("\t{");
for (int i = 0; i < LocalizationConfiguration.Instance.LanguageTypeNames.Count; i++)
{ {
sb.AppendLine($"\t\t\"{LocalizationConfiguration.Instance.LanguageTypeNames[i]}\","); Directory.CreateDirectory(directory);
} }
sb.AppendLine("\t};"); string template = string.IsNullOrEmpty(_generateLanguageTypesTemplate.stringValue)
? LocalizationConfiguration.DefaultTemplate
: _generateLanguageTypesTemplate.stringValue;
sb.AppendLine(""); string generatedCode = BuildLanguageTypesCode(template, _generateLanguageTypesNamespace.stringValue, LocalizationConfiguration.Instance.LanguageTypeNames);
sb.AppendLine("\tpublic static string IndexToString(int index)"); File.WriteAllText(filePath, generatedCode, Encoding.UTF8);
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(); AssetDatabase.Refresh();
} }
public override void OnDeactivate() private static string BuildLanguageTypesCode(string template, string namespaceName, IReadOnlyList<string> languages)
{ {
base.OnDeactivate(); StringBuilder constantsBuilder = new StringBuilder();
LocalizationConfiguration.Save(); 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() private void ApplyLanguageChanges()
{ {
// Get current language list
List<string> currentLanguages = new List<string>(); List<string> currentLanguages = new List<string>();
for (int i = 0; i < _languageTypes.arraySize; i++) for (int i = 0; i < _languageTypes.arraySize; i++)
{ {
currentLanguages.Add(_languageTypes.GetArrayElementAtIndex(i).stringValue); currentLanguages.Add(_languageTypes.GetArrayElementAtIndex(i).stringValue);
} }
// Detect changes
List<string> addedLanguages = new List<string>(); List<string> addedLanguages = new List<string>();
List<string> removedLanguages = new List<string>(); List<string> removedLanguages = new List<string>();
Dictionary<string, string> renamedLanguages = new Dictionary<string, string>(); // old -> new Dictionary<string, string> renamedLanguages = new Dictionary<string, string>();
// Find added languages foreach (string lang in currentLanguages)
foreach (var lang in currentLanguages)
{ {
if (!_originalLanguages.Contains(lang)) if (!_originalLanguages.Contains(lang))
{ {
@ -259,8 +399,7 @@ namespace AlicizaX.Localization.Editor
} }
} }
// Find removed languages foreach (string lang in _originalLanguages)
foreach (var lang in _originalLanguages)
{ {
if (!currentLanguages.Contains(lang)) if (!currentLanguages.Contains(lang))
{ {
@ -268,12 +407,10 @@ namespace AlicizaX.Localization.Editor
} }
} }
// Detect renames (same index, different name)
for (int i = 0; i < Mathf.Min(_originalLanguages.Count, currentLanguages.Count); i++) for (int i = 0; i < Mathf.Min(_originalLanguages.Count, currentLanguages.Count); i++)
{ {
if (_originalLanguages[i] != currentLanguages[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])) if (!addedLanguages.Contains(currentLanguages[i]) && !removedLanguages.Contains(_originalLanguages[i]))
{ {
renamedLanguages[_originalLanguages[i]] = currentLanguages[i]; renamedLanguages[_originalLanguages[i]] = currentLanguages[i];
@ -281,7 +418,6 @@ namespace AlicizaX.Localization.Editor
} }
} }
// Apply changes to all tables
string[] guids = AssetDatabase.FindAssets("t:GameLocaizationTable"); string[] guids = AssetDatabase.FindAssets("t:GameLocaizationTable");
int tablesUpdated = 0; int tablesUpdated = 0;
@ -291,12 +427,13 @@ namespace AlicizaX.Localization.Editor
GameLocaizationTable table = AssetDatabase.LoadAssetAtPath<GameLocaizationTable>(assetPath); GameLocaizationTable table = AssetDatabase.LoadAssetAtPath<GameLocaizationTable>(assetPath);
if (table == null) if (table == null)
{
continue; continue;
}
bool tableModified = false; bool tableModified = false;
// Handle renames first foreach (KeyValuePair<string, string> rename in renamedLanguages)
foreach (var rename in renamedLanguages)
{ {
LocalizationLanguage language = table.Languages.Find(lang => lang.LanguageName == rename.Key); LocalizationLanguage language = table.Languages.Find(lang => lang.LanguageName == rename.Key);
if (language != null) if (language != null)
@ -307,26 +444,24 @@ namespace AlicizaX.Localization.Editor
} }
} }
// Handle additions foreach (string newLang in addedLanguages)
foreach (var newLang in addedLanguages)
{ {
// Skip if already exists
if (table.Languages.Exists(lang => lang.LanguageName == newLang)) if (table.Languages.Exists(lang => lang.LanguageName == newLang))
{
continue; continue;
}
// Create new LocalizationLanguage asset
LocalizationLanguage newLanguage = ScriptableObject.CreateInstance<LocalizationLanguage>(); LocalizationLanguage newLanguage = ScriptableObject.CreateInstance<LocalizationLanguage>();
newLanguage.name = newLang; newLanguage.name = newLang;
newLanguage.LanguageName = newLang; newLanguage.LanguageName = newLang;
newLanguage.Strings = new List<LocalizationLanguage.LocalizationString>(); newLanguage.Strings = new List<LocalizationLanguage.LocalizationString>();
// Synchronize keys from existing table structure foreach (GameLocaizationTable.TableData section in table.TableSheet)
foreach (var section in table.TableSheet)
{ {
foreach (var item in section.SectionSheet) foreach (GameLocaizationTable.SheetItem item in section.SectionSheet)
{ {
string sectionKey = section.SectionName.Replace(" ", ""); string sectionKey = section.SectionName.Replace(" ", string.Empty);
string itemKey = item.Key.Replace(" ", ""); string itemKey = item.Key.Replace(" ", string.Empty);
string fullKey = sectionKey + "." + itemKey; string fullKey = sectionKey + "." + itemKey;
newLanguage.Strings.Add(new LocalizationLanguage.LocalizationString newLanguage.Strings.Add(new LocalizationLanguage.LocalizationString
@ -344,8 +479,7 @@ namespace AlicizaX.Localization.Editor
tableModified = true; tableModified = true;
} }
// Handle removals foreach (string removedLang in removedLanguages)
foreach (var removedLang in removedLanguages)
{ {
LocalizationLanguage languageToDelete = table.Languages.Find(lang => lang.LanguageName == removedLang); LocalizationLanguage languageToDelete = table.Languages.Find(lang => lang.LanguageName == removedLang);
if (languageToDelete != null) if (languageToDelete != null)
@ -366,33 +500,27 @@ namespace AlicizaX.Localization.Editor
AssetDatabase.SaveAssets(); AssetDatabase.SaveAssets();
AssetDatabase.Refresh(); AssetDatabase.Refresh();
// Update original languages list
_originalLanguages.Clear(); _originalLanguages.Clear();
_originalLanguages.AddRange(currentLanguages); _originalLanguages.AddRange(currentLanguages);
_hasUnsavedChanges = false; _hasUnsavedChanges = false;
RefreshLocalizationTables();
// Log results
if (addedLanguages.Count > 0) if (addedLanguages.Count > 0)
{
Debug.Log($"Added {addedLanguages.Count} language(s) to {tablesUpdated} table(s): {string.Join(", ", addedLanguages)}"); Debug.Log($"Added {addedLanguages.Count} language(s) to {tablesUpdated} table(s): {string.Join(", ", addedLanguages)}");
}
if (removedLanguages.Count > 0) if (removedLanguages.Count > 0)
{
Debug.Log($"Removed {removedLanguages.Count} language(s) from {tablesUpdated} table(s): {string.Join(", ", removedLanguages)}"); Debug.Log($"Removed {removedLanguages.Count} language(s) from {tablesUpdated} table(s): {string.Join(", ", removedLanguages)}");
}
if (renamedLanguages.Count > 0) if (renamedLanguages.Count > 0)
{
Debug.Log($"Renamed {renamedLanguages.Count} language(s) in {tablesUpdated} table(s)"); Debug.Log($"Renamed {renamedLanguages.Count} language(s) in {tablesUpdated} table(s)");
}
EditorUtility.DisplayDialog("Success", $"Language changes applied to {tablesUpdated} GameLocalizationTable(s).", "OK"); 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;
}
} }
} }

View File

@ -1,3 +1,11 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 5647a4aaf19f42bf852a701955e888f5 guid: 5647a4aaf19f42bf852a701955e888f5
timeCreated: 1758196661 MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,6 +1,7 @@
using System.Linq; using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using System.IO;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
@ -591,10 +592,28 @@ namespace AlicizaX.Localization.Editor
public static void GenerateCode(GameLocaizationTable table) public static void GenerateCode(GameLocaizationTable table)
{ {
string filePath = table.GenerateScriptCodePath; string filePath = table.GenerateScriptCodePath;
List<LocalizationLanguage.LocalizationString> strings = new List<LocalizationLanguage.LocalizationString>(); if (string.IsNullOrWhiteSpace(filePath))
{
EditorUtility.DisplayDialog("Invalid Path", "Localization key output path cannot be empty.", "OK");
return;
}
string directory = Path.GetDirectoryName(filePath);
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
var localizationLanguage = table.Languages.Find(t => t.LanguageName == LocalizationConfiguration.Instance.GenerateScriptCodeFirstConfig); var localizationLanguage = table.Languages.Find(t => t.LanguageName == LocalizationConfiguration.Instance.GenerateScriptCodeFirstConfig);
string namespaceName = table.GenerateScriptCodeNamespace;
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
if (!string.IsNullOrWhiteSpace(namespaceName))
{
sb.AppendLine($"namespace {namespaceName}");
sb.AppendLine("{");
}
sb.AppendLine("/// <summary>"); sb.AppendLine("/// <summary>");
sb.AppendLine("/// AutoGenerate"); sb.AppendLine("/// AutoGenerate");
sb.AppendLine("/// </summary>"); sb.AppendLine("/// </summary>");
@ -634,7 +653,14 @@ namespace AlicizaX.Localization.Editor
sb.AppendLine(); sb.AppendLine();
sb.AppendLine("}"); sb.AppendLine("}");
System.IO.File.WriteAllText(filePath, sb.ToString());
if (!string.IsNullOrWhiteSpace(namespaceName))
{
sb.AppendLine();
sb.AppendLine("}");
}
File.WriteAllText(filePath, sb.ToString(), Encoding.UTF8);
AssetDatabase.Refresh(); AssetDatabase.Refresh();
} }
} }

View File

@ -43,6 +43,7 @@ namespace AlicizaX.Localization.Editor
using (new EditorDrawing.BorderBoxScope(new GUIContent("GenCode"), roundedBox: false)) using (new EditorDrawing.BorderBoxScope(new GUIContent("GenCode"), roundedBox: false))
{ {
Properties.Draw("GenerateScriptCodePath", new GUIContent("File Path")); Properties.Draw("GenerateScriptCodePath", new GUIContent("File Path"));
Properties.Draw("GenerateScriptCodeNamespace", new GUIContent("Namespace"));
} }
} }
serializedObject.ApplyModifiedProperties(); serializedObject.ApplyModifiedProperties();
@ -96,10 +97,9 @@ namespace AlicizaX.Localization.Editor
window.Show(); window.Show();
} }
[MenuItem("Tools/AlicizaX/Localization/Open Localization ProjectSetting")]
public static void OpenSettings() public static void OpenSettings()
{ {
SettingsService.OpenProjectSettings("Project/Localization Settings"); EditorApplication.ExecuteMenuItem("Tools/AlicizaX/Localization/Open Localization Settings");
} }

View File

@ -8,6 +8,7 @@ namespace AlicizaX.Localization
{ {
#if UNITY_EDITOR #if UNITY_EDITOR
[SerializeField] internal string GenerateScriptCodePath = string.Empty; [SerializeField] internal string GenerateScriptCodePath = string.Empty;
[SerializeField] internal string GenerateScriptCodeNamespace = string.Empty;
[Serializable] [Serializable]
public struct SheetItem public struct SheetItem