Quellcode durchsuchen

work on EC fixes

randoman vor 6 Jahren
Ursprung
Commit
e9d4dc11d3
23 geänderte Dateien mit 441 neuen und 145 gelöschten Zeilen
  1. 6 1
      CHANGELOG.md
  2. 12 1
      XUnity.AutoTranslator.sln
  3. 1 1
      src/XUnity.AutoTranslator.Patcher/Patcher.cs
  4. 1 1
      src/XUnity.AutoTranslator.Plugin.BepIn-5x/XUnity.AutoTranslator.Plugin.BepIn-5x.csproj
  5. 1 1
      src/XUnity.AutoTranslator.Plugin.BepIn/XUnity.AutoTranslator.Plugin.BepIn.csproj
  6. 73 26
      src/XUnity.AutoTranslator.Plugin.Core/AutoTranslationPlugin.cs
  7. 1 1
      src/XUnity.AutoTranslator.Plugin.Core/Constants/PluginData.cs
  8. 20 15
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/TranslationEndpointManager.cs
  9. 1 2
      src/XUnity.AutoTranslator.Plugin.Core/Extensions/GameObjectExtensions.cs
  10. 40 10
      src/XUnity.AutoTranslator.Plugin.Core/Extensions/StringExtensions.cs
  11. 7 0
      src/XUnity.AutoTranslator.Plugin.Core/Properties/AssemblyInfo.cs
  12. 61 0
      src/XUnity.AutoTranslator.Plugin.Core/RegexTranslation.cs
  13. 74 8
      src/XUnity.AutoTranslator.Plugin.Core/TextTranslationCache.cs
  14. 2 2
      src/XUnity.AutoTranslator.Plugin.Core/TranslationJob.cs
  15. 0 71
      src/XUnity.AutoTranslator.Plugin.Core/TranslationKey.cs
  16. 73 0
      src/XUnity.AutoTranslator.Plugin.Core/UntranslatedTextInfo.cs
  17. 1 2
      src/XUnity.AutoTranslator.Plugin.Core/XUnity.AutoTranslator.Plugin.Core.csproj
  18. 1 1
      src/XUnity.AutoTranslator.Plugin.IPA/XUnity.AutoTranslator.Plugin.IPA.csproj
  19. 1 1
      src/XUnity.AutoTranslator.Plugin.UnityInjector/XUnity.AutoTranslator.Plugin.UnityInjector.csproj
  20. 1 1
      src/XUnity.AutoTranslator.Setup/XUnity.AutoTranslator.Setup.csproj
  21. 19 0
      test/XUnity.AutoTranslator.Plugin.Core.Tests/RegexTranslationTests.cs
  22. 25 0
      test/XUnity.AutoTranslator.Plugin.Core.Tests/StringExtensionTests.cs
  23. 20 0
      test/XUnity.AutoTranslator.Plugin.Core.Tests/XUnity.AutoTranslator.Plugin.Core.Tests.csproj

+ 6 - 1
CHANGELOG.md

@@ -1,4 +1,9 @@
-### 3.2.0
+### 3.3.0
+ * FEATURE - Support TARC regex formatting in translation files
+ * MISC - The text trimming process will now maintain all prepended newlines regardless of the translator used
+ * BUG FIX - Allow hooking of text with components named 'Dummy'
+
+### 3.2.0
  * FEATURE - BepInEx 5.x plugin support
  * CHANGE - Restructured large portions of the internal code to support more features going forward
  * BUG FIX - Interacting with UI now blocks input to game

+ 12 - 1
XUnity.AutoTranslator.sln

@@ -83,7 +83,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XUnity.RuntimeHooker.Consol
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XUnity.RuntimeHooker.Benchmark", "test\XUnity.RuntimeHooker.Benchmark\XUnity.RuntimeHooker.Benchmark.csproj", "{E2F50278-9134-4DC8-9C50-4C0A52063A89}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XUnity.AutoTranslator.Plugin.BepIn-5x", "src\XUnity.AutoTranslator.Plugin.BepIn-5x\XUnity.AutoTranslator.Plugin.BepIn-5x.csproj", "{ADCCF172-7D31-42C6-B9D4-1779EAC8B403}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XUnity.AutoTranslator.Plugin.BepIn-5x", "src\XUnity.AutoTranslator.Plugin.BepIn-5x\XUnity.AutoTranslator.Plugin.BepIn-5x.csproj", "{ADCCF172-7D31-42C6-B9D4-1779EAC8B403}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XUnity.AutoTranslator.Plugin.Core.Tests", "test\XUnity.AutoTranslator.Plugin.Core.Tests\XUnity.AutoTranslator.Plugin.Core.Tests.csproj", "{4E1A0EB3-563A-419A-BE9D-5870A1A44CAD}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -301,6 +303,14 @@ Global
 		{ADCCF172-7D31-42C6-B9D4-1779EAC8B403}.Release|Any CPU.Build.0 = Release|Any CPU
 		{ADCCF172-7D31-42C6-B9D4-1779EAC8B403}.Release|x86.ActiveCfg = Release|Any CPU
 		{ADCCF172-7D31-42C6-B9D4-1779EAC8B403}.Release|x86.Build.0 = Release|Any CPU
+		{4E1A0EB3-563A-419A-BE9D-5870A1A44CAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{4E1A0EB3-563A-419A-BE9D-5870A1A44CAD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{4E1A0EB3-563A-419A-BE9D-5870A1A44CAD}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{4E1A0EB3-563A-419A-BE9D-5870A1A44CAD}.Debug|x86.Build.0 = Debug|Any CPU
+		{4E1A0EB3-563A-419A-BE9D-5870A1A44CAD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{4E1A0EB3-563A-419A-BE9D-5870A1A44CAD}.Release|Any CPU.Build.0 = Release|Any CPU
+		{4E1A0EB3-563A-419A-BE9D-5870A1A44CAD}.Release|x86.ActiveCfg = Release|Any CPU
+		{4E1A0EB3-563A-419A-BE9D-5870A1A44CAD}.Release|x86.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -333,6 +343,7 @@ Global
 		{20E57B16-F3C7-4D80-88FC-09D6C5A4CCC3} = {2A4A3DDF-338C-40C0-8E26-2A810BAAADD6}
 		{E2F50278-9134-4DC8-9C50-4C0A52063A89} = {2A4A3DDF-338C-40C0-8E26-2A810BAAADD6}
 		{ADCCF172-7D31-42C6-B9D4-1779EAC8B403} = {0F9B38FC-4E57-4B83-AF0B-0993B8470823}
+		{4E1A0EB3-563A-419A-BE9D-5870A1A44CAD} = {2A4A3DDF-338C-40C0-8E26-2A810BAAADD6}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {EE803FED-4447-4D19-B3D6-88C56E8DFCCA}

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

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

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

@@ -2,7 +2,7 @@
 
   <PropertyGroup>
     <TargetFramework>net35</TargetFramework>
-    <Version>3.2.0</Version>
+    <Version>3.3.0</Version>
   </PropertyGroup>
 
   <ItemGroup>

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

@@ -2,7 +2,7 @@
 
    <PropertyGroup>
       <TargetFramework>net35</TargetFramework>
-      <Version>3.2.0</Version>
+      <Version>3.3.0</Version>
    </PropertyGroup>
 
    <ItemGroup>

+ 73 - 26
src/XUnity.AutoTranslator.Plugin.Core/AutoTranslationPlugin.cs

@@ -253,7 +253,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
          if( TranslationManager.CurrentEndpoint != endpoint )
          {
             TranslationManager.CurrentEndpoint = endpoint;
-            
+
             if( TranslationManager.CurrentEndpoint != null )
             {
                if( !Settings.IsShutdown )
@@ -371,12 +371,12 @@ namespace XUnity.AutoTranslator.Plugin.Core
          TextureCache.LoadTranslationFiles();
       }
 
-      private void CreateTranslationJobFor( TranslationEndpointManager endpoint, object ui, TranslationKey key, TranslationResult translationResult, ParserTranslationContext context )
+      private void CreateTranslationJobFor( TranslationEndpointManager endpoint, object ui, UntranslatedTextInfo key, TranslationResult translationResult, ParserTranslationContext context )
       {
          var added = endpoint.EnqueueTranslation( ui, key, translationResult, context );
          if( added && translationResult == null )
          {
-            SpamChecker.PerformChecks( key.GetDictionaryLookupKey() );
+            SpamChecker.PerformChecks( key.GetUntranslatedText() );
          }
       }
 
@@ -429,14 +429,14 @@ namespace XUnity.AutoTranslator.Plugin.Core
          }
       }
 
-      private void QueueNewUntranslatedForClipboard( TranslationKey key )
+      private void QueueNewUntranslatedForClipboard( UntranslatedTextInfo key )
       {
          if( Settings.CopyToClipboard && Features.SupportsClipboard )
          {
-            if( !_textsToCopyToClipboard.Contains( key.RelevantText ) )
+            if( !_textsToCopyToClipboard.Contains( key.UntranslatedText ) )
             {
-               _textsToCopyToClipboard.Add( key.RelevantText );
-               _textsToCopyToClipboardOrdered.Add( key.RelevantText );
+               _textsToCopyToClipboard.Add( key.UntranslatedText );
+               _textsToCopyToClipboardOrdered.Add( key.UntranslatedText );
 
                _clipboardUpdated = Time.realtimeSinceStartup;
             }
@@ -585,7 +585,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
                // NGUI only behaves if you set the text after the resize behaviour
                ui.SetText( text );
-
+               
                info?.ResetScrollIn( ui );
 
                if( TranslationAggregatorWindow != null && info != null && !ui.IsSpammingComponent() )
@@ -935,18 +935,21 @@ namespace XUnity.AutoTranslator.Plugin.Core
       private string TranslateImmediate( object ui, string text, TextTranslationInfo info, bool ignoreComponentState )
       {
          // Get the trimmed text
+         string originalText = text;
+
          text = ( text ?? ui.GetText() ).TrimIfConfigured();
 
          if( !string.IsNullOrEmpty( text ) && TextCache.IsTranslatable( text ) && ShouldTranslateTextComponent( ui, ignoreComponentState ) && !IsCurrentlySetting( info ) )
          {
-            info?.Reset( text );
+            info?.Reset( originalText );
 
             //var textKey = new TranslationKey( ui, text, !ui.SupportsStabilization(), false );
-            var textKey = new TranslationKey( ui, text, ui.IsSpammingComponent(), false );
+            var isSpammer = ui.IsSpammingComponent();
+            var textKey = GetUntranslatedTextInfo( ui, text, isSpammer, false );
 
             // if we already have translation loaded in our _translatios dictionary, simply load it and set text
             string translation;
-            if( TextCache.TryGetTranslation( textKey, out translation ) )
+            if( TextCache.TryGetTranslation( textKey, false, out translation ) )
             {
                if( !string.IsNullOrEmpty( translation ) )
                {
@@ -994,7 +997,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
          // Ensure that we actually want to translate this text and its owning UI element.
          if( !string.IsNullOrEmpty( text ) && endpoint.IsTranslatable( text ) && IsBelowMaxLength( text ) )
          {
-            var textKey = new TranslationKey( null, text, false, context != null );
+            var textKey = GetUntranslatedTextInfo( null, text, false, context != null );
 
             // if we already have translation loaded in our _translatios dictionary, simply load it and set text
             string translation;
@@ -1117,11 +1120,11 @@ namespace XUnity.AutoTranslator.Plugin.Core
             if( isSpammer && !IsBelowMaxLength( text ) ) return null; // avoid templating long strings every frame for IMGUI, important!
 
             //var textKey = new TranslationKey( ui, text, !supportsStabilization, context != null );
-            var textKey = new TranslationKey( ui, text, isSpammer, context != null );
+            var textKey = GetUntranslatedTextInfo( ui, text, isSpammer, context != null );
 
             // if we already have translation loaded in our _translatios dictionary, simply load it and set text
             string translation;
-            if( TextCache.TryGetTranslation( textKey, out translation ) )
+            if( TextCache.TryGetTranslation( textKey, !isSpammer, out translation ) )
             {
                if( context == null && !isSpammer )
                {
@@ -1210,14 +1213,14 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
                               if( !string.IsNullOrEmpty( stabilizedText ) && TextCache.IsTranslatable( stabilizedText ) )
                               {
-                                 var stabilizedTextKey = new TranslationKey( ui, stabilizedText, false );
+                                 var stabilizedTextKey = GetUntranslatedTextInfo( ui, stabilizedText, false, false );
 
                                  QueueNewUntranslatedForClipboard( stabilizedTextKey );
 
                                  info?.Reset( originalText );
 
                                  // once the text has stabilized, attempt to look it up
-                                 if( TextCache.TryGetTranslation( stabilizedTextKey, out translation ) )
+                                 if( TextCache.TryGetTranslation( stabilizedTextKey, true, out translation ) )
                                  {
                                     if( !string.IsNullOrEmpty( translation ) )
                                     {
@@ -1311,7 +1314,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
                                  // once the text has stabilized, attempt to look it up
                                  if( !Settings.IsShutdown && !endpoint.HasFailedDueToConsecutiveErrors )
                                  {
-                                    if( !TextCache.TryGetTranslation( textKey, out translation ) )
+                                    if( !TextCache.TryGetTranslation( textKey, true, out translation ) )
                                     {
                                        CreateTranslationJobFor( endpoint, ui, textKey, null, context );
                                     }
@@ -1339,7 +1342,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
             if( !string.IsNullOrEmpty( untranslatedTextPart ) && TextCache.IsTranslatable( untranslatedTextPart ) && IsBelowMaxLength( untranslatedTextPart ) )
             {
                string partTranslation;
-               if( TextCache.TryGetTranslation( untranslatedTextPart, out partTranslation ) )
+               if( TextCache.TryGetTranslation( untranslatedTextPart, false, out partTranslation ) )
                {
                   translations.Add( variableName, partTranslation );
                }
@@ -1406,9 +1409,9 @@ namespace XUnity.AutoTranslator.Plugin.Core
       /// for global text, where the component cannot tell us if the text
       /// has changed itself.
       /// </summary>
-      private IEnumerator WaitForTextStablization( TranslationKey textKey, float delay, Action onTextStabilized, Action onFailed = null )
+      private IEnumerator WaitForTextStablization( UntranslatedTextInfo textKey, float delay, Action onTextStabilized, Action onFailed = null )
       {
-         var text = textKey.GetDictionaryLookupKey();
+         var text = textKey.GetUntranslatedText();
 
          if( !_immediatelyTranslating.Contains( text ) )
          {
@@ -1510,7 +1513,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
             {
                ConnectionTrackingWebClient.CheckServicePoints();
             }
-            
+
             if( Input.anyKey )
             {
                var isAltPressed = Input.GetKey( KeyCode.LeftAlt ) || Input.GetKey( KeyCode.RightAlt );
@@ -1700,7 +1703,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
             try
             {
                var text = component.GetText().TrimIfConfigured();
-               if( text == job.Key.OriginalText )
+               if( text == job.Key.TrimmedOriginalText )
                {
                   var info = component.GetOrCreateTextTranslationInfo();
                   if( !string.IsNullOrEmpty( job.TranslatedText ) )
@@ -1787,6 +1790,33 @@ namespace XUnity.AutoTranslator.Plugin.Core
          }
       }
 
+      private static string GetCacheKey( object ui, string trimmedOriginalText, bool neverRemoveWhitespace )
+      {
+         if( !neverRemoveWhitespace
+            && ( ( Settings.IgnoreWhitespaceInDialogue && trimmedOriginalText.Length > Settings.MinDialogueChars ) || ( Settings.IgnoreWhitespaceInNGUI && ui.IsNGUI() ) ) )
+         {
+            return trimmedOriginalText.RemoveWhitespaceAndNewlines();
+         }
+         else
+         {
+            return trimmedOriginalText;
+         }
+      }
+
+      private static UntranslatedTextInfo GetUntranslatedTextInfo( object ui, string trimmedOriginalText, bool templatizeByNumbers, bool neverRemoveWhitespace )
+      {
+         var untranslatedTextKey = GetCacheKey( ui, trimmedOriginalText, neverRemoveWhitespace );
+         var untranslatedText = untranslatedTextKey.TrimLeadingNewlines( out int count );
+
+         TemplatedString templatedText = null;
+         if( templatizeByNumbers )
+         {
+            templatedText = untranslatedText.TemplatizeByNumbers();
+         }
+
+         return new UntranslatedTextInfo( trimmedOriginalText, untranslatedTextKey, untranslatedText, count, templatedText );
+      }
+
       private void ReloadTranslations()
       {
          LoadTranslations();
@@ -1802,10 +1832,11 @@ namespace XUnity.AutoTranslator.Plugin.Core
                   if( component.gameObject?.activeSelf ?? false )
                   {
                      var tti = kvp.Value as TextTranslationInfo;
-                     if( tti != null && !string.IsNullOrEmpty( tti.OriginalText ) )
+                     var trimmedOriginalText = tti.OriginalText.TrimIfConfigured();
+                     if( tti != null && !string.IsNullOrEmpty( trimmedOriginalText ) )
                      {
-                        var key = new TranslationKey( kvp.Key, tti.OriginalText, false );
-                        if( TextCache.TryGetTranslation( key, out string translatedText ) && !string.IsNullOrEmpty( translatedText ) )
+                        var key = GetCacheKey( kvp.Key, trimmedOriginalText, false );
+                        if( TextCache.TryGetTranslation( key, true, out string translatedText ) && !string.IsNullOrEmpty( translatedText ) )
                         {
                            SetTranslatedText( kvp.Key, translatedText, tti ); // no need to untemplatize the translated text
                         }
@@ -2056,7 +2087,23 @@ namespace XUnity.AutoTranslator.Plugin.Core
          if( obj != null )
          {
             var layer = LayerMask.LayerToName( obj.layer );
-            var components = string.Join( ", ", obj.GetComponents<Component>().Select( x => x?.GetType()?.Name ).Where( x => x != null ).ToArray() );
+            var components = string.Join( ", ", obj.GetComponents<Component>().Select( x =>
+            {
+               string output = null;
+               var type = x?.GetType();
+               if( type != null )
+               {
+                  output = type.Name;
+
+                  var text = x.GetText();
+                  if( !string.IsNullOrEmpty( text ) )
+                  {
+                     output += " (" + text + ")";
+                  }
+               }
+
+               return output;
+            } ).Where( x => x != null ).ToArray() );
             var line = string.Format( "{0,-50} {1,100}",
                identation + obj.name + " [" + layer + "]",
                components );

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

@@ -23,6 +23,6 @@ namespace XUnity.AutoTranslator.Plugin.Core.Constants
       /// <summary>
       /// Gets the version of the plugin.
       /// </summary>
-      public const string Version = "3.2.0";
+      public const string Version = "3.3.0";
    }
 }

+ 20 - 15
src/XUnity.AutoTranslator.Plugin.Core/Endpoints/TranslationEndpointManager.cs

@@ -61,9 +61,9 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
 
       public bool HasFailedDueToConsecutiveErrors => ConsecutiveErrors >= Settings.MaxErrors;
 
-      public bool TryGetTranslation( TranslationKey key, out string value )
+      public bool TryGetTranslation( UntranslatedTextInfo key, out string value )
       {
-         return TryGetTranslation( key.GetDictionaryLookupKey(), out value );
+         return TryGetTranslation( key.GetCacheKey(), out value );
       }
 
       public bool TryGetTranslation( string key, out string value )
@@ -82,7 +82,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
          // FIXME: Implement
       }
 
-      public void AddTranslationToCache( TranslationKey key, string value )
+      public void AddTranslationToCache( UntranslatedTextInfo key, string value )
       {
          // UNRELEASED: Not included in current release
          //AddTranslationToCache( key.GetDictionaryLookupKey(), value );
@@ -128,7 +128,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
                _unstartedJobs.Remove( key );
                Manager.UnstartedTranslations--;
 
-               var untranslatedText = job.Key.GetDictionaryLookupKey();
+               var untranslatedText = job.Key.GetUntranslatedText();
                if( CanTranslate( untranslatedText ) )
                {
                   jobs.Add( job );
@@ -185,7 +185,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
             _unstartedJobs.Remove( key );
             Manager.UnstartedTranslations--;
 
-            var untranslatedText = job.Key.GetDictionaryLookupKey();
+            var untranslatedText = job.Key.GetUntranslatedText();
             if( CanTranslate( untranslatedText ) )
             {
                _ongoingJobs[ key ] = job;
@@ -233,9 +233,9 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
                job.TranslatedText = PostProcessTranslation( job.Key, translatedText );
                job.State = TranslationJobState.Succeeded;
 
-               RemoveOngoingTranslation( job.Key.GetDictionaryLookupKey() );
+               RemoveOngoingTranslation( job.Key.GetUntranslatedText() );
 
-               XuaLogger.Current.Info( $"Completed: '{job.Key.GetDictionaryLookupKey()}' => '{job.TranslatedText}'" );
+               XuaLogger.Current.Info( $"Completed: '{job.Key.GetUntranslatedText()}' => '{job.TranslatedText}'" );
 
                Manager.InvokeJobCompleted( job );
             }
@@ -252,7 +252,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
             {
                var job = jobs[ i ];
 
-               var key = job.Key.GetDictionaryLookupKey();
+               var key = job.Key.GetUntranslatedText();
                AddUnstartedJob( key, job );
                RemoveOngoingTranslation( key );
             }
@@ -270,14 +270,14 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
          job.TranslatedText = PostProcessTranslation( job.Key, translatedText );
          job.State = TranslationJobState.Succeeded;
 
-         RemoveOngoingTranslation( job.Key.GetDictionaryLookupKey() );
+         RemoveOngoingTranslation( job.Key.GetUntranslatedText() );
 
-         XuaLogger.Current.Info( $"Completed: '{job.Key.GetDictionaryLookupKey()}' => '{job.TranslatedText}'" );
+         XuaLogger.Current.Info( $"Completed: '{job.Key.GetUntranslatedText()}' => '{job.TranslatedText}'" );
 
          Manager.InvokeJobCompleted( job );
       }
 
-      private string PostProcessTranslation( TranslationKey key, string translatedText )
+      private string PostProcessTranslation( UntranslatedTextInfo key, string translatedText )
       {
          var hasTranslation = !string.IsNullOrEmpty( translatedText );
          if( hasTranslation )
@@ -297,6 +297,11 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
             {
                translatedText = translatedText.SplitToLines( Settings.ForceSplitTextAfterCharacters, '\n', ' ', ' ' );
             }
+
+            if( key.PrependedNewlines > 0 && key.TemplatedText == null )
+            {
+               translatedText = new string( '\n', key.PrependedNewlines ) + translatedText;
+            }
          }
 
          return translatedText;
@@ -317,7 +322,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
          {
             foreach( var job in jobs )
             {
-               var untranslatedText = job.Key.GetDictionaryLookupKey();
+               var untranslatedText = job.Key.GetUntranslatedText();
                job.State = TranslationJobState.Failed;
                job.ErrorMessage = error;
 
@@ -340,7 +345,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
             {
                var job = jobs[ i ];
 
-               var key = job.Key.GetDictionaryLookupKey();
+               var key = job.Key.GetUntranslatedText();
                AddUnstartedJob( key, job );
                RemoveOngoingTranslation( key );
             }
@@ -370,9 +375,9 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
          XuaLogger.Current.Info( "Re-enabled batching." );
       }
 
-      public bool EnqueueTranslation( object ui, TranslationKey key, TranslationResult translationResult, ParserTranslationContext context )
+      public bool EnqueueTranslation( object ui, UntranslatedTextInfo key, TranslationResult translationResult, ParserTranslationContext context )
       {
-         var lookupKey = key.GetDictionaryLookupKey();
+         var lookupKey = key.GetUntranslatedText();
 
          var added = AssociateWithExistingJobIfPossible( ui, lookupKey, translationResult, context );
          if( added )

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

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

+ 40 - 10
src/XUnity.AutoTranslator.Plugin.Core/Extensions/StringExtensions.cs

@@ -60,8 +60,9 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
       };
       private static readonly HashSet<char> InvalidFileNameChars = new HashSet<char>( Path.GetInvalidFileNameChars() );
 
-      private static readonly char[] NewlinesCharacters = new char[] { '\r', '\n' };
+      private static readonly string[] NewlinesCharacters = new string[] { "\r\n", "\n" };
       private static readonly char[] WhitespacesAndNewlines = new char[] { '\r', '\n', ' ', ' ' };
+      private static readonly char[] Spaces = new char[] { ' ', ' ' };
 
       public static string SanitizeForFileSystem( this string path )
       {
@@ -83,7 +84,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
          StringBuilder carg = null;
          char arg = 'A';
 
-         for( int i = 0 ; i < str.Length ; i++ )
+         for( int i = 0; i < str.Length; i++ )
          {
             var c = str[ i ];
             if( isNumber )
@@ -206,17 +207,36 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
 
          if( Settings.TrimAllText )
          {
-            return text.Trim();
+            return text.Trim( Spaces ).TrimEnd( WhitespacesAndNewlines );
          }
          return text;
       }
 
+      public static string TrimLeadingNewlines( this string text, out int newlineCount )
+      {
+         int i = 0;
+         int count = 0;
+         while( i < text.Length && char.IsWhiteSpace( text[ i ] ) )
+         {
+            if( i < text.Length && text[ i ] == '\n' )
+            {
+               count++;
+            }
+
+            i++;
+         }
+         newlineCount = count;
+
+         text = text.Substring( i, text.Length - i );
+         return text;
+      }
+
       public static string RemoveWhitespaceAndNewlines( this string text )
       {
          var builder = new StringBuilder( text.Length );
          if( Settings.WhitespaceRemovalStrategy == WhitespaceHandlingStrategy.AllOccurrences )
          {
-            for( int i = 0 ; i < text.Length ; i++ )
+            for( int i = 0; i < text.Length; i++ )
             {
                var c = text[ i ];
                switch( c )
@@ -234,15 +254,25 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
          }
          else // if( Settings.WhitespaceHandlingStrategy == WhitespaceHandlingStrategy.TrimPerNewline )
          {
-            var lines = text.Split( NewlinesCharacters, StringSplitOptions.RemoveEmptyEntries );
+            var lines = text.Split( NewlinesCharacters, StringSplitOptions.None );
             var lastLine = lines.Length - 1;
-            for( int i = 0 ; i < lines.Length ; i++ )
+            bool hasAddedText = false;
+            for( int i = 0; i < lines.Length; i++ )
             {
                var line = lines[ i ].Trim( WhitespacesAndNewlines );
-               for( int j = 0 ; j < line.Length ; j++ )
+               if( line != string.Empty )
+               {
+                  for( int j = 0; j < line.Length; j++ )
+                  {
+                     hasAddedText = true;
+
+                     var c = line[ j ];
+                     builder.Append( c );
+                  }
+               }
+               else if( !hasAddedText )
                {
-                  var c = line[ j ];
-                  builder.Append( c );
+                  builder.Append( "\n" );
                }
 
                // do we need to add a space when merging lines?
@@ -260,7 +290,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
          var len = Math.Min( str.Length, prefix.Length );
          if( len < prefix.Length ) return false;
 
-         for( int i = 0 ; i < len ; i++ )
+         for( int i = 0; i < len; i++ )
          {
             if( str[ i ] != prefix[ i ] ) return false;
          }

+ 7 - 0
src/XUnity.AutoTranslator.Plugin.Core/Properties/AssemblyInfo.cs

@@ -0,0 +1,7 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+
+[assembly: InternalsVisibleTo( "XUnity.AutoTranslator.Plugin.Core.Tests" )]

+ 61 - 0
src/XUnity.AutoTranslator.Plugin.Core/RegexTranslation.cs

@@ -0,0 +1,61 @@
+using System;
+using System.Text.RegularExpressions;
+
+namespace XUnity.AutoTranslator.Plugin.Core
+{
+   class RegexTranslation
+   {
+      public RegexTranslation( string key, string value )
+      {
+         // remove r:
+         if( key.StartsWith( "r:" ) )
+         {
+            key = key.Substring( 2, key.Length - 2 );
+         }
+
+         var startIdx = key.IndexOf( '"' ) + 1;
+         if( startIdx == -1 )
+         {
+            // take entire string
+         }
+         else
+         {
+            var endIdx = key.LastIndexOf( '"', key.Length - 1 );
+            if( endIdx != startIdx )
+            {
+               key = key.Substring( startIdx, endIdx - startIdx );
+            }
+         }
+
+         // remove r:
+         if( value.StartsWith( "r:" ) )
+         {
+            value = value.Substring( 2, value.Length - 2 );
+         }
+
+         startIdx = value.IndexOf( '"' ) + 1;
+         if( startIdx == -1 )
+         {
+            // take entire string
+         }
+         else
+         {
+            var endIdx = value.LastIndexOf( '"', value.Length - 1 );
+            if( endIdx != startIdx )
+            {
+               value = value.Substring( startIdx, endIdx - startIdx );
+            }
+         }
+
+         CompiledRegex = new Regex( key );
+         Original = key;
+         Translation = value;
+      }
+
+      public Regex CompiledRegex { get; set; }
+
+      public string Original { get; set; }
+
+      public string Translation { get; set; }
+   }
+}

+ 74 - 8
src/XUnity.AutoTranslator.Plugin.Core/TextTranslationCache.cs

@@ -21,6 +21,9 @@ namespace XUnity.AutoTranslator.Plugin.Core
       private Dictionary<string, string> _translations = new Dictionary<string, string>();
       private Dictionary<string, string> _reverseTranslations = new Dictionary<string, string>();
 
+      private List<RegexTranslation> _defaultRegexes = new List<RegexTranslation>();
+      private HashSet<string> _registeredRegexes = new HashSet<string>();
+
       /// <summary>
       /// These are the new translations that has not yet been persisted to the file system.
       /// </summary>
@@ -48,6 +51,9 @@ namespace XUnity.AutoTranslator.Plugin.Core
                Directory.CreateDirectory( Path.Combine( PluginEnvironment.Current.TranslationPath, Settings.TranslationDirectory ).Parameterize() );
                Directory.CreateDirectory( Path.GetDirectoryName( Settings.AutoTranslationsFilePath ) );
 
+               _registeredRegexes.Clear();
+               _defaultRegexes.Clear();
+
                var mainTranslationFile = Settings.AutoTranslationsFilePath;
                LoadTranslationsInFile( mainTranslationFile );
                foreach( var fullFileName in GetTranslationFiles().Reverse().Except( new[] { mainTranslationFile } ) )
@@ -56,7 +62,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
                }
             }
             var endTime = Time.realtimeSinceStartup;
-            XuaLogger.Current.Info( $"Loaded text files (took {Math.Round( endTime - startTime, 2 )} seconds)" );
+            XuaLogger.Current.Info( $"Loaded text files ({_translations.Count} translations and {_defaultRegexes.Count} regex translations) (took {Math.Round( endTime - startTime, 2 )} seconds)" );
          }
          catch( Exception e )
          {
@@ -84,7 +90,23 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
                      if( !string.IsNullOrEmpty( key ) && !string.IsNullOrEmpty( value ) && IsTranslatable( key ) )
                      {
-                        AddTranslation( key, value );
+                        if( key.StartsWith( "r:" ) )
+                        {
+                           try
+                           {
+                              var regex = new RegexTranslation( key, value );
+
+                              AddTranslationRegex( regex );
+                           }
+                           catch( Exception e )
+                           {
+                              XuaLogger.Current.Warn( e, $"An error occurred while constructing regex translation: '{translation}'." );
+                           }
+                        }
+                        else
+                        {
+                           AddTranslation( key, value );
+                        }
                         break;
                      }
                   }
@@ -148,6 +170,19 @@ namespace XUnity.AutoTranslator.Plugin.Core
          }
       }
 
+      private void AddTranslationRegex( RegexTranslation regex )
+      {
+         if( !_registeredRegexes.Contains( regex.Original ) )
+         {
+            _registeredRegexes.Add( regex.Original );
+            _defaultRegexes.Add( regex );
+         }
+         //else
+         //{
+         //   XuaLogger.Current.Warn( $"Could not register translation regex '{regex.Original}' because it has already been registered." );
+         //}
+      }
+
       private bool HasTranslated( string key )
       {
          return _translations.ContainsKey( key );
@@ -172,9 +207,9 @@ namespace XUnity.AutoTranslator.Plugin.Core
          }
       }
 
-      internal void AddTranslationToCache( TranslationKey key, string value )
+      internal void AddTranslationToCache( UntranslatedTextInfo key, string value )
       {
-         AddTranslationToCache( key.GetDictionaryLookupKey(), value );
+         AddTranslationToCache( key.GetCacheKey(), value );
       }
 
       internal void AddTranslationToCache( string key, string value )
@@ -186,19 +221,49 @@ namespace XUnity.AutoTranslator.Plugin.Core
          }
       }
 
-      internal bool TryGetTranslation( TranslationKey key, out string value )
+      internal bool TryGetTranslation( UntranslatedTextInfo key, bool allowRegex, out string value )
       {
-         return TryGetTranslation( key.GetDictionaryLookupKey(), out value );
+         return TryGetTranslation( key.GetCacheKey(), allowRegex, out value );
       }
 
-      internal bool TryGetTranslation( string key, out string value )
+      internal bool TryGetTranslation( string key, bool allowRegex, out string value )
       {
          var result = _translations.TryGetValue( key, out value );
          if( result )
          {
             return result;
          }
-         else if( _staticTranslations.Count > 0 )
+
+         if( allowRegex )
+         {
+            bool found = false;
+
+            var len = _defaultRegexes.Count;
+            for( int i = 0; i < len; i++ )
+            {
+               var regex = _defaultRegexes[ i ];
+               var match = regex.CompiledRegex.Match( key );
+               if( !match.Success ) continue;
+
+               var translation = regex.CompiledRegex.Replace( key, regex.Translation );
+               
+               //AddTranslation( key, translation );
+               AddTranslationToCache( key, translation ); // Would store it to file... Should we????
+
+               value = translation;
+               found = true;
+
+               XuaLogger.Current.Info( $"Regex translation: '{key}' => '{value}'" );
+               break;
+            }
+
+            if( found )
+            {
+               return true;
+            }
+         }
+
+         if( _staticTranslations.Count > 0 )
          {
             if( _staticTranslations.TryGetValue( key, out value ) )
             {
@@ -207,6 +272,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
                return true;
             }
          }
+
          return result;
       }
 

+ 2 - 2
src/XUnity.AutoTranslator.Plugin.Core/TranslationJob.cs

@@ -11,7 +11,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
 {
    internal class TranslationJob
    {
-      public TranslationJob( TranslationEndpointManager endpoint, TranslationKey key, bool saveResult )
+      public TranslationJob( TranslationEndpointManager endpoint, UntranslatedTextInfo key, bool saveResult )
       {
          Endpoint = endpoint;
          Key = key;
@@ -32,7 +32,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
       public HashSet<TranslationResult> TranslationResults { get; private set; }
 
-      public TranslationKey Key { get; private set; }
+      public UntranslatedTextInfo Key { get; private set; }
 
       public string TranslatedText { get; set; }
 

+ 0 - 71
src/XUnity.AutoTranslator.Plugin.Core/TranslationKey.cs

@@ -1,71 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using XUnity.AutoTranslator.Plugin.Core.Configuration;
-using XUnity.AutoTranslator.Plugin.Core.Extensions;
-
-namespace XUnity.AutoTranslator.Plugin.Core
-{
-   internal struct TranslationKey
-   {
-      public TranslationKey( object ui, string key, bool templatizeByNumbers, bool neverRemoveWhitespace = false )
-      {
-         OriginalText = key;
-
-         if( !neverRemoveWhitespace
-            && ( ( Settings.IgnoreWhitespaceInDialogue && key.Length > Settings.MinDialogueChars ) || ( Settings.IgnoreWhitespaceInNGUI && ui.IsNGUI() ) ) )
-         {
-            RelevantText = key.RemoveWhitespaceAndNewlines();
-         }
-         else
-         {
-            RelevantText = key;
-         }
-
-         if( templatizeByNumbers )
-         {
-            TemplatedText = RelevantText.TemplatizeByNumbers();
-         }
-         else
-         {
-            TemplatedText = null;
-         }
-      }
-
-      public TemplatedString TemplatedText { get; }
-
-      public string RelevantText { get; }
-
-      public string OriginalText { get; set; }
-
-      public string GetDictionaryLookupKey()
-      {
-         if( TemplatedText != null )
-         {
-            return TemplatedText.Template;
-         }
-         return RelevantText;
-      }
-
-      public string Untemplate( string text )
-      {
-         if( TemplatedText != null )
-         {
-            return TemplatedText.Untemplate( text );
-         }
-
-         return text;
-      }
-
-      public string RepairTemplate( string text )
-      {
-         if( TemplatedText != null )
-         {
-            return TemplatedText.RepairTemplate( text );
-         }
-
-         return text;
-      }
-   }
-}

+ 73 - 0
src/XUnity.AutoTranslator.Plugin.Core/UntranslatedTextInfo.cs

@@ -0,0 +1,73 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using XUnity.AutoTranslator.Plugin.Core.Configuration;
+using XUnity.AutoTranslator.Plugin.Core.Extensions;
+
+namespace XUnity.AutoTranslator.Plugin.Core
+{
+   internal class UntranslatedTextInfo
+   {
+      public UntranslatedTextInfo( string trimmedOriginalText, string untranslatedTextKey, string untranslatedText, int prependedNewlineCount, TemplatedString templatedText )
+      {
+         TrimmedOriginalText = trimmedOriginalText;
+         UntranslatedText = untranslatedText;
+         UntranslatedTextKey = untranslatedTextKey;
+         PrependedNewlines = prependedNewlineCount;
+         TemplatedText = templatedText;
+      }
+
+      public int PrependedNewlines { get; set; }
+
+      public TemplatedString TemplatedText { get; }
+
+      public string UntranslatedTextKey { get; }
+
+      public string UntranslatedText { get; }
+
+      public string TrimmedOriginalText { get; }
+
+      public string GetUntranslatedText()
+      {
+         // Should NOT contain prepended newlines (problem with template text?)
+
+         if( TemplatedText != null )
+         {
+            return TemplatedText.Template;
+         }
+         return UntranslatedText;
+      }
+
+      public string GetCacheKey()
+      {
+         // Should contain prepended newlines (problem with template text?)
+
+         if( TemplatedText != null )
+         {
+            return TemplatedText.Template;
+         }
+         return UntranslatedTextKey;
+      }
+
+      public string Untemplate( string text )
+      {
+         if( TemplatedText != null )
+         {
+            return TemplatedText.Untemplate( text );
+         }
+
+         return text;
+      }
+
+      public string RepairTemplate( string text )
+      {
+         if( TemplatedText != null )
+         {
+            return TemplatedText.RepairTemplate( text );
+         }
+
+         return text;
+      }
+   }
+}

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

@@ -2,8 +2,7 @@
 
    <PropertyGroup>
       <TargetFramework>net35</TargetFramework>
-      <Version>3.2.0</Version>
-      <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+      <Version>3.3.0</Version>
    </PropertyGroup>
 
    <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">

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

@@ -2,7 +2,7 @@
 
    <PropertyGroup>
       <TargetFramework>net35</TargetFramework>
-      <Version>3.2.0</Version>
+      <Version>3.3.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>3.2.0</Version>
+      <Version>3.3.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>3.2.0</Version>
+      <Version>3.3.0</Version>
       <ApplicationIcon>icon.ico</ApplicationIcon>
       <Win32Resource />
    </PropertyGroup>

+ 19 - 0
test/XUnity.AutoTranslator.Plugin.Core.Tests/RegexTranslationTests.cs

@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace XUnity.AutoTranslator.Plugin.Core.Tests
+{
+   public class RegexTranslationTests
+   {
+      [Theory( DisplayName = "Can_Create_Regex" )]
+      [InlineData( "r:\"^タイプ([0-90-9]+)$\"", "r:\"Type $1\"" )]
+      public void Can_Create_Regex( string key, string value )
+      {
+         var regex = new RegexTranslation( key, value );
+      }
+   }
+}

+ 25 - 0
test/XUnity.AutoTranslator.Plugin.Core.Tests/StringExtensionTests.cs

@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+using XUnity.AutoTranslator.Plugin.Core.Extensions;
+
+namespace XUnity.AutoTranslator.Plugin.Core.Tests
+{
+   public class StringExtensionTests
+   {
+      [Theory( DisplayName = "Can_Trim_Leading_Newlines" )]
+      [InlineData( "\r\n \r\nHello", "Hello", 2 )]
+      [InlineData( "\r\n \r\nHello\n", "Hello\n", 2 )]
+      [InlineData( "\r\r\r\r\n \n Hello", "Hello", 2 )]
+      public void Can_Trim_Leading_Newlines( string input, string expectedOutput, int expectedNewlineCount )
+      {
+         var output = input.TrimLeadingNewlines( out int newlineCount );
+
+         Assert.Equal( output, expectedOutput );
+         Assert.Equal( expectedNewlineCount, newlineCount );
+      }
+   }
+}

+ 20 - 0
test/XUnity.AutoTranslator.Plugin.Core.Tests/XUnity.AutoTranslator.Plugin.Core.Tests.csproj

@@ -0,0 +1,20 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net471</TargetFramework>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.0.1" />
+    <PackageReference Include="xunit" Version="2.4.1" />
+    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
+      <PrivateAssets>all</PrivateAssets>
+      <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
+    </PackageReference>
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\src\XUnity.AutoTranslator.Plugin.Core\XUnity.AutoTranslator.Plugin.Core.csproj" />
+  </ItemGroup>
+
+</Project>