{"record":{"id":"26da96f13dbf0afc","repo":"tonhowtf/omniget","slug":"libretranslate-http","errorCode":null,"errorMessage":"LibreTranslate: HTTP {} {}","messagePattern":"LibreTranslate: HTTP (.+?) (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-tauri/omniget-core/src/core/tools/srt_translate.rs","lineNumber":146,"sourceCode":"    let client = super::client()?;\n    let url = format!(\"{}/translate\", base_url.trim_end_matches('/'));\n    let mut body = serde_json::json!({\n        \"q\": lines,\n        \"source\": if opts.source_lang.is_empty() { \"auto\" } else { opts.source_lang.as_str() },\n        \"target\": opts.target_lang,\n        \"format\": \"text\",\n    });\n    if !api_key.is_empty() {\n        body[\"api_key\"] = serde_json::Value::String(api_key.to_string());\n    }\n    let resp = client.post(&url).json(&body).send().await?;\n    let status = resp.status();\n    let v: serde_json::Value = resp\n        .json()\n        .await\n        .map_err(|e| anyhow!(\"LibreTranslate: resposta invalida ({})\", e))?;\n    if !status.is_success() {\n        return Err(anyhow!(\n            \"LibreTranslate: HTTP {} {}\",\n            status.as_u16(),\n            v[\"error\"].as_str().unwrap_or(\"\")\n        ));\n    }\n    let out = match &v[\"translatedText\"] {\n        serde_json::Value::Array(a) => a\n            .iter()\n            .map(|x| x.as_str().map(|s| s.to_string()))\n            .collect(),\n        serde_json::Value::String(s) => vec![Some(s.clone())],\n        _ => vec![None; lines.len()],\n    };\n    Ok(out)\n}\n\npub async fn translate_cues(\n    cues: &[Cue],","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/omniget-core/src/core/tools/srt_translate.rs#L128-L164","documentation":"This error is thrown by translate_batch_libre when the LibreTranslate server responds with a non-success HTTP status code. It formats the numeric status plus the optional \"error\" field from the parsed JSON body. Common statuses are 400 (bad source/target language), 403 (invalid or missing API key), 429 (rate limit), and 5xx (server failure).","triggerScenarios":"Calling translate_cues when the LibreTranslate server rejects the batch request: unsupported language code, missing/invalid api_key when the server requires one, exceeding the character/request limit, or the server being overloaded and returning 5xx.","commonSituations":"Using language codes LibreTranslate does not recognize (e.g. 'pt-br' vs 'pt'); self-hosted instance with --api-keys enabled but no api_key sent; free public endpoint rate-limiting large SRT batches; hitting a fronting proxy's request-size limit with a huge batch.","solutions":["Read the status code in the message: fix the language codes for 400, supply a valid api_key for 403, slow down / batch smaller for 429, retry later for 5xx","Normalize language codes to what LibreTranslate expects (ISO 639-1, e.g. 'pt' not 'pt-BR') by querying /languages first","If the server requires authentication, send the api_key field in the request body","Split large subtitle batches into smaller requests to stay under rate and size limits","Check the LibreTranslate server logs at the time of the request to confirm the server-side reason"],"exampleFix":"// before: single huge batch, no retry\ntranslate_batch_libre(client, &url, &all_texts, src, dst, api_key).await?;\n// after: chunked batches with backoff on 429/5xx\nfor chunk in texts.chunks(50) {\n    match translate_batch_libre(client, &url, chunk, src, dst, api_key).await {\n        Ok(v) => results.extend(v),\n        Err(e) if is_retryable(&e) => { tokio::time::sleep(Duration::from_secs(5)).await;\n            results.extend(translate_batch_libre(client, &url, chunk, src, dst, api_key).await?); }\n        Err(e) => return Err(e),\n    }\n}","handlingStrategy":"retry","validationCode":"// Validate language codes and API key before calling\nlet langs: Vec<serde_json::Value> = client.get(format!(\"{url}/languages\"))\n    .send().await?.json().await?;\nif !langs.iter().any(|l| l[\"code\"] == src) || !langs.iter().any(|l| l[\"code\"] == dst) {\n    return Err(anyhow!(\"idioma nao suportado pelo servidor\"));\n}\nif api_key.is_none() && server_requires_key { return Err(anyhow!(\"api_key obrigatoria\")); }","typeGuard":"fn is_retryable_status(err: &anyhow::Error) -> bool {\n    let s = err.to_string();\n    [\"429\", \"500\", \"502\", \"503\", \"504\"].iter().any(|c| s.contains(&format!(\"HTTP {c}\")))\n}","tryCatchPattern":"match translate_batch_libre(...).await {\n    Ok(v) => v,\n    Err(e) if is_retryable_status(&e) => {\n        tokio::time::sleep(BACKOFF).await;\n        translate_batch_libre(...).await? // retry once with smaller batch\n    }\n    Err(e) if e.to_string().contains(\"HTTP 403\") => {\n        return Err(anyhow!(\"chave de API invalida ou ausente: {e}\"));\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Normalize language codes to LibreTranslate's ISO 639-1 set (query /languages first)","Always send api_key when the server runs with --api-keys","Chunk large SRT batches to stay under request-size and rate limits","Back off exponentially on 429/5xx instead of hammering the server"],"tags":["http","api","rust","network","translation"],"backgroundTag":"http-error-response","analyzedSha":"8600b91f4246848bac346874daa9e61c1fc5677a","analyzedAt":"2026-09-12T14:29:19.317Z","contentChangedAt":"2026-09-12T14:29:19.317Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}