Przeglądaj źródła

eliminate concurrency issues

gravydevsupreme 7 lat temu
rodzic
commit
3109eebfbd

+ 93 - 149
src/XUnity.AutoTranslator.Plugin.Core/AutoTranslationPlugin.cs

@@ -29,13 +29,16 @@ namespace XUnity.AutoTranslator.Plugin.Core
 {
    public class AutoTranslationPlugin : MonoBehaviour
    {
-      private static readonly string TextPropertyName = "text";
+      /// <summary>
+      /// Allow the instance to be accessed statically, as only one will exist.
+      /// </summary>
+      public static AutoTranslationPlugin Current;
 
       /// <summary>
       /// These are the currently running translation jobs (being translated by an http request).
       /// </summary>
       private List<TranslationJob> _completedJobs = new List<TranslationJob>();
-      private List<TranslationJob> _unstartedJobs = new List<TranslationJob>();
+      private Dictionary<string, TranslationJob> _unstartedJobs = new Dictionary<string, TranslationJob>();
 
       /// <summary>
       /// All the translations are stored in this dictionary.
@@ -68,12 +71,15 @@ namespace XUnity.AutoTranslator.Plugin.Core
       private Func<string, bool> _symbolCheck;
 
       private bool _isInTranslatedMode = true;
+      private bool _hooksEnabled = true;
 
       public void Initialize()
       {
+         Current = this;
+
          Settings.Configure();
 
-         HooksSetup.InstallHooks( Any_TextChanged );
+         HooksSetup.InstallHooks( Override_TextChanged );
 
          AutoTranslateClient.Configure();
 
@@ -90,16 +96,6 @@ namespace XUnity.AutoTranslator.Plugin.Core
          var t2 = new Thread( SaveTranslationsLoop );
          t2.IsBackground = true;
          t2.Start();
-
-
-         // subscribe to text changes
-         UGUIHooks.TextAwakened += UguiTextEvents_OnTextAwaken;
-         UGUIHooks.TextChanged += UguiTextEvents_OnTextChanged;
-         IMGUIHooks.TextChanged += IMGUITextEvents_GUIContentChanged;
-         NGUIHooks.TextChanged += NGUITextEvents_TextChanged;
-
-         TextMeshProHooks.TextAwakened += TextMeshProHooks_OnTextAwaken;
-         TextMeshProHooks.TextChanged += TextMeshProHooks_OnTextChanged;
       }
 
       private string[] GetTranslationFiles()
@@ -116,41 +112,13 @@ namespace XUnity.AutoTranslator.Plugin.Core
       {
          while( true )
          {
-            //// What a brilliant solution...
-            //try
-            //{
-            //   AutoTranslateClient.RemoveUnusedClients();
-            //}
-            //catch( Exception e )
-            //{
-            //   Console.WriteLine( "An unexpected error occurred in XUnity.AutoTranslator: " + Environment.NewLine + e );
-            //}
-            //finally
-            //{
-            //   Thread.Sleep( 1000 * 20 );
-            //}
-
-            //try
-            //{
-            //   AutoTranslateClient.RemoveUnusedClients();
-            //}
-            //catch( Exception e )
-            //{
-            //   Console.WriteLine( "An unexpected error occurred in XUnity.AutoTranslator: " + Environment.NewLine + e );
-            //}
-            //finally
-            //{
-            //   Thread.Sleep( 1000 * 20 );
-            //}
-
             try
             {
-               //AutoTranslateClient.RemoveUnusedClients();
                ObjectExtensions.Cull();
             }
             catch( Exception e )
             {
-               Console.WriteLine( "An unexpected error occurred in XUnity.AutoTranslator: " + Environment.NewLine + e );
+               Console.WriteLine( "[ERROR][XUnity.AutoTranslator]: An unexpected error occurred while removing GC'ed resources." + Environment.NewLine + e );
             }
             finally
             {
@@ -192,7 +160,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
          }
          catch( Exception e )
          {
-            Console.WriteLine( e );
+            Console.WriteLine( "[ERROR][XUnity.AutoTranslator]: An error occurred while saving translations to disk. " + Environment.NewLine + e );
          }
       }
 
@@ -233,8 +201,29 @@ namespace XUnity.AutoTranslator.Plugin.Core
          }
          catch( Exception e )
          {
-            Console.WriteLine( e );
+            Console.WriteLine( "[ERROR][XUnity.AutoTranslator]: An error occurred while loading translations. " + Environment.NewLine + e );
+         }
+      }
+
+      private TranslationJob GetOrCreateTranslationJobFor( string untranslatedText )
+      {
+         if( _unstartedJobs.TryGetValue( untranslatedText, out TranslationJob job ) )
+         {
+            return job;
+         }
+         
+         foreach( var completedJob in _completedJobs )
+         {
+            if( completedJob.UntranslatedText == untranslatedText )
+            {
+               return completedJob;
+            }
          }
+
+         job = new TranslationJob( untranslatedText );
+         _unstartedJobs.Add( untranslatedText, job );
+
+         return job;
       }
 
       private void AddTranslation( string key, string value )
@@ -262,39 +251,29 @@ namespace XUnity.AutoTranslator.Plugin.Core
          return _translations.TryGetValue( key, out value ) || ( Settings.IgnoreWhitespaceInDialogue && _translations.TryGetValue( key.RemoveWhitespace(), out value ) );
       }
 
-      private string Any_TextChanged( object graphic, string text )
+      private string Override_TextChanged( object ui, string text )
       {
-         return TranslateOrQueueWebJob( graphic, text, true );
-      }
-
-      private void NGUITextEvents_TextChanged( object graphic )
-      {
-         TranslateOrQueueWebJob( graphic, null, true );
-      }
-
-      private void IMGUITextEvents_GUIContentChanged( object content )
-      {
-         TranslateOrQueueWebJob( content, null, true );
-      }
-
-      private void UguiTextEvents_OnTextChanged( object text )
-      {
-         TranslateOrQueueWebJob( text, null, false );
-      }
-
-      private void UguiTextEvents_OnTextAwaken( object text )
-      {
-         TranslateOrQueueWebJob( text, null, true );
+         if( _hooksEnabled )
+         {
+            return TranslateOrQueueWebJob( ui, text, true );
+         }
+         return null;
       }
 
-      private void TextMeshProHooks_OnTextChanged( object text )
+      public void Hook_TextChanged( object ui )
       {
-         TranslateOrQueueWebJob( text, null, false );
+         if( _hooksEnabled )
+         {
+            TranslateOrQueueWebJob( ui, null, false );
+         }
       }
 
-      private void TextMeshProHooks_OnTextAwaken( object text )
+      public void Hook_TextInitialized( object ui )
       {
-         TranslateOrQueueWebJob( text, null, true );
+         if( _hooksEnabled )
+         {
+            TranslateOrQueueWebJob( ui, null, true );
+         }
       }
 
       private void SetTranslatedText( object ui, string text, TranslationInfo info )
@@ -317,29 +296,15 @@ namespace XUnity.AutoTranslator.Plugin.Core
          {
             try
             {
-               UGUIHooks.TextChanged -= UguiTextEvents_OnTextChanged;
-               NGUIHooks.TextChanged -= NGUITextEvents_TextChanged;
-               TextMeshProHooks.TextChanged -= TextMeshProHooks_OnTextChanged;
+               // TODO: Disable ANY Hook
+               _hooksEnabled = false;
+
                if( info != null )
                {
                   info.IsCurrentlySettingText = true;
                }
 
-
-               if( ui is Text )
-               {
-                  ( (Text)ui ).text = text;
-               }
-               else if( ui is GUIContent )
-               {
-                  ( (GUIContent)ui ).text = text;
-               }
-               else
-               {
-                  // fallback to reflective approach
-                  var type = ui.GetType();
-                  type.GetProperty( TextPropertyName )?.GetSetMethod()?.Invoke( ui, new[] { text } );
-               }
+               ui.SetText( text );
 
                if( isTranslated )
                {
@@ -356,9 +321,8 @@ namespace XUnity.AutoTranslator.Plugin.Core
             }
             finally
             {
-               UGUIHooks.TextChanged += UguiTextEvents_OnTextChanged;
-               NGUIHooks.TextChanged += NGUITextEvents_TextChanged;
-               TextMeshProHooks.TextChanged += TextMeshProHooks_OnTextChanged;
+               _hooksEnabled = true;
+
                if( info != null )
                {
                   info.IsCurrentlySettingText = false;
@@ -367,26 +331,6 @@ namespace XUnity.AutoTranslator.Plugin.Core
          }
       }
 
-      private string GetText( object ui )
-      {
-         string text = null;
-
-         if( ui is Text )
-         {
-            text = ( (Text)ui ).text;
-         }
-         else if( ui is GUIContent )
-         {
-            text = ( (GUIContent)ui ).text;
-         }
-         else
-         {
-            text = (string)ui.GetType()?.GetProperty( TextPropertyName )?.GetValue( ui, null );
-         }
-
-         return text ?? string.Empty;
-      }
-
       /// <summary>
       /// Determines if a text should be translated.
       /// </summary>
@@ -445,7 +389,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
          return null;
       }
 
-      public static bool IsAlreadyTranslating( TranslationInfo info )
+      public static bool IsCurrentlySetting( TranslationInfo info )
       {
          if( info == null ) return false;
 
@@ -459,13 +403,13 @@ namespace XUnity.AutoTranslator.Plugin.Core
       private string TranslateOrQueueWebJobImmediate( object ui, string text, TranslationInfo info )
       {
          // Get the trimmed text
-         text = ( text ?? GetText( ui ) ).Trim();
+         text = ( text ?? ui.GetText() ).Trim();
 
          // Ensure that we actually want to translate this text and its owning UI element. 
-         if( !string.IsNullOrEmpty( text ) && IsTranslatable( text ) && ShouldTranslate( ui ) && !IsAlreadyTranslating( info ) )
+         if( !string.IsNullOrEmpty( text ) && IsTranslatable( text ) && ShouldTranslate( ui ) && !IsCurrentlySetting( info ) )
          {
             info?.Reset( text );
-
+            
             // if we already have translation loaded in our _translatios dictionary, simply load it and set text
             string translation;
             if( TryGetTranslation( text, out translation ) )
@@ -520,14 +464,13 @@ namespace XUnity.AutoTranslator.Plugin.Core
                                        SetTranslatedText( ui, translation, info );
                                     }
                                  }
-
-                                 if( translation == null )
+                                 else
                                  {
                                     // Lets try not to spam a service that might not be there...
                                     if( AutoTranslateClient.IsConfigured && _consecutiveErrors < Settings.MaxErrors )
                                     {
-                                       var job = new TranslationJob { UI = ui, UntranslatedText = stabilizedText };
-                                       _unstartedJobs.Add( job );
+                                       var job = GetOrCreateTranslationJobFor( stabilizedText );
+                                       job.Components.Add( ui );
                                     }
                                     else
                                     {
@@ -552,8 +495,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
                      // Lets try not to spam a service that might not be there...
                      if( AutoTranslateClient.IsConfigured && _consecutiveErrors < Settings.MaxErrors )
                      {
-                        var job = new TranslationJob { UntranslatedText = text };
-                        _unstartedJobs.Add( job );
+                        var job = GetOrCreateTranslationJobFor( text );
                      }
                      else
                      {
@@ -581,9 +523,9 @@ namespace XUnity.AutoTranslator.Plugin.Core
       {
          if( currentTries < maxTries ) // shortcircuit
          {
-            var beforeText = GetText( ui );
+            var beforeText = ui.GetText();
             yield return new WaitForSeconds( delay );
-            var afterText = GetText( ui );
+            var afterText = ui.GetText();
 
             if( beforeText == afterText )
             {
@@ -636,28 +578,26 @@ namespace XUnity.AutoTranslator.Plugin.Core
          }
          catch( Exception e )
          {
-            Console.WriteLine( e );
+            Console.WriteLine( "[ERROR][XUnity.AutoTranslator]: An error occurred in Update callback. " + Environment.NewLine + e );
          }
       }
 
+      // create this as a field instead of local var, to prevent new creation on EVERY game loop
+      private readonly List<string> _kickedOff = new List<string>();
+
       private void KickoffTranslations()
       {
-         while( AutoTranslateClient.HasAvailableClients && _unstartedJobs.Count > 0 )
+         foreach( var kvp in _unstartedJobs )
          {
-            var job = _unstartedJobs[ _unstartedJobs.Count - 1 ];
-            _unstartedJobs.RemoveAt( _unstartedJobs.Count - 1 );
+            if( !AutoTranslateClient.HasAvailableClients ) break;
+
+            var key = kvp.Key;
+            var job = kvp.Value;
+            _kickedOff.Add( key );
 
             // lets see if the text should still be translated before kicking anything off
-            if( job.UI != null )
-            {
-               var text = GetText( job.UI ).Trim();
-               if( text != job.UntranslatedText )
-               {
-                  continue; // just ignore this UI component, as the text has already changed anyway (maybe from game, maybe from other plugin)
-               }
-            }
+            if( !job.AnyComponentsStillHasOriginalUntranslatedText() ) continue;
 
-            job.UntranslatedDialogueText = job.UntranslatedText.ChangeToSingleLineForDialogue();
             StartCoroutine( AutoTranslateClient.TranslateByWWW( job.UntranslatedDialogueText, Settings.FromLanguage, Settings.Language, translatedText =>
             {
                _consecutiveErrors = 0;
@@ -667,15 +607,22 @@ namespace XUnity.AutoTranslator.Plugin.Core
                if( !string.IsNullOrEmpty( translatedText ) )
                {
                   AddNewTranslation( Settings.IgnoreWhitespaceInDialogue ? job.UntranslatedDialogueText : job.UntranslatedText, translatedText );
-               }
 
-               _completedJobs.Add( job );
+                  _completedJobs.Add( job );
+               }
             },
             () =>
             {
                _consecutiveErrors++;
             } ) );
          }
+
+         for( int i = 0 ; i < _kickedOff.Count ; i++ )
+         {
+            _unstartedJobs.Remove( _kickedOff[ i ] );
+         }
+
+         _kickedOff.Clear();
       }
 
       private void FinishTranslations()
@@ -687,21 +634,18 @@ namespace XUnity.AutoTranslator.Plugin.Core
                var job = _completedJobs[ i ];
                _completedJobs.RemoveAt( i );
 
-               if( !string.IsNullOrEmpty( job.TranslatedText ) )
+               foreach( var component in job.Components )
                {
-                  if( job.UI != null )
+                  // update the original text, but only if it has not been chaanged already for some reason (could be other translator plugin or game itself)
+                  var text = component.GetText().Trim();
+                  if( text == job.UntranslatedText )
                   {
-                     // update the original text, but only if it has not been chaanged already for some reason (could be other translator plugin or game itself)
-                     var text = GetText( job.UI ).Trim();
-                     if( text == job.UntranslatedText )
-                     {
-                        var info = job.UI.GetTranslationInfo( false );
-                        SetTranslatedText( job.UI, job.TranslatedText, info );
-                     }
+                     var info = component.GetTranslationInfo( false );
+                     SetTranslatedText( component, job.TranslatedText, info );
                   }
-
-                  AddTranslation( job.UntranslatedText, job.TranslatedText );
                }
+
+               AddTranslation( job.UntranslatedText, job.TranslatedText );
             }
          }
       }

+ 53 - 0
src/XUnity.AutoTranslator.Plugin.Core/Extensions/ComponentExtensions.cs

@@ -0,0 +1,53 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using UnityEngine;
+using UnityEngine.UI;
+
+namespace XUnity.AutoTranslator.Plugin.Core.Extensions
+{
+   public static class ComponentExtensions
+   {
+      private static readonly string TextPropertyName = "text";
+
+      public static string GetText( this object ui )
+      {
+         string text = null;
+
+         if( ui is Text )
+         {
+            text = ( (Text)ui ).text;
+         }
+         else if( ui is GUIContent )
+         {
+            text = ( (GUIContent)ui ).text;
+         }
+         else
+         {
+            // fallback to reflective approach
+            text = (string)ui.GetType()?.GetProperty( TextPropertyName )?.GetValue( ui, null );
+         }
+
+         return text ?? string.Empty;
+      }
+
+      public static void SetText( this object ui, string text )
+      {
+         if( ui is Text )
+         {
+            ( (Text)ui ).text = text;
+         }
+         else if( ui is GUIContent )
+         {
+            ( (GUIContent)ui ).text = text;
+         }
+         else
+         {
+            // fallback to reflective approach
+            var type = ui.GetType();
+            type.GetProperty( TextPropertyName )?.GetSetMethod()?.Invoke( ui, new[] { text } );
+         }
+      }
+   }
+}

+ 10 - 19
src/XUnity.AutoTranslator.Plugin.Core/Hooks/IMGUIHooks.cs

@@ -9,8 +9,6 @@ using static UnityEngine.GUI;
 
 namespace XUnity.AutoTranslator.Plugin.Core.IMGUI
 {
-   public delegate void IMGUITextChanged( object text );
-
    public static class IMGUIHooks
    {
       public static readonly Type[] All = new[] {
@@ -25,13 +23,6 @@ namespace XUnity.AutoTranslator.Plugin.Core.IMGUI
          typeof( DoTextFieldHook ),
          typeof( DoToggleHook ),
       };
-
-      public static event IMGUITextChanged TextChanged;
-
-      public static void FireTextChanged( object graphic )
-      {
-         TextChanged?.Invoke( graphic );
-      }
    }
 
    [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
@@ -49,7 +40,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.IMGUI
 
       static void Prefix( GUIContent content )
       {
-         IMGUIHooks.FireTextChanged( content );
+         AutoTranslationPlugin.Current.Hook_TextChanged( content );
       }
    }
 
@@ -68,7 +59,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.IMGUI
 
       static void Prefix( GUIContent content )
       {
-         IMGUIHooks.FireTextChanged( content );
+         AutoTranslationPlugin.Current.Hook_TextChanged( content );
       }
 
    }
@@ -88,7 +79,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.IMGUI
 
       static void Prefix( GUIContent content )
       {
-         IMGUIHooks.FireTextChanged( content );
+         AutoTranslationPlugin.Current.Hook_TextChanged( content );
       }
    }
 
@@ -107,7 +98,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.IMGUI
 
       static void Prefix( GUIContent content )
       {
-         IMGUIHooks.FireTextChanged( content );
+         AutoTranslationPlugin.Current.Hook_TextChanged( content );
       }
    }
 
@@ -126,7 +117,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.IMGUI
 
       static void Prefix( GUIContent content )
       {
-         IMGUIHooks.FireTextChanged( content );
+         AutoTranslationPlugin.Current.Hook_TextChanged( content );
       }
    }
 
@@ -145,7 +136,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.IMGUI
 
       static void Prefix( GUIContent content )
       {
-         IMGUIHooks.FireTextChanged( content );
+         AutoTranslationPlugin.Current.Hook_TextChanged( content );
       }
    }
 
@@ -164,7 +155,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.IMGUI
 
       static void Prefix( GUIContent title )
       {
-         IMGUIHooks.FireTextChanged( title );
+         AutoTranslationPlugin.Current.Hook_TextChanged( title );
       }
    }
 
@@ -185,7 +176,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.IMGUI
       {
          foreach( var content in contents )
          {
-            IMGUIHooks.FireTextChanged( content );
+            AutoTranslationPlugin.Current.Hook_TextChanged( content );
          }
       }
    }
@@ -205,7 +196,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.IMGUI
 
       static void Prefix( GUIContent content )
       {
-         IMGUIHooks.FireTextChanged( content );
+         AutoTranslationPlugin.Current.Hook_TextChanged( content );
       }
    }
 
@@ -224,7 +215,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.IMGUI
 
       static void Prefix( GUIContent content )
       {
-         IMGUIHooks.FireTextChanged( content );
+         AutoTranslationPlugin.Current.Hook_TextChanged( content );
       }
    }
 }

+ 1 - 10
src/XUnity.AutoTranslator.Plugin.Core/Hooks/NGUIHooks.cs

@@ -10,20 +10,11 @@ using XUnity.AutoTranslator.Plugin.Core.Constants;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Hooks.NGUI
 {
-   public delegate void NGUITextChanged( object graphic );
-
    public static class NGUIHooks
    {
       public static readonly Type[] All = new[] {
          typeof( TextPropertyHook )
       };
-
-      public static event NGUITextChanged TextChanged;
-
-      public static void FireTextChanged( object graphic )
-      {
-         TextChanged?.Invoke( graphic );
-      }
    }
 
    [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
@@ -41,7 +32,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.NGUI
 
       public static void Postfix( object __instance )
       {
-         NGUIHooks.FireTextChanged( __instance );
+         AutoTranslationPlugin.Current.Hook_TextChanged( __instance );
       }
    }
 }

+ 9 - 25
src/XUnity.AutoTranslator.Plugin.Core/Hooks/TextMeshProHooks.cs

@@ -8,9 +8,6 @@ using XUnity.AutoTranslator.Plugin.Core.Constants;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
 {
-   public delegate void TextMeshProChanged( object graphic );
-   public delegate void TextMeshProAwakened( object graphic );
-
    public static class TextMeshProHooks
    {
       public static readonly Type[] All = new[] {
@@ -24,19 +21,6 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
          typeof( SetCharArrayHook2 ),
          typeof( SetCharArrayHook3 ),
       };
-
-      public static event TextMeshProChanged TextChanged;
-      public static event TextMeshProAwakened TextAwakened;
-
-      public static void FireTextAwakened( object graphic )
-      {
-         TextAwakened?.Invoke( graphic );
-      }
-
-      public static void FireTextChanged( object graphic )
-      {
-         TextChanged?.Invoke( graphic );
-      }
    }
 
    [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
@@ -54,7 +38,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
 
       static void Postfix( object __instance )
       {
-         TextMeshProHooks.FireTextAwakened( __instance );
+         AutoTranslationPlugin.Current.Hook_TextInitialized( __instance );
       }
    }
 
@@ -73,7 +57,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
 
       static void Postfix( object __instance )
       {
-         TextMeshProHooks.FireTextAwakened( __instance );
+         AutoTranslationPlugin.Current.Hook_TextInitialized( __instance );
       }
    }
 
@@ -92,7 +76,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
 
       static void Postfix( object __instance )
       {
-         TextMeshProHooks.FireTextChanged( __instance );
+         AutoTranslationPlugin.Current.Hook_TextChanged( __instance );
       }
    }
 
@@ -111,7 +95,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
 
       static void Postfix( object __instance )
       {
-         TextMeshProHooks.FireTextChanged( __instance );
+         AutoTranslationPlugin.Current.Hook_TextChanged( __instance );
       }
    }
 
@@ -130,7 +114,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
 
       static void Postfix( object __instance )
       {
-         TextMeshProHooks.FireTextChanged( __instance );
+         AutoTranslationPlugin.Current.Hook_TextChanged( __instance );
       }
    }
 
@@ -149,7 +133,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
 
       static void Postfix( object __instance )
       {
-         TextMeshProHooks.FireTextChanged( __instance );
+         AutoTranslationPlugin.Current.Hook_TextChanged( __instance );
       }
    }
 
@@ -168,7 +152,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
 
       static void Postfix( object __instance )
       {
-         TextMeshProHooks.FireTextChanged( __instance );
+         AutoTranslationPlugin.Current.Hook_TextChanged( __instance );
       }
    }
 
@@ -187,7 +171,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
 
       static void Postfix( object __instance )
       {
-         TextMeshProHooks.FireTextChanged( __instance );
+         AutoTranslationPlugin.Current.Hook_TextChanged( __instance );
       }
    }
 
@@ -206,7 +190,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
 
       static void Postfix( object __instance )
       {
-         TextMeshProHooks.FireTextChanged( __instance );
+         AutoTranslationPlugin.Current.Hook_TextChanged( __instance );
       }
    }
 }

+ 2 - 18
src/XUnity.AutoTranslator.Plugin.Core/Hooks/UGUIHooks.cs

@@ -8,28 +8,12 @@ using XUnity.AutoTranslator.Plugin.Core.Constants;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Hooks.UGUI
 {
-   public delegate void UGUITextChanged( object graphic );
-   public delegate void UGUITextAwakened( object graphic );
-
    public static class UGUIHooks
    {
       public static readonly Type[] All = new[] {
          typeof( TextPropertyHook ),
          typeof( OnEnableHook ),
       };
-
-      public static event UGUITextChanged TextChanged;
-      public static event UGUITextAwakened TextAwakened;
-
-      public static void FireTextAwakened( object graphic )
-      {
-         TextAwakened?.Invoke( graphic );
-      }
-
-      public static void FireTextChanged( object graphic )
-      {
-         TextChanged?.Invoke( graphic );
-      }
    }
 
    [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
@@ -48,7 +32,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.UGUI
 
       static void Postfix( object __instance )
       {
-         UGUIHooks.FireTextChanged( __instance );
+         AutoTranslationPlugin.Current.Hook_TextChanged( __instance );
       }
    }
 
@@ -68,7 +52,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.UGUI
 
       static void Postfix( object __instance )
       {
-         UGUIHooks.FireTextAwakened( __instance );
+         AutoTranslationPlugin.Current.Hook_TextInitialized( __instance );
       }
    }
 }

+ 28 - 3
src/XUnity.AutoTranslator.Plugin.Core/TranslationJob.cs

@@ -4,17 +4,42 @@ using System.Linq;
 using System.Net;
 using System.Text;
 using UnityEngine.UI;
+using XUnity.AutoTranslator.Plugin.Core.Extensions;
 
 namespace XUnity.AutoTranslator.Plugin.Core
 {
    public class TranslationJob
    {
-      public object UI { get; set; }
+      public TranslationJob( string untranslatedText )
+      {
+         UntranslatedText = untranslatedText;
+         UntranslatedDialogueText = untranslatedText.ChangeToSingleLineForDialogue();
 
-      public string UntranslatedText { get; set; }
+         Components = new List<object>();
+      }
 
-      public string UntranslatedDialogueText { get; set; }
+      public List<object> Components { get; private set; }
+
+      public string UntranslatedText { get; private set; }
+
+      public string UntranslatedDialogueText { get; private set; }
 
       public string TranslatedText { get; set; }
+
+      public bool AnyComponentsStillHasOriginalUntranslatedText()
+      {
+         if( Components.Count == 0 ) return true; // we do not know
+
+         foreach( var component in Components )
+         {
+            var text = component.GetText().Trim();
+            if( text == UntranslatedText )
+            {
+               return true;
+            }
+         }
+
+         return false;
+      }
    }
 }