فهرست منبع

bepinex 5.x support

randoman 6 سال پیش
والد
کامیت
5d1444364d
39فایلهای تغییر یافته به همراه586 افزوده شده و 228 حذف شده
  1. 5 1
      CHANGELOG.md
  2. 11 12
      README.md
  3. 11 0
      XUnity.AutoTranslator.sln
  4. BIN
      libs/BepInEx 5.0/BepInEx.Harmony.dll
  5. 51 0
      libs/BepInEx 5.0/BepInEx.Harmony.xml
  6. BIN
      libs/BepInEx 5.0/BepInEx.Preloader.dll
  7. BIN
      libs/BepInEx 5.0/BepInEx.dll
  8. 7 1
      src/Translators/BingTranslate/BingTranslateEndpoint.cs
  9. 7 1
      src/Translators/GoogleTranslate/GoogleTranslateEndpoint.cs
  10. 1 1
      src/XUnity.AutoTranslator.Patcher/Patcher.cs
  11. 62 0
      src/XUnity.AutoTranslator.Plugin.BepIn-5x/AutoTranslatorPlugin.cs
  12. 42 0
      src/XUnity.AutoTranslator.Plugin.BepIn-5x/BepInLogger.cs
  13. 37 0
      src/XUnity.AutoTranslator.Plugin.BepIn-5x/XUnity.AutoTranslator.Plugin.BepIn-5x.csproj
  14. 1 1
      src/XUnity.AutoTranslator.Plugin.BepIn/XUnity.AutoTranslator.Plugin.BepIn.csproj
  15. 97 58
      src/XUnity.AutoTranslator.Plugin.Core/AutoTranslationPlugin.cs
  16. 6 1
      src/XUnity.AutoTranslator.Plugin.Core/Configuration/Settings.cs
  17. 1 1
      src/XUnity.AutoTranslator.Plugin.Core/Constants/PluginData.cs
  18. 4 4
      src/XUnity.AutoTranslator.Plugin.Core/Constants/UserAgents.cs
  19. 75 50
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/TranslationEndpointManager.cs
  20. 3 6
      src/XUnity.AutoTranslator.Plugin.Core/TextTranslationCache.cs
  21. 1 1
      src/XUnity.AutoTranslator.Plugin.Core/TextTranslationInfo.cs
  22. 0 3
      src/XUnity.AutoTranslator.Plugin.Core/TranslationManager.cs
  23. 21 7
      src/XUnity.AutoTranslator.Plugin.Core/UI/AggregatedTranslationViewModel.cs
  24. 20 30
      src/XUnity.AutoTranslator.Plugin.Core/UI/DropdownGUI.cs
  25. 37 5
      src/XUnity.AutoTranslator.Plugin.Core/UI/DropdownOptionViewModel.cs
  26. 3 1
      src/XUnity.AutoTranslator.Plugin.Core/UI/GUIUtil.cs
  27. 7 1
      src/XUnity.AutoTranslator.Plugin.Core/UI/IndividualTranslationViewModel.cs
  28. 0 2
      src/XUnity.AutoTranslator.Plugin.Core/UI/ScrollViewGUI.cs
  29. 4 1
      src/XUnity.AutoTranslator.Plugin.Core/UI/ToggleViewModel.cs
  30. 18 7
      src/XUnity.AutoTranslator.Plugin.Core/UI/TranslationAggregatorOptionsWindow.cs
  31. 20 8
      src/XUnity.AutoTranslator.Plugin.Core/UI/TranslationAggregatorViewModel.cs
  32. 17 17
      src/XUnity.AutoTranslator.Plugin.Core/UI/TranslationAggregatorWindow.cs
  33. 4 3
      src/XUnity.AutoTranslator.Plugin.Core/UI/XuaViewModel.cs
  34. 1 1
      src/XUnity.AutoTranslator.Plugin.Core/UI/XuaWindow.cs
  35. 8 0
      src/XUnity.AutoTranslator.Plugin.Core/Utilities/LanguageHelper.cs
  36. 1 1
      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. 1 1
      src/XUnity.AutoTranslator.Setup/XUnity.AutoTranslator.Setup.csproj

+ 5 - 1
CHANGELOG.md

@@ -1,9 +1,13 @@
 ### 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
  * BUG FIX - Better handling of error'ed translations in relation to rich text
+ * BUG FIX - Minor fixes to 'copy to clipboard' to disable IMGUI spam
+ * BUG FIX - Fixed potential NullReferenceException in GoogleTranslate and BingTranslate during timeout errors
  * MISC - Removed 'Dump Untranslated Texts' hotkey due to feature bloat
- * MISC - Improved Utage image hooking to support DicingImage
+ * MISC - Allow unselecting translation endpoint in UI
+ * MISC - Increased request timeout from 50 to 150 seconds to ensure better error logging of failed requests
 
 ### 3.1.0
  * FEATURE - Support for games with 'netstandard2.0' API surface through config option 'EnableExperimentalHooks'

+ 11 - 12
README.md

@@ -2,11 +2,11 @@
 
 ## Index
  * [Introduction](#introduction)
+ * [Plugin Frameworks](#plugin-frameworks)
  * [Installation](#installation)
  * [Key Mapping](#key-mapping)
  * [Translators](#translators)
  * [Text Frameworks](#text-frameworks)
- * [Plugin Frameworks](#plugin-frameworks)
  * [Configuration](#configuration)
  * [Frequently Asked Questions](#frequently-asked-questions)
  * [Translating Mods](#translating-mods)
@@ -27,6 +27,16 @@ If you intend on redistributing this plugin as part of a translation suite for a
 
 From version 3.0.0 it is possible to implement custom translators. See [this section](#implementing-a-translator) for more info.
 
+## Plugin Frameworks
+The mod can be installed into the following Plugin Managers:
+ * [BepInEx](https://github.com/bbepis/BepInEx) (preferred approach)
+ * [IPA](https://github.com/Eusth/IPA)
+ * UnityInjector
+
+Installation instructions for all methods can be found below.
+
+Additionally it can be installed without a dependency on a plugin manager through ReiPatcher. However, this approach is not recommended if you use one of the above mentioned Plugin Managers!
+
 ## Installation
 The plugin can be installed in following ways:
 
@@ -188,16 +198,6 @@ The following text frameworks are supported.
  * [TextMeshPro](http://digitalnativestudios.com/textmeshpro/docs/)
  * [Utage (VN Game Engine)](http://madnesslabo.net/utage/?lang=en)
 
-## Plugin Frameworks
-The mod can be installed into the following Plugin Managers:
- * [BepInEx](https://github.com/bbepis/BepInEx) (preferred approach)
- * [IPA](https://github.com/Eusth/IPA)
- * UnityInjector
-
-Installation instructions for all methods can be found below.
-
-Additionally it can be installed without a dependency on a plugin manager through ReiPatcher. However, this approach is not recommended if you use one of the above mentioned Plugin Managers!
-
 ## Configuration
 The default configuration file, looks as such:
 
@@ -390,7 +390,6 @@ In this context, the `Translation\_AutoGeneratedTranslations.{lang}.txt` (Output
 ## Regarding Redistribution
 Redistributing this plugin for various games is absolutely encouraged. However, if you do so, please keep the following in mind:
  * **Distribute the _AutoGeneratedTranslations.{lang}.txt file along with the redistribution with as many translations as possible to ensure the online translator is hit as little as possible.**
- * **Do not redistribute the mod with the configuration option `EnableIMGUI=True`.**
  * **Test your redistribution with logging/console enabled to ensure the game does not exhibit undesirable behaviour such as spamming the endpoints.**
  * Ensure you keep the plugin up-to-date, as much as reasonably possible.
  * If you use image loading feature, make sure you read [this section](#texture-translation).

+ 11 - 0
XUnity.AutoTranslator.sln

@@ -83,6 +83,8 @@ 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}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -291,6 +293,14 @@ Global
 		{E2F50278-9134-4DC8-9C50-4C0A52063A89}.Release|Any CPU.Build.0 = Release|Any CPU
 		{E2F50278-9134-4DC8-9C50-4C0A52063A89}.Release|x86.ActiveCfg = Release|Any CPU
 		{E2F50278-9134-4DC8-9C50-4C0A52063A89}.Release|x86.Build.0 = Release|Any CPU
+		{ADCCF172-7D31-42C6-B9D4-1779EAC8B403}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{ADCCF172-7D31-42C6-B9D4-1779EAC8B403}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{ADCCF172-7D31-42C6-B9D4-1779EAC8B403}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{ADCCF172-7D31-42C6-B9D4-1779EAC8B403}.Debug|x86.Build.0 = Debug|Any CPU
+		{ADCCF172-7D31-42C6-B9D4-1779EAC8B403}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{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
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -322,6 +332,7 @@ Global
 		{BE7ED338-106C-4CC6-BA19-8ADB2534326A} = {0F9B38FC-4E57-4B83-AF0B-0993B8470823}
 		{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}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {EE803FED-4447-4D19-B3D6-88C56E8DFCCA}

BIN
libs/BepInEx 5.0/BepInEx.Harmony.dll


+ 51 - 0
libs/BepInEx 5.0/BepInEx.Harmony.xml

@@ -0,0 +1,51 @@
+<?xml version="1.0"?>
+<doc>
+    <assembly>
+        <name>BepInEx.Harmony</name>
+    </assembly>
+    <members>
+        <member name="T:BepInEx.Harmony.ParameterByRefAttribute">
+            <summary>
+            Specifies the indices of parameters that are ByRef.
+            </summary>
+        </member>
+        <member name="P:BepInEx.Harmony.ParameterByRefAttribute.ParameterIndices">
+            <summary>
+            The indices of parameters that are ByRef.
+            </summary>
+        </member>
+        <member name="M:BepInEx.Harmony.ParameterByRefAttribute.#ctor(System.Int32[])">
+            <param name="parameterIndices">The indices of parameters that are ByRef.</param>
+        </member>
+        <member name="T:BepInEx.Harmony.HarmonyWrapper">
+            <summary>
+            A wrapper for Harmony based operations.
+            </summary>
+        </member>
+        <member name="P:BepInEx.Harmony.HarmonyWrapper.DefaultInstance">
+            <summary>
+            The HarmonyInstance that is used when none is specified.
+            </summary>
+        </member>
+        <member name="M:BepInEx.Harmony.HarmonyWrapper.PatchAll(System.Type,Harmony.HarmonyInstance)">
+            <summary>
+            Applies all patches specified in the type.
+            </summary>
+            <param name="type">The type to scan.</param>
+            <param name="harmonyInstance">The HarmonyInstance to use.</param>
+        </member>
+        <member name="M:BepInEx.Harmony.HarmonyWrapper.PatchAll(System.Reflection.Assembly,Harmony.HarmonyInstance)">
+            <summary>
+            Applies all patches specified in the assembly.
+            </summary>
+            <param name="assembly">The assembly to scan.</param>
+            <param name="harmonyInstance">The HarmonyInstance to use.</param>
+        </member>
+        <member name="M:BepInEx.Harmony.HarmonyWrapper.PatchAll(Harmony.HarmonyInstance)">
+            <summary>
+            Applies all patches specified in the calling assembly.
+            </summary>
+            <param name="harmonyInstance">The HarmonyInstance to use.</param>
+        </member>
+    </members>
+</doc>

BIN
libs/BepInEx 5.0/BepInEx.Preloader.dll


BIN
libs/BepInEx 5.0/BepInEx.dll


+ 7 - 1
src/Translators/BingTranslate/BingTranslateEndpoint.cs

@@ -200,7 +200,11 @@ namespace BingTranslate
          var iterator = response.GetSupportedEnumerator();
          while( iterator.MoveNext() ) yield return iterator.Current;
 
-         InspectResponse( response );
+         if( response.IsTimedOut )
+         {
+            XuaLogger.Current.Warn( "A timeout error occurred while setting up BingTranslate IG. Proceeding without..." );
+            yield break;
+         }
 
          // failure
          if( response.Error != null )
@@ -216,6 +220,8 @@ namespace BingTranslate
             yield break;
          }
 
+         InspectResponse( response );
+
          try
          {
             var html = response.Data;

+ 7 - 1
src/Translators/GoogleTranslate/GoogleTranslateEndpoint.cs

@@ -249,7 +249,11 @@ namespace GoogleTranslate
          var iterator = response.GetSupportedEnumerator();
          while( iterator.MoveNext() ) yield return iterator.Current;
 
-         InspectResponse( response );
+         if( response.IsTimedOut )
+         {
+            XuaLogger.Current.Warn( "A timeout error occurred while setting up GoogleTranslate TKK. Using fallback TKK values instead." );
+            yield break;
+         }
 
          // failure
          if( response.Error != null )
@@ -265,6 +269,8 @@ namespace GoogleTranslate
             yield break;
          }
 
+         InspectResponse( response );
+
          try
          {
             var html = response.Data;

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

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

+ 62 - 0
src/XUnity.AutoTranslator.Plugin.BepIn-5x/AutoTranslatorPlugin.cs

@@ -0,0 +1,62 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using BepInEx;
+using ExIni;
+using XUnity.AutoTranslator.Plugin.Core;
+using XUnity.AutoTranslator.Plugin.Core.Configuration;
+using XUnity.AutoTranslator.Plugin.Core.Constants;
+
+namespace XUnity.AutoTranslator.Plugin.BepIn_5x
+{
+   [BepInPlugin( GUID: PluginData.Identifier, Name: PluginData.Name, Version: PluginData.Version )]
+   public class AutoTranslatorPlugin : BaseUnityPlugin, IPluginEnvironment
+   {
+      private IniFile _file;
+      private string _configPath;
+
+      public AutoTranslatorPlugin()
+      {
+         DataPath = @"BepInEx\plugins\XUnity.AutoTranslator";
+         _configPath = Path.Combine( DataPath, "AutoTranslatorConfig.ini" );
+         XuaLogger.Current = new BepInLogger();
+      }
+
+      public IniFile Preferences
+      {
+         get
+         {
+            return ( _file ?? ( _file = ReloadConfig() ) );
+         }
+      }
+
+      public string DataPath { get; }
+
+      public IniFile ReloadConfig()
+      {
+         if( !File.Exists( _configPath ) )
+         {
+            return ( _file ?? new IniFile() );
+         }
+         IniFile ini = IniFile.FromFile( _configPath );
+         if( _file == null )
+         {
+            return ( _file = ini );
+         }
+         _file.Merge( ini );
+         return _file;
+      }
+
+      public void SaveConfig()
+      {
+         _file.Save( _configPath );
+      }
+
+      void Awake()
+      {
+         PluginLoader.LoadWithConfig( this );
+      }
+   }
+}

+ 42 - 0
src/XUnity.AutoTranslator.Plugin.BepIn-5x/BepInLogger.cs

@@ -0,0 +1,42 @@
+using BepInEx.Logging;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using XUnity.AutoTranslator.Plugin.Core;
+using XUnity.AutoTranslator.Plugin.Core.Constants;
+
+namespace XUnity.AutoTranslator.Plugin.BepIn_5x
+{
+   public class BepInLogger : XuaLogger
+   {
+      private readonly ManualLogSource _logger = Logger.CreateLogSource( "XUnity.AutoTranslator " + PluginData.Version );
+
+      public BepInLogger()
+      {
+         RespectSettings = false;
+      }
+
+      protected override void Log( Core.LogLevel level, string message )
+      {
+         _logger.Log( Convert( level ), message );
+      }
+
+      public BepInEx.Logging.LogLevel Convert( Core.LogLevel level )
+      {
+         switch( level )
+         {
+            case Core.LogLevel.Debug:
+               return BepInEx.Logging.LogLevel.Debug;
+            case Core.LogLevel.Info:
+               return BepInEx.Logging.LogLevel.Info;
+            case Core.LogLevel.Warn:
+               return BepInEx.Logging.LogLevel.Warning;
+            case Core.LogLevel.Error:
+               return BepInEx.Logging.LogLevel.Error;
+            default:
+               return BepInEx.Logging.LogLevel.None;
+         }
+      }
+   }
+}

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

@@ -0,0 +1,37 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net35</TargetFramework>
+    <Version>3.2.0</Version>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\XUnity.AutoTranslator.Plugin.Core\XUnity.AutoTranslator.Plugin.Core.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Reference Include="BepInEx">
+      <HintPath>..\..\libs\BepInEx 5.0\BepInEx.dll</HintPath>
+    </Reference>
+    <Reference Include="ExIni">
+      <HintPath>..\..\libs\ExIni.dll</HintPath>
+    </Reference>
+    <Reference Include="UnityEngine">
+      <HintPath>..\..\libs\UnityEngine.dll</HintPath>
+    </Reference>
+    <Reference Include="UnityEngine.UI">
+      <HintPath>..\..\libs\UnityEngine.UI.dll</HintPath>
+    </Reference>
+  </ItemGroup>
+
+  <Target Name="PostBuild" AfterTargets="PostBuildEvent">
+    <GetAssemblyIdentity AssemblyFiles="$(TargetPath)">
+      <Output TaskParameter="Assemblies" ItemName="Targets" />
+    </GetAssemblyIdentity>
+    <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-5x\BepInEx\plugins\XUnity.AutoTranslator\Translators\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)ExIni.dll&quot; &quot;$(SolutionDir)dist\BepIn-5x\BepInEx\plugins\XUnity.AutoTranslator\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)XUnity.AutoTranslator.Plugin.Core.dll&quot; &quot;$(SolutionDir)dist\BepIn-5x\BepInEx\plugins\XUnity.AutoTranslator\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)XUnity.AutoTranslator.Plugin.ExtProtocol.dll&quot; &quot;$(SolutionDir)dist\BepIn-5x\BepInEx\plugins\XUnity.AutoTranslator\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)XUnity.RuntimeHooker.Core.dll&quot; &quot;$(SolutionDir)dist\BepIn-5x\BepInEx\plugins\XUnity.AutoTranslator\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)XUnity.RuntimeHooker.dll&quot; &quot;$(SolutionDir)dist\BepIn-5x\BepInEx\plugins\XUnity.AutoTranslator\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)$(TargetName)$(TargetExt)&quot; &quot;$(SolutionDir)dist\BepIn-5x\BepInEx\plugins\XUnity.AutoTranslator\&quot;&#xD;&#xA;   COPY /Y &quot;$(SolutionDir)README.md&quot; &quot;$(SolutionDir)dist\BepIn-5x\BepInEx\README (AutoTranslator).md&quot;&#xD;&#xA;   powershell Compress-Archive -Path '$(SolutionDir)dist\BepIn-5x\BepInEx' -DestinationPath '$(SolutionDir)dist\BepIn-5x\XUnity.AutoTranslator-BepIn-5x-@(VersionNumber).zip' -Force)&#xD;&#xA;)" />
+  </Target>
+
+</Project>

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

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

+ 97 - 58
src/XUnity.AutoTranslator.Plugin.Core/AutoTranslationPlugin.cs

@@ -145,9 +145,10 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
             MainWindow = new XuaWindow( CreateXuaViewModel() );
 
-            var vm = CreateTranslationAggregatorViewModel();
-            TranslationAggregatorWindow = new TranslationAggregatorWindow( vm );
-            TranslationAggregatorOptionsWindow = new TranslationAggregatorOptionsWindow( vm );
+            // UNRELEASED: Not included in current release
+            //var vm = CreateTranslationAggregatorViewModel();
+            //TranslationAggregatorWindow = new TranslationAggregatorWindow( vm );
+            //TranslationAggregatorOptionsWindow = new TranslationAggregatorOptionsWindow( vm );
          }
          catch( Exception e )
          {
@@ -161,7 +162,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
       private TranslationAggregatorViewModel CreateTranslationAggregatorViewModel()
       {
-         return new TranslationAggregatorViewModel( TranslationManager.ConfiguredEndpoints );
+         return new TranslationAggregatorViewModel( TranslationManager );
       }
 
       private XuaViewModel CreateXuaViewModel()
@@ -175,7 +176,10 @@ namespace XUnity.AutoTranslator.Plugin.Core
                   "<b>NOT TRANSLATED</b>\nThe plugin currently displays untranslated texts.",
                   ToggleTranslation, () => _isInTranslatedMode )
             },
-            TranslationManager.AllEndpoints.Select( x => new TranslatorDropdownOptionViewModel( () => x == TranslationManager.CurrentEndpoint, x, OnEndpointSelected ) ).ToList(),
+            new DropdownViewModel<TranslatorDropdownOptionViewModel, TranslationEndpointManager>(
+               TranslationManager.AllEndpoints.Select( x => new TranslatorDropdownOptionViewModel( () => x == TranslationManager.CurrentEndpoint, x ) ).ToList(),
+               OnEndpointSelected
+            ),
             new List<ButtonViewModel>
             {
                new ButtonViewModel( "Reboot", "<b>REBOOT PLUGIN</b>\nReboots the plugin if it has been shutdown. This only works if the plugin was shut down due to consequtive errors towards the translation endpoint.", RebootPlugin, null ),
@@ -186,7 +190,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
             {
                new LabelViewModel( "Version: ", () => PluginData.Version ),
                new LabelViewModel( "Plugin status: ", () => Settings.IsShutdown ? "Shutdown" : "Running" ),
-               new LabelViewModel( "Translator status: ", () => TranslationManager.CurrentEndpoint.HasFailedDueToConsecutiveErrors ? "Shutdown" : "Running" ),
+               new LabelViewModel( "Translator status: ", GetCurrentEndpointStatus ),
                new LabelViewModel( "Running translations: ", () => $"{(TranslationManager.OngoingTranslations)}" ),
                new LabelViewModel( "Served translations: ", () => $"{Settings.TranslationCount} / {Settings.MaxTranslationsBeforeShutdown}" ),
                new LabelViewModel( "Queued translations: ", () => $"{(TranslationManager.UnstartedTranslations)} / {Settings.MaxUnstartedJobs}" ),
@@ -194,6 +198,20 @@ namespace XUnity.AutoTranslator.Plugin.Core
             } );
       }
 
+      private string GetCurrentEndpointStatus()
+      {
+         var endpoint = TranslationManager.CurrentEndpoint;
+         if( endpoint == null )
+         {
+            return "Not selected";
+         }
+         else if( endpoint.HasFailedDueToConsecutiveErrors )
+         {
+            return "Shutdown";
+         }
+         return "Running";
+      }
+
       private void StartMaintenance()
       {
          // start a thread that will periodically removed unused references
@@ -235,17 +253,22 @@ namespace XUnity.AutoTranslator.Plugin.Core
          if( TranslationManager.CurrentEndpoint != endpoint )
          {
             TranslationManager.CurrentEndpoint = endpoint;
-
-            if( !Settings.IsShutdown )
+            
+            if( TranslationManager.CurrentEndpoint != null )
             {
-               if( TranslationManager.CurrentEndpoint.HasFailedDueToConsecutiveErrors )
+               if( !Settings.IsShutdown )
                {
-                  RebootPlugin();
+                  if( TranslationManager.CurrentEndpoint.HasFailedDueToConsecutiveErrors )
+                  {
+                     RebootPlugin();
+                  }
+                  ManualHook();
                }
-               ManualHook();
             }
 
-            Settings.SetEndpoint( TranslationManager.CurrentEndpoint.Endpoint.Id );
+            Settings.SetEndpoint( TranslationManager.CurrentEndpoint?.Endpoint.Id );
+
+            XuaLogger.Current.Info( $"Set translator endpoint to '{TranslationManager.CurrentEndpoint?.Endpoint.Id}'." );
          }
       }
 
@@ -353,7 +376,6 @@ namespace XUnity.AutoTranslator.Plugin.Core
          var added = endpoint.EnqueueTranslation( ui, key, translationResult, context );
          if( added && translationResult == null )
          {
-            // FIXME: Check that we still enter this!
             SpamChecker.PerformChecks( key.GetDictionaryLookupKey() );
          }
       }
@@ -970,8 +992,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
          }
 
          // Ensure that we actually want to translate this text and its owning UI element.
-         // FIXME: Using TextCache here is wrong!
-         if( !string.IsNullOrEmpty( text ) && TextCache.IsTranslatable( text ) && IsBelowMaxLength( text ) )
+         if( !string.IsNullOrEmpty( text ) && endpoint.IsTranslatable( text ) && IsBelowMaxLength( text ) )
          {
             var textKey = new TranslationKey( null, text, false, context != null );
 
@@ -1041,7 +1062,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
          {
             var variableName = kvp.Key;
             var untranslatedTextPart = kvp.Value;
-            if( !string.IsNullOrEmpty( untranslatedTextPart ) && TextCache.IsTranslatable( untranslatedTextPart ) && IsBelowMaxLength( untranslatedTextPart ) )
+            if( !string.IsNullOrEmpty( untranslatedTextPart ) && endpoint.IsTranslatable( untranslatedTextPart ) && IsBelowMaxLength( untranslatedTextPart ) )
             {
                string partTranslation;
                if( endpoint.TryGetTranslation( untranslatedTextPart, out partTranslation ) )
@@ -1244,7 +1265,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
                                     {
                                        if( IsBelowMaxLength( stabilizedText ) )
                                        {
-                                          if( !Settings.IsShutdown || !endpoint.HasFailedDueToConsecutiveErrors )
+                                          if( !Settings.IsShutdown && !endpoint.HasFailedDueToConsecutiveErrors )
                                           {
                                              CreateTranslationJobFor( endpoint, ui, stabilizedTextKey, null, context );
                                           }
@@ -1268,7 +1289,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
                      var endpoint = context?.Endpoint ?? TranslationManager.CurrentEndpoint;
                      if( endpoint != null )
                      {
-                        if( !Settings.IsShutdown || !endpoint.HasFailedDueToConsecutiveErrors )
+                        if( !Settings.IsShutdown && !endpoint.HasFailedDueToConsecutiveErrors )
                         {
                            // once the text has stabilized, attempt to look it up
                            CreateTranslationJobFor( endpoint, ui, textKey, null, context );
@@ -1288,7 +1309,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
                               if( endpoint != null )
                               {
                                  // once the text has stabilized, attempt to look it up
-                                 if( !Settings.IsShutdown || !endpoint.HasFailedDueToConsecutiveErrors )
+                                 if( !Settings.IsShutdown && !endpoint.HasFailedDueToConsecutiveErrors )
                                  {
                                     if( !TextCache.TryGetTranslation( textKey, out translation ) )
                                     {
@@ -1483,55 +1504,73 @@ namespace XUnity.AutoTranslator.Plugin.Core
             }
 
             // perform this check every 100 frames!
-            if( Time.frameCount % 100 == 0 && TranslationManager.OngoingTranslations == 0 )
+            if( Time.frameCount % 100 == 0
+               && TranslationManager.OngoingTranslations == 0
+               && TranslationManager.UnstartedTranslations == 0 )
             {
                ConnectionTrackingWebClient.CheckServicePoints();
             }
-
+            
             if( Input.anyKey )
             {
                var isAltPressed = Input.GetKey( KeyCode.LeftAlt ) || Input.GetKey( KeyCode.RightAlt );
 
-               if( Settings.EnablePrintHierarchy && isAltPressed && Input.GetKeyDown( KeyCode.Y ) )
-               {
-                  PrintObjects();
-               }
-               else if( isAltPressed && Input.GetKeyDown( KeyCode.T ) )
-               {
-                  ToggleTranslation();
-               }
-               else if( isAltPressed && Input.GetKeyDown( KeyCode.F ) )
-               {
-                  ToggleFont();
-               }
-               else if( isAltPressed && Input.GetKeyDown( KeyCode.R ) )
-               {
-                  ReloadTranslations();
-               }
-               else if( isAltPressed && Input.GetKeyDown( KeyCode.U ) )
-               {
-                  ManualHook();
-               }
-               else if( isAltPressed && Input.GetKeyDown( KeyCode.Q ) )
+               if( isAltPressed )
                {
-                  RebootPlugin();
-               }
-               //else if( isAltPressed && Input.GetKeyDown( KeyCode.B ) )
-               //{
-               //   ConnectionTrackingWebClient.CloseServicePoints();
-               //}
-               else if( isAltPressed && ( Input.GetKeyDown( KeyCode.Alpha0 ) || Input.GetKeyDown( KeyCode.Keypad0 ) ) )
-               {
-                  if( MainWindow != null )
+                  var isCtrlPressed = Input.GetKey( KeyCode.LeftControl );
+
+                  if( Settings.EnablePrintHierarchy && Input.GetKeyDown( KeyCode.Y ) )
                   {
-                     MainWindow.IsShown = !MainWindow.IsShown;
+                     PrintObjects();
                   }
-               }
-               else if( isAltPressed && ( Input.GetKeyDown( KeyCode.Alpha1 ) || Input.GetKeyDown( KeyCode.Keypad1 ) ) )
-               {
-                  if( TranslationAggregatorWindow != null )
+                  else if( Input.GetKeyDown( KeyCode.T ) )
+                  {
+                     ToggleTranslation();
+                  }
+                  else if( Input.GetKeyDown( KeyCode.F ) )
                   {
-                     TranslationAggregatorWindow.IsShown = !TranslationAggregatorWindow.IsShown;
+                     ToggleFont();
+                  }
+                  else if( Input.GetKeyDown( KeyCode.R ) )
+                  {
+                     ReloadTranslations();
+                  }
+                  else if( Input.GetKeyDown( KeyCode.U ) )
+                  {
+                     ManualHook();
+                  }
+                  else if( Input.GetKeyDown( KeyCode.Q ) )
+                  {
+                     RebootPlugin();
+                  }
+                  //else if( Input.GetKeyDown( KeyCode.B ) )
+                  //{
+                  //   ConnectionTrackingWebClient.CloseServicePoints();
+                  //}
+                  else if( Input.GetKeyDown( KeyCode.Alpha0 ) || Input.GetKeyDown( KeyCode.Keypad0 ) )
+                  {
+                     if( MainWindow != null )
+                     {
+                        MainWindow.IsShown = !MainWindow.IsShown;
+                     }
+                  }
+                  else if( Input.GetKeyDown( KeyCode.Alpha1 ) || Input.GetKeyDown( KeyCode.Keypad1 ) )
+                  {
+                     if( TranslationAggregatorWindow != null )
+                     {
+                        TranslationAggregatorWindow.IsShown = !TranslationAggregatorWindow.IsShown;
+                     }
+                  }
+                  else if( isCtrlPressed )
+                  {
+                     if( Input.GetKeyDown( KeyCode.Keypad9 ) )
+                     {
+                        Settings.SimulateError = !Settings.SimulateError;
+                     }
+                     else if( Input.GetKeyDown( KeyCode.Keypad8 ) )
+                     {
+                        Settings.SimulateDelayedError = !Settings.SimulateDelayedError;
+                     }
                   }
                }
             }
@@ -1707,7 +1746,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
                            TextCache.AddTranslationToCache( context.Result.OriginalText, translatedText );
                         }
 
-                        job.Endpoint.AddTranslationToCache( job.Key, job.TranslatedText );
+                        job.Endpoint.AddTranslationToCache( context.Result.OriginalText, translatedText );
                      }
 
                      if( text == result.OriginalText )

+ 6 - 1
src/XUnity.AutoTranslator.Plugin.Core/Configuration/Settings.cs

@@ -33,7 +33,10 @@ namespace XUnity.AutoTranslator.Plugin.Core.Configuration
       public static readonly int MaximumConsecutiveSecondsTranslated = 60;
       public static bool UsesWhitespaceBetweenWords = false;
       public static string ApplicationName;
-      public static float Timeout = 50.0f;
+      public static float Timeout = 150.0f;
+
+      public static bool SimulateError = false;
+      public static bool SimulateDelayedError = false;
 
       public static bool InvokeEvents = true;
       public static Action<object> RemakeTextData = null;
@@ -197,6 +200,8 @@ namespace XUnity.AutoTranslator.Plugin.Core.Configuration
 
       public static void SetEndpoint( string id )
       {
+         id = id ?? string.Empty;
+
          ServiceEndpoint = id;
          PluginEnvironment.Current.Preferences[ "Service" ][ "Endpoint" ].Value = id;
          Save();

+ 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.1.0";
+      public const string Version = "3.2.0";
    }
 }

+ 4 - 4
src/XUnity.AutoTranslator.Plugin.Core/Constants/UserAgents.cs

@@ -15,21 +15,21 @@ namespace XUnity.AutoTranslator.Plugin.Core.Constants
       /// <summary>
       /// Latest Chrome Win10 user-agent as of 2019-02-09.
       /// </summary>
-      public static readonly string Chrome_Win10_Latest = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36";
+      public static readonly string Chrome_Win10_Latest = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36";
 
       /// <summary>
       /// Latest Chrome Win7 user-agent as of 2019-02-09.
       /// </summary>
-      public static readonly string Chrome_Win7_Latest = "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36";
+      public static readonly string Chrome_Win7_Latest = "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36";
 
       /// <summary>
       /// Latest Firefox Win10 user-agent as of 2019-02-09.
       /// </summary>
-      public static readonly string Firefox_Win10_Latest = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:64.0) Gecko/20100101 Firefox/64.0";
+      public static readonly string Firefox_Win10_Latest = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0";
 
       /// <summary>
       /// Latest Edge Win10 user-agent as of 2019-02-09.
       /// </summary>
-      public static readonly string Edge_Win10_Latest = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/17.17134";
+      public static readonly string Edge_Win10_Latest = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/18.17763";
    }
 }

+ 75 - 50
src/XUnity.AutoTranslator.Plugin.Core/Endpoints/TranslationEndpointManager.cs

@@ -20,6 +20,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
 
       // used for prototyping
       private Dictionary<string, string> _translations;
+      private Dictionary<string, string> _reverseTranslations;
 
       public TranslationEndpointManager( ITranslateEndpoint endpoint, Exception error )
       {
@@ -32,6 +33,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
          _ongoingJobs = new Dictionary<string, TranslationJob>();
 
          _translations = new Dictionary<string, string>();
+         _reverseTranslations = new Dictionary<string, string>();
 
          HasBatchLogicFailed = false;
          AvailableBatchOperations = Settings.MaxAvailableBatchOperations;
@@ -69,15 +71,10 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
          return _translations.TryGetValue( key, out value );
       }
 
-      private void AddTranslation( TranslationKey key, string value )
-      {
-         var lookup = key.GetDictionaryLookupKey();
-         _translations[ lookup ] = value;
-      }
-
       private void AddTranslation( string key, string value )
       {
          _translations[ key ] = value;
+         _reverseTranslations[ value ] = key;
       }
 
       private void QueueNewTranslationForDisk( string key, string value )
@@ -87,16 +84,28 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
 
       public void AddTranslationToCache( TranslationKey key, string value )
       {
-         AddTranslationToCache( key.GetDictionaryLookupKey(), value );
+         // UNRELEASED: Not included in current release
+         //AddTranslationToCache( key.GetDictionaryLookupKey(), value );
       }
 
       public void AddTranslationToCache( string key, string value )
       {
-         if( !HasTranslated( key ) )
-         {
-            AddTranslation( key, value );
-            QueueNewTranslationForDisk( key, value );
-         }
+         // UNRELEASED: Not included in current release
+         //if( !HasTranslated( key ) )
+         //{
+         //   AddTranslation( key, value );
+         //   QueueNewTranslationForDisk( key, value );
+         //}
+      }
+
+      public bool IsTranslatable( string text )
+      {
+         return LanguageHelper.IsTranslatable( text ) && !IsTranslation( text );
+      }
+
+      private bool IsTranslation( string translation )
+      {
+         return _reverseTranslations.ContainsKey( translation );
       }
 
       private bool HasTranslated( string key )
@@ -224,8 +233,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
                job.TranslatedText = PostProcessTranslation( job.Key, translatedText );
                job.State = TranslationJobState.Succeeded;
 
-               _ongoingJobs.Remove( job.Key.GetDictionaryLookupKey() );
-               Manager.OngoingTranslations--;
+               RemoveOngoingTranslation( job.Key.GetDictionaryLookupKey() );
 
                XuaLogger.Current.Info( $"Completed: '{job.Key.GetDictionaryLookupKey()}' => '{job.TranslatedText}'" );
 
@@ -246,8 +254,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
 
                var key = job.Key.GetDictionaryLookupKey();
                AddUnstartedJob( key, job );
-               _ongoingJobs.Remove( key );
-               Manager.OngoingTranslations--;
+               RemoveOngoingTranslation( key );
             }
 
             XuaLogger.Current.Error( "A batch operation failed. Disabling batching and restarting failed jobs." );
@@ -263,8 +270,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
          job.TranslatedText = PostProcessTranslation( job.Key, translatedText );
          job.State = TranslationJobState.Succeeded;
 
-         _ongoingJobs.Remove( job.Key.GetDictionaryLookupKey() );
-         Manager.OngoingTranslations--;
+         RemoveOngoingTranslation( job.Key.GetDictionaryLookupKey() );
 
          XuaLogger.Current.Info( $"Completed: '{job.Key.GetDictionaryLookupKey()}' => '{job.TranslatedText}'" );
 
@@ -315,8 +321,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
                job.State = TranslationJobState.Failed;
                job.ErrorMessage = error;
 
-               _ongoingJobs.Remove( untranslatedText );
-               Manager.OngoingTranslations--;
+               RemoveOngoingTranslation( untranslatedText );
 
                RegisterTranslationFailureFor( untranslatedText );
 
@@ -337,8 +342,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
 
                var key = job.Key.GetDictionaryLookupKey();
                AddUnstartedJob( key, job );
-               _ongoingJobs.Remove( key );
-               Manager.OngoingTranslations--;
+               RemoveOngoingTranslation( key );
             }
 
             XuaLogger.Current.Error( "A batch operation failed. Disabling batching and restarting failed jobs." );
@@ -350,7 +354,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
 
             if( HasFailedDueToConsecutiveErrors )
             {
-               XuaLogger.Current.Error( $"{Settings.MaxErrors} or more consecutive errors occurred. Shutting down plugin." );
+               XuaLogger.Current.Error( $"{Settings.MaxErrors} or more consecutive errors occurred. Shutting down translator endpoint." );
 
                ClearAllJobs();
             }
@@ -403,7 +407,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
          return AddUnstartedJob( lookupKey, newJob );
       }
 
-      public bool AssociateWithExistingJobIfPossible( object ui, string key, TranslationResult translationResult, ParserTranslationContext context )
+      private bool AssociateWithExistingJobIfPossible( object ui, string key, TranslationResult translationResult, ParserTranslationContext context )
       {
          if( _unstartedJobs.TryGetValue( key, out TranslationJob unstartedJob ) )
          {
@@ -439,6 +443,14 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
          return false;
       }
 
+      private void RemoveOngoingTranslation( string key )
+      {
+         if( _ongoingJobs.Remove( key ) )
+         {
+            Manager.OngoingTranslations--;
+         }
+      }
+
       public void ClearAllJobs()
       {
          var ongoingCount = _ongoingJobs.Count;
@@ -464,7 +476,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
          Manager.UnscheduleUnstartedJobs( this );
       }
 
-      public bool CanTranslate( string untranslatedText )
+      private bool CanTranslate( string untranslatedText )
       {
          if( _failedTranslations.TryGetValue( untranslatedText, out var count ) )
          {
@@ -473,7 +485,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
          return true;
       }
 
-      public void RegisterTranslationFailureFor( string untranslatedText )
+      private void RegisterTranslationFailureFor( string untranslatedText )
       {
          byte count;
          if( !_failedTranslations.TryGetValue( untranslatedText, out count ) )
@@ -496,36 +508,49 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
 
          try
          {
-            bool ok = false;
-            var iterator = Endpoint.Translate( context );
-            if( iterator != null )
+            if( Settings.SimulateDelayedError )
             {
-            TryMe: try
-               {
-                  ok = iterator.MoveNext();
+               yield return new WaitForSeconds( 1 );
 
-                  // check for timeout
-                  var now = Time.realtimeSinceStartup;
-                  if( now - startTime > Settings.Timeout )
+               context.FailWithoutThrowing( "Simulating delayed error. Press CTRL+ALT+NP8 to disable!", null );
+            }
+            else if( Settings.SimulateError )
+            {
+               context.FailWithoutThrowing( "Simulating error. Press CTRL+ALT+NP9 to disable!", null );
+            }
+            else
+            {
+               bool ok = false;
+               var iterator = Endpoint.Translate( context );
+               if( iterator != null )
+               {
+               TryMe: try
+                  {
+                     ok = iterator.MoveNext();
+
+                     // check for timeout
+                     var now = Time.realtimeSinceStartup;
+                     if( now - startTime > Settings.Timeout )
+                     {
+                        ok = false;
+                        context.FailWithoutThrowing( $"Timeout occurred during translation (took more than {Settings.Timeout} seconds)", null );
+                     }
+                  }
+                  catch( TranslationContextException )
                   {
                      ok = false;
-                     context.FailWithoutThrowing( $"Timeout occurred during translation (took more than {Settings.Timeout} seconds)", null );
                   }
-               }
-               catch( TranslationContextException )
-               {
-                  ok = false;
-               }
-               catch( Exception e )
-               {
-                  ok = false;
-                  context.FailWithoutThrowing( "Error occurred during translation.", e );
-               }
+                  catch( Exception e )
+                  {
+                     ok = false;
+                     context.FailWithoutThrowing( "Error occurred during translation.", e );
+                  }
 
-               if( ok )
-               {
-                  yield return iterator.Current;
-                  goto TryMe;
+                  if( ok )
+                  {
+                     yield return iterator.Current;
+                     goto TryMe;
+                  }
                }
             }
          }

+ 3 - 6
src/XUnity.AutoTranslator.Plugin.Core/TextTranslationCache.cs

@@ -174,7 +174,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
       internal void AddTranslationToCache( TranslationKey key, string value )
       {
-         AddTranslation( key.GetDictionaryLookupKey(), value );
+         AddTranslationToCache( key.GetDictionaryLookupKey(), value );
       }
 
       internal void AddTranslationToCache( string key, string value )
@@ -215,12 +215,9 @@ namespace XUnity.AutoTranslator.Plugin.Core
          return _reverseTranslations.TryGetValue( value, out key );
       }
 
-      internal bool IsTranslatable( string str )
+      internal bool IsTranslatable( string text )
       {
-         return LanguageHelper.ContainsLanguageSymbolsForSourceLanguage( str )
-            //&& str.Length <= Settings.MaxCharactersPerTranslation
-            && !IsTranslation( str )
-            && !Settings.IgnoreTextStartingWith.Any( x => str.StartsWithStrict( x ) );
+         return LanguageHelper.IsTranslatable( text ) && !IsTranslation( text );
       }
    }
 }

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

@@ -96,7 +96,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
          if( graphic is Text )
          {
             var ui = (Text)graphic;
-
+            
             // text is likely to be longer than there is space for, simply expand out anyway then
             var componentWidth = ( (RectTransform)ui.transform ).rect.width;
             var quarterScreenSize = Screen.width / 4;

+ 0 - 3
src/XUnity.AutoTranslator.Plugin.Core/TranslationManager.cs

@@ -191,9 +191,6 @@ namespace XUnity.AutoTranslator.Plugin.Core
          {
             endpoint.ClearAllJobs();
          }
-
-         UnstartedTranslations = 0;
-         OngoingTranslations = 0;
       }
 
       public void RebootAllEndpoints()

+ 21 - 7
src/XUnity.AutoTranslator.Plugin.Core/UI/AggregatedTranslationViewModel.cs

@@ -10,14 +10,13 @@ namespace XUnity.AutoTranslator.Plugin.Core.UI
    {
       private List<Translation> _translations;
       private TranslationAggregatorViewModel _parent;
-      private float _started;
+      private float? _started;
 
       public AggregatedTranslationViewModel( TranslationAggregatorViewModel parent, List<Translation> translations )
       {
-         _started = Time.realtimeSinceStartup;
          _parent = parent;
          _translations = translations;
-         AggregatedTranslations = parent.Endpoints.Select(
+         AggregatedTranslations = parent.AvailableTranslators.Select(
             x => new IndividualTranslatorTranslationViewModel(
                x,
                new IndividualTranslationViewModel(
@@ -35,13 +34,28 @@ namespace XUnity.AutoTranslator.Plugin.Core.UI
       {
          if( _parent.IsShown )
          {
-            var timeSince = Time.realtimeSinceStartup - _started;
-            if( timeSince > 1.0f )
+            if( _parent.Manager.OngoingTranslations == 0 && _parent.Manager.UnstartedTranslations == 0 )
             {
-               foreach( var additionTranslation in AggregatedTranslations )
+               if( _started.HasValue )
                {
-                  additionTranslation.Translation.Update();
+                  var timeSince = Time.realtimeSinceStartup - _started.Value;
+                  if( timeSince > 1.0f )
+                  {
+                     foreach( var additionTranslation in AggregatedTranslations )
+                     {
+                        additionTranslation.Translation.StartTranslations();
+                     }
+                  }
                }
+               else
+               {
+                  _started = Time.realtimeSinceStartup;
+               }
+            }
+
+            foreach( var additionTranslation in AggregatedTranslations )
+            {
+               additionTranslation.Translation.CheckCompleted();
             }
          }
       }

+ 20 - 30
src/XUnity.AutoTranslator.Plugin.Core/UI/DropdownGUI.cs

@@ -6,13 +6,14 @@ namespace XUnity.AutoTranslator.Plugin.Core.UI
 
    internal class DropdownGUI<TDropdownOptionViewModel, TSelection>
       where TDropdownOptionViewModel : DropdownOptionViewModel<TSelection>
+      where TSelection : class
    {
 
       private const float MaxHeight = GUIUtil.RowHeight * 5;
 
       private GUIContent _noSelection;
-      private List<TDropdownOptionViewModel> _options;
-      private TDropdownOptionViewModel _currentSelection;
+      private GUIContent _unselect;
+      private DropdownViewModel<TDropdownOptionViewModel, TSelection> _viewModel;
 
       private float _x;
       private float _y;
@@ -20,35 +21,20 @@ namespace XUnity.AutoTranslator.Plugin.Core.UI
       private bool _isShown;
       private Vector2 _scrollPosition;
 
-      public DropdownGUI( float x, float y, float width, IEnumerable<TDropdownOptionViewModel> options )
+      public DropdownGUI( float x, float y, float width, DropdownViewModel<TDropdownOptionViewModel, TSelection> viewModel )
       {
          _x = x;
          _y = y;
          _width = width;
          _noSelection = new GUIContent( "----", "<b>SELECT TRANSLATOR</b>\nNo translator is currently selected, which means no new translations will be performed. Please select one from the dropdown." );
+         _unselect = new GUIContent( "----", "<b>UNSELECT TRANSLATOR</b>\nThis will unselect the current translator, which means no new translations will be performed." );
 
-         _options = new List<TDropdownOptionViewModel>();
-         foreach( var item in options )
-         {
-            if( item.IsSelected() )
-            {
-               _currentSelection = item;
-            }
-            _options.Add( item );
-         }
-      }
-
-      public void Select( TDropdownOptionViewModel option )
-      {
-         if( option.IsSelected() ) return;
-
-         _currentSelection = option;
-         _currentSelection.OnSelected?.Invoke( _currentSelection.Selection );
+         _viewModel = viewModel;
       }
 
       public void OnGUI()
       {
-         bool clicked = GUI.Button( GUIUtil.R( _x, _y, _width, GUIUtil.RowHeight ), _currentSelection?.Text ?? _noSelection, _isShown ? GUIUtil.NoMarginButtonPressedStyle : GUI.skin.button );
+         bool clicked = GUI.Button( GUIUtil.R( _x, _y, _width, GUIUtil.RowHeight ), _viewModel.CurrentSelection?.Text ?? _noSelection, _isShown ? GUIUtil.NoMarginButtonPressedStyle : GUI.skin.button );
          if( clicked )
          {
             _isShown = !_isShown;
@@ -56,7 +42,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.UI
 
          if( _isShown )
          {
-            _scrollPosition = ShowDropdown( _x, _y + GUIUtil.RowHeight, _width, GUI.skin.button, _scrollPosition );
+            ShowDropdown( _x, _y + GUIUtil.RowHeight, _width, GUI.skin.button );
          }
 
          if( !clicked && Event.current.isMouse )
@@ -65,21 +51,27 @@ namespace XUnity.AutoTranslator.Plugin.Core.UI
          }
       }
 
-      private Vector2 ShowDropdown( float x, float y, float width, GUIStyle buttonStyle, Vector2 scrollPosition )
+      private void ShowDropdown( float x, float y, float width, GUIStyle buttonStyle )
       {
-         var rect = GUIUtil.R( x, y, width, _options.Count * GUIUtil.RowHeight > MaxHeight ? MaxHeight : _options.Count * GUIUtil.RowHeight );
+         var rect = GUIUtil.R( x, y, width, _viewModel.Options.Count * GUIUtil.RowHeight > MaxHeight ? MaxHeight : _viewModel.Options.Count * GUIUtil.RowHeight );
 
          GUILayout.BeginArea( rect, GUIUtil.NoSpacingBoxStyle );
-         scrollPosition = GUILayout.BeginScrollView( scrollPosition );
+         _scrollPosition = GUILayout.BeginScrollView( _scrollPosition );
 
-         foreach( var option in _options )
+         var style = _viewModel.CurrentSelection == null ? GUIUtil.NoMarginButtonPressedStyle : GUIUtil.NoMarginButtonStyle;
+         if( GUILayout.Button( _unselect, style ) )
          {
-            var style = option.IsSelected() ? GUIUtil.NoMarginButtonPressedStyle : GUIUtil.NoMarginButtonStyle;
+            _viewModel.Select( null );
+            _isShown = false;
+         }
 
+         foreach( var option in _viewModel.Options )
+         {
+            style = option.IsSelected() ? GUIUtil.NoMarginButtonPressedStyle : GUIUtil.NoMarginButtonStyle;
             GUI.enabled = option?.IsEnabled() ?? true;
             if( GUILayout.Button( option.Text, style ) )
             {
-               Select( option );
+               _viewModel.Select( option );
                _isShown = false;
             }
             GUI.enabled = true;
@@ -87,8 +79,6 @@ namespace XUnity.AutoTranslator.Plugin.Core.UI
 
          GUILayout.EndScrollView();
          GUILayout.EndArea();
-
-         return scrollPosition;
       }
    }
 }

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

@@ -1,18 +1,52 @@
 using System;
+using System.Collections.Generic;
 using UnityEngine;
 using XUnity.AutoTranslator.Plugin.Core.Endpoints;
 
 namespace XUnity.AutoTranslator.Plugin.Core.UI
 {
+   internal class DropdownViewModel<TDropdownOptionViewModel, TSelection>
+      where TDropdownOptionViewModel : DropdownOptionViewModel<TSelection>
+      where TSelection : class
+   {
+      private Action<TSelection> _onSelected;
+
+      public DropdownViewModel( IEnumerable<TDropdownOptionViewModel> options, Action<TSelection> onSelected )
+      {
+         _onSelected = onSelected;
+
+         Options = new List<TDropdownOptionViewModel>();
+         foreach( var item in options )
+         {
+            if( item.IsSelected() )
+            {
+               CurrentSelection = item;
+            }
+            Options.Add( item );
+         }
+      }
+
+      public TDropdownOptionViewModel CurrentSelection { get; set; }
+
+      public List<TDropdownOptionViewModel> Options { get; set; }
+
+      public void Select( TDropdownOptionViewModel option )
+      {
+         if( option?.IsSelected() == true ) return;
+
+         CurrentSelection = option;
+         _onSelected?.Invoke( CurrentSelection?.Selection );
+      }
+   }
+
    internal class DropdownOptionViewModel<TSelection>
    {
-      public DropdownOptionViewModel( string text, Func<bool> isSelected, Func<bool> isEnabled, TSelection selection, Action<TSelection> onSelected )
+      public DropdownOptionViewModel( string text, Func<bool> isSelected, Func<bool> isEnabled, TSelection selection )
       {
          Text = new GUIContent( text );
          IsSelected = isSelected;
          IsEnabled = isEnabled;
          Selection = selection;
-         OnSelected = onSelected;
       }
 
       public virtual GUIContent Text { get; set; }
@@ -22,8 +56,6 @@ namespace XUnity.AutoTranslator.Plugin.Core.UI
       public Func<bool> IsSelected { get; set; }
 
       public TSelection Selection { get; set; }
-
-      public Action<TSelection> OnSelected { get; set; }
    }
 
    internal class TranslatorDropdownOptionViewModel : DropdownOptionViewModel<TranslationEndpointManager>
@@ -32,7 +64,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.UI
       private GUIContent _normal;
       private GUIContent _disabled;
 
-      public TranslatorDropdownOptionViewModel( Func<bool> isSelected, TranslationEndpointManager selection, Action<TranslationEndpointManager> onSelected ) : base( selection.Endpoint.FriendlyName, isSelected, () => selection.Error == null, selection, onSelected )
+      public TranslatorDropdownOptionViewModel( Func<bool> isSelected, TranslationEndpointManager selection ) : base( selection.Endpoint.FriendlyName, isSelected, () => selection.Error == null, selection )
       {
          _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 the initialization failed. {selection.Error?.Message}" );

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

@@ -19,7 +19,9 @@ namespace XUnity.AutoTranslator.Plugin.Core.UI
 
       public static readonly GUIStyle LabelTranslation = new GUIStyle( GUI.skin.label )
       {
-         richText = false
+         richText = false,
+         margin = new RectOffset( GUI.skin.label.margin.left, GUI.skin.label.margin.right, 0, 0 ),
+         padding = new RectOffset( GUI.skin.label.padding.left, GUI.skin.label.padding.right, 0, 0 )
       };
 
       public static readonly GUIStyle LabelCenter = new GUIStyle( GUI.skin.label )

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

@@ -37,7 +37,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.UI
          }
       }
 
-      public void Update()
+      public void StartTranslations()
       {
          if( _translator.IsEnabled )
          {
@@ -50,7 +50,13 @@ namespace XUnity.AutoTranslator.Plugin.Core.UI
                   translation.PerformTranslation( _translator.Endpoint );
                }
             }
+         }
+      }
 
+      public void CheckCompleted()
+      {
+         if( _translator.IsEnabled )
+         {
             if( !_isTranslated )
             {
                if( _translations.All( x => x.TranslatedText != null ) )

+ 0 - 2
src/XUnity.AutoTranslator.Plugin.Core/UI/ScrollViewGUI.cs

@@ -5,8 +5,6 @@ namespace XUnity.AutoTranslator.Plugin.Core.UI
 {
    internal class ScrollPositioned
    {
-      private Vector2 _scrollPosition;
-
       public ScrollPositioned()
       {
       }

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

@@ -8,12 +8,13 @@ namespace XUnity.AutoTranslator.Plugin.Core.UI
       private GUIContent _enabled;
       private GUIContent _disabled;
 
-      public ToggleViewModel( string text, string enabledTooltip, string disabledTooltip, Action onToggled, Func<bool> isToggled )
+      public ToggleViewModel( string text, string enabledTooltip, string disabledTooltip, Action onToggled, Func<bool> isToggled, bool enabled = true )
       {
          _enabled = new GUIContent( text, enabledTooltip );
          _disabled = new GUIContent( text, disabledTooltip );
          OnToggled = onToggled;
          IsToggled = isToggled;
+         Enabled = enabled;
       }
 
       public GUIContent Text
@@ -28,6 +29,8 @@ namespace XUnity.AutoTranslator.Plugin.Core.UI
          }
       }
 
+      public bool Enabled { get; set; }
+
       public Action OnToggled { get; set; }
 
       public Func<bool> IsToggled { get; set; }

+ 18 - 7
src/XUnity.AutoTranslator.Plugin.Core/UI/TranslationAggregatorOptionsWindow.cs

@@ -7,7 +7,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.UI
    internal class TranslationAggregatorOptionsWindow
    {
       private const int WindowId = 45733721;
-      private const float WindowWidth = 300;
+      private const float WindowWidth = 320;
 
       private Rect _windowRect = new Rect( 20, 20, WindowWidth, 400 );
       private bool _isMouseDownOnWindow = false;
@@ -18,13 +18,14 @@ namespace XUnity.AutoTranslator.Plugin.Core.UI
       public TranslationAggregatorOptionsWindow( TranslationAggregatorViewModel viewModel )
       {
          _viewModel = viewModel;
-         _toggles = _viewModel.Endpoints.Select( x =>
+         _toggles = _viewModel.AllTranslators.Select( x =>
          new ToggleViewModel(
-            x.Endpoint.Endpoint.FriendlyName,
+            " " + x.Endpoint.Endpoint.FriendlyName,
             null,
             null,
             () => x.IsEnabled = !x.IsEnabled,
-            () => x.IsEnabled ) ).ToList();
+            () => x.IsEnabled,
+            x.Endpoint.Error == null ) ).ToList();
       }
 
       public bool IsShown
@@ -72,22 +73,32 @@ namespace XUnity.AutoTranslator.Plugin.Core.UI
          
          foreach( var vm in _toggles )
          {
+            var previousEnabled = GUI.enabled;
+
+            GUI.enabled = vm.Enabled;
             var previousValue = vm.IsToggled();
             var newValue = GUILayout.Toggle( previousValue, vm.Text );
             if( previousValue != newValue )
             {
                vm.OnToggled();
             }
+
+            GUI.enabled = previousEnabled;
          }
 
          GUILayout.EndScrollView();
 
-         GUILayout.Label( "Height per Translator" );
+         GUILayout.BeginHorizontal();
+         GUILayout.Label( "Height" );
+         _viewModel.Height = GUILayout.HorizontalSlider( _viewModel.Height, 50, 300, GUILayout.MaxWidth( 250 ) );
+         GUILayout.EndHorizontal();
 
-         _viewModel.HeightPerTranslator = GUILayout.HorizontalSlider( _viewModel.HeightPerTranslator, 50, 300 );
+         GUILayout.BeginHorizontal();
+         GUILayout.Label( "Width" );
+         _viewModel.Width = GUILayout.HorizontalSlider( _viewModel.Width, 200, 1000, GUILayout.MaxWidth( 250 ) );
+         GUILayout.EndHorizontal();
 
          GUI.DragWindow();
-
       }
    }
 }

+ 20 - 8
src/XUnity.AutoTranslator.Plugin.Core/UI/TranslationAggregatorViewModel.cs

@@ -15,24 +15,36 @@ namespace XUnity.AutoTranslator.Plugin.Core.UI
       private HashSet<string> _textsToAggregate = new HashSet<string>();
       private float _lastUpdate = 0.0f;
 
-      public TranslationAggregatorViewModel( IEnumerable<TranslationEndpointManager> endpoints )
+      public TranslationAggregatorViewModel( TranslationManager translationManager )
       {
          _translations = new LinkedList<AggregatedTranslationViewModel>();
 
-         HeightPerTranslator = 100; // TODO: Get from config
-         Endpoints = endpoints
-            .Where( x => x.Error == null )
+         Manager = translationManager;
+         Height = 100; // TODO: Get from config
+         Width = 400; // TODO: Get from config
+
+         AllTranslators = translationManager.AllEndpoints
             .Select( x => new TranslatorViewModel( x ) )
             .ToList();
+
+         AvailableTranslators = AllTranslators
+            .Where( x => x.Endpoint.Error == null )
+            .ToList();
       }
 
       public bool IsShown { get; set; }
 
       public bool IsShowingOptions { get; set; }
 
-      public float HeightPerTranslator { get; set; }
+      public float Height { get; set; }
+
+      public float Width { get; set; }
+
+      public List<TranslatorViewModel> AvailableTranslators { get; }
+
+      public List<TranslatorViewModel> AllTranslators { get; }
 
-      public List<TranslatorViewModel> Endpoints { get; }
+      public TranslationManager Manager { get; set; }
 
       public AggregatedTranslationViewModel Current => _current?.Value;
 
@@ -78,8 +90,8 @@ namespace XUnity.AutoTranslator.Plugin.Core.UI
                }
             }
 
-            // ensure we never have more than 1000
-            if( _translations.Count >= 1000 )
+            // ensure we never have more than 100
+            if( _translations.Count >= 100 )
             {
                var first = _translations.First;
                _translations.RemoveFirst();

+ 17 - 17
src/XUnity.AutoTranslator.Plugin.Core/UI/TranslationAggregatorWindow.cs

@@ -11,7 +11,6 @@ namespace XUnity.AutoTranslator.Plugin.Core.UI
       private static string[] Empty = new string[ 0 ];
 
       private const int WindowId = 2387602;
-      private const float WindowWidth = 400;
 
       private Rect _windowRect;
       private bool _isMouseDownOnWindow = false;
@@ -25,11 +24,11 @@ namespace XUnity.AutoTranslator.Plugin.Core.UI
       {
          _viewModel = viewModel;
 
-         _windowRect = new Rect( 20, 20, WindowWidth, WindowHeight );
+         _windowRect = new Rect( 20, 20, _viewModel.Width, WindowHeight );
 
          _originalText = new ScrollPositioned();
          _defaultTranslation = new ScrollPositioned();
-         _translationViews = viewModel.Endpoints.Select( x => new ScrollPositioned<TranslatorViewModel>( x ) ).ToArray();
+         _translationViews = viewModel.AvailableTranslators.Select( x => new ScrollPositioned<TranslatorViewModel>( x ) ).ToArray();
       }
 
       public bool IsShown
@@ -38,11 +37,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.UI
          set => _viewModel.IsShown = value;
       }
 
-      private float WindowHeight => ( ( _viewModel.Endpoints.Count( x => x.IsEnabled ) + 2 ) * _viewModel.HeightPerTranslator ) + 30 + GUIUtil.LabelHeight + GUIUtil.ComponentSpacing;
+      private float WindowHeight => ( ( _viewModel.AvailableTranslators.Count( x => x.IsEnabled ) + 2 ) * _viewModel.Height ) + 30 + GUIUtil.LabelHeight + GUIUtil.ComponentSpacing;
 
       public void OnGUI()
       {
          _windowRect.height = WindowHeight;
+         _windowRect.width = _viewModel.Width;
          _windowRect = GUI.Window( WindowId, _windowRect, CreateWindowUI, "---- Translation Aggregator ----" );
 
          if( GUIUtil.IsAnyMouseButtonOrScrollWheelDown )
@@ -78,7 +78,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.UI
       {
          float posy = GUIUtil.WindowTitleClearance + GUIUtil.ComponentSpacing;
 
-         if( GUI.Button( GUIUtil.R( WindowWidth - 22, 2, 20, 16 ), "X" ) )
+         if( GUI.Button( GUIUtil.R( _viewModel.Width - 22, 2, 20, 16 ), "X" ) )
          {
             IsShown = false;
          }
@@ -87,10 +87,10 @@ namespace XUnity.AutoTranslator.Plugin.Core.UI
          if( current != null )
          {
             DrawTextArea( posy, _originalText, "Original Text", current.OriginalTexts );
-            posy += _viewModel.HeightPerTranslator;
+            posy += _viewModel.Height;
 
             DrawTextArea( posy, _defaultTranslation, "Default Translation", current.DefaultTranslations );
-            posy += _viewModel.HeightPerTranslator;
+            posy += _viewModel.Height;
 
             for( int i = 0; i < current.AggregatedTranslations.Count; i++ )
             {
@@ -104,21 +104,21 @@ namespace XUnity.AutoTranslator.Plugin.Core.UI
                      scroller,
                      aggregatedTranslation.Translator.Endpoint.Endpoint.FriendlyName,
                      aggregatedTranslation.Translation.Translations );
-                  posy += _viewModel.HeightPerTranslator;
+                  posy += _viewModel.Height;
                }
             }
          }
          else
          {
             DrawTextArea( posy, _originalText, "Original Text", Empty );
-            posy += _viewModel.HeightPerTranslator;
+            posy += _viewModel.Height;
 
             DrawTextArea( posy, _defaultTranslation, "Default Translation", Empty );
-            posy += _viewModel.HeightPerTranslator;
+            posy += _viewModel.Height;
 
-            for( int i = 0; i < _viewModel.Endpoints.Count; i++ )
+            for( int i = 0; i < _viewModel.AvailableTranslators.Count; i++ )
             {
-               var translator = _viewModel.Endpoints[ i ];
+               var translator = _viewModel.AvailableTranslators[ i ];
                if( translator.IsEnabled )
                {
                   var scroller = _translationViews[ i ];
@@ -128,7 +128,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.UI
                      scroller,
                      translator.Endpoint.Endpoint.FriendlyName,
                      Empty );
-                  posy += _viewModel.HeightPerTranslator;
+                  posy += _viewModel.Height;
                }
             }
          }
@@ -156,7 +156,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.UI
          }
 
          GUI.enabled = true;
-         if( GUI.Button( GUIUtil.R( GUIUtil.HalfComponentSpacing * 4 + 75 * 3, posy, 75, GUIUtil.LabelHeight ), "Options" ) )
+         if( GUI.Button( GUIUtil.R( _viewModel.Width - GUIUtil.HalfComponentSpacing - 75, posy, 75, GUIUtil.LabelHeight ), "Options" ) )
          {
             _viewModel.IsShowingOptions = true;
          }
@@ -168,12 +168,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.UI
 
       private void DrawTextArea( float posy, ScrollPositioned positioned, string title, IEnumerable<string> texts )
       {
-         GUI.Label( GUIUtil.R( GUIUtil.HalfComponentSpacing + 5, posy + 5, WindowWidth - GUIUtil.ComponentSpacing, GUIUtil.LabelHeight ), title );
+         GUI.Label( GUIUtil.R( GUIUtil.HalfComponentSpacing + 5, posy + 5, _viewModel.Width - GUIUtil.ComponentSpacing, GUIUtil.LabelHeight ), title );
 
          posy += GUIUtil.LabelHeight + GUIUtil.HalfComponentSpacing;
 
-         float boxWidth = WindowWidth - GUIUtil.ComponentSpacing;
-         float boxHeight = _viewModel.HeightPerTranslator - GUIUtil.LabelHeight;
+         float boxWidth = _viewModel.Width - GUIUtil.ComponentSpacing;
+         float boxHeight = _viewModel.Height - GUIUtil.LabelHeight;
          GUILayout.BeginArea( GUIUtil.R( GUIUtil.HalfComponentSpacing, posy, boxWidth, boxHeight ) );
          positioned.ScrollPosition = GUILayout.BeginScrollView( positioned.ScrollPosition, GUI.skin.box );
 

+ 4 - 3
src/XUnity.AutoTranslator.Plugin.Core/UI/XuaViewModel.cs

@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
+using XUnity.AutoTranslator.Plugin.Core.Endpoints;
 
 namespace XUnity.AutoTranslator.Plugin.Core.UI
 {
@@ -9,12 +10,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.UI
    {
       public XuaViewModel(
          List<ToggleViewModel> toggles,
-         List<TranslatorDropdownOptionViewModel> endpoints,
+         DropdownViewModel<TranslatorDropdownOptionViewModel, TranslationEndpointManager> dropdown,
          List<ButtonViewModel> commandButtons,
          List<LabelViewModel> labels )
       {
          Toggles = toggles;
-         EndpointOptions = endpoints;
+         Dropdown = dropdown;
          CommandButtons = commandButtons;
          Labels = labels;
       }
@@ -23,7 +24,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.UI
 
       public List<ToggleViewModel> Toggles { get; }
 
-      public List<TranslatorDropdownOptionViewModel> EndpointOptions { get; }
+      public DropdownViewModel<TranslatorDropdownOptionViewModel, TranslationEndpointManager> Dropdown { get; }
 
       public List<ButtonViewModel> CommandButtons { get; }
 

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

@@ -145,7 +145,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.UI
             posy += GUIUtil.RowHeight + GUIUtil.ComponentSpacing;
          }
 
-         var endpointDropdown = _endpointDropdown ?? ( _endpointDropdown = new DropdownGUI<TranslatorDropdownOptionViewModel, TranslationEndpointManager>( col2x, endpointDropdownPosy, col2, _viewModel.EndpointOptions ) );
+         var endpointDropdown = _endpointDropdown ?? ( _endpointDropdown = new DropdownGUI<TranslatorDropdownOptionViewModel, TranslationEndpointManager>( col2x, endpointDropdownPosy, col2, _viewModel.Dropdown ) );
          endpointDropdown.OnGUI();
 
          GUI.Label( GUIUtil.R( col1x, posy, col12, GUIUtil.RowHeight * 5 ), GUI.tooltip, GUIUtil.LabelRich );

+ 8 - 0
src/XUnity.AutoTranslator.Plugin.Core/Utilities/LanguageHelper.cs

@@ -3,6 +3,7 @@ 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.Utilities
 {
@@ -53,6 +54,13 @@ namespace XUnity.AutoTranslator.Plugin.Core.Utilities
          return DefaultSymbolCheck( text );
       }
 
+      public static bool IsTranslatable( string text )
+      {
+         return ContainsLanguageSymbolsForSourceLanguage( text )
+            //&& str.Length <= Settings.MaxCharactersPerTranslation
+            && !Settings.IgnoreTextStartingWith.Any( x => text.StartsWithStrict( x ) );
+      }
+
       public static bool ContainsJapaneseSymbols( string text )
       {
          // Unicode Kanji Table:

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

@@ -2,7 +2,7 @@
 
    <PropertyGroup>
       <TargetFramework>net35</TargetFramework>
-      <Version>3.1.0</Version>
+      <Version>3.2.0</Version>
       <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
    </PropertyGroup>
 

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

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