{"record":{"id":"ea8e0977482d8c31","repo":"SubtitleEdit/subtitleedit","slug":"translation-engine-translator-name-returned-no-t-ea8e09","errorCode":null,"errorMessage":"Translation engine {translator.Name} returned no translation for line {index + 1} after {noProgressCount} attempts","messagePattern":"Translation engine (.+?) returned no translation for line (.+?) after (.+?) attempts","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/ui/Features/Translate/AutoTranslateViewModel.cs","lineNumber":1605,"sourceCode":"                    });\n\n                    if (_onlyCurrentLine)\n                    {\n                        _translationProgressIndex = index - 1;\n                        Dispatcher.UIThread.Invoke(() => IsTranslateEnabled = true);\n                        break;\n                    }\n                }\n                else\n                {\n                    forceSingleLineMode = true;\n\n                    // The engine keeps returning nothing for this line without throwing -\n                    // without a cap the loop would retry the same line forever.\n                    noProgressCount++;\n                    if (noProgressCount > 3)\n                    {\n                        throw new Exception($\"Translation engine {translator.Name} returned no translation for line {index + 1} after {noProgressCount} attempts\");\n                    }\n                }\n            }\n\n        }\n        catch (OperationCanceledException) when (_abort || _cancellationTokenSource.IsCancellationRequested)\n        {\n            // User pressed Cancel — let the finally block report it; do not surface as an error.\n            // Check both _abort and the token: Cancel() sets _abort then fires the token, but the\n            // worker thread can observe cancellation before _abort is visible across threads.\n        }\n        catch (Exception ex)\n        {\n            _ = Dispatcher.UIThread.Invoke(async () =>\n            {\n                var details = new System.Text.StringBuilder();\n\n                // Lead with the endpoint actually used - reports often only show the error","sourceCodeStart":1587,"sourceCodeEnd":1623,"githubUrl":"https://github.com/SubtitleEdit/subtitleedit/blob/17a9f0748781032255db3526b7215d2fb891e3af/src/ui/Features/Translate/AutoTranslateViewModel.cs#L1587-L1623","documentation":"Thrown when a translation engine (MergeAndTranslateIfPossible via a translator) returns zero translated lines for the same subtitle line more than 3 consecutive times without throwing an exception. This is an anti-infinite-loop guard: without it, the outer while loop would retry the same line forever because translateCount stays 0 and index never advances.","triggerScenarios":"The translator engine (any ITranslator implementation) returns translateCount == 0 on every attempt for a specific line index. This occurs when the engine's API responds with an empty or unparseable result body that doesn't throw, or the source line is empty/degenerate. The counter increments per failed attempt and throws at attempt 4 (noProgressCount > 3).","commonSituations":"An LLM-based translator returns empty text for a line containing only special characters or formatting tags; the translation API rate-limits and returns empty 200 responses instead of errors; a model produces output that fails the engine's parsing/validation so zero lines are counted; the source line is a single character or punctuation that the model considers untranslatable.","solutions":["Inspect the specific line {index+1} in the subtitle file for degenerate content (empty, special chars only, pure formatting tags) and edit or skip it.","Switch to a different translation engine or model that handles the problematic line.","Enable forceSingleLineMode permanently for the batch to reduce context/parsing complexity.","Check the translator engine's logs for empty API responses or parsing failures on that line.","Increase the retry cap if the engine is intermittently returning empty (though this just delays the throw)."],"exampleFix":"// before\nnoProgressCount++;\nif (noProgressCount > 3)\n{\n    throw new Exception($\"Translation engine {translator.Name} returned no translation for line {index + 1} after {noProgressCount} attempts\");\n}\n\n// after — skip the degenerate line with a warning instead of aborting the whole batch\nnoProgressCount++;\nif (noProgressCount > 3)\n{\n    ApplyRowUpdateOnUiThread(index, $\"[SKIPPED] {Rows[index].Original.Text}\");\n    SeLogger.LogWarning($\"{translator.Name} returned no translation for line {index + 1} after {noProgressCount} attempts; skipping.\");\n    index++;\n    noProgressCount = 0;\n}","handlingStrategy":"try-catch","validationCode":"// Pre-check: skip empty or degenerate lines before calling the translator\nvar lineText = Rows[index].Original.Text?.Trim();\nif (string.IsNullOrWhiteSpace(lineText) ||\n    lineText.All(c => \"♪#§~_-\".Contains(c)))\n{\n    index++;\n    continue;\n}","typeGuard":null,"tryCatchPattern":"try { /* translation loop */ }\ncatch (Exception ex) when (ex.Message.Contains(\"returned no translation\"))\n{ /* log, skip to next line or switch translator engine, resume batch */ }","preventionTips":["Pre-filter subtitle lines that are empty or contain only music/special characters before translation.","Try a different translation engine or model if one consistently fails on certain line types.","Enable single-line mode proactively for lines the batch mode struggles with."],"tags":["ai","translate","retry-exhausted","infinite-loop-guard"],"backgroundTag":null,"analyzedSha":"17a9f0748781032255db3526b7215d2fb891e3af","analyzedAt":"2026-08-13T18:11:43.374Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}