ソースを参照

Update 2.15.0
implemented manual hooking
implemented integration options for other plugins to disable this plugin
generally improved hooking capability to support additional software
fixed rich text bug as it relates to "ruby" and "group" tags

randoman 6 年 前
コミット
8f538c5e61

+ 8 - 1
CHANGELOG.md

@@ -1,4 +1,11 @@
-### 2.14.1
+### 2.15.0
+ * FEATURE - Manual hooking - press ALT + U. This will lookup all game objects and attempt translation immediately
+ * FEATURE - Capability for other plugins to tell the Auto Translator not to translate texts
+ * BUG FIX - Initialization hooking that will attempt to hook any game object that was missed during game initialization
+ * BUG FIX - Minor fixes to handling of rich text to better support tags that should be ignored, such as ruby, group
+ * MISC - Generally better hooking capabilities
+
+### 2.14.1
  * BUG FIX - Never allow text to be queued for translation before stabilization check for rich text 
  * MISC - Improved a spam detection check
 

+ 47 - 7
README.md

@@ -25,7 +25,7 @@ The default configuration file, looks as such (2.6.0+):
 
 ```ini
 [Service]
-Endpoint=GoogleTranslate         ;Endpoint to use. Can be ["GoogleTranslate", "GoogleTranslateLegitimate", "BaiduTranslate", "YandexTranslate", "WatsonTranslate", "ExciteTranslate"]
+Endpoint=GoogleTranslate         ;Endpoint to use. Can be ["GoogleTranslate", "GoogleTranslateLegitimate", "BaiduTranslate", "YandexTranslate", "WatsonTranslate", "ExciteTranslate", ""]. If empty, it simply means: Only use cached translations
 
 [General]
 Language=en                      ;The language to translate into
@@ -91,6 +91,7 @@ The following key inputs are mapped:
  * ALT + T: Alternate between translated and untranslated versions of all texts provided by this plugin.
  * ALT + D: Dump untranslated texts (if no endpoint is configured)
  * ALT + R: Reload translation files. Useful if you change the text files on the fly.
+ * ALT + U: Manual hooking. The default hooks wont always pick up texts. This will attempt to make lookups manually.
 
 ## Installation
 The plugin can be installed in following ways:
@@ -106,7 +107,7 @@ The file structure should like like this:
 {GameDirectory}/BepInEx/XUnity.AutoTranslator.Plugin.Core.dll
 {GameDirectory}/BepInEx/XUnity.AutoTranslator.Plugin.Core.BepInEx.dll
 {GameDirectory}/BepInEx/ExIni.dll
-{GameDirectory}/BepInEx/Translation/AnyTranslationFile.txt (this files will be auto generated by plugin!)
+{GameDirectory}/BepInEx/Translation/AnyTranslationFile.txt (these files will be auto generated by plugin!)
 ```
 
 ### IPA Plugin
@@ -121,7 +122,7 @@ The file structure should like like this
 {GameDirectory}/Plugins/XUnity.AutoTranslator.Plugin.Core.IPA.dll
 {GameDirectory}/Plugins/0Harmony.dll
 {GameDirectory}/Plugins/ExIni.dll
-{GameDirectory}/Plugins/Translation/AnyTranslationFile.txt (this files will be auto generated by plugin!)
+{GameDirectory}/Plugins/Translation/AnyTranslationFile.txt (these files will be auto generated by plugin!)
  ```
 
 ### UnityInjector Plugin
@@ -136,7 +137,7 @@ The file structure should like like this
 {GameDirectory}/UnityInjector/XUnity.AutoTranslator.Plugin.Core.UnityInjector.dll
 {GameDirectory}/UnityInjector/0Harmony.dll
 {GameDirectory}/UnityInjector/ExIni.dll
-{GameDirectory}/UnityInjector/Translation/AnyTranslationFile.txt (this files will be auto generated by plugin!)
+{GameDirectory}/UnityInjector/Translation/AnyTranslationFile.txt (these files will be auto generated by plugin!)
  ```
  
 ### Standalone Installation (ReiPatcher)
@@ -162,16 +163,16 @@ The file structure should like like this
 {GameDirectory}/{GameExeName}_Data/Managed/XUnity.AutoTranslator.Plugin.Core.dll
 {GameDirectory}/{GameExeName}_Data/Managed/0Harmony.dll
 {GameDirectory}/{GameExeName}_Data/Managed/ExIni.dll
-{GameDirectory}/AutoTranslator/AnyTranslationFile.txt (this files will be auto generated by plugin!)
+{GameDirectory}/AutoTranslator/AnyTranslationFile.txt (these files will be auto generated by plugin!)
  ```
 
 ## Translating Mods
 Often other mods UI are implemented through IMGUI. As you can see above, this is disabled by default. By changing the "EnableIMGUI" value to "True", it will start translating IMGUI as well, which likely means that other mods UI will be translated.
 
 ## Integrating with Auto Translator
-I have implemented a system that allows other dedicated translation mods to integrate with XUnity AutoTranslator.
 
-Basically, as a mod author, you are able to, if you cannot find a translation to a string, simply delegate it to this mod, and you can do it without taking any references to this plugin.
+### Implementing a dedicated translation component
+As a mod author implementing a translation plugin, you are able to, if you cannot find a translation to a string, simply delegate it to this mod, and you can do it without taking any references to this plugin.
 
 Here's how it works, and what is required:
  * You must implement a Component (MonoBehaviour for instance) that this plugin is able to locate by simply traversing all objects during startup.
@@ -185,3 +186,42 @@ Here's how it works, and what is required:
     3. NGUI: public static event Func<object, string, string> OnUnableToTranslateNGUI
     3. IMGUI: public static event Func<object, string, string> OnUnableToTranslateIMGUI
  * Also, the events can be either instance based or static.
+
+### Implementing a component that the Auto Translator should not interfere with
+As a mod author, you might not want the Auto Translator to interfere with your mods UI. If this is the case there's two ways to tell Auto Translator not to perform any translation:
+ * If your UI is based on GameObjects, you can simply name your GameObjects containing the text element (for example Text class) to something that contains the string "XUAIGNORE". The Auto Translator will check for this and ignore components that contains the string.
+ * If your UI is based on IMGUI, the above approach is not possible, because there are no GameObject. In that case you can do the following instead:
+
+```C#
+public class MyPlugin : XPluginBase
+{
+   private GameObject _xua;
+   private bool _lookedForXua;
+
+   public void OnGUI()
+   {
+      // make sure we only do this lookup once, as it otherwise may be detrimental to performance!
+      // also: do not attempt to do this in the Awake method or similar of your plugin, as your plugin may be instantiated before the auto translator!
+      if(!_lookedForXua)
+      {
+         _lookedForXua = true;
+         _xua = GameObject.Find( "___XUnityAutoTranslator" );
+      }
+
+      // try-finally block is important to make sure you re-enable the plugin
+      try
+      {
+         _xua?.SendMessage("DisableAutoTranslator");
+
+         // do your GUI things here
+         GUILayout.Button( "こんにちは!" );
+      }
+      finally
+      {
+         _xua?.SendMessage("EnableAutoTranslator");
+      }
+   }
+}
+```
+
+This approach requires version 2.15.0 or later!

+ 1 - 1
src/XUnity.AutoTranslator.Patcher/Patcher.cs

@@ -29,7 +29,7 @@ namespace XUnity.AutoTranslator.Patcher
       {
          get
          {
-            return "2.14.1";
+            return "2.15.0";
          }
       }
 

+ 1 - 1
src/XUnity.AutoTranslator.Plugin.BepIn/XUnity.AutoTranslator.Plugin.BepIn.csproj

@@ -2,7 +2,7 @@
 
    <PropertyGroup>
       <TargetFramework>net35</TargetFramework>
-      <Version>2.14.1</Version>
+      <Version>2.15.0</Version>
    </PropertyGroup>
 
    <ItemGroup>

+ 113 - 42
src/XUnity.AutoTranslator.Plugin.Core/AutoTranslationPlugin.cs

@@ -112,6 +112,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
       private bool _changeFont = false;
       private bool _initialized = false;
+      private bool _temporarilyDisabled = false;
 
       public void Initialize()
       {
@@ -625,18 +626,22 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
       private bool TryGetTranslation( TranslationKey key, out string value )
       {
-         var lookup = key.GetDictionaryLookupKey();
-         var result = _translations.TryGetValue( lookup, out value );
+         return TryGetTranslation( key.GetDictionaryLookupKey(), out value );
+      }
+
+      private bool TryGetTranslation( string key, out string value )
+      {
+         var result = _translations.TryGetValue( key, out value );
          if( result )
          {
             return result;
          }
          else if( _staticTranslations.Count > 0 )
          {
-            if( _staticTranslations.TryGetValue( lookup, out value ) )
+            if( _staticTranslations.TryGetValue( key, out value ) )
             {
-               QueueNewTranslationForDisk( lookup, value );
-               AddTranslation( lookup, value );
+               QueueNewTranslationForDisk( key, value );
+               AddTranslation( key, value );
                return true;
             }
          }
@@ -652,26 +657,18 @@ namespace XUnity.AutoTranslator.Plugin.Core
       {
          if( !ui.IsKnownType() ) return null;
 
-         if( _hooksEnabled )
+         if( _hooksEnabled && !_temporarilyDisabled )
          {
-            return TranslateOrQueueWebJob( ui, text, true );
+            return TranslateOrQueueWebJob( ui, text );
          }
          return null;
       }
 
       public void Hook_TextChanged( object ui )
       {
-         if( _hooksEnabled )
+         if( _hooksEnabled && !_temporarilyDisabled )
          {
-            TranslateOrQueueWebJob( ui, null, false );
-         }
-      }
-
-      public void Hook_TextInitialized( object ui )
-      {
-         if( _hooksEnabled )
-         {
-            TranslateOrQueueWebJob( ui, null, true );
+            TranslateOrQueueWebJob( ui, null );
          }
       }
 
@@ -729,6 +726,8 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
                // NGUI only behaves if you set the text after the resize behaviour
                ui.SetText( text );
+
+               info?.ResetScrollIn( ui );
             }
             catch( TargetInvocationException )
             {
@@ -769,18 +768,25 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
       public bool ShouldTranslate( object ui )
       {
-         var cui = ui as Component;
-         if( cui != null )
+         var component = ui as Component;
+         if( component != null )
          {
-            var go = cui.gameObject;
-            var isDummy = go.IsDummy();
-            if( isDummy )
+            // dummy check
+            var go = component.gameObject;
+            var ignore = go.HasIgnoredName();
+            if( ignore )
             {
                return false;
             }
 
-            var inputField = cui.gameObject.GetFirstComponentInSelfOrAncestor( Constants.Types.InputField )
-               ?? cui.gameObject.GetFirstComponentInSelfOrAncestor( Constants.Types.TMP_InputField );
+            var behaviour = component as Behaviour;
+            if( behaviour?.isActiveAndEnabled == false )
+            {
+               return false;
+            }
+
+            var inputField = component.gameObject.GetFirstComponentInSelfOrAncestor( Constants.Types.InputField )
+               ?? component.gameObject.GetFirstComponentInSelfOrAncestor( Constants.Types.TMP_InputField );
 
             return inputField == null;
          }
@@ -788,13 +794,9 @@ namespace XUnity.AutoTranslator.Plugin.Core
          return true;
       }
 
-      private string TranslateOrQueueWebJob( object ui, string text, bool isAwakening )
+      private string TranslateOrQueueWebJob( object ui, string text )
       {
-         var info = ui.GetTranslationInfo( isAwakening );
-         if( !info?.IsAwake ?? false )
-         {
-            return null;
-         }
+         var info = ui.GetTranslationInfo();
 
          if( _ongoingOperations.Contains( ui ) )
          {
@@ -857,8 +859,15 @@ namespace XUnity.AutoTranslator.Plugin.Core
       /// </summary>
       private string TranslateOrQueueWebJobImmediate( object ui, string text, TranslationInfo info, bool supportsStabilization, TranslationContext context = null )
       {
-         // Get the trimmed text
-         text = ( text ?? ui.GetText() ).TrimIfConfigured();
+         // make sure text exists
+         text = text ?? ui.GetText();
+         if( context == null )
+         {
+            // Get the trimmed text
+            text = text.TrimIfConfigured();
+         }
+
+         //Logger.Current.Debug( ui.GetType().Name + ": " + text );
 
          // Ensure that we actually want to translate this text and its owning UI element. 
          if( !string.IsNullOrEmpty( text ) && IsTranslatable( text ) && ShouldTranslate( ui ) && !IsCurrentlySetting( info ) )
@@ -867,7 +876,6 @@ namespace XUnity.AutoTranslator.Plugin.Core
             var isSpammer = ui.IsSpammingComponent();
             var textKey = new TranslationKey( ui, text, isSpammer, context != null );
 
-
             // if we already have translation loaded in our _translatios dictionary, simply load it and set text
             string translation;
             if( TryGetTranslation( textKey, out translation ) )
@@ -876,7 +884,10 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
                if( !string.IsNullOrEmpty( translation ) )
                {
-                  SetTranslatedText( ui, textKey.Untemplate( translation ), info );
+                  if( context == null ) // never set text if operation is contextualized (only a part translation)
+                  {
+                     SetTranslatedText( ui, textKey.Untemplate( translation ), info );
+                  }
                   return translation;
                }
             }
@@ -890,12 +901,18 @@ namespace XUnity.AutoTranslator.Plugin.Core
                      var result = parser.Parse( text );
                      if( result.HasRichSyntax )
                      {
-                        translation = TranslateOrQueueWebJobImmediateByParserResult( ui, result, false );
+                        var isWhitelisted = ui.IsWhitelistedForImmediateRichTextTranslation();
+
+                        translation = TranslateOrQueueWebJobImmediateByParserResult( ui, result, isWhitelisted );
                         if( translation != null )
                         {
                            SetTranslatedText( ui, translation, info );
                            return translation;
                         }
+                        else if( isWhitelisted )
+                        {
+                           return null;
+                        }
                      }
                   }
                }
@@ -1023,9 +1040,8 @@ namespace XUnity.AutoTranslator.Plugin.Core
             var value = kvp.Value.TrimIfConfigured();
             if( !string.IsNullOrEmpty( value ) && IsTranslatable( value ) )
             {
-               var valueKey = new TranslationKey( ui, value, false, true );
                string partTranslation;
-               if( TryGetTranslation( valueKey, out partTranslation ) )
+               if( TryGetTranslation( value, out partTranslation ) )
                {
                   translations.Add( key, partTranslation );
                }
@@ -1033,7 +1049,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
                {
                   // incomplete, must start job
                   var context = new TranslationContext( ui, result );
-                  TranslateOrQueueWebJobImmediate( null, value, null, false, context );
+                  TranslateOrQueueWebJobImmediate( ui, value, null, false, context );
                }
             }
             else
@@ -1097,7 +1113,16 @@ namespace XUnity.AutoTranslator.Plugin.Core
          if( !_initialized )
          {
             _initialized = true;
-            Initialize();
+
+            try
+            {
+               Initialize();
+               ManualHook();
+            }
+            catch( Exception e )
+            {
+               Logger.Current.Error( e, "An unexpected error occurred during plugin initialization." );
+            }
          }
       }
 
@@ -1148,6 +1173,10 @@ namespace XUnity.AutoTranslator.Plugin.Core
                {
                   ReloadTranslations();
                }
+               else if( ( Input.GetKey( KeyCode.LeftAlt ) || Input.GetKey( KeyCode.RightAlt ) ) && Input.GetKeyDown( KeyCode.U ) )
+               {
+                  ManualHook();
+               }
             }
          }
          catch( Exception e )
@@ -1376,7 +1405,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
                      var text = component.GetText().TrimIfConfigured();
                      if( text == job.Key.OriginalText )
                      {
-                        var info = component.GetTranslationInfo( false );
+                        var info = component.GetTranslationInfo();
                         SetTranslatedText( component, job.TranslatedText, info );
                      }
                   }
@@ -1398,7 +1427,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
                         var text = context.Component.GetText().TrimIfConfigured();
                         var result = context.Result;
                         Dictionary<string, string> translations = new Dictionary<string, string>();
-                        var translatedText = TranslateOrQueueWebJobImmediateByParserResult( null, result, false );
+                        var translatedText = TranslateOrQueueWebJobImmediateByParserResult( context.Component, result, false );
 
                         if( !string.IsNullOrEmpty( translatedText ) )
                         {
@@ -1412,7 +1441,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
                            {
                               if( translatedText != null )
                               {
-                                 var info = context.Component.GetTranslationInfo( false );
+                                 var info = context.Component.GetTranslationInfo();
                                  SetTranslatedText( context.Component, translatedText, info );
                               }
                            }
@@ -1607,6 +1636,14 @@ namespace XUnity.AutoTranslator.Plugin.Core
          }
       }
 
+      private void ManualHook()
+      {
+         foreach( var root in GetAllRoots() )
+         {
+            TraverseChildrenManualHook( root );
+         }
+      }
+
       private IEnumerable<GameObject> GetAllRoots()
       {
          var objects = GameObject.FindObjectsOfType<GameObject>();
@@ -1641,5 +1678,39 @@ namespace XUnity.AutoTranslator.Plugin.Core
             }
          }
       }
+
+      private void TraverseChildrenManualHook( GameObject obj )
+      {
+         if( obj != null )
+         {
+            var components = obj.GetComponents<Component>();
+            foreach( var component in components )
+            {
+               if( component.IsKnownType() )
+               {
+                  Hook_TextChanged( component );
+               }
+            }
+
+            if( obj.transform != null )
+            {
+               for( int i = 0 ; i < obj.transform.childCount ; i++ )
+               {
+                  var child = obj.transform.GetChild( i );
+                  TraverseChildrenManualHook( child.gameObject );
+               }
+            }
+         }
+      }
+
+      public void DisableAutoTranslator()
+      {
+         _temporarilyDisabled = true;
+      }
+
+      public void EnableAutoTranslator()
+      {
+         _temporarilyDisabled = false;
+      }
    }
 }

+ 1 - 1
src/XUnity.AutoTranslator.Plugin.Core/Constants/PluginData.cs

@@ -11,6 +11,6 @@ namespace XUnity.AutoTranslator.Plugin.Core.Constants
 
       public const string Name = "XUnity Auto Translator";
 
-      public const string Version = "2.14.1";
+      public const string Version = "2.15.0";
    }
 }

+ 2 - 0
src/XUnity.AutoTranslator.Plugin.Core/Constants/Types.cs

@@ -23,6 +23,8 @@ namespace XUnity.AutoTranslator.Plugin.Core.Constants
 
       public static readonly Type WWW = FindType( "UnityEngine.WWW" );
 
+      public static readonly Type Typewriter = FindType( "Typewriter" );
+
       public static readonly Type UguiNovelText = FindType( "Utage.UguiNovelText" );
 
       public static readonly Type AdvCommand = FindType( "Utage.AdvCommand" );

+ 3 - 2
src/XUnity.AutoTranslator.Plugin.Core/Extensions/GameObjectExtensions.cs

@@ -9,6 +9,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
    public static class GameObjectExtensions
    {
       private static readonly string DummyName = "Dummy";
+      private static readonly string XuaIgnore = "XUAIGNORE";
 
       public static Component GetFirstComponentInSelfOrAncestor( this GameObject go, Type type )
       {
@@ -30,9 +31,9 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
          return null;
       }
 
-      public static bool IsDummy( this GameObject go )
+      public static bool HasIgnoredName( this GameObject go )
       {
-         return go.name.EndsWith( DummyName ) || go?.transform?.parent?.name.EndsWith( DummyName ) == true;
+         return go.name.EndsWith( DummyName ) || go.name.Contains( XuaIgnore ) || go.transform?.parent?.name.EndsWith( DummyName ) == true;
       }
    }
 }

+ 10 - 3
src/XUnity.AutoTranslator.Plugin.Core/Extensions/ObjectExtensions.cs

@@ -60,6 +60,15 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
          return ui is UnityEngine.GUIContent;
       }
 
+      public static bool IsWhitelistedForImmediateRichTextTranslation( this object ui )
+      {
+         if( ui == null ) return false;
+
+         var type = ui.GetType();
+
+         return Types.AdvCommand != null && Types.AdvCommand.IsAssignableFrom( type );
+      }
+
       public static bool IsNGUI( this object ui )
       {
          if( ui == null ) return false;
@@ -69,7 +78,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
          return Types.UILabel != null && Types.UILabel.IsAssignableFrom( type );
       }
 
-      public static TranslationInfo GetTranslationInfo( this object obj, bool isAwakening )
+      public static TranslationInfo GetTranslationInfo( this object obj )
       {
          if( !Settings.EnableObjectTracking ) return null;
 
@@ -77,8 +86,6 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
 
          var info = obj.Get<TranslationInfo>();
 
-         info.IsAwake = info.IsAwake || isAwakening;
-
          return info;
       }
 

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

@@ -14,7 +14,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.NGUI
    {
       public static readonly Type[] All = new[] {
          typeof( TextPropertyHook ),
-         typeof( OnStartHook )
+         typeof( OnEnableHook )
       };
    }
 
@@ -33,12 +33,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.NGUI
 
       public static void Postfix( object __instance )
       {
-         AutoTranslationPlugin.Current.Hook_TextInitialized( __instance );
+         AutoTranslationPlugin.Current.Hook_TextChanged( __instance );
       }
    }
 
    [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
-   public static class OnStartHook
+   public static class OnEnableHook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -47,12 +47,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.NGUI
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Method( Constants.Types.UILabel, "OnStart" );
+         return AccessTools.Method( Constants.Types.UILabel, "OnEnable" );
       }
 
       public static void Postfix( object __instance )
       {
-         AutoTranslationPlugin.Current.Hook_TextInitialized( __instance );
+         AutoTranslationPlugin.Current.Hook_TextChanged( __instance );
       }
    }
 }

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

@@ -11,8 +11,8 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
    public static class TextMeshProHooks
    {
       public static readonly Type[] All = new[] {
-         typeof( TeshMeshProUGUIAwakeHook ),
-         typeof( TeshMeshProAwakeHook ),
+         typeof( TeshMeshProUGUIOnEnableHook ),
+         typeof( TeshMeshProOnEnableHook ),
          typeof( TextPropertyHook ),
          typeof( SetTextHook1 ),
          typeof( SetTextHook2 ),
@@ -24,7 +24,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
    }
 
    [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
-   public static class TeshMeshProUGUIAwakeHook
+   public static class TeshMeshProUGUIOnEnableHook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -33,17 +33,17 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Method( Types.TextMeshProUGUI, "Awake" );
+         return AccessTools.Method( Types.TextMeshProUGUI, "OnEnable" );
       }
 
       static void Postfix( object __instance )
       {
-         AutoTranslationPlugin.Current.Hook_TextInitialized( __instance );
+         AutoTranslationPlugin.Current.Hook_TextChanged( __instance );
       }
    }
 
    [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
-   public static class TeshMeshProAwakeHook
+   public static class TeshMeshProOnEnableHook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -52,12 +52,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Method( Types.TextMeshPro, "Awake" );
+         return AccessTools.Method( Types.TextMeshPro, "OnEnable" );
       }
 
       static void Postfix( object __instance )
       {
-         AutoTranslationPlugin.Current.Hook_TextInitialized( __instance );
+         AutoTranslationPlugin.Current.Hook_TextChanged( __instance );
       }
    }
 

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

@@ -52,7 +52,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.UGUI
 
       static void Postfix( object __instance )
       {
-         AutoTranslationPlugin.Current.Hook_TextInitialized( __instance );
+         AutoTranslationPlugin.Current.Hook_TextChanged( __instance );
       }
    }
 }

+ 3 - 2
src/XUnity.AutoTranslator.Plugin.Core/Parsing/ParserResult.cs

@@ -4,10 +4,11 @@ namespace XUnity.AutoTranslator.Plugin.Core.Parsing
 {
    public class ParserResult
    {
-      public ParserResult( string originalText, string template, Dictionary<string, string> args )
+      public ParserResult( string originalText, string template, bool hasRichText, Dictionary<string, string> args )
       {
          OriginalText = originalText;
          Template = template;
+         HasRichSyntax = hasRichText;
          Arguments = args;
       }
 
@@ -17,7 +18,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Parsing
 
       public Dictionary<string, string> Arguments { get; private set; }
 
-      public bool HasRichSyntax => Template.Length > 5; // {{A}} <-- 5 chars
+      public bool HasRichSyntax { get; private set; }
 
       public string Untemplate( Dictionary<string, string> arguments )
       {

+ 3 - 4
src/XUnity.AutoTranslator.Plugin.Core/Parsing/RichTextParser.cs

@@ -4,7 +4,6 @@ using System.Text.RegularExpressions;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Parsing
 {
-
    public class RichTextParser
    {
       private static readonly char[] TagNameEnders = new char[] { '=', ' ' };
@@ -94,7 +93,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Parsing
          // catch any remaining text
          if( offset < input.Length )
          {
-            var argument = "{{" + ( arg++ ) + "}}";
+            var argument = "{{" + ( arg ) + "}}";
             var text = input.Substring( offset, input.Length - offset );
             args.Add( argument, text );
             template.Append( argument );
@@ -116,7 +115,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Parsing
 
             var fullText = text1 + text2;
             var fullKey = key1 + key2;
-            var newKey = "{{" + ( ++arg ) + "}}";
+            var newKey = "{{" + ( arg1 ) + "}}";
 
             args.Remove( key1 );
             args.Remove( key2 );
@@ -124,7 +123,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Parsing
             templateString = templateString.Replace( fullKey, newKey );
          }
 
-         return new ParserResult( input, templateString, args );
+         return new ParserResult( input, templateString, arg != 'A', args );
       }
    }
 }

+ 1 - 1
src/XUnity.AutoTranslator.Plugin.Core/PluginLoader.cs

@@ -20,7 +20,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
             _loaded = true;
             Config.Current = config;
 
-            var obj = new GameObject( "Auto Translator" );
+            var obj = new GameObject( "___XUnityAutoTranslator" );
             var instance = obj.AddComponent<AutoTranslationPlugin>();
             GameObject.DontDestroyOnLoad( obj );
          }

+ 27 - 2
src/XUnity.AutoTranslator.Plugin.Core/TranslationInfo.cs

@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
+using Harmony;
 using UnityEngine;
 using UnityEngine.UI;
 using XUnity.AutoTranslator.Plugin.Core.Configuration;
@@ -11,6 +12,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
 {
    public class TranslationInfo
    {
+      private static readonly string OnEnableMethodName = "OnEnable";
       private static readonly string MultiLinePropertyName = "multiLine";
       private static readonly string OverflowMethodPropertyName = "overflowMethod";
       private static readonly string UILabelClassName = "UILabel";
@@ -18,6 +20,9 @@ namespace XUnity.AutoTranslator.Plugin.Core
       private Action<object> _unresize;
       private Action<object> _unfont;
 
+      private bool _hasCheckedTypeWriter;
+      private MonoBehaviour _typewriter;
+
       public TranslationInfo()
       {
       }
@@ -28,10 +33,30 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
       public bool IsTranslated { get; set; }
 
-      public bool IsAwake { get; set; }
-
       public bool IsCurrentlySettingText { get; set; }
 
+      public void ResetScrollIn( object graphic )
+      {
+         if( !_hasCheckedTypeWriter )
+         {
+            _hasCheckedTypeWriter = true;
+
+            if( Constants.Types.Typewriter != null )
+            {
+               var ui = graphic as Component;
+               if( ui != null )
+               {
+                  _typewriter = (MonoBehaviour)ui.GetComponent( Constants.Types.Typewriter );
+               }
+            }
+         }
+
+         if( _typewriter != null )
+         {
+            AccessTools.Method( Constants.Types.Typewriter, "OnEnable" )?.Invoke( _typewriter, null );
+         }
+      }
+
       public void ChangeFont( object graphic )
       {
          if( graphic == null ) return;

+ 1 - 1
src/XUnity.AutoTranslator.Plugin.Core/XUnity.AutoTranslator.Plugin.Core.csproj

@@ -2,7 +2,7 @@
 
    <PropertyGroup>
       <TargetFramework>net35</TargetFramework>
-      <Version>2.14.1</Version>
+      <Version>2.15.0</Version>
    </PropertyGroup>
 
    <ItemGroup>

+ 1 - 1
src/XUnity.AutoTranslator.Plugin.IPA/XUnity.AutoTranslator.Plugin.IPA.csproj

@@ -2,7 +2,7 @@
 
    <PropertyGroup>
       <TargetFramework>net35</TargetFramework>
-      <Version>2.14.1</Version>
+      <Version>2.15.0</Version>
    </PropertyGroup>
 
    <ItemGroup>

+ 1 - 1
src/XUnity.AutoTranslator.Plugin.UnityInjector/XUnity.AutoTranslator.Plugin.UnityInjector.csproj

@@ -2,7 +2,7 @@
 
    <PropertyGroup>
       <TargetFramework>net35</TargetFramework>
-      <Version>2.14.1</Version>
+      <Version>2.15.0</Version>
    </PropertyGroup>
 
    <ItemGroup>

+ 1 - 1
src/XUnity.AutoTranslator.Setup/XUnity.AutoTranslator.Setup.csproj

@@ -4,7 +4,7 @@
       <OutputType>Exe</OutputType>
       <TargetFramework>net40</TargetFramework>
       <AssemblyName>SetupReiPatcherAndAutoTranslator</AssemblyName>
-      <Version>2.14.1</Version>
+      <Version>2.15.0</Version>
    </PropertyGroup>
 
    <ItemGroup>