Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified 1.6/Assemblies/AutoTranslation.dll
Binary file not shown.
2 changes: 2 additions & 0 deletions Languages/English/Keyed/Mod.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
<AT_Setting_CustomPrompt>Custom system prompt: </AT_Setting_CustomPrompt>
<AT_Setting_BaseURL>Base URL: </AT_Setting_BaseURL>
<AT_Setting_CustomBaseURL>Custom base URL: </AT_Setting_CustomBaseURL>
<AT_Setting_SleepTime>Sleep time between requests ({0}ms)</AT_Setting_SleepTime>
<AT_Setting_WorkerCount>Concurrent worker count ({0})</AT_Setting_WorkerCount>

<AT_Setting_Misc>::Misc::</AT_Setting_Misc>
<AT_Setting_ResetDefCache>Reset all Def cache data (If some defs are not translated)</AT_Setting_ResetDefCache>
Expand Down
2 changes: 2 additions & 0 deletions Languages/Korean/Keyed/Mod.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@
<AT_Setting_CustomPrompt>사용자 지정 시스템 프롬프트: </AT_Setting_CustomPrompt>
<AT_Setting_BaseURL>기본 URL: </AT_Setting_BaseURL>
<AT_Setting_CustomBaseURL>사용자 지정 URL: </AT_Setting_CustomBaseURL>
<AT_Setting_WorkerCount>동시 작업자 수 ({0}개)</AT_Setting_WorkerCount>

<AT_Setting_Misc>::기타::</AT_Setting_Misc>
<AT_Setting_SleepTime>요청 간 대기 시간 ({0}ms)</AT_Setting_SleepTime>
<AT_Setting_ResetDefCache>모든 Def 캐시 데이터 초기화 (몇몇 def들이 번역되지 않았을 경우)</AT_Setting_ResetDefCache>

<AT_Status1>아직 번역이 진행 중입니다. 하지만 평소처럼 게임을 플레이하실 수 있습니다.</AT_Status1>
Expand Down
Binary file modified Source/AutoTranslation/.vs/AutoTranslation/v17/.suo
Binary file not shown.
244 changes: 170 additions & 74 deletions Source/AutoTranslation/.vs/AutoTranslation/v17/DocumentLayout.json

Large diffs are not rendered by default.

19 changes: 9 additions & 10 deletions Source/AutoTranslation/APIKeyRotater.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,15 @@ namespace AutoTranslation
{
public class APIKeyRotater
{
private readonly Queue<string> keys = new Queue<string>();
//private readonly Queue<string> keys = new Queue<string>();
private readonly string[] keys;
private int _index = 0;

public APIKeyRotater(IEnumerable<string> keys)
{
foreach (var key in keys)
{
this.keys.Enqueue(key.Trim());
}
this.keys = keys.Select(key => key.Trim()).ToArray();

if (this.keys.Count == 0)
if (this.keys.Length == 0)
{
throw new ArgumentException("No keys provided");
}
Expand All @@ -27,18 +26,18 @@ public string Key
{
get
{
var key = keys.Peek();
var key = keys[_index];
Rotate();
return key;
}
}

public string KeyNoRotate => keys.Peek();
public int Count => keys.Count;
public string KeyNoRotate => keys[_index];
public int Count => keys.Length;

public void Rotate()
{
keys.Enqueue(keys.Dequeue());
_index = (_index + 1) % keys.Length;
}
}
}
3 changes: 2 additions & 1 deletion Source/AutoTranslation/AutoTranslation.csproj
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@
<Compile Include="Translators\Translator_DeepSeek.cs" />
<Compile Include="Translators\Translator_Gemini.cs" />
<Compile Include="Translators\Translator_Google.cs" />
<Compile Include="Translators\Translator_BaseOnlineAIModel.cs" />
<Compile Include="Translators\Translator_Ollama.cs" />
<Compile Include="Translators\Translator_BaseAIModel.cs" />
<Compile Include="Translators\Translator_BaseTraditional.cs" />
</ItemGroup>
<ItemGroup />
Expand Down
1 change: 1 addition & 0 deletions Source/AutoTranslation/CacheFileTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public static void Export(string name, Dictionary<string, string> cache)
var key = StripInvalidXmlChars(k);
if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(v))
continue;
if (char.IsDigit(key[0])) continue;
try
{
e.AppendElement(key, v);
Expand Down
4 changes: 2 additions & 2 deletions Source/AutoTranslation/Helpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,14 @@ public static string GetStringValueFromJson(this string json, string key)
{
var pattern = $"\"{key}\"\\s*:\\s*\"((?:\\\\\"|[^\"])*)\"";
var match = Regex.Match(json, pattern);
return match.Success ? match.Groups[1].Value.Replace("\\\"", "\"") : null;
return match.Success ? match.Groups[1].Value.Replace("\\\"", "\"").Trim() : null;
}

public static List<string> GetStringValuesFromJson(this string json, string key)
{
var pattern = $"\"{key}\"\\s*:\\s*\"((?:\\\\\"|[^\"])*)\"";
var matches = Regex.Matches(json, pattern);
return matches.Cast<Match>().Select(match => match.Groups[1].Value.Replace("\\\"", "\"")).ToList();
return matches.Cast<Match>().Select(match => match.Groups[1].Value.Replace("\\\"", "\"").Trim()).ToList();
}

public static string GetResponseAndReadText(this WebRequest request)
Expand Down
36 changes: 19 additions & 17 deletions Source/AutoTranslation/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public class Settings : ModSettings
public static string TranslatorName = "Google";
public static bool ShowOriginal = false;
public static HashSet<string> BlackListModPackageIds = new HashSet<string>();
public static int SleepTime = 0;
public static int ConcurrentWorkerCount = 1;

public static string SelectedModel = string.Empty;
public static string CustomBaseURL = string.Empty;
Expand All @@ -42,6 +44,9 @@ public override void ExposeData()
Scribe_Values.Look(ref SelectedModel, "AutoTranslation_SelectedModel", string.Empty);
Scribe_Values.Look(ref CustomBaseURL, "AutoTranslation_CustomBaseURL", string.Empty);
Scribe_Values.Look(ref CustomPrompt, "AutoTranslation_CustomPrompt", string.Empty);
Scribe_Values.Look(ref SleepTime, "AutoTranslation_SleepTime", 0);
Scribe_Values.Look(ref ConcurrentWorkerCount, "AutoTranslation_WorkerCount", 1);

Scribe_Collections.Look(ref BlackListModPackageIds, "AutoTranslation_WhiteListModPackageIds", LookMode.Value);
if (BlackListModPackageIds == null) BlackListModPackageIds = new HashSet<string>();
}
Expand Down Expand Up @@ -256,7 +261,7 @@ public float DoSettingsWindowContentsLeft(Rect inRect)
APIKey = Widgets.TextEntryLabeled(textRect, "API Key:", APIKey);
}

if (targetTranslator is Translator_BaseOnlineAIModel aiTranslator)
if (targetTranslator is Translator_BaseAIModel aiTranslator)
{
ls.Label("AT_Setting_BaseURL".Translate() + aiTranslator.BaseURL);
var textRect = ls.GetRect(Text.LineHeight);
Expand Down Expand Up @@ -296,7 +301,7 @@ public float DoSettingsWindowContentsLeft(Rect inRect)

if (Widgets.ButtonText(mid, "AT_Setting_TestTranslation".Translate()))
{
if (targetTranslator is Translator_BaseOnlineAIModel ait)
if (targetTranslator is Translator_BaseAIModel ait)
{
ait.ResetSettings();
}
Expand Down Expand Up @@ -330,6 +335,10 @@ public float DoSettingsWindowContentsRight(Rect inRect)
ls.Label("AT_Setting_Misc".Translate());
ls.GapLine();

SleepTime = (int)ls.SliderLabeled("AT_Setting_SleepTime".Translate(SleepTime), SleepTime, 0, 5000);

ConcurrentWorkerCount = (int)ls.SliderLabeled("AT_Setting_WorkerCount".Translate(ConcurrentWorkerCount), ConcurrentWorkerCount, 1, 5);

if (ls.ButtonText("AT_Setting_ResetDefCache".Translate()))
{
ResetDefCaches();
Expand Down Expand Up @@ -360,27 +369,20 @@ public float DoSettingsWindowContentsRight(Rect inRect)
if (ls.ButtonText("AT_Setting_RestartWork".Translate()))
{
var t = TranslatorManager.GetTranslator(TranslatorName);
if (t is Translator_BaseOnlineAIModel aiTranslator)
if (t is Translator_BaseAIModel aiTranslator)
{
aiTranslator.ResetSettings();
}
if (t.Ready)
{
TranslatorManager.ClearQueue();
TranslatorManager.CurrentTranslator = t;
TranslatorManager.ClearQueue();
TranslatorManager.CurrentTranslator = t;

InjectionManager.UndoInjectAll();
InjectionManager.ClearDefInjectedTranslations();
InjectionManager.ReverseTranslator.Clear();
InjectionManager.UndoInjectAll();
InjectionManager.ClearDefInjectedTranslations();
InjectionManager.ReverseTranslator.Clear();

InjectionManager.InjectAll();
InjectionManager.InjectAll();

Messages.Message("AT_Message_RestartWork".Translate(), MessageTypeDefOf.NeutralEvent);
}
else
{
Messages.Message("AT_Message_RestartFailed".Translate(), MessageTypeDefOf.NegativeEvent);
}
Messages.Message("AT_Message_RestartWork".Translate(), MessageTypeDefOf.NeutralEvent);
}
Comment on lines 369 to 386
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the context of restart logic and translator state management

echo "=== Searching for RestartWork and related patterns ==="
rg -n "RestartWork" --type=cs -B3 -A15

echo -e "\n=== Searching for ClearQueue usage patterns ==="
rg -n "ClearQueue" --type=cs -B2 -A2

echo -e "\n=== Checking TranslatorManager implementation ==="
fd "TranslatorManager" --type=cs --exec cat {} \;

echo -e "\n=== Checking for Translator Ready state patterns ==="
rg -n "\.Ready|Prepare\(\)" --type=cs -B2 -A2

echo -e "\n=== Checking for previous conditional patterns in InjectionManager ==="
rg -n "InjectionManager\." --type=cs -B2 -A2 | head -100

Repository: csh1668/AutoTranslation

Length of output: 10158


🏁 Script executed:

#!/bin/bash
# Check git history for this file to see what was changed
echo "=== Git diff for Settings.cs (recent changes) ==="
git log --oneline -n 20 -- Source/AutoTranslation/Settings.cs

echo -e "\n=== Git show diff for RestartWork context ==="
git log -p -n 5 --follow -- Source/AutoTranslation/Settings.cs | grep -A30 -B5 "RestartWork" | head -100

echo -e "\n=== Check InjectionManager.InjectAll method ==="
rg -A10 "public.*InjectAll\(\)" 

echo -e "\n=== Check InjectionManager.UndoInjectAll method ==="
rg -A10 "public.*UndoInjectAll\(\)"

echo -e "\n=== Check ClearQueue full implementation ==="
rg -A5 "public static void ClearQueue"

echo -e "\n=== Check for translator validation/readiness patterns ==="
rg -n "CurrentTranslator\s*=" --type=cs -B2 -A2

Repository: csh1668/AutoTranslation

Length of output: 3083


🏁 Script executed:

#!/bin/bash
# Find InjectionManager file and examine InjectAll/UndoInjectAll methods
echo "=== Find InjectionManager implementation ==="
fd "InjectionManager" -t f --type=cs

echo -e "\n=== Check InjectionManager.cs for InjectAll ==="
rg -A15 "void InjectAll\(\)|void UndoInjectAll\(\)" 

echo -e "\n=== Check for any validation before InjectAll calls ==="
rg -B5 "InjectAll\(\)" | head -80

echo -e "\n=== Look at the full git diff for the latest commit ==="
git show --stat 83c80de

echo -e "\n=== Check for any translator null validation in RestartWork context ==="
rg -B10 -A20 "if.*t is Translator_BaseAIModel"

Repository: csh1668/AutoTranslation

Length of output: 9724


🏁 Script executed:

#!/bin/bash
# Check GetTranslator implementation to see if it can return null
echo "=== Check GetTranslator method signature and implementation ==="
rg -A10 "public static.*GetTranslator\("

echo -e "\n=== Check if null translator is handled elsewhere ==="
rg -B2 -A2 "GetTranslator.*null|GetTranslator.*=="

Repository: csh1668/AutoTranslation

Length of output: 961


Add null check for translator assignment.

The translator retrieved via GetTranslator(TranslatorName) can be null (it uses FirstOrDefault()), but is assigned directly to CurrentTranslator without validation. Assign only if the translator is valid, or fall back to a default translator as done in TranslatorManager.Prepare().


if (ls.ButtonText("AT_Setting_OpenDir".Translate()))
Expand Down
98 changes: 53 additions & 45 deletions Source/AutoTranslation/TranslatorManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public static class TranslatorManager

private static readonly ConcurrentDictionary<string, byte> _inQueue = new ConcurrentDictionary<string, byte>();
private static Task _translationThread;
private static Task[] _translationThreads;
private static Timer _cacheSaver;
private static readonly Regex StringFormatSymbolsRegex = new Regex("{.*?}");
private static readonly StringBuilder sb = new StringBuilder(1024);
Expand All @@ -43,15 +44,13 @@ public static void Prepare()
foreach (var translatorType in translatorTypes)
{
var t = (ITranslator)Activator.CreateInstance(translatorType);
t.Prepare();
translators.Add(t);
}
CurrentTranslator = GetTranslator(Settings.TranslatorName);
if (CurrentTranslator?.Ready == false) CurrentTranslator = null;

if (CurrentTranslator == null)
{
CurrentTranslator = translators.FirstOrDefault(x => x.Ready);
CurrentTranslator = translators.FirstOrDefault(x => x.Name == "Google");
Log.Error(AutoTranslation.LogPrefix +
$"Selected translator named {Settings.TranslatorName} is not ready, changing to other translator.. {CurrentTranslator?.Name}");
}
Expand Down Expand Up @@ -129,50 +128,15 @@ public static void StartThread()
}

Working = true;
_translationThread = Task.Factory.StartNew(() =>
{
while (true)
{
if (!Working)
{
Thread.Sleep(1000);
continue;
}
while (_queue.Count > 0)
{
if (!_queue.TryDequeue(out var pair)) continue;

_inQueue.TryRemove(pair.Key, out _);
var translated = string.Empty;
var success = true;
if (pair.Key.Length > 200)
{
translated = pair.Key.Tokenize().Aggregate(translated, (current, token) =>
{
success &= CurrentTranslator.TryTranslate(token, out var tmp);
return current + ' ' + tmp;
});
}
else
{
success = CurrentTranslator.TryTranslate(pair.Key, out translated);
}

if (success)
{
translated = PolishText(translated);
//translated = UnityWebRequest.UnEscapeURL(translated, Encoding.UTF8).Trim();
}
workCnt++;
//_finished.Enqueue(pair);
pair.Value(translated, success);
}
Thread.Sleep(1000);
}
}).ContinueWith(t =>
_translationThreads = new Task[Settings.ConcurrentWorkerCount];
for (int i = 0; i < Settings.ConcurrentWorkerCount; i++)
{
Log.Warning($"Translation thread was killed! {t.Exception?.Message}");
});
_translationThreads[i] = Task.Factory.StartNew(DoInnerThreadWork).ContinueWith(t =>
{
Log.Warning($"Translation thread {i} was killed! {t.Exception?.Message}");
});
}
Comment on lines +132 to +139
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix closure capture bug in thread completion handlers.

The loop variable i is captured in the closure at line 137, causing all thread completion handlers to log the same thread index (the final value of i after the loop completes). This is a classic closure capture issue in C#.

🔎 Proposed fix
 _translationThreads = new Task[Settings.ConcurrentWorkerCount];
 for (int i = 0; i < Settings.ConcurrentWorkerCount; i++)
 {
-    _translationThreads[i] = Task.Factory.StartNew(DoInnerThreadWork).ContinueWith(t =>
+    int threadIndex = i;  // Capture loop variable in local scope
+    _translationThreads[i] = Task.Factory.StartNew(DoInnerThreadWork).ContinueWith(t =>
     {
-        Log.Warning($"Translation thread {i} was killed! {t.Exception?.Message}");
+        Log.Warning($"Translation thread {threadIndex} was killed! {t.Exception?.Message}");
     });
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
_translationThreads = new Task[Settings.ConcurrentWorkerCount];
for (int i = 0; i < Settings.ConcurrentWorkerCount; i++)
{
Log.Warning($"Translation thread was killed! {t.Exception?.Message}");
});
_translationThreads[i] = Task.Factory.StartNew(DoInnerThreadWork).ContinueWith(t =>
{
Log.Warning($"Translation thread {i} was killed! {t.Exception?.Message}");
});
}
_translationThreads = new Task[Settings.ConcurrentWorkerCount];
for (int i = 0; i < Settings.ConcurrentWorkerCount; i++)
{
int threadIndex = i; // Capture loop variable in local scope
_translationThreads[i] = Task.Factory.StartNew(DoInnerThreadWork).ContinueWith(t =>
{
Log.Warning($"Translation thread {threadIndex} was killed! {t.Exception?.Message}");
});
}
🤖 Prompt for AI Agents
In Source/AutoTranslation/TranslatorManager.cs around lines 132 to 139, the
for-loop captures the loop variable i in the ContinueWith closure, causing every
completion handler to log the same (final) index; fix by copying i to a local
variable inside the loop (e.g. int threadIndex = i) and use that local in the
ContinueWith lambda so each handler logs its own thread index, or alternatively
pass the index as state to StartNew/ContinueWith to avoid the closure capture.


_cacheSaver = new Timer(state =>
{
Expand All @@ -194,6 +158,50 @@ public static void StartThread()
}, null, 0, 60000);
}

private static void DoInnerThreadWork()
{
while (true)
{
if (!Working)
{
Thread.Sleep(1000);
continue;
}
while (_queue.Count > 0)
{
if (Settings.SleepTime > 0) Thread.Sleep(Settings.SleepTime);

if (!_queue.TryDequeue(out var pair)) continue;

_inQueue.TryRemove(pair.Key, out _);
var translated = string.Empty;
var success = true;
if (pair.Key.Length > 200)
{
translated = pair.Key.Tokenize().Aggregate(translated, (current, token) =>
{
success &= CurrentTranslator.TryTranslate(token, out var tmp);
return current + ' ' + tmp;
});
}
else
{
success = CurrentTranslator.TryTranslate(pair.Key, out translated);
}

if (success)
{
translated = PolishText(translated);
//translated = UnityWebRequest.UnEscapeURL(translated, Encoding.UTF8).Trim();
}
workCnt++;
//_finished.Enqueue(pair);
pair.Value(translated, success);
}
Thread.Sleep(1000);
}
}

public static void Translate(string orig, Action<string> callBack) => Translate(orig, string.Empty, callBack);

public static void Translate(string orig, string additionalKey, Action<string> callBack)
Expand Down
3 changes: 0 additions & 3 deletions Source/AutoTranslation/Translators/ITranslator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@ public interface ITranslator
{
string Name { get; }

bool Ready { get; set; }
bool RequiresKey { get; }

void Prepare();
bool TryTranslate(string text, out string translated);
bool TryTranslate(string text, out string translated, bool skipRetry);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,16 @@

namespace AutoTranslation.Translators
{
public abstract class Translator_BaseOnlineAIModel : ITranslator
public abstract class Translator_BaseAIModel : ITranslator
{
public abstract string Name { get; }
public bool Ready { get; set; }
public bool RequiresKey => true;
public virtual bool RequiresKey => true;

public virtual string Model => _model ?? (_model = Settings.SelectedModel);
public List<string> Models => _models ?? (_models = GetModels());

public abstract string BaseURL { get; }

public virtual void Prepare()
{
if (string.IsNullOrEmpty(Settings.APIKey)) return;
Ready = true;
}

public bool TryTranslate(string text, out string translated)
{
return TryTranslate(text, out translated, false);
Expand Down Expand Up @@ -105,7 +98,6 @@ public void ResetSettings()
_rotater = null;
_prompt = null;
_baseURL = null;
Prepare();
}

protected abstract string GetResponseUnsafe(string text);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,11 @@ namespace AutoTranslation.Translators
public abstract class Translator_BaseTraditional : ITranslator
{
public abstract string Name { get; }
public bool Ready { get; set; }
public virtual bool RequiresKey { get; } = true;

public virtual string StartLanguage => "auto";
public abstract string TranslateLanguage { get; }

public abstract void Prepare();

public abstract bool TryTranslate(string text, out string translated);

// Default implementation calls the original method with skipRetry=false
Expand Down
2 changes: 1 addition & 1 deletion Source/AutoTranslation/Translators/Translator_ChatGPT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

namespace AutoTranslation.Translators
{
public class Translator_ChatGPT : Translator_BaseOnlineAIModel
public class Translator_ChatGPT : Translator_BaseAIModel
{
public override string Name => "ChatGPT";
public override string BaseURL => "https://api.openai.com/v1/";
Expand Down
2 changes: 1 addition & 1 deletion Source/AutoTranslation/Translators/Translator_Claude.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

namespace AutoTranslation.Translators
{
public class Translator_Claude : Translator_BaseOnlineAIModel
public class Translator_Claude : Translator_BaseAIModel
{
public override string Name => "Claude";

Expand Down
Loading