Selaa lähdekoodia

improved various interfaces

randoman 6 vuotta sitten
vanhempi
commit
b739c414aa
41 muutettua tiedostoa jossa 686 lisäystä ja 320 poistoa
  1. 5 2
      README.md
  2. 47 0
      XUnity.AutoTranslator.Plugin.ExtProtocol/ExtProtocolConvert.cs
  3. 11 0
      XUnity.AutoTranslator.Plugin.ExtProtocol/ProtocolMessage.cs
  4. 26 0
      XUnity.AutoTranslator.Plugin.ExtProtocol/TranslationError.cs
  5. 34 0
      XUnity.AutoTranslator.Plugin.ExtProtocol/TranslationRequest.cs
  6. 26 0
      XUnity.AutoTranslator.Plugin.ExtProtocol/TranslationResponse.cs
  7. 11 0
      XUnity.AutoTranslator.Plugin.ExtProtocol/XUnity.AutoTranslator.Plugin.ExtProtocol.csproj
  8. 8 1
      XUnity.AutoTranslator.sln
  9. 3 2
      src/Translators/XUnity.AutoTranslator.Plugin.DummyTranslator/ReverseTranslator.cs
  10. 13 7
      src/Translators/XUnity.AutoTranslator.Plugin.Lec/Program.cs
  11. 4 0
      src/Translators/XUnity.AutoTranslator.Plugin.Lec/XUnity.AutoTranslator.Plugin.Lec.csproj
  12. 1 1
      src/Translators/XUnity.AutoTranslator.Plugin.LecPowerTranslator15/XUnity.AutoTranslator.Plugin.LecPowerTranslator15.csproj
  13. 1 1
      src/XUnity.AutoTranslator.Plugin.BepIn/XUnity.AutoTranslator.Plugin.BepIn.csproj
  14. 1 1
      src/XUnity.AutoTranslator.Plugin.Core/AutoTranslationPlugin.cs
  15. 4 0
      src/XUnity.AutoTranslator.Plugin.Core/AutoTranslationState.cs
  16. 4 1
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/ConfiguredEndpoint.cs
  17. 60 30
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/ExtProtocol/ExtProtocolEndpoint.cs
  18. 5 6
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/ExtProtocol/LecPowerTranslator15Endpoint.cs
  19. 21 31
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Http/BaiduTranslateEndpoint.cs
  20. 29 28
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Http/BingTranslateEndpoint.cs
  21. 16 18
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Http/BingTranslateLegitimateEndpoint.cs
  22. 6 7
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Http/CustomTranslateEndpoint.cs
  23. 50 35
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Http/GoogleTranslateEndpoint.cs
  24. 23 25
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Http/GoogleTranslateLegitimateEndpoint.cs
  25. 16 27
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Http/HttpEndpoint.cs
  26. 32 0
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Http/HttpTranslationContext.cs
  27. 23 34
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Http/YandexTranslateEndpoint.cs
  28. 1 1
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/ITranslateEndpoint.cs
  29. 17 1
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/InitializationContext.cs
  30. 1 0
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/KnownEndpoints.cs
  31. 70 0
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/TranslationContext.cs
  32. 25 30
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Www/WatsonTranslateEndpoint.cs
  33. 17 28
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Www/WwwEndpoint.cs
  34. 53 0
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Www/WwwTranslationContext.cs
  35. 1 1
      src/XUnity.AutoTranslator.Plugin.Core/UI/DropdownOptionViewModel.cs
  36. 4 0
      src/XUnity.AutoTranslator.Plugin.Core/XUnity.AutoTranslator.Plugin.Core.csproj
  37. 1 1
      src/XUnity.AutoTranslator.Plugin.IPA/XUnity.AutoTranslator.Plugin.IPA.csproj
  38. 1 1
      src/XUnity.AutoTranslator.Plugin.UnityInjector/XUnity.AutoTranslator.Plugin.UnityInjector.csproj
  39. 2 0
      src/XUnity.AutoTranslator.Setup/Program.cs
  40. 10 0
      src/XUnity.AutoTranslator.Setup/Properties/Resources.Designer.cs
  41. 3 0
      src/XUnity.AutoTranslator.Setup/Properties/Resources.resx

+ 5 - 2
README.md

@@ -451,6 +451,8 @@ With that in mind, consider the following:
  * The `WWW` class in Unity establishes a new TCP connection on each request you make, making it extremely poor at this kind of job. Especially if SSL (https) is involved because it has to do the entire handshake procedure each time.
  * The `UnityWebRequest` class in Unity does not exist in most games, because they use an old engine, so it is not a good choice either.
  * The `WebClient` class from .NET is capable of using persistent connections (it does so by default), but has its own problems with SSL. The version of Mono used in most Unity games rejects all certificates by default making all HTTPS connections fail. This, however, can be remedied during the initialization phase of the translator (see examples below). Another shortcoming of this API is the fact that the runtime will never release the TCP connections it has used until the process ends. The API also integrates terribly with Unity because callbacks return on a background thread.
+ * The `WebRequest` class from .NET is essentially the same as WebClient.
+ * The `HttpClient` class from .NET is also unlikely to exist in most Unity games.
 
 None of these are therefore an ideal solution.
 
@@ -486,9 +488,10 @@ public class ReverseTranslator : ITranslateEndpoint
 
    }
 
-   public IEnumerator Translate( string untranslatedText, string from, string to, Action<string> success, Action<string, Exception> failure )
+   public IEnumerator Translate( TranslationContext context )
    {
-      success( new string( untranslatedText.Reverse().ToArray() ) );
+      var reversedText = new string( context.UntranslatedText.Reverse().ToArray() );
+      context.Complete( reversedText );
 
       return null;
    }

+ 47 - 0
XUnity.AutoTranslator.Plugin.ExtProtocol/ExtProtocolConvert.cs

@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+namespace XUnity.AutoTranslator.Plugin.ExtProtocol
+{
+   public static class ExtProtocolConvert
+   {
+      private static readonly Dictionary<string, Type> IdToType = new Dictionary<string, Type>();
+      private static readonly Dictionary<Type, string> TypeToId = new Dictionary<Type, string>();
+
+      static ExtProtocolConvert()
+      {
+         Register( TranslationRequest.Type, typeof( TranslationRequest ) );
+         Register( TranslationResponse.Type, typeof( TranslationResponse ) );
+         Register( TranslationError.Type, typeof( TranslationError ) );
+      }
+
+      internal static void Register( string id, Type type )
+      {
+         IdToType[ id ] = type;
+         TypeToId[ type ] = id;
+      }
+
+      public static string Encode( ProtocolMessage message )
+      {
+         var writer = new StringWriter();
+         var id = TypeToId[ message.GetType() ];
+         writer.WriteLine( id );
+         message.Encode( writer );
+         return Convert.ToBase64String( Encoding.UTF8.GetBytes( writer.ToString() ) );
+      }
+
+      public static ProtocolMessage Decode( string message )
+      {
+         var payload = Encoding.UTF8.GetString( Convert.FromBase64String( message ) );
+         var reader = new StringReader( payload );
+         var id = reader.ReadLine();
+         var type = IdToType[ id ];
+         var protocolMessage = (ProtocolMessage)Activator.CreateInstance( type );
+         protocolMessage.Decode( reader );
+         return protocolMessage;
+      }
+   }
+}

+ 11 - 0
XUnity.AutoTranslator.Plugin.ExtProtocol/ProtocolMessage.cs

@@ -0,0 +1,11 @@
+using System.IO;
+
+namespace XUnity.AutoTranslator.Plugin.ExtProtocol
+{
+   public abstract class ProtocolMessage
+   {
+      internal abstract void Decode( TextReader reader );
+
+      internal abstract void Encode( TextWriter writer );
+   }
+}

+ 26 - 0
XUnity.AutoTranslator.Plugin.ExtProtocol/TranslationError.cs

@@ -0,0 +1,26 @@
+using System;
+using System.IO;
+
+namespace XUnity.AutoTranslator.Plugin.ExtProtocol
+{
+   public class TranslationError : ProtocolMessage
+   {
+      public static readonly string Type = "3";
+
+      public Guid Id { get; set; }
+
+      public string Reason { get; set; }
+
+      internal override void Decode( TextReader reader )
+      {
+         Id = new Guid( reader.ReadLine() );
+         Reason = reader.ReadToEnd();
+      }
+
+      internal override void Encode( TextWriter writer )
+      {
+         writer.WriteLine( Id.ToString() );
+         writer.Write( Reason );
+      }
+   }
+}

+ 34 - 0
XUnity.AutoTranslator.Plugin.ExtProtocol/TranslationRequest.cs

@@ -0,0 +1,34 @@
+using System;
+using System.IO;
+
+namespace XUnity.AutoTranslator.Plugin.ExtProtocol
+{
+   public class TranslationRequest : ProtocolMessage
+   {
+      public static readonly string Type = "1";
+
+      public Guid Id { get; set; }
+
+      public string SourceLanguage { get; set; }
+
+      public string DestinationLanguage { get; set; }
+
+      public string UntranslatedText { get; set; }
+
+      internal override void Decode( TextReader reader )
+      {
+         Id = new Guid( reader.ReadLine() );
+         SourceLanguage = reader.ReadLine();
+         DestinationLanguage = reader.ReadLine();
+         UntranslatedText = reader.ReadToEnd();
+      }
+
+      internal override void Encode( TextWriter writer )
+      {
+         writer.WriteLine( Id.ToString() );
+         writer.WriteLine( SourceLanguage );
+         writer.WriteLine( DestinationLanguage );
+         writer.Write( UntranslatedText );
+      }
+   }
+}

+ 26 - 0
XUnity.AutoTranslator.Plugin.ExtProtocol/TranslationResponse.cs

@@ -0,0 +1,26 @@
+using System;
+using System.IO;
+
+namespace XUnity.AutoTranslator.Plugin.ExtProtocol
+{
+   public class TranslationResponse : ProtocolMessage
+   {
+      public static readonly string Type = "2";
+
+      public Guid Id { get; set; }
+
+      public string TranslatedText { get; set; }
+
+      internal override void Decode( TextReader reader )
+      {
+         Id = new Guid( reader.ReadLine() );
+         TranslatedText = reader.ReadToEnd();
+      }
+
+      internal override void Encode( TextWriter writer )
+      {
+         writer.WriteLine( Id.ToString() );
+         writer.Write( TranslatedText );
+      }
+   }
+}

+ 11 - 0
XUnity.AutoTranslator.Plugin.ExtProtocol/XUnity.AutoTranslator.Plugin.ExtProtocol.csproj

@@ -0,0 +1,11 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net35</TargetFramework>
+  </PropertyGroup>
+
+  <Target Name="PostBuild" AfterTargets="PostBuildEvent">
+    <Exec Command="if $(ConfigurationName) == Release (&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)$(TargetName)$(TargetExt)&quot; &quot;$(SolutionDir)dist\Translators\&quot;&#xD;&#xA;)" />
+  </Target>
+
+</Project>

+ 8 - 1
XUnity.AutoTranslator.sln

@@ -94,7 +94,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XUnity.AutoTranslator.Plugi
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XUnity.AutoTranslator.Plugin.Lec", "src\Translators\XUnity.AutoTranslator.Plugin.Lec\XUnity.AutoTranslator.Plugin.Lec.csproj", "{AD0C7EF2-D394-43B5-9CCE-FA8A0A820076}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XUnity.AutoTranslator.Plugin.GoogleTranslateLegitimate", "src\Translators\XUnity.AutoTranslator.Plugin.GoogleTranslateLegitimate\XUnity.AutoTranslator.Plugin.GoogleTranslateLegitimate.csproj", "{41B612A5-2974-4988-A82E-84EEF2212A61}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XUnity.AutoTranslator.Plugin.GoogleTranslateLegitimate", "src\Translators\XUnity.AutoTranslator.Plugin.GoogleTranslateLegitimate\XUnity.AutoTranslator.Plugin.GoogleTranslateLegitimate.csproj", "{41B612A5-2974-4988-A82E-84EEF2212A61}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XUnity.AutoTranslator.Plugin.ExtProtocol", "XUnity.AutoTranslator.Plugin.ExtProtocol\XUnity.AutoTranslator.Plugin.ExtProtocol.csproj", "{65500234-7AD4-48B8-B51B-945ADBFF1133}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -170,6 +172,10 @@ Global
 		{41B612A5-2974-4988-A82E-84EEF2212A61}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{41B612A5-2974-4988-A82E-84EEF2212A61}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{41B612A5-2974-4988-A82E-84EEF2212A61}.Release|Any CPU.Build.0 = Release|Any CPU
+		{65500234-7AD4-48B8-B51B-945ADBFF1133}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{65500234-7AD4-48B8-B51B-945ADBFF1133}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{65500234-7AD4-48B8-B51B-945ADBFF1133}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{65500234-7AD4-48B8-B51B-945ADBFF1133}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -193,6 +199,7 @@ Global
 		{AE28F88E-E877-456B-98AB-BD03A59A3E44} = {7A01BA34-3B96-4910-AC70-462BA59417CB}
 		{AD0C7EF2-D394-43B5-9CCE-FA8A0A820076} = {7A01BA34-3B96-4910-AC70-462BA59417CB}
 		{41B612A5-2974-4988-A82E-84EEF2212A61} = {7A01BA34-3B96-4910-AC70-462BA59417CB}
+		{65500234-7AD4-48B8-B51B-945ADBFF1133} = {0F9B38FC-4E57-4B83-AF0B-0993B8470823}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {EE803FED-4447-4D19-B3D6-88C56E8DFCCA}

+ 3 - 2
src/Translators/XUnity.AutoTranslator.Plugin.DummyTranslator/ReverseTranslator.cs

@@ -20,9 +20,10 @@ namespace XUnity.AutoTranslator.Plugin.DummyTranslator
 
       }
 
-      public IEnumerator Translate( string untranslatedText, string from, string to, Action<string> success, Action<string, Exception> failure )
+      public IEnumerator Translate( TranslationContext context )
       {
-         success( new string( untranslatedText.Reverse().ToArray() ) );
+         var reversedText = new string( context.UntranslatedText.Reverse().ToArray() );
+         context.Complete( reversedText );
 
          return null;
       }

+ 13 - 7
src/Translators/XUnity.AutoTranslator.Plugin.Lec/Program.cs

@@ -2,6 +2,7 @@
 using System.IO;
 using System.Text;
 using System.Threading;
+using XUnity.AutoTranslator.Plugin.ExtProtocol;
 
 namespace XUnity.AutoTranslator.Plugin.Lec
 {
@@ -33,15 +34,20 @@ namespace XUnity.AutoTranslator.Plugin.Lec
                   using( var reader = new StreamReader( stdin ) )
                   {
                      var receivedPayload = reader.ReadLine();
-                     if( string.IsNullOrEmpty( receivedPayload ) )
-                     {
-                        return;
-                     }
+                     if( string.IsNullOrEmpty( receivedPayload ) ) return;
+
+                     var message = ExtProtocolConvert.Decode( receivedPayload ) as TranslationRequest;
+                     if( message == null ) return;
 
-                     var receivedLine = Encoding.UTF8.GetString( Convert.FromBase64String( receivedPayload ) );
-                     var translatedLine = translator.Translate( receivedLine );
+                     var translatedLine = translator.Translate( message.UntranslatedText );
+
+                     var response = new TranslationResponse
+                     {
+                        Id = message.Id,
+                        TranslatedText = translatedLine
+                     };
 
-                     var translatedPayload = Convert.ToBase64String( Encoding.UTF8.GetBytes( translatedLine ) );
+                     var translatedPayload = ExtProtocolConvert.Encode( response );
                      writer.WriteLine( translatedPayload );
                   }
                }

+ 4 - 0
src/Translators/XUnity.AutoTranslator.Plugin.Lec/XUnity.AutoTranslator.Plugin.Lec.csproj

@@ -11,4 +11,8 @@
       <Exec Command="if $(ConfigurationName) == Release (&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)$(TargetName)$(TargetExt)&quot; &quot;$(SolutionDir)dist\Translators\&quot;&#xD;&#xA;)" />
    </Target>
 
+   <ItemGroup>
+     <ProjectReference Include="..\..\..\XUnity.AutoTranslator.Plugin.ExtProtocol\XUnity.AutoTranslator.Plugin.ExtProtocol.csproj" />
+   </ItemGroup>
+
 </Project>

+ 1 - 1
src/Translators/XUnity.AutoTranslator.Plugin.LecPowerTranslator15/XUnity.AutoTranslator.Plugin.LecPowerTranslator15.csproj

@@ -6,7 +6,7 @@
    </PropertyGroup>
 
    <ItemGroup>
-     <Compile Include="..\..\XUnity.AutoTranslator.Plugin.Core\Endpoints\ProcessLineProtocol\LecPowerTranslator15Endpoint.cs" Link="LecPowerTranslator15Endpoint.cs" />
+     <Compile Include="..\..\XUnity.AutoTranslator.Plugin.Core\Endpoints\ExtProtocol\LecPowerTranslator15Endpoint.cs" Link="LecPowerTranslator15Endpoint.cs" />
    </ItemGroup>
 
    <ItemGroup>

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

@@ -31,7 +31,7 @@
       <ItemGroup>
          <VersionNumber Include="$([System.Text.RegularExpressions.Regex]::Replace(&quot;%(Targets.Version)&quot;, &quot;^(.+?)(\.0+)$&quot;, &quot;$1&quot;))" />
       </ItemGroup>
-      <Exec Command="if $(ConfigurationName) == Release (&#xD;&#xA;   for %%f in (&quot;$(SolutionDir)dist\Translators\*&quot;) do XCOPY /Y /I &quot;%%f&quot; &quot;$(SolutionDir)dist\BepIn\BepInEx\Translators\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)ExIni.dll&quot; &quot;$(SolutionDir)dist\BepIn\BepInEx\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)XUnity.AutoTranslator.Plugin.Core.dll&quot; &quot;$(SolutionDir)dist\BepIn\BepInEx\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)$(TargetName)$(TargetExt)&quot; &quot;$(SolutionDir)dist\BepIn\BepInEx\&quot;&#xD;&#xA;   COPY /Y &quot;$(SolutionDir)README.md&quot; &quot;$(SolutionDir)dist\BepIn\BepInEx\README (AutoTranslator).md&quot;&#xD;&#xA;   powershell Compress-Archive -Path '$(SolutionDir)dist\BepIn\BepInEx' -DestinationPath '$(SolutionDir)dist\BepIn\XUnity.AutoTranslator-BepIn-@(VersionNumber).zip' -Force)&#xD;&#xA;)" />
+      <Exec Command="if $(ConfigurationName) == Release (&#xD;&#xA;   for %%f in (&quot;$(SolutionDir)dist\Translators\*&quot;) do XCOPY /Y /I &quot;%%f&quot; &quot;$(SolutionDir)dist\BepIn\BepInEx\Translators\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)ExIni.dll&quot; &quot;$(SolutionDir)dist\BepIn\BepInEx\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)XUnity.AutoTranslator.Plugin.Core.dll&quot; &quot;$(SolutionDir)dist\BepIn\BepInEx\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)XUnity.AutoTranslator.Plugin.ExtProtocol.dll&quot; &quot;$(SolutionDir)dist\BepIn\BepInEx\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)$(TargetName)$(TargetExt)&quot; &quot;$(SolutionDir)dist\BepIn\BepInEx\&quot;&#xD;&#xA;   COPY /Y &quot;$(SolutionDir)README.md&quot; &quot;$(SolutionDir)dist\BepIn\BepInEx\README (AutoTranslator).md&quot;&#xD;&#xA;   powershell Compress-Archive -Path '$(SolutionDir)dist\BepIn\BepInEx' -DestinationPath '$(SolutionDir)dist\BepIn\XUnity.AutoTranslator-BepIn-@(VersionNumber).zip' -Force)&#xD;&#xA;)" />
    </Target>
 
 </Project>

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

@@ -168,7 +168,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
          _httpSecurity = new HttpSecurity();
          try
          {
-            var context = new InitializationContext( Config.Current, _httpSecurity );
+            var context = new InitializationContext( Config.Current, _httpSecurity, Settings.FromLanguage, Settings.Language );
 
             _configuredEndpoints = KnownEndpoints.CreateEndpoints( gameObject, context )
                .OrderBy( x => x.Error != null )

+ 4 - 0
src/XUnity.AutoTranslator.Plugin.Core/AutoTranslationState.cs

@@ -11,5 +11,9 @@ namespace XUnity.AutoTranslator.Plugin.Core
       public static int TranslationCount => Settings.TranslationCount;
 
       public static string UserAgent => Settings.UserAgent;
+
+      public static string SourceLanguage => Settings.FromLanguage;
+
+      public static string DestinationLanguage => Settings.Language;
    }
 }

+ 4 - 1
src/XUnity.AutoTranslator.Plugin.Core/Endpoints/ConfiguredEndpoint.cs

@@ -22,10 +22,11 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
 
       public IEnumerator Translate( string untranslatedText, string from, string to, Action<string> success, Action<string, Exception> failure )
       {
+         var context = new TranslationContext( untranslatedText, from, to, success, failure );
          _ongoingTranslations++;
          try
          {
-            var iterator = Endpoint.Translate( untranslatedText, from, to, success, failure );
+            var iterator = Endpoint.Translate( context );
             if( iterator != null )
             {
                while( iterator.MoveNext() )
@@ -37,6 +38,8 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
          finally
          {
             _ongoingTranslations--;
+
+            context.FailIfNotCompleted();
          }
       }
    }

+ 60 - 30
src/XUnity.AutoTranslator.Plugin.Core/Endpoints/ProcessLineProtocol/ProcessLineProtocolEndpoint.cs → src/XUnity.AutoTranslator.Plugin.Core/Endpoints/ExtProtocol/ExtProtocolEndpoint.cs

@@ -9,11 +9,12 @@ using UnityEngine;
 using XUnity.AutoTranslator.Plugin.Core.Configuration;
 using XUnity.AutoTranslator.Plugin.Core.Shim;
 using XUnity.AutoTranslator.Plugin.Core.Web;
+using XUnity.AutoTranslator.Plugin.ExtProtocol;
 
-namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.ProcessLineProtocol
+namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.ExtProtocol
 {
 
-   public abstract class ProcessLineProtocolEndpoint : ITranslateEndpoint, IDisposable
+   public abstract class ExtProtocolEndpoint : ITranslateEndpoint, IDisposable
    {
       private bool _disposed = false;
       private Process _process;
@@ -28,18 +29,14 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.ProcessLineProtocol
 
       public int MaxConcurrency => 1;
 
-      protected void Process_OutputDataReceived( object sender, DataReceivedEventArgs e )
+      public IEnumerator Translate( TranslationContext context )
       {
-      }
-
-      public IEnumerator Translate( string untranslatedText, string from, string to, Action<string> success, Action<string, Exception> failure )
-      {
-         var _result = new StreamReaderResult();
+         var result = new StreamReaderResult();
          try
          {
-            try
+            ThreadPool.QueueUserWorkItem( state =>
             {
-               ThreadPool.QueueUserWorkItem( state =>
+               try
                {
                   if( _process == null )
                   {
@@ -52,7 +49,6 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.ProcessLineProtocol
                      _process.StartInfo.RedirectStandardInput = true;
                      _process.StartInfo.RedirectStandardOutput = true;
                      _process.StartInfo.RedirectStandardError = true;
-                     _process.OutputDataReceived += Process_OutputDataReceived;
                      _process.Start();
 
                      // wait a second...
@@ -61,33 +57,39 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.ProcessLineProtocol
 
                   if( _process.HasExited )
                   {
-                     _result.SetCompleted( null, "The translation process exited. Likely due to invalid path to installation." );
+                     result.SetCompleted( null, "The translation process exited. Likely due to invalid path to installation." );
                      return;
                   }
 
-                  var payload = Convert.ToBase64String( Encoding.UTF8.GetBytes( untranslatedText ) );
+                  var request = new TranslationRequest
+                  {
+                     Id = Guid.NewGuid(),
+                     SourceLanguage = context.SourceLanguage,
+                     DestinationLanguage = context.DestinationLanguage,
+                     UntranslatedText = context.UntranslatedText
+                  };
+                  var payload = ExtProtocolConvert.Encode( request );
                   _process.StandardInput.WriteLine( payload );
 
                   var returnedPayload = _process.StandardOutput.ReadLine();
-                  var returnedLine = Encoding.UTF8.GetString( Convert.FromBase64String( returnedPayload ) );
+                  var response = ExtProtocolConvert.Decode( returnedPayload );
 
-                  _result.SetCompleted( returnedLine, string.IsNullOrEmpty( returnedLine ) ? "Nothing was returned." : null );
-               } );
-            }
-            catch( Exception e )
-            {
-               failure( "Error occurred while retrieving translation.", e );
-               yield break;
-            }
+                  HandleProtocolMessage( result, response );
+               }
+               catch( Exception e )
+               {
+                  result.SetCompleted( null, e.Message );
+               }
+            } );
 
             // yield-wait for completion
             if( Features.SupportsCustomYieldInstruction )
             {
-               yield return _result;
+               yield return result;
             }
             else
             {
-               while( !_result.IsCompleted )
+               while( !result.IsCompleted )
                {
                   yield return new WaitForSeconds( 0.2f );
                }
@@ -95,27 +97,55 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.ProcessLineProtocol
 
             try
             {
-               if( _result.Succeeded && _result.Result != null )
+               if( result.Succeeded )
                {
-                  success( _result.Result );
+                  context.Complete( result.Result );
                }
                else
                {
-                  failure( "Error occurred while retrieving translation." + Environment.NewLine + _result.Error, null );
+                  context.Fail( "Error occurred while retrieving translation." + Environment.NewLine + result.Error, null );
                }
             }
             catch( Exception e )
             {
-               failure( "Error occurred while retrieving translation.", e );
+               context.Fail( "Error occurred while retrieving translation.", e );
             }
          }
          finally
          {
-            _result = null;
+            result = null;
+
+            context.FailIfNotCompleted();
+         }
+      }
+
+      private static void HandleProtocolMessage( StreamReaderResult result, ProtocolMessage message )
+      {
+         switch( message )
+         {
+            case TranslationResponse translationResponse:
+               HandleTranslationResponse( result, translationResponse );
+               break;
+            case TranslationError translationResponse:
+               HandleTranslationError( result, translationResponse );
+               break;
+            default:
+               result.SetCompleted( null, "Received invalid response." );
+               break;
          }
       }
 
-      public class StreamReaderResult : CustomYieldInstructionShim
+      private static void HandleTranslationResponse( StreamReaderResult result, TranslationResponse message )
+      {
+         result.SetCompleted( message.TranslatedText, null );
+      }
+
+      private static void HandleTranslationError( StreamReaderResult result, TranslationError message )
+      {
+         result.SetCompleted( null, message.Reason );
+      }
+
+      private class StreamReaderResult : CustomYieldInstructionShim
       {
          public void SetCompleted( string result, string error )
          {

+ 5 - 6
src/XUnity.AutoTranslator.Plugin.Core/Endpoints/ProcessLineProtocol/LecPowerTranslator15Endpoint.cs → src/XUnity.AutoTranslator.Plugin.Core/Endpoints/ExtProtocol/LecPowerTranslator15Endpoint.cs

@@ -5,9 +5,9 @@ using XUnity.AutoTranslator.Plugin.Core.Configuration;
 using XUnity.AutoTranslator.Plugin.Core.Extensions;
 using XUnity.AutoTranslator.Plugin.Core.Web;
 
-namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.ProcessLineProtocol
+namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.ExtProtocol
 {
-   internal class LecPowerTranslator15Endpoint : ProcessLineProtocolEndpoint
+   internal class LecPowerTranslator15Endpoint : ExtProtocolEndpoint
    {
       public override string Id => "LecPowerTranslator15";
 
@@ -15,12 +15,8 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.ProcessLineProtocol
 
       public override void Initialize( InitializationContext context )
       {
-         var to = context.Config.Preferences[ "General" ][ "Language" ].Value;
-         var from = context.Config.Preferences[ "General" ][ "FromLanguage" ].Value;
          var pathToLec = context.Config.Preferences[ "LecPowerTranslator15" ][ "InstallationPath" ].GetOrDefault( "" );
          if( string.IsNullOrEmpty( pathToLec ) ) throw new Exception( "The LecPowerTranslator15 requires the path to the installation folder." );
-         if( !from.Equals( "ja", StringComparison.OrdinalIgnoreCase ) ) throw new Exception( "Only japanese to english is supported." );
-         if( !to.Equals( "en", StringComparison.OrdinalIgnoreCase ) ) throw new Exception( "Only japanese to english is supported." );
 
          var path1 = context.Config.DataPath;
          var exePath1 = Path.Combine( path1, @"Translators\Lec.exe" );
@@ -40,6 +36,9 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.ProcessLineProtocol
          {
             throw new Exception( "Unexpected error occurred." );
          }
+
+         if( context.SourceLanguage != "ja" ) throw new Exception( "Current implementation only supports japanese-to-english." );
+         if( context.DestinationLanguage != "en" ) throw new Exception( "Current implementation only supports japanese-to-english." );
       }
    }
 }

+ 21 - 31
src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Http/BaiduTranslateEndpoint.cs

@@ -36,17 +36,17 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
          context.HttpSecurity.EnableSslFor( "api.fanyi.baidu.com" );
       }
 
-      public override XUnityWebRequest CreateTranslationRequest( string untranslatedText, string from, string to )
+      public override XUnityWebRequest CreateTranslationRequest( HttpTranslationContext context )
       {
          string salt = DateTime.UtcNow.Millisecond.ToString();
-         var md5 = CreateMD5( _appId + untranslatedText + salt + _appSecret );
+         var md5 = CreateMD5( _appId + context.UntranslatedText + salt + _appSecret );
 
          var request = new XUnityWebRequest(
             string.Format(
                HttpServicePointTemplateUrl,
-               WWW.EscapeURL( untranslatedText ),
-               from,
-               to,
+               WWW.EscapeURL( context.UntranslatedText ),
+               context.SourceLanguage,
+               context.DestinationLanguage,
                _appId,
                salt,
                md5 ) );
@@ -57,40 +57,30 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
          return request;
       }
 
-      public override bool TryExtractTranslated( string result, out string translated )
+      public override void ExtractTranslatedText( HttpTranslationContext context )
       {
-         try
+         if( context.ResultData.StartsWith( "{\"error" ) )
          {
-            if( result.StartsWith( "{\"error" ) )
-            {
-               translated = null;
-               return false;
-            }
-
+            return;
+         }
 
-            var obj = JSON.Parse( result );
-            var lineBuilder = new StringBuilder( result.Length );
+         var data = context.ResultData;
+         var obj = JSON.Parse( data );
+         var lineBuilder = new StringBuilder( data.Length );
 
-            foreach( JSONNode entry in obj.AsObject[ "trans_result" ].AsArray )
-            {
-               var token = entry.AsObject[ "dst" ].ToString();
-               token = token.Substring( 1, token.Length - 2 ).UnescapeJson();
+         foreach( JSONNode entry in obj.AsObject[ "trans_result" ].AsArray )
+         {
+            var token = entry.AsObject[ "dst" ].ToString();
+            token = token.Substring( 1, token.Length - 2 ).UnescapeJson();
 
-               if( !lineBuilder.EndsWithWhitespaceOrNewline() ) lineBuilder.Append( "\n" );
+            if( !lineBuilder.EndsWithWhitespaceOrNewline() ) lineBuilder.Append( "\n" );
 
-               lineBuilder.Append( token );
-            }
+            lineBuilder.Append( token );
+         }
 
-            translated = lineBuilder.ToString();
+         var translated = lineBuilder.ToString();
 
-            var success = !string.IsNullOrEmpty( translated );
-            return success;
-         }
-         catch( Exception )
-         {
-            translated = null;
-            return false;
-         }
+         context.Complete( translated );
       }
 
       private static string CreateMD5( string input )

+ 29 - 28
src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Http/BingTranslateEndpoint.cs

@@ -18,6 +18,11 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
 {
    internal class BingTranslateEndpoint : HttpEndpoint
    {
+      private static readonly HashSet<string> SupportedLanguages = new HashSet<string>
+      {
+         "af","ar","bn","bs","bg","yue","ca","zh-Hans","zh-Hant","hr","cs","da","nl","en","et","fj","fil","fi","fr","de","el","ht","he","hi","mww","hu","is","id","it","ja","sw","tlh","tlh-Qaak","ko","lv","lt","mg","ms","mt","nb","fa","pl","pt","otq","ro","ru","sm","sr-Cyrl","sr-Latn","sk","sl","es","sv","ty","ta","te","th","to","tr","uk","ur","vi","cy","yua"
+      };
+
       private static readonly string HttpsServicePointTemplateUrl = "https://www.bing.com/ttranslate?&category=&IG={0}&IID={1}.{2}";
       private static readonly string HttpsServicePointTemplateUrlWithoutIG = "https://www.bing.com/ttranslate?&category=";
       private static readonly string HttpsTranslateUserSite = "https://www.bing.com/translator";
@@ -59,9 +64,11 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
       {
          // Configure service points / service point manager
          context.HttpSecurity.EnableSslFor( "www.bing.com" );
+
+         if( !SupportedLanguages.Contains( context.DestinationLanguage ) ) throw new Exception( $"The destination language {context.DestinationLanguage} is not supported." );
       }
 
-      public override XUnityWebRequest CreateTranslationRequest( string untranslatedText, string from, string to )
+      public override XUnityWebRequest CreateTranslationRequest( HttpTranslationContext context )
       {
          _translationCount++;
          string address = null;
@@ -75,7 +82,11 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
             address = string.Format( HttpsServicePointTemplateUrl, _ig, _iid, _translationCount );
          }
 
-         var data = string.Format( RequestTemplate, WWW.EscapeURL( untranslatedText ), from, to );
+         var data = string.Format(
+            RequestTemplate,
+            WWW.EscapeURL( context.UntranslatedText ),
+            context.SourceLanguage,
+            context.DestinationLanguage );
 
          var request = new XUnityWebRequest( "POST", address, data );
 
@@ -125,7 +136,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
          }
       }
 
-      public override void InspectTranslationResponse( XUnityWebResponse response )
+      public override void InspectTranslationResponse( HttpTranslationContext context, XUnityWebResponse response )
       {
          CookieCollection cookies = response.NewCookies;
 
@@ -133,7 +144,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
          _cookieContainer.Add( cookies );
       }
 
-      public override IEnumerator OnBeforeTranslate()
+      public override IEnumerator OnBeforeTranslate( HttpTranslationContext context )
       {
          if( !_hasSetup || AutoTranslationState.TranslationCount % _resetAfter == 0 )
          {
@@ -141,7 +152,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
             _hasSetup = true;
 
             // Setup TKK and cookies
-            var enumerator = SetupIGAndIID();
+            var enumerator = SetupIGAndIID( context );
             while( enumerator.MoveNext() )
             {
                yield return enumerator.Current;
@@ -149,7 +160,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
          }
       }
 
-      public IEnumerator SetupIGAndIID()
+      public IEnumerator SetupIGAndIID( HttpTranslationContext context )
       {
          XUnityWebResponse response = null;
 
@@ -180,7 +191,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
             }
          }
 
-         InspectTranslationResponse( response );
+         InspectTranslationResponse( context, response );
 
          // failure
          if( response.Error != null )
@@ -232,32 +243,22 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
          return null;
       }
 
-      public override bool TryExtractTranslated( string result, out string translated )
+      public override void ExtractTranslatedText( HttpTranslationContext context )
       {
-         try
-         {
-            var obj = JSON.Parse( result );
+         var obj = JSON.Parse( context.ResultData );
 
-            var code = obj[ "statusCode" ].AsInt;
-            if( code != 200 )
-            {
-               translated = null;
-               return false;
-            }
+         var code = obj[ "statusCode" ].AsInt;
+         if( code != 200 )
+         {
+            return;
+         }
 
-            var token = obj[ "translationResponse" ].ToString();
-            token = token.Substring( 1, token.Length - 2 ).UnescapeJson();
+         var token = obj[ "translationResponse" ].ToString();
+         token = token.Substring( 1, token.Length - 2 ).UnescapeJson();
 
-            translated = token;
+         var translated = token;
 
-            var success = !string.IsNullOrEmpty( translated );
-            return success;
-         }
-         catch
-         {
-            translated = null;
-            return false;
-         }
+         context.Complete( translated );
       }
    }
 }

+ 16 - 18
src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Http/BingTranslateLegitimateEndpoint.cs

@@ -18,6 +18,11 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
 {
    internal class BingTranslateLegitimateEndpoint : HttpEndpoint
    {
+      private static readonly HashSet<string> SupportedLanguages = new HashSet<string>
+      {
+         "af","ar","bn","bs","bg","yue","ca","zh-Hans","zh-Hant","hr","cs","da","nl","en","et","fj","fil","fi","fr","de","el","ht","he","hi","mww","hu","is","id","it","ja","sw","tlh","tlh-Qaak","ko","lv","lt","mg","ms","mt","nb","fa","pl","pt","otq","ro","ru","sm","sr-Cyrl","sr-Latn","sk","sl","es","sv","ty","ta","te","th","to","tr","uk","ur","vi","cy","yua"
+      };
+
       private static readonly string HttpsServicePointTemplateUrl = "https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&from={0}&to={1}";
       private static readonly string RequestTemplate = "[{{\"Text\":\"{0}\"}}]";
       private static readonly System.Random RandomNumbers = new System.Random();
@@ -41,14 +46,16 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
 
          // Configure service points / service point manager
          context.HttpSecurity.EnableSslFor( "api.cognitive.microsofttranslator.com" );
+
+         if( !SupportedLanguages.Contains( context.DestinationLanguage ) ) throw new Exception( $"The destination language {context.DestinationLanguage} is not supported." );
       }
 
-      public override XUnityWebRequest CreateTranslationRequest( string untranslatedText, string from, string to )
+      public override XUnityWebRequest CreateTranslationRequest( HttpTranslationContext context )
       {
          var request = new XUnityWebRequest(
             "POST",
-            string.Format( HttpsServicePointTemplateUrl, from, to ),
-            string.Format( RequestTemplate, untranslatedText.EscapeJson() ) );
+            string.Format( HttpsServicePointTemplateUrl, context.SourceLanguage, context.DestinationLanguage ),
+            string.Format( RequestTemplate, context.UntranslatedText.EscapeJson() ) );
 
          if( Accept != null )
          {
@@ -63,25 +70,16 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
          return request;
       }
 
-      public override bool TryExtractTranslated( string result, out string translated )
+      public override void ExtractTranslatedText( HttpTranslationContext context )
       {
-         try
-         {
-            var arr = JSON.Parse( result );
+         var arr = JSON.Parse( context.ResultData );
 
-            var token = arr.AsArray[ 0 ]?.AsObject[ "translations" ]?.AsArray[ 0 ]?.AsObject[ "text" ]?.ToString();
-            token = token.Substring( 1, token.Length - 2 ).UnescapeJson();
+         var token = arr.AsArray[ 0 ]?.AsObject[ "translations" ]?.AsArray[ 0 ]?.AsObject[ "text" ]?.ToString();
+         token = token.Substring( 1, token.Length - 2 ).UnescapeJson();
 
-            translated = token;
+         var translated = token;
 
-            var success = !string.IsNullOrEmpty( translated );
-            return success;
-         }
-         catch
-         {
-            translated = null;
-            return false;
-         }
+         context.Complete( translated );
       }
    }
 }

+ 6 - 7
src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Http/CustomTranslateEndpoint.cs

@@ -35,23 +35,22 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
          _friendlyName += " (" + uri.Host + ")";
       }
 
-      public override XUnityWebRequest CreateTranslationRequest( string untranslatedText, string from, string to )
+      public override XUnityWebRequest CreateTranslationRequest( HttpTranslationContext context )
       {
          var request = new XUnityWebRequest(
             string.Format(
                ServicePointTemplateUrl,
                _endpoint,
-               from,
-               to,
-               WWW.EscapeURL( untranslatedText ) ) );
+               context.SourceLanguage,
+               context.DestinationLanguage,
+               WWW.EscapeURL( context.UntranslatedText ) ) );
 
          return request;
       }
 
-      public override bool TryExtractTranslated( string result, out string translated )
+      public override void ExtractTranslatedText( HttpTranslationContext context )
       {
-         translated = result;
-         return true;
+         context.Complete( context.ResultData );
       }
    }
 }

+ 50 - 35
src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Http/GoogleTranslateEndpoint.cs

@@ -18,7 +18,13 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
 {
    internal class GoogleTranslateEndpoint : HttpEndpoint
    {
-      private static readonly string HttpsServicePointTemplateUrl = "https://translate.googleapis.com/translate_a/single?client=t&dt=t&sl={0}&tl={1}&ie=UTF-8&oe=UTF-8&tk={2}&q={3}";
+      private static readonly HashSet<string> SupportedLanguages = new HashSet<string>
+      {
+         "romaji","af","sq","am","ar","hy","az","eu","be","bn","bs","bg","ca","ceb","zh-CN","zh-TW","co","hr","cs","da","nl","en","eo","et","fi","fr","fy","gl","ka","de","el","gu","ht","ha","haw","he","hi","hmn","hu","is","ig","id","ga","it","ja","jw","kn","kk","km","ko","ku","ky","lo","la","lv","lt","lb","mk","mg","ms","ml","mt","mi","mr","mn","my","ne","no","ny","ps","fa","pl","pt","pa","ro","ru","sm","gd","sr","st","sn","sd","si","sk","sl","so","es","su","sw","sv","tl","tg","ta","te","th","tr","uk","ur","uz","vi","cy","xh","yi","yo","zu"
+      };
+
+      private static readonly string HttpsServicePointTranslateTemplateUrl = "https://translate.googleapis.com/translate_a/single?client=webapp&sl={0}&tl={1}&dt=t&tk={2}&q={3}";
+      private static readonly string HttpsServicePointRomanizeTemplateUrl = "https://translate.googleapis.com/translate_a/single?client=webapp&sl={0}&tl=en&dt=rm&tk={1}&q={2}";
       private static readonly string HttpsTranslateUserSite = "https://translate.google.com";
       private static readonly string DefaultUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36";
       private static readonly System.Random RandomNumbers = new System.Random();
@@ -50,17 +56,32 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
       public override void Initialize( InitializationContext context )
       {
          context.HttpSecurity.EnableSslFor( "translate.google.com", "translate.googleapis.com" );
+
+         if( !SupportedLanguages.Contains( context.DestinationLanguage ) ) throw new Exception( $"The destination language {context.DestinationLanguage} is not supported." );
       }
 
-      public override XUnityWebRequest CreateTranslationRequest( string untranslatedText, string from, string to )
+      public override XUnityWebRequest CreateTranslationRequest( HttpTranslationContext context )
       {
-         var request = new XUnityWebRequest(
-            string.Format(
-               HttpsServicePointTemplateUrl,
-               from,
-               to,
-               Tk( untranslatedText ),
-               WWW.EscapeURL( untranslatedText ) ) );
+         XUnityWebRequest request;
+         if( context.DestinationLanguage == "romaji" )
+         {
+            request = new XUnityWebRequest(
+               string.Format(
+                  HttpsServicePointRomanizeTemplateUrl,
+                  context.SourceLanguage,
+                  Tk( context.UntranslatedText ),
+                  WWW.EscapeURL( context.UntranslatedText ) ) );
+         }
+         else
+         {
+            request = new XUnityWebRequest(
+               string.Format(
+                  HttpsServicePointTranslateTemplateUrl,
+                  context.SourceLanguage,
+                  context.DestinationLanguage,
+                  Tk( context.UntranslatedText ),
+                  WWW.EscapeURL( context.UntranslatedText ) ) );
+         }
 
          request.Cookies = _cookieContainer;
          AddHeaders( request, true );
@@ -99,7 +120,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
          }
       }
 
-      public override void InspectTranslationResponse( XUnityWebResponse response )
+      public override void InspectTranslationResponse( HttpTranslationContext context, XUnityWebResponse response )
       {
          CookieCollection cookies = response.NewCookies;
          foreach( Cookie cookie in cookies )
@@ -112,14 +133,14 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
          _cookieContainer.Add( cookies );
       }
 
-      public override IEnumerator OnBeforeTranslate()
+      public override IEnumerator OnBeforeTranslate( HttpTranslationContext context )
       {
          if( !_hasSetup || AutoTranslationState.TranslationCount % 100 == 0 )
          {
             _hasSetup = true;
 
             // Setup TKK and cookies
-            var enumerator = SetupTKK();
+            var enumerator = SetupTKK( context );
             while( enumerator.MoveNext() )
             {
                yield return enumerator.Current;
@@ -127,7 +148,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
          }
       }
 
-      public IEnumerator SetupTKK()
+      public IEnumerator SetupTKK( HttpTranslationContext context )
       {
          XUnityWebResponse response = null;
 
@@ -157,7 +178,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
             }
          }
 
-         InspectTranslationResponse( response );
+         InspectTranslationResponse( context, response );
 
          // failure
          if( response.Error != null )
@@ -274,33 +295,27 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
          return p.ToString( CultureInfo.InvariantCulture ) + "." + ( p ^ m ).ToString( CultureInfo.InvariantCulture );
       }
 
-      public override bool TryExtractTranslated( string result, out string translated )
+      public override void ExtractTranslatedText( HttpTranslationContext context )
       {
-         try
-         {
-            var arr = JSON.Parse( result );
-            var lineBuilder = new StringBuilder( result.Length );
-
-            foreach( JSONNode entry in arr.AsArray[ 0 ].AsArray )
-            {
-               var token = entry.AsArray[ 0 ].ToString();
-               token = token.Substring( 1, token.Length - 2 ).UnescapeJson();
+         var dataIndex = context.DestinationLanguage == "romaji" ? 3 : 0;
 
-               if( !lineBuilder.EndsWithWhitespaceOrNewline() ) lineBuilder.Append( "\n" );
+         var data = context.ResultData;
+         var arr = JSON.Parse( data );
+         var lineBuilder = new StringBuilder( data.Length );
 
-               lineBuilder.Append( token );
-            }
+         foreach( JSONNode entry in arr.AsArray[ 0 ].AsArray )
+         {
+            var token = entry.AsArray[ dataIndex ].ToString();
+            token = token.Substring( 1, token.Length - 2 ).UnescapeJson();
 
-            translated = lineBuilder.ToString();
+            if( !lineBuilder.EndsWithWhitespaceOrNewline() ) lineBuilder.Append( "\n" );
 
-            var success = !string.IsNullOrEmpty( translated );
-            return success;
-         }
-         catch
-         {
-            translated = null;
-            return false;
+            lineBuilder.Append( token );
          }
+
+         var translated = lineBuilder.ToString();
+
+         context.Complete( translated );
       }
    }
 }

+ 23 - 25
src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Http/GoogleTranslateLegitimateEndpoint.cs

@@ -18,6 +18,11 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
 {
    internal class GoogleTranslateLegitimateEndpoint : HttpEndpoint
    {
+      private static readonly HashSet<string> SupportedLanguages = new HashSet<string>
+      {
+         "af","sq","am","ar","hy","az","eu","be","bn","bs","bg","ca","ceb","zh-CN","zh-TW","co","hr","cs","da","nl","en","eo","et","fi","fr","fy","gl","ka","de","el","gu","ht","ha","haw","he","hi","hmn","hu","is","ig","id","ga","it","ja","jw","kn","kk","km","ko","ku","ky","lo","la","lv","lt","lb","mk","mg","ms","ml","mt","mi","mr","mn","my","ne","no","ny","ps","fa","pl","pt","pa","ro","ru","sm","gd","sr","st","sn","sd","si","sk","sl","so","es","su","sw","sv","tl","tg","ta","te","th","tr","uk","ur","uz","vi","cy","xh","yi","yo","zu"
+      };
+
       private static readonly string HttpsServicePointTemplateUrl = "https://translation.googleapis.com/language/translate/v2?key={0}";
 
       private string _key;
@@ -33,15 +38,17 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
 
          // Configure service points / service point manager
          context.HttpSecurity.EnableSslFor( "translation.googleapis.com" );
+
+         if( !SupportedLanguages.Contains( context.DestinationLanguage ) ) throw new Exception( $"The destination language {context.DestinationLanguage} is not supported." );
       }
 
-      public override XUnityWebRequest CreateTranslationRequest( string untranslatedText, string from, string to )
+      public override XUnityWebRequest CreateTranslationRequest( HttpTranslationContext context )
       {
          var b = new StringBuilder();
          b.Append( "{" );
-         b.Append( "\"q\":\"" ).Append( untranslatedText.EscapeJson() ).Append( "\"," );
-         b.Append( "\"target\":\"" ).Append( to ).Append( "\"," );
-         b.Append( "\"source\":\"" ).Append( from ).Append( "\"," );
+         b.Append( "\"q\":\"" ).Append( context.UntranslatedText.EscapeJson() ).Append( "\"," );
+         b.Append( "\"target\":\"" ).Append( context.DestinationLanguage ).Append( "\"," );
+         b.Append( "\"source\":\"" ).Append( context.SourceLanguage ).Append( "\"," );
          b.Append( "\"format\":\"text\"" );
          b.Append( "}" );
          var data = b.ToString();
@@ -54,33 +61,24 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
          return request;
       }
 
-      public override bool TryExtractTranslated( string result, out string translated )
+      public override void ExtractTranslatedText( HttpTranslationContext context )
       {
-         try
-         {
-            var obj = JSON.Parse( result );
-            var lineBuilder = new StringBuilder( result.Length );
+         var obj = JSON.Parse( context.ResultData );
+         var lineBuilder = new StringBuilder( context.ResultData.Length );
 
-            foreach( JSONNode entry in obj.AsObject[ "data" ].AsObject[ "translations" ].AsArray )
-            {
-               var token = entry.AsObject[ "translatedText" ].ToString();
-               token = token.Substring( 1, token.Length - 2 ).UnescapeJson();
+         foreach( JSONNode entry in obj.AsObject[ "data" ].AsObject[ "translations" ].AsArray )
+         {
+            var token = entry.AsObject[ "translatedText" ].ToString();
+            token = token.Substring( 1, token.Length - 2 ).UnescapeJson();
 
-               if( !lineBuilder.EndsWithWhitespaceOrNewline() ) lineBuilder.Append( "\n" );
+            if( !lineBuilder.EndsWithWhitespaceOrNewline() ) lineBuilder.Append( "\n" );
 
-               lineBuilder.Append( token );
-            }
+            lineBuilder.Append( token );
+         }
 
-            translated = lineBuilder.ToString();
+         var translated = lineBuilder.ToString();
 
-            var success = !string.IsNullOrEmpty( translated );
-            return success;
-         }
-         catch
-         {
-            translated = null;
-            return false;
-         }
+         context.Complete( translated );
       }
    }
 }

+ 16 - 27
src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Http/HttpEndpoint.cs

@@ -1,10 +1,6 @@
 using System;
 using System.Collections;
-using System.Net;
-using System.Threading;
 using UnityEngine;
-using XUnity.AutoTranslator.Plugin.Core.Configuration;
-using XUnity.AutoTranslator.Plugin.Core.Shim;
 using XUnity.AutoTranslator.Plugin.Core.Web;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
@@ -15,26 +11,26 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
 
       public abstract string FriendlyName { get; }
 
-      public XUnityWebClient Client { get; }
-
       public int MaxConcurrency => 1;
 
       public abstract void Initialize( InitializationContext context );
 
-      public abstract bool TryExtractTranslated( string result, out string translated );
+      public abstract void ExtractTranslatedText( HttpTranslationContext context );
 
-      public abstract XUnityWebRequest CreateTranslationRequest( string untranslatedText, string from, string to );
+      public abstract XUnityWebRequest CreateTranslationRequest( HttpTranslationContext context );
 
-      public virtual void InspectTranslationResponse( XUnityWebResponse response )
+      public virtual void InspectTranslationResponse( HttpTranslationContext context, XUnityWebResponse response )
       {
       }
 
-      public virtual IEnumerator OnBeforeTranslate() => null;
+      public virtual IEnumerator OnBeforeTranslate( HttpTranslationContext context ) => null;
 
-      public IEnumerator Translate( string untranslatedText, string from, string to, Action<string> success, Action<string, Exception> failure )
+      public IEnumerator Translate( TranslationContext context )
       {
+         var httpContext = new HttpTranslationContext( context );
+
          // allow implementer of HttpEndpoint to do anything before starting translation
-         var setup = OnBeforeTranslate();
+         var setup = OnBeforeTranslate( httpContext );
          if( setup != null )
          {
             while( setup.MoveNext() )
@@ -48,12 +44,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
          {
             // prepare request
             var client = new XUnityWebClient();
-            var request = CreateTranslationRequest( untranslatedText, from, to );
+            var request = CreateTranslationRequest( httpContext );
             response = client.Send( request );
          }
          catch( Exception e )
          {
-            failure( "Error occurred while setting up translation request.", e );
+            httpContext.Fail( "Error occurred while setting up translation request.", e );
             yield break;
          }
 
@@ -70,39 +66,32 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
             }
          }
 
-         InspectTranslationResponse( response );
+         InspectTranslationResponse( httpContext, response );
 
          // failure
          if( response.Error != null )
          {
-            failure( "Error occurred while retrieving translation.", response.Error );
+            httpContext.Fail( "Error occurred while retrieving translation.", response.Error );
             yield break;
          }
 
          // failure
          if( response.Result == null )
          {
-            failure( "Error occurred while retrieving translation. Nothing was returned.", null );
+            httpContext.Fail( "Error occurred while retrieving translation. Nothing was returned.", null );
             yield break;
          }
 
+         httpContext.ResultData = response.Result;
 
          try
          {
             // attempt to extract translation from data
-            if( TryExtractTranslated( response.Result, out var translatedText ) )
-            {
-               translatedText = translatedText ?? string.Empty;
-               success( translatedText );
-            }
-            else
-            {
-               failure( "Error occurred while extracting translation.", null );
-            }
+            ExtractTranslatedText( httpContext );
          }
          catch( Exception e )
          {
-            failure( "Error occurred while retrieving translation.", e );
+            httpContext.Fail( "Error occurred while retrieving translation.", e );
          }
       }
    }

+ 32 - 0
src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Http/HttpTranslationContext.cs

@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
+{
+   public class HttpTranslationContext
+   {
+      private readonly TranslationContext _context;
+
+      internal HttpTranslationContext( TranslationContext context )
+      {
+         _context = context;
+      }
+
+      public string UntranslatedText => _context.UntranslatedText;
+      public string SourceLanguage => _context.SourceLanguage;
+      public string DestinationLanguage => _context.DestinationLanguage;
+      public string ResultData { get; internal set; }
+
+      public void Complete( string translatedText )
+      {
+         _context.Complete( translatedText );
+      }
+
+      public void Fail( string reason, Exception exception )
+      {
+         _context.Fail( reason, exception );
+      }
+   }
+}

+ 23 - 34
src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Http/YandexTranslateEndpoint.cs

@@ -33,16 +33,19 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
          if( string.IsNullOrEmpty( _key ) ) throw new Exception( "The YandexTranslate endpoint requires an API key which has not been provided." );
 
          context.HttpSecurity.EnableSslFor( "translate.yandex.net" );
+         
+         if( context.SourceLanguage != "ja" ) throw new Exception( "Current implementation only supports japanese-to-english." );
+         if( context.DestinationLanguage != "en" ) throw new Exception( "Current implementation only supports japanese-to-english." );
       }
 
-      public override XUnityWebRequest CreateTranslationRequest( string untranslatedText, string from, string to )
+      public override XUnityWebRequest CreateTranslationRequest( HttpTranslationContext context )
       {
          var request = new XUnityWebRequest(
             string.Format(
                HttpsServicePointTemplateUrl,
-               from,
-               to,
-               WWW.EscapeURL( untranslatedText ),
+               context.SourceLanguage,
+               context.DestinationLanguage,
+               WWW.EscapeURL( context.UntranslatedText ),
                _key ) );
 
          request.Headers[ HttpRequestHeader.UserAgent ] = string.IsNullOrEmpty( AutoTranslationState.UserAgent ) ? "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.183 Safari/537.36 Vivaldi/1.96.1147.55" : AutoTranslationState.UserAgent;
@@ -52,43 +55,29 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
          return request;
       }
 
-      public override bool TryExtractTranslated( string result, out string translated )
+      public override void ExtractTranslatedText( HttpTranslationContext context )
       {
-         try
-         {
-            var obj = JSON.Parse( result );
-            var lineBuilder = new StringBuilder( result.Length );
+         var data = context.ResultData;
+         var obj = JSON.Parse( data );
+         var lineBuilder = new StringBuilder( data.Length );
 
-            var code = obj.AsObject[ "code" ].ToString();
+         var code = obj.AsObject[ "code" ].ToString();
 
-            if( code == "200" )
+         if( code == "200" )
+         {
+            var token = obj.AsObject[ "text" ].ToString();
+            token = token.Substring( 2, token.Length - 4 ).UnescapeJson();
+            if( string.IsNullOrEmpty( token ) )
             {
-               var token = obj.AsObject[ "text" ].ToString();
-               token = token.Substring( 2, token.Length - 4 ).UnescapeJson();
-               if( String.IsNullOrEmpty( token ) )
-               {
-                  translated = null;
-                  return false;
-               }
+               return;
+            }
 
-               if( !lineBuilder.EndsWithWhitespaceOrNewline() ) lineBuilder.Append( "\n" );
-               lineBuilder.Append( token );
+            if( !lineBuilder.EndsWithWhitespaceOrNewline() ) lineBuilder.Append( "\n" );
+            lineBuilder.Append( token );
 
-               translated = lineBuilder.ToString();
+            var translated = lineBuilder.ToString();
 
-               var success = !string.IsNullOrEmpty( translated );
-               return success;
-            }
-            else
-            {
-               translated = null;
-               return false;
-            }
-         }
-         catch( Exception )
-         {
-            translated = null;
-            return false;
+            context.Complete( translated );
          }
       }
    }

+ 1 - 1
src/XUnity.AutoTranslator.Plugin.Core/Endpoints/ITranslateEndpoint.cs

@@ -34,6 +34,6 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
       /// Attempt to translated the provided untranslated text. Will be used in a "coroutine", so it can be implemented
       /// in an async fashion.
       /// </summary>
-      IEnumerator Translate( string untranslatedText, string from, string to, Action<string> success, Action<string, Exception> failure );
+      IEnumerator Translate( TranslationContext context );
    }
 }

+ 17 - 1
src/XUnity.AutoTranslator.Plugin.Core/Endpoints/InitializationContext.cs

@@ -9,10 +9,16 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
 {
    public class InitializationContext
    {
-      public InitializationContext( IConfiguration config, HttpSecurity httpSecurity )
+      internal InitializationContext(
+         IConfiguration config,
+         HttpSecurity httpSecurity,
+         string sourceLanguage,
+         string destinationLanguage )
       {
          Config = config;
          HttpSecurity = httpSecurity;
+         SourceLanguage = sourceLanguage;
+         DestinationLanguage = destinationLanguage;
       }
 
       /// <summary>
@@ -25,5 +31,15 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
       /// Gets the HttpSecurity class which enables setting up SSL for endpoints.
       /// </summary>
       public HttpSecurity HttpSecurity { get; }
+
+      /// <summary>
+      /// Gets the source language.
+      /// </summary>
+      public string SourceLanguage { get; }
+
+      /// <summary>
+      /// Gets the destination language.
+      /// </summary>
+      public string DestinationLanguage { get; }
    }
 }

+ 1 - 0
src/XUnity.AutoTranslator.Plugin.Core/Endpoints/KnownEndpoints.cs

@@ -38,6 +38,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
             {
                // allow implementing plugins to hook into Unity lifecycle
                endpoint = (ITranslateEndpoint)go.AddComponent( type );
+               UnityEngine.Object.DontDestroyOnLoad( (UnityEngine.Object)endpoint );
             }
             else
             {

+ 70 - 0
src/XUnity.AutoTranslator.Plugin.Core/Endpoints/TranslationContext.cs

@@ -0,0 +1,70 @@
+using System;
+
+namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
+{
+   public class TranslationContext
+   {
+      private Action<string> _complete;
+      private Action<string, Exception> _fail;
+
+      public TranslationContext(
+         string untranslatedText,
+         string sourceLanguage,
+         string destinationLanguage,
+         Action<string> complete,
+         Action<string, Exception> fail )
+      {
+         UntranslatedText = untranslatedText;
+         SourceLanguage = sourceLanguage;
+         DestinationLanguage = destinationLanguage;
+
+         _complete = complete;
+         _fail = fail;
+      }
+
+      public string UntranslatedText { get; }
+      public string SourceLanguage { get; }
+      public string DestinationLanguage { get; }
+
+      internal bool Completed { get; set; }
+
+      public void Complete( string translatedText )
+      {
+         try
+         {
+            if( !string.IsNullOrEmpty( translatedText ) )
+            {
+               _complete( translatedText );
+            }
+            else
+            {
+               _fail( "Received empty translation from translator.", null );
+            }
+         }
+         finally
+         {
+            Completed = true;
+         }
+      }
+
+      public void Fail( string reason, Exception exception )
+      {
+         try
+         {
+            _fail( reason, exception );
+         }
+         finally
+         {
+            Completed = true;
+         }
+      }
+
+      internal void FailIfNotCompleted()
+      {
+         if( !Completed )
+         {
+            Fail( "The translation request was not completed before returning from translator!", null );
+         }
+      }
+   }
+}

+ 25 - 30
src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Www/WatsonTranslateEndpoint.cs

@@ -38,52 +38,47 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Www
          if( string.IsNullOrEmpty( _key ) ) throw new Exception( "The WatsonTranslate endpoint requires a key which has not been provided." );
 
          _template = _url.TrimEnd( '/' ) + "/v3/translate?version=2018-05-01";
-      }
 
-      public override string GetServiceUrl( string untranslatedText, string from, string to )
-      {
-         return _template;
+         if( context.SourceLanguage != "ja" ) throw new Exception( "Current implementation only supports japanese-to-english." );
+         if( context.DestinationLanguage != "en" ) throw new Exception( "Current implementation only supports japanese-to-english." );
       }
 
-      public override string GetRequestObject( string untranslatedText, string from, string to )
+      public override void CreateTranslationRequest( WwwTranslationContext context )
       {
-         return string.Format( RequestTemplate, from, to, TextHelper.EscapeJson( untranslatedText ) );
-      }
+         context.SetServiceUrl( _template );
+         context.SetRequestObject(
+            string.Format(
+               RequestTemplate,
+               context.SourceLanguage,
+               context.DestinationLanguage,
+               TextHelper.EscapeJson( context.UntranslatedText ) ) );
 
-      public override void ApplyHeaders( Dictionary<string, string> headers )
-      {
+         var headers = new Dictionary<string, string>();
          headers[ "User-Agent" ] = string.IsNullOrEmpty( AutoTranslationState.UserAgent ) ? "curl/7.55.1" : AutoTranslationState.UserAgent;
          headers[ "Accept" ] = "application/json";
          headers[ "Content-Type" ] = "application/json";
          headers[ "Authorization" ] = "Basic " + Convert.ToBase64String( Encoding.ASCII.GetBytes( "apikey:" + _key ) );
+         context.SetHeaders( headers );
       }
 
-      public override bool TryExtractTranslated( string result, out string translated )
+      public override void ExtractTranslatedText( WwwTranslationContext context )
       {
-         try
-         {
-            var obj = JSON.Parse( result );
-            var lineBuilder = new StringBuilder( result.Length );
+         var data = context.ResultData;
+         var obj = JSON.Parse( data );
+         var lineBuilder = new StringBuilder( data.Length );
 
-            foreach( JSONNode entry in obj.AsObject[ "translations" ].AsArray )
-            {
-               var token = entry.AsObject[ "translation" ].ToString();
-               token = token.Substring( 1, token.Length - 2 ).UnescapeJson();
-
-               if( !lineBuilder.EndsWithWhitespaceOrNewline() ) lineBuilder.Append( "\n" );
+         foreach( JSONNode entry in obj.AsObject[ "translations" ].AsArray )
+         {
+            var token = entry.AsObject[ "translation" ].ToString();
+            token = token.Substring( 1, token.Length - 2 ).UnescapeJson();
 
-               lineBuilder.Append( token );
-            }
-            translated = lineBuilder.ToString();
+            if( !lineBuilder.EndsWithWhitespaceOrNewline() ) lineBuilder.Append( "\n" );
 
-            var success = !string.IsNullOrEmpty( translated );
-            return success;
-         }
-         catch( Exception )
-         {
-            translated = null;
-            return false;
+            lineBuilder.Append( token );
          }
+         var translated = lineBuilder.ToString();
+
+         context.Complete( translated );
       }
    }
 }

+ 17 - 28
src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Www/WwwEndpoint.cs

@@ -21,22 +21,20 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Www
 
       public abstract void Initialize( InitializationContext context );
 
-      public abstract string GetServiceUrl( string untranslatedText, string from, string to );
+      public abstract void CreateTranslationRequest( WwwTranslationContext context );
 
-      public virtual string GetRequestObject( string untranslatedText, string from, string to ) => null;
+      public abstract void ExtractTranslatedText( WwwTranslationContext context );
 
-      public abstract void ApplyHeaders( Dictionary<string, string> headers );
-
-      public abstract bool TryExtractTranslated( string result, out string translated );
-
-      public virtual IEnumerator OnBeforeTranslate() => null;
+      public virtual IEnumerator OnBeforeTranslate( WwwTranslationContext context ) => null;
 
       public int MaxConcurrency => 1;
 
-      public IEnumerator Translate( string untranslatedText, string from, string to, Action<string> success, Action<string, Exception> failure )
+      public IEnumerator Translate( TranslationContext context )
       {
+         var wwwContext = new WwwTranslationContext( context );
+
          // allow implementer of HttpEndpoint to do anything before starting translation
-         var setup = OnBeforeTranslate();
+         var setup = OnBeforeTranslate( wwwContext );
          if( setup != null )
          {
             while( setup.MoveNext() )
@@ -49,23 +47,23 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Www
          try
          {
             // prepare request
-            var headers = new Dictionary<string, string>();
-            ApplyHeaders( headers );
-            var url = GetServiceUrl( untranslatedText, from, to );
-            var data = GetRequestObject( untranslatedText, from, to );
+            CreateTranslationRequest( wwwContext );
+            var url = wwwContext.ServiceUrl;
+            var data = wwwContext.Data;
+            var headers = wwwContext.Headers ?? new Dictionary<string, string>();
 
             // execute request
             www = WwwConstructor.Invoke( new object[] { url, data != null ? Encoding.UTF8.GetBytes( data ) : null, headers } );
          }
          catch( Exception e )
          {
-            failure( "Error occurred while setting up translation request.", e );
+            wwwContext.Fail( "Error occurred while setting up translation request.", e );
             yield break;
          }
 
          if( www == null )
          {
-            failure( "Unexpected error occurred while retrieving translation.", null );
+            wwwContext.Fail( "Unexpected error occurred while retrieving translation.", null );
             yield break;
          }
 
@@ -87,7 +85,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Www
 
             if( error != null )
             {
-               failure( "Error occurred while retrieving translation." + Environment.NewLine + error, null );
+               wwwContext.Fail( "Error occurred while retrieving translation." + Environment.NewLine + error, null );
                yield break;
             }
 
@@ -95,24 +93,15 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Www
             var text = (string)AccessTools.Property( Constants.ClrTypes.WWW, "text" ).GetValue( www, null );
             if( text == null )
             {
-               failure( "Error occurred while extracting text from response.", null );
+               wwwContext.Fail( "Error occurred while extracting text from response.", null );
                yield break;
             }
 
-            // attempt to extract translation from data
-            if( TryExtractTranslated( text, out var translatedText ) )
-            {
-               translatedText = translatedText ?? string.Empty;
-               success( translatedText );
-            }
-            else
-            {
-               failure( "Error occurred while extracting translation.", null );
-            }
+            ExtractTranslatedText( wwwContext );
          }
          catch( Exception e )
          {
-            failure( "Error occurred while retrieving translation.", e );
+            wwwContext.Fail( "Error occurred while retrieving translation.", e );
          }
       }
    }

+ 53 - 0
src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Www/WwwTranslationContext.cs

@@ -0,0 +1,53 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Www
+{
+   public class WwwTranslationContext
+   {
+      private readonly TranslationContext _context;
+
+      internal WwwTranslationContext( TranslationContext context )
+      {
+         _context = context;
+      }
+
+      public string UntranslatedText => _context.UntranslatedText;
+      public string SourceLanguage => _context.SourceLanguage;
+      public string DestinationLanguage => _context.DestinationLanguage;
+      public string ResultData { get; internal set; }
+
+      internal string ServiceUrl { get; private set; }
+      internal string Data { get; private set; }
+      internal Dictionary<string, string> Headers { get; private set; }
+
+      public void SetServiceUrl( string serviceUrl )
+      {
+         if( string.IsNullOrEmpty( serviceUrl ) ) throw new ArgumentNullException( nameof( serviceUrl ), "Received empty service url from translator." );
+
+         ServiceUrl = serviceUrl;
+      }
+
+      public void SetRequestObject( string data )
+      {
+         Data = data;
+      }
+
+      public void SetHeaders( Dictionary<string, string> headers )
+      {
+         Headers = headers;
+      }
+
+      public void Complete( string translatedText )
+      {
+         _context.Complete( translatedText );
+      }
+
+      public void Fail( string reason, Exception exception )
+      {
+         _context.Fail( reason, exception );
+      }
+   }
+}

+ 1 - 1
src/XUnity.AutoTranslator.Plugin.Core/UI/DropdownOptionViewModel.cs

@@ -35,7 +35,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.UI
       public TranslatorDropdownOptionViewModel( Func<bool> isSelected, ConfiguredEndpoint selection, Action<ConfiguredEndpoint> onSelected ) : base( selection.Endpoint.FriendlyName, isSelected, () => selection.Error == null, selection, onSelected )
       {
          _selected = new GUIContent( selection.Endpoint.FriendlyName, $"<b>CURRENT TRANSLATOR</b>\n{selection.Endpoint.FriendlyName} is the currently selected translator that will be used to perform translations." );
-         _disabled = new GUIContent( selection.Endpoint.FriendlyName, $"<b>CANNOT SELECT TRANSLATOR</b>\n{selection.Endpoint.FriendlyName} cannot be selected because it must be configured first. {selection.Error?.Message}" );
+         _disabled = new GUIContent( selection.Endpoint.FriendlyName, $"<b>CANNOT SELECT TRANSLATOR</b>\n{selection.Endpoint.FriendlyName} cannot be selected because it an error occurred during initialization. {selection.Error?.Message}" );
          _normal = new GUIContent( selection.Endpoint.FriendlyName, $"<b>SELECT TRANSLATOR</b>\n{selection.Endpoint.FriendlyName} will be selected as translator." );
       }
 

+ 4 - 0
src/XUnity.AutoTranslator.Plugin.Core/XUnity.AutoTranslator.Plugin.Core.csproj

@@ -24,6 +24,10 @@
       <Folder Include="MonoHttp\" />
    </ItemGroup>
 
+   <ItemGroup>
+     <ProjectReference Include="..\..\XUnity.AutoTranslator.Plugin.ExtProtocol\XUnity.AutoTranslator.Plugin.ExtProtocol.csproj" />
+   </ItemGroup>
+
    <ItemGroup>
       <Compile Update="Properties\Resources.Designer.cs">
          <DesignTime>True</DesignTime>

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

@@ -28,7 +28,7 @@
       <ItemGroup>
          <VersionNumber Include="$([System.Text.RegularExpressions.Regex]::Replace(&quot;%(Targets.Version)&quot;, &quot;^(.+?)(\.0+)$&quot;, &quot;$1&quot;))" />
       </ItemGroup>
-      <Exec Command="if $(ConfigurationName) == Release (&#xD;&#xA;   for %%f in (&quot;$(SolutionDir)dist\Translators\*&quot;) do XCOPY /Y /I &quot;%%f&quot; &quot;$(SolutionDir)dist\IPA\Plugins\Translators\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)ExIni.dll&quot; &quot;$(SolutionDir)dist\IPA\Plugins\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)0Harmony.dll&quot; &quot;$(SolutionDir)dist\IPA\Plugins\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)XUnity.AutoTranslator.Plugin.Core.dll&quot; &quot;$(SolutionDir)dist\IPA\Plugins\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)$(TargetName)$(TargetExt)&quot; &quot;$(SolutionDir)dist\IPA\Plugins\&quot;&#xD;&#xA;   COPY /Y &quot;$(SolutionDir)README.md&quot; &quot;$(SolutionDir)dist\IPA\Plugins\README (AutoTranslator).md&quot;&#xD;&#xA;   powershell Compress-Archive -Path '$(SolutionDir)dist\IPA\Plugins' -DestinationPath '$(SolutionDir)dist\IPA\XUnity.AutoTranslator-IPA-@(VersionNumber).zip' -Force)&#xD;&#xA;)" />
+      <Exec Command="if $(ConfigurationName) == Release (&#xD;&#xA;   for %%f in (&quot;$(SolutionDir)dist\Translators\*&quot;) do XCOPY /Y /I &quot;%%f&quot; &quot;$(SolutionDir)dist\IPA\Plugins\Translators\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)ExIni.dll&quot; &quot;$(SolutionDir)dist\IPA\Plugins\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)0Harmony.dll&quot; &quot;$(SolutionDir)dist\IPA\Plugins\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)XUnity.AutoTranslator.Plugin.Core.dll&quot; &quot;$(SolutionDir)dist\IPA\Plugins\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)XUnity.AutoTranslator.Plugin.ExtProtocol.dll&quot; &quot;$(SolutionDir)dist\IPA\Plugins\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)$(TargetName)$(TargetExt)&quot; &quot;$(SolutionDir)dist\IPA\Plugins\&quot;&#xD;&#xA;   COPY /Y &quot;$(SolutionDir)README.md&quot; &quot;$(SolutionDir)dist\IPA\Plugins\README (AutoTranslator).md&quot;&#xD;&#xA;   powershell Compress-Archive -Path '$(SolutionDir)dist\IPA\Plugins' -DestinationPath '$(SolutionDir)dist\IPA\XUnity.AutoTranslator-IPA-@(VersionNumber).zip' -Force)&#xD;&#xA;)" />
    </Target>
 
 </Project>

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

@@ -28,7 +28,7 @@
       <ItemGroup>
          <VersionNumber Include="$([System.Text.RegularExpressions.Regex]::Replace(&quot;%(Targets.Version)&quot;, &quot;^(.+?)(\.0+)$&quot;, &quot;$1&quot;))" />
       </ItemGroup>
-      <Exec Command="if $(ConfigurationName) == Release (&#xD;&#xA;   for %%f in (&quot;$(SolutionDir)dist\Translators\*&quot;) do XCOPY /Y /I &quot;%%f&quot; &quot;$(SolutionDir)dist\UnityInjector\UnityInjector\Translators\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)ExIni.dll&quot; &quot;$(SolutionDir)dist\UnityInjector\UnityInjector\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)0Harmony.dll&quot; &quot;$(SolutionDir)dist\UnityInjector\UnityInjector\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)XUnity.AutoTranslator.Plugin.Core.dll&quot; &quot;$(SolutionDir)dist\UnityInjector\UnityInjector\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)$(TargetName)$(TargetExt)&quot; &quot;$(SolutionDir)dist\UnityInjector\UnityInjector\&quot;&#xD;&#xA;   COPY /Y &quot;$(SolutionDir)README.md&quot; &quot;$(SolutionDir)dist\UnityInjector\UnityInjector\README (AutoTranslator).md&quot;&#xD;&#xA;   powershell Compress-Archive -Path '$(SolutionDir)dist\UnityInjector\UnityInjector' -DestinationPath '$(SolutionDir)dist\UnityInjector\XUnity.AutoTranslator-UnityInjector-@(VersionNumber).zip' -Force)&#xD;&#xA;)" />
+      <Exec Command="if $(ConfigurationName) == Release (&#xD;&#xA;   for %%f in (&quot;$(SolutionDir)dist\Translators\*&quot;) do XCOPY /Y /I &quot;%%f&quot; &quot;$(SolutionDir)dist\UnityInjector\UnityInjector\Translators\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)ExIni.dll&quot; &quot;$(SolutionDir)dist\UnityInjector\UnityInjector\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)0Harmony.dll&quot; &quot;$(SolutionDir)dist\UnityInjector\UnityInjector\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)XUnity.AutoTranslator.Plugin.Core.dll&quot; &quot;$(SolutionDir)dist\UnityInjector\UnityInjector\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)XUnity.AutoTranslator.Plugin.ExtProtocol.dll&quot; &quot;$(SolutionDir)dist\UnityInjector\UnityInjector\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)$(TargetName)$(TargetExt)&quot; &quot;$(SolutionDir)dist\UnityInjector\UnityInjector\&quot;&#xD;&#xA;   COPY /Y &quot;$(SolutionDir)README.md&quot; &quot;$(SolutionDir)dist\UnityInjector\UnityInjector\README (AutoTranslator).md&quot;&#xD;&#xA;   powershell Compress-Archive -Path '$(SolutionDir)dist\UnityInjector\UnityInjector' -DestinationPath '$(SolutionDir)dist\UnityInjector\XUnity.AutoTranslator-UnityInjector-@(VersionNumber).zip' -Force)&#xD;&#xA;)" />
    </Target>
 
 </Project>

+ 2 - 0
src/XUnity.AutoTranslator.Setup/Program.cs

@@ -41,6 +41,7 @@ namespace XUnity.AutoTranslator.Setup
          AddFile( Path.Combine( reiPath, "Mono.Cecil.Rocks.dll" ), Resources.Mono_Cecil_Rocks );
          AddFile( Path.Combine( reiPath, "ReiPatcher.exe" ), Resources.ReiPatcher );
          AddFile( Path.Combine( patchesPath, "XUnity.AutoTranslator.Patcher.dll" ), Resources.XUnity_AutoTranslator_Patcher, true );
+         AddFile( Path.Combine( translatorsPath, "XUnity.AutoTranslator.Plugin.ExtProtocol.dll" ), Resources.XUnity_AutoTranslator_Plugin_ExtProtocol, true );
          AddFile( Path.Combine( translatorsPath, "BaiduTranslate.dll" ), Resources.XUnity_AutoTranslator_Plugin_BaiduTranslate, true );
          AddFile( Path.Combine( translatorsPath, "BingTranslate.dll" ), Resources.XUnity_AutoTranslator_Plugin_BingTranslate, true );
          AddFile( Path.Combine( translatorsPath, "BingLegitimateTranslate.dll" ), Resources.XUnity_AutoTranslator_Plugin_BingTranslateLegitimate, true );
@@ -59,6 +60,7 @@ namespace XUnity.AutoTranslator.Setup
             AddFile( Path.Combine( managedDir, "ExIni.dll" ), Resources.ExIni );
             AddFile( Path.Combine( managedDir, "ReiPatcher.exe" ), Resources.ReiPatcher );
             AddFile( Path.Combine( managedDir, "XUnity.AutoTranslator.Plugin.Core.dll" ), Resources.XUnity_AutoTranslator_Plugin_Core, true );
+            AddFile( Path.Combine( managedDir, "XUnity.AutoTranslator.Plugin.ExtProtocol.dll" ), Resources.XUnity_AutoTranslator_Plugin_ExtProtocol, true );
 
             // create an .ini file for each launcher, if it does not already exist
             var iniInfo = new FileInfo( Path.Combine( reiPath, Path.GetFileNameWithoutExtension( launcher.Executable.Name ) + ".ini" ) );

+ 10 - 0
src/XUnity.AutoTranslator.Setup/Properties/Resources.Designer.cs

@@ -200,6 +200,16 @@ namespace XUnity.AutoTranslator.Setup.Properties {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized resource of type System.Byte[].
+        /// </summary>
+        internal static byte[] XUnity_AutoTranslator_Plugin_ExtProtocol {
+            get {
+                object obj = ResourceManager.GetObject("XUnity_AutoTranslator_Plugin_ExtProtocol", resourceCulture);
+                return ((byte[])(obj));
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized resource of type System.Byte[].
         /// </summary>

+ 3 - 0
src/XUnity.AutoTranslator.Setup/Properties/Resources.resx

@@ -172,6 +172,9 @@
    <data name="XUnity_AutoTranslator_Plugin_Core" type="System.Resources.ResXFileRef, System.Windows.Forms">
       <value>..\..\XUnity.AutoTranslator.Plugin.Core\bin\Release\net35\XUnity.AutoTranslator.Plugin.Core.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
    </data>
+   <data name="XUnity_AutoTranslator_Plugin_ExtProtocol" type="System.Resources.ResXFileRef, System.Windows.Forms">
+      <value>..\..\XUnity.AutoTranslator.Plugin.Core\bin\Release\net35\XUnity.AutoTranslator.Plugin.ExtProtocol.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+   </data>
    <data name="XUnity_AutoTranslator_Plugin_Lec" type="System.Resources.ResXFileRef, System.Windows.Forms">
       <value>..\..\Translators\XUnity.AutoTranslator.Plugin.Lec\bin\x86\Release\net35\Lec.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
    </data>