2025-09-25 11:11:19 +08:00
using System.Collections.Generic ;
2025-09-23 19:27:17 +08:00
using System.Text ;
2025-09-25 11:11:19 +08:00
using AlicizaX.Editor ;
2025-09-23 12:04:39 +08:00
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 ;
2025-09-23 19:27:17 +08:00
private SerializedProperty _genLangaugeTypePath ;
2025-09-25 11:11:19 +08:00
private SerializedProperty generateScriptCodeFirstConfig ;
private List < string > popConfig = new List < string > ( ) ;
2025-09-23 12:04:39 +08:00
2025-12-26 18:08:15 +08:00
// Track original state for comparison
private List < string > _originalLanguages = new List < string > ( ) ;
private bool _hasUnsavedChanges = false ;
2025-09-23 19:27:17 +08:00
public LocalizationSettingsProvider ( ) : base ( "Project/Localization Settings" , SettingsScope . Project )
{
}
2025-09-23 12:04:39 +08:00
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" ) ;
2025-09-23 19:27:17 +08:00
_genLangaugeTypePath = _serializedObject . FindProperty ( "_genLangaugeTypePath" ) ;
2025-09-25 11:11:19 +08:00
generateScriptCodeFirstConfig = _serializedObject . FindProperty ( "generateScriptCodeFirstConfig" ) ;
2025-12-26 18:08:15 +08:00
// Store original language list
_originalLanguages . Clear ( ) ;
for ( int i = 0 ; i < _languageTypes . arraySize ; i + + )
{
_originalLanguages . Add ( _languageTypes . GetArrayElementAtIndex ( i ) . stringValue ) ;
}
2025-09-23 12:04:39 +08:00
// 自定义 ReorderableList
_languageList = new ReorderableList ( _serializedObject , _languageTypes ,
2025-09-25 11:11:19 +08:00
draggable : false ,
2025-09-23 12:04:39 +08:00
displayHeader : true ,
displayAddButton : true ,
displayRemoveButton : true ) ;
2025-09-23 19:27:17 +08:00
_languageList . drawHeaderCallback = rect = > { EditorGUI . LabelField ( rect , "Language Types" ) ; } ;
2025-09-23 12:04:39 +08:00
_languageList . drawElementCallback = ( rect , index , isActive , isFocused ) = >
{
var element = _languageTypes . GetArrayElementAtIndex ( index ) ;
rect . y + = 2 ;
2025-09-25 11:11:19 +08:00
if ( index < 2 ) // 前两个(中文、英文)禁止修改
{
EditorGUI . BeginDisabledGroup ( true ) ;
EditorGUI . TextField (
new Rect ( rect . x , rect . y , rect . width , EditorGUIUtility . singleLineHeight ) ,
element . stringValue ) ;
EditorGUI . EndDisabledGroup ( ) ;
}
else
{
2025-12-26 18:08:15 +08:00
EditorGUI . BeginChangeCheck ( ) ;
string newValue = EditorGUI . TextField (
2025-09-25 11:11:19 +08:00
new Rect ( rect . x , rect . y , rect . width , EditorGUIUtility . singleLineHeight ) ,
element . stringValue ) ;
2025-12-26 18:08:15 +08:00
if ( EditorGUI . EndChangeCheck ( ) )
{
element . stringValue = newValue ;
_hasUnsavedChanges = true ;
}
2025-09-25 11:11:19 +08:00
}
2025-09-23 12:04:39 +08:00
} ;
2025-09-25 11:11:19 +08:00
// 禁止删除前两个
_languageList . onCanRemoveCallback = list = >
{
return list . index > = 2 ; // 只有索引 >=2 的项才能删除
} ;
2025-12-26 18:08:15 +08:00
// 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 ;
} ;
2025-09-25 11:11:19 +08:00
popConfig . Clear ( ) ;
if ( setting . LanguageTypeNames . Count > 0 )
{
foreach ( var lang in setting . LanguageTypeNames )
{
string name = lang . Or ( "Unknown" ) ;
popConfig . Add ( name ) ;
}
}
2025-09-23 12:04:39 +08:00
}
public override void OnGUI ( string searchContext )
{
if ( _serializedObject = = null | | ! _serializedObject . targetObject )
{
InitGUI ( ) ;
}
_serializedObject . Update ( ) ;
EditorGUI . BeginChangeCheck ( ) ;
2025-09-25 11:11:19 +08:00
2025-09-23 19:27:17 +08:00
EditorGUILayout . PropertyField ( _genLangaugeTypePath ) ;
2025-09-25 11:11:19 +08:00
EditorDrawing . DrawStringSelectPopup ( new GUIContent ( "Gen Lang" ) , new GUIContent ( "None" ) , popConfig . ToArray ( ) , generateScriptCodeFirstConfig . stringValue ,
( e ) = >
{
generateScriptCodeFirstConfig . stringValue = e ;
_serializedObject . ApplyModifiedProperties ( ) ;
LocalizationConfiguration . Save ( ) ;
} ) ;
2025-09-23 19:27:17 +08:00
if ( GUILayout . Button ( "Generate Language Types" ) )
{
RegenerateLanguageTypes ( ) ;
}
2025-09-23 12:04:39 +08:00
_languageList . DoLayoutList ( ) ;
if ( EditorGUI . EndChangeCheck ( ) )
{
_serializedObject . ApplyModifiedProperties ( ) ;
LocalizationConfiguration . Save ( ) ;
}
2025-12-26 18:08:15 +08:00
// 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 ) ;
}
2025-09-23 12:04:39 +08:00
}
2025-09-23 19:27:17 +08:00
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 ( "{" ) ;
2025-10-13 20:18:01 +08:00
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>" ) ;
2025-09-23 19:27:17 +08:00
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{" ) ;
2025-10-13 20:18:01 +08:00
sb . AppendLine ( "\t\tif (index < 0 || index >= Languages.Count) return \"Unknown\";" ) ;
sb . AppendLine ( "\t\treturn Languages[index];" ) ;
2025-09-23 19:27:17 +08:00
sb . AppendLine ( "\t}" ) ;
sb . AppendLine ( "" ) ;
sb . AppendLine ( "\tpublic static int StringToIndex(string s)" ) ;
sb . AppendLine ( "\t{" ) ;
sb . AppendLine ( "\t\tint index = -1;" ) ;
2025-10-13 20:18:01 +08:00
sb . AppendLine ( "\t\tfor (int i = 0; i < Languages.Count; i++)" ) ;
2025-09-23 19:27:17 +08:00
sb . AppendLine ( "\t\t{" ) ;
2025-10-13 20:18:01 +08:00
sb . AppendLine ( "\t\t\tif (Languages[i] == s)" ) ;
2025-09-23 19:27:17 +08:00
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 ( "}" ) ;
2025-09-25 11:11:19 +08:00
2025-09-23 19:27:17 +08:00
System . IO . File . WriteAllText ( _genLangaugeTypePath . stringValue , sb . ToString ( ) ) ;
AssetDatabase . Refresh ( ) ;
}
2025-09-23 12:04:39 +08:00
public override void OnDeactivate ( )
{
base . OnDeactivate ( ) ;
LocalizationConfiguration . Save ( ) ;
}
2025-12-26 18:08:15 +08:00
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" ) ;
}
2025-09-23 12:04:39 +08:00
static LocalizationSettingsProvider s_provider ;
[SettingsProvider]
public static SettingsProvider CreateMyCustomSettingsProvider ( )
{
if ( s_provider = = null )
{
s_provider = new LocalizationSettingsProvider ( ) ;
}
2025-09-23 19:27:17 +08:00
2025-09-23 12:04:39 +08:00
return s_provider ;
}
}
}