SubtitleEdit/subtitleedit · error · Exception

{StaticName} returned an unexpected response: {responseStrin

Error message

{StaticName} returned an unexpected response: {responseString}

What it means

Thrown by NoLanguageLeftBehindApi when the self-hosted NLLB service returns a 2xx response (EnsureSuccessStatusCode passed) but the JSON has no top-level 'result' field that the parser can extract. Indicates the service responded successfully but with an unexpected/empty schema, or the wrong service is at the configured URL.

Source

Thrown at src/libuilogic/AutoTranslate/NoLanguageLeftBehindApi.cs:67

        {
            return new NoLanguageLeftBehindServe().GetSupportedTargetLanguages();
        }

        public async Task<string> Translate(string text, string sourceLanguageCode, string targetLanguageCode, CancellationToken cancellationToken)
        {
            var content = new StringContent("{ \"text\": \"" + Json.EncodeJsonText(text) + "\",  \"source\": \"" + sourceLanguageCode + "\", \"target\": \"" + targetLanguageCode + "\" }", Encoding.UTF8, "application/json");
            using var result = await _httpClient.PostAsync("translator", content, cancellationToken);
            result.EnsureSuccessStatusCode();

            var responseString = await result.Content.ReadAsStringAsync(cancellationToken);

            var parser = new SeJsonParser();
            var resultText = parser.GetFirstObject(responseString, "result");
            if (resultText == null)
            {
                Error = responseString;
                SeLogger.Error($"{GetType().Name} got unexpected JSON: {responseString}");
                throw new Exception($"{StaticName} returned an unexpected response: {responseString}");
            }

            return Json.DecodeJsonText(resultText);
        }

        public void Dispose() => _httpClient?.Dispose();
    }
}

View on GitHub (pinned to 17a9f07487)

Solutions

  1. Confirm AutoTranslateNllbApiUrl points at the winstxnhdw/nllb-api implementation (returns {"result": ...}).
  2. Log/capture responseString to see the actual body and find the right key.
  3. Ensure the URL has a trailing slash (the code adds one, but verify the stored value).
  4. If using a different NLLB fork, switch to the matching translator class (e.g. NoLanguageLeftBehindServe).

Example fix

// before
var resultText = parser.GetFirstObject(responseString, "result");
if (resultText == null)
{
    Error = responseString;
    SeLogger.Error($"{GetType().Name} got unexpected JSON: {responseString}");
    throw new Exception($"{StaticName} returned an unexpected response: {responseString}");
}

// after - try common alternative keys before giving up, and hint at a mismatched service
var resultText = parser.GetFirstObject(responseString, "result")
    ?? parser.GetFirstObject(responseString, "translatedText")
    ?? parser.GetFirstObject(responseString, "translation");
if (resultText == null)
{
    Error = responseString;
    SeLogger.Error($"{GetType().Name} got unexpected JSON (expected a 'result' key). URL: {_httpClient.BaseAddress}. Body: {responseString}");
    throw new Exception($"{StaticName} returned an unexpected response (no 'result' key - is this the winstxnhdw/nllb-api service?): {responseString}");
}
Defensive patterns

Strategy: validation

Validate before calling

// Validate the NLLB URL and probe the service shape before translating
var url = Configuration.Settings.Tools.AutoTranslateNllbApiUrl;
if (string.IsNullOrWhiteSpace(url))
    throw new InvalidOperationException("NLLB API URL is not set.");
using var c = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };
var body = "{\"text\":\"hi\",\"source\":\"eng_Latn\",\"target\":\"spa_Latn\"}";
var resp = await c.PostAsync(url.TrimEnd('/') + "/translator", new StringContent(body, Encoding.UTF8, "application/json"));
var json = await resp.Content.ReadAsStringAsync();
if (!json.Contains("\"result\""))
    throw new InvalidOperationException($"NLLB service at {url} did not return a 'result' key - is this winstxnhdw/nllb-api? Body: {json}");

Type guard

public static bool ResponseHasResultKey(string body) => body.Contains("\"result\"");

Try / catch

try
{
    return await nllb.Translate(text, src, tgt, token);
}
catch (Exception ex) when (ex.Message.Contains("unexpected response"))
{
    throw new InvalidOperationException("NLLB returned a 2xx with no 'result' key - check the URL points at winstxnhdw/nllb-api and the schema matches.", ex);
}

Prevention

When it happens

Trigger: Translate() posts {text, source, target} to <base>/translator; result.EnsureSuccessStatusCode() passes; SeJsonParser.GetFirstObject(responseString, "result") returns null. Happens when the URL points to a different NLLB variant (e.g. nllb-serve vs winstxnhdw-nllb-api which returns {"result": ...}), or when the service returns an error-shaped 200 like {"error": ...}.

Common situations: AutoTranslateNllbApiUrl pointing at a different NLLB implementation whose response key is not 'result'; service returning {"translatedText": ...} or an error object with HTTP 200; missing trailing slash on the URL (the code appends one at Initialize to avoid a .NET base-address 404, but a wrong path still misroutes).

Related errors


AI-assisted analysis of SubtitleEdit/subtitleedit@17a9f07487 (2026-08-13). Data as JSON: /api/errors/a9183d4f2d677474. Report an issue: GitHub.