From 4c23531762101dd5698e4d4353a695d7a198ffdf Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 6 Feb 2026 23:44:57 +0000 Subject: [PATCH 1/2] refactor: P3 code quality improvements across translation services - DoubaoService: replace List + string.Join with StringBuilder for consistency with GeminiService and BaseOpenAIService; rename RemoveSurroundingQuotes to CleanupResult to match naming convention - GeminiService: remove unused StringBuilder buffer in ParseGeminiStreamAsync - HotkeyService: add volatile modifier to _isDisposed and _isInitialized for defensive consistency with MouseHookService - DeepLService: merge duplicate GetDeepLLanguageCode/GetDeepLWebLanguageCode into single method with isWeb parameter (only difference: Portuguese PT vs PT-PT), removing ~28 lines of duplication https://claude.ai/code/session_01Sz7TonncZGm4PgXYBzPEBo --- .../Services/DeepLService.cs | 44 +++---------------- .../Services/DoubaoService.cs | 11 +++-- .../Services/GeminiService.cs | 2 - .../Easydict.WinUI/Services/HotkeyService.cs | 4 +- 4 files changed, 13 insertions(+), 48 deletions(-) diff --git a/dotnet/src/Easydict.TranslationService/Services/DeepLService.cs b/dotnet/src/Easydict.TranslationService/Services/DeepLService.cs index 542af831..3ad67cb5 100644 --- a/dotnet/src/Easydict.TranslationService/Services/DeepLService.cs +++ b/dotnet/src/Easydict.TranslationService/Services/DeepLService.cs @@ -156,10 +156,10 @@ private async Task TranslateWebAsync( TranslationRequest request, CancellationToken cancellationToken) { - var targetCode = GetDeepLWebLanguageCode(request.ToLanguage); + var targetCode = GetDeepLLanguageCode(request.ToLanguage, isWeb: true); var sourceCode = request.FromLanguage == Language.Auto ? "auto" - : GetDeepLWebLanguageCode(request.FromLanguage); + : GetDeepLLanguageCode(request.FromLanguage, isWeb: true); // Generate anti-detection values (matching macOS implementation) var requestId = GetRandomRequestId(); @@ -385,9 +385,10 @@ private TranslationResult ParseWebResponse(string json, TranslationRequest reque } /// - /// Get language code for official DeepL API. + /// Get language code for DeepL API or web JSON-RPC. + /// The only difference is Portuguese: API uses "PT", web uses "PT-PT". /// - private static string GetDeepLLanguageCode(Language language) => language switch + private static string GetDeepLLanguageCode(Language language, bool isWeb = false) => language switch { Language.SimplifiedChinese => "ZH", Language.TraditionalChinese => "ZH-HANT", @@ -396,40 +397,7 @@ private TranslationResult ParseWebResponse(string json, TranslationRequest reque Language.Korean => "KO", Language.French => "FR", Language.Spanish => "ES", - Language.Portuguese => "PT", - Language.Italian => "IT", - Language.German => "DE", - Language.Russian => "RU", - Language.Dutch => "NL", - Language.Polish => "PL", - Language.Bulgarian => "BG", - Language.Czech => "CS", - Language.Danish => "DA", - Language.Finnish => "FI", - Language.Greek => "EL", - Language.Hungarian => "HU", - Language.Indonesian => "ID", - Language.Norwegian => "NB", - Language.Romanian => "RO", - Language.Swedish => "SV", - Language.Turkish => "TR", - Language.Ukrainian => "UK", - _ => language.ToIso639().ToUpper() - }; - - /// - /// Get language code for DeepL web JSON-RPC (slightly different format). - /// - private static string GetDeepLWebLanguageCode(Language language) => language switch - { - Language.SimplifiedChinese => "ZH", - Language.TraditionalChinese => "ZH-HANT", - Language.English => "EN", - Language.Japanese => "JA", - Language.Korean => "KO", - Language.French => "FR", - Language.Spanish => "ES", - Language.Portuguese => "PT-PT", + Language.Portuguese => isWeb ? "PT-PT" : "PT", Language.Italian => "IT", Language.German => "DE", Language.Russian => "RU", diff --git a/dotnet/src/Easydict.TranslationService/Services/DoubaoService.cs b/dotnet/src/Easydict.TranslationService/Services/DoubaoService.cs index 701a3fd7..6410b3d7 100644 --- a/dotnet/src/Easydict.TranslationService/Services/DoubaoService.cs +++ b/dotnet/src/Easydict.TranslationService/Services/DoubaoService.cs @@ -82,14 +82,13 @@ protected override async Task TranslateInternalAsync( TranslationRequest request, CancellationToken cancellationToken = default) { - var chunks = new List(); + var sb = new StringBuilder(); await foreach (var chunk in TranslateStreamAsync(request, cancellationToken)) { - chunks.Add(chunk); + sb.Append(chunk); } - var translatedText = string.Join("", chunks).Trim(); - translatedText = RemoveSurroundingQuotes(translatedText); + var translatedText = CleanupResult(sb.ToString()); return new TranslationResult { @@ -314,9 +313,9 @@ private static async IAsyncEnumerable ParseDoubaoStreamAsync( }; /// - /// Remove surrounding quotes from translated text if present. + /// Clean up translated text by trimming and removing surrounding quotes. /// - private static string RemoveSurroundingQuotes(string text) + private static string CleanupResult(string text) { if (string.IsNullOrEmpty(text)) return text; diff --git a/dotnet/src/Easydict.TranslationService/Services/GeminiService.cs b/dotnet/src/Easydict.TranslationService/Services/GeminiService.cs index d7aa8cae..fe955b6f 100644 --- a/dotnet/src/Easydict.TranslationService/Services/GeminiService.cs +++ b/dotnet/src/Easydict.TranslationService/Services/GeminiService.cs @@ -220,8 +220,6 @@ private static async IAsyncEnumerable ParseGeminiStreamAsync( [EnumeratorCancellation] CancellationToken cancellationToken) { using var reader = new StreamReader(stream); - var buffer = new StringBuilder(); - while (!reader.EndOfStream) { var line = await reader.ReadLineAsync(cancellationToken); diff --git a/dotnet/src/Easydict.WinUI/Services/HotkeyService.cs b/dotnet/src/Easydict.WinUI/Services/HotkeyService.cs index c20228d5..49864175 100644 --- a/dotnet/src/Easydict.WinUI/Services/HotkeyService.cs +++ b/dotnet/src/Easydict.WinUI/Services/HotkeyService.cs @@ -26,8 +26,8 @@ public sealed class HotkeyService : IDisposable private readonly Window _window; private readonly nint _hwnd; - private bool _isDisposed; - private bool _isInitialized; + private volatile bool _isDisposed; + private volatile bool _isInitialized; // Window subclass delegate - must keep reference to prevent GC private delegate nint SubclassProc(nint hWnd, uint uMsg, nint wParam, nint lParam, nuint uIdSubclass, nuint dwRefData); From 886461b6ebce24fe36c443ad53a218c12a5048a8 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 7 Feb 2026 08:28:04 +0000 Subject: [PATCH 2/2] fix: DoubaoService CleanupResult always returns trimmed text Address PR review comments: - Fix regression where CleanupResult returned untrimmed text when no surrounding quotes were found (return `trimmed` instead of `text`) - Add unit test verifying whitespace is trimmed even without quotes https://claude.ai/code/session_01Sz7TonncZGm4PgXYBzPEBo --- .../Services/DoubaoService.cs | 2 +- .../Services/DoubaoServiceTests.cs | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/dotnet/src/Easydict.TranslationService/Services/DoubaoService.cs b/dotnet/src/Easydict.TranslationService/Services/DoubaoService.cs index 6410b3d7..c0b8af81 100644 --- a/dotnet/src/Easydict.TranslationService/Services/DoubaoService.cs +++ b/dotnet/src/Easydict.TranslationService/Services/DoubaoService.cs @@ -328,6 +328,6 @@ private static string CleanupResult(string text) return trimmed[1..^1]; } - return text; + return trimmed; } } diff --git a/dotnet/tests/Easydict.TranslationService.Tests/Services/DoubaoServiceTests.cs b/dotnet/tests/Easydict.TranslationService.Tests/Services/DoubaoServiceTests.cs index 808234dd..f31d7510 100644 --- a/dotnet/tests/Easydict.TranslationService.Tests/Services/DoubaoServiceTests.cs +++ b/dotnet/tests/Easydict.TranslationService.Tests/Services/DoubaoServiceTests.cs @@ -387,6 +387,37 @@ public async Task TranslateStreamAsync_SendsBearerToken() auth.Should().Be("Bearer my-doubao-key"); } + [Fact] + public async Task TranslateAsync_TrimsWhitespace_WhenNoSurroundingQuotes() + { + // Arrange + _service.Configure("test-key"); + + var sseContent = new StringBuilder(); + sseContent.AppendLine("event: response.output_text.delta"); + sseContent.AppendLine("""data: {"delta":" Hello World \n"}"""); + sseContent.AppendLine(); + sseContent.AppendLine("data: [DONE]"); + + var response = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(sseContent.ToString(), Encoding.UTF8, "text/event-stream") + }; + _mockHandler.EnqueueResponse(response); + + var request = new TranslationRequest + { + Text = "你好世界", + ToLanguage = Language.English + }; + + // Act + var result = await _service.TranslateAsync(request); + + // Assert - whitespace should be trimmed even without surrounding quotes + result.TranslatedText.Should().Be("Hello World"); + } + [Theory] [InlineData(Language.SimplifiedChinese, "zh")] [InlineData(Language.TraditionalChinese, "zh-Hant")]