Client code in five languages
Non ancora tradotta — mostrata in inglese.
We do not publish a client package. The API is one JSON call over HTTPS, and a dependency for that adds a supply chain without removing much typing. The functions below are complete; copy the one you need and adapt it.
All of them read the key from the environment variable LANGUAGE_API_KEY.
Three behaviours worth implementing
- Batch. Up to 50 texts per request. One call with many strings uses one
rate-limit slot and one round trip. See Request rate limits.
- Retry selectively. Only
rate_limited,engine_unavailable,
service_unavailable and internal_error are worth a second attempt. Branch on code, never on detail. See Error codes reference.
- Record
X-Request-Id. Without it, a support question about a specific
request cannot be answered quickly.
Python
import os
import random
import time
import requests
API = "https://api.langapi.xyz/v1/translate"
TRANSIENT = {"rate_limited", "engine_unavailable", "service_unavailable", "internal_error"}
def translate_batch(strings: list[str], target: str, *, source: str | None = None,
retries: int = 5, **extra) -> list[str]:
body = {"text": strings, "target_lang": target, **extra}
if source:
body["source_lang"] = source
auth = {"Authorization": f"Bearer {os.environ['LANGUAGE_API_KEY']}"}
for n in range(retries):
r = requests.post(API, json=body, headers=auth, timeout=40)
if r.ok:
return [item["text"] for item in r.json()["translations"]]
problem = r.json()
if problem["code"] not in TRANSIENT:
raise RuntimeError(f"{problem['code']} [{problem['request_id']}]: {problem['detail']}")
wait = float(r.headers.get("Retry-After", 2 ** n)) + random.uniform(0, 0.3)
time.sleep(wait)
raise RuntimeError("translate_batch: retries exhausted")
if __name__ == "__main__":
print(translate_batch(["Save changes", "Discard"], "CS", source="EN", formality="prefer_less"))
TypeScript
type Translation = { text: string; detected_source_language: string };
type Reply = { translations: Translation[]; request_id: string; characters: number };
type Problem = { code: string; detail: string; request_id: string };
const TRANSIENT = new Set(['rate_limited', 'engine_unavailable', 'service_unavailable', 'internal_error']);
export async function translateBatch(
strings: string[],
target: string,
extra: Record<string, unknown> = {},
retries = 5,
): Promise<Translation[]> {
const body = JSON.stringify({ text: strings, target_lang: target, ...extra });
for (let n = 0; n < retries; n++) {
const res = await fetch('https://api.langapi.xyz/v1/translate', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LANGUAGE_API_KEY}`,
'Content-Type': 'application/json',
},
body,
});
if (res.ok) {
return ((await res.json()) as Reply).translations;
}
const problem = (await res.json()) as Problem;
if (!TRANSIENT.has(problem.code)) {
throw new Error(`${problem.code} [${problem.request_id}]: ${problem.detail}`);
}
const seconds = Number(res.headers.get('Retry-After') ?? 2 ** n);
await new Promise((r) => setTimeout(r, seconds * 1000 + Math.random() * 300));
}
throw new Error('translateBatch: retries exhausted');
}
PHP
<?php
declare(strict_types=1);
/**
* @param list<string> $strings
* @param array<string, mixed> $extra
* @return list<string>
*/
function translateBatch(array $strings, string $target, array $extra = []): array
{
$body = json_encode(['text' => $strings, 'target_lang' => $target] + $extra, JSON_THROW_ON_ERROR);
$handle = curl_init('https://api.langapi.xyz/v1/translate');
curl_setopt_array($handle, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 40,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('LANGUAGE_API_KEY'),
'Content-Type: application/json',
],
]);
$raw = curl_exec($handle);
$status = (int) curl_getinfo($handle, CURLINFO_RESPONSE_CODE);
curl_close($handle);
if (!is_string($raw)) {
throw new RuntimeException('no response from the API');
}
$data = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
if ($status >= 400) {
throw new RuntimeException(sprintf('%s [%s]: %s', $data['code'], $data['request_id'], $data['detail']));
}
return array_map(static fn(array $t): string => $t['text'], $data['translations']);
}
var_dump(translateBatch(['Save changes', 'Discard'], 'CS', ['source_lang' => 'EN']));
Go
package translate
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
"time"
)
type payload struct {
Text []string `json:"text"`
TargetLang string `json:"target_lang"`
SourceLang string `json:"source_lang,omitempty"`
}
type reply struct {
Translations []struct {
Text string `json:"text"`
} `json:"translations"`
RequestID string `json:"request_id"`
}
type problem struct {
Code string `json:"code"`
Detail string `json:"detail"`
RequestID string `json:"request_id"`
}
var httpClient = &http.Client{Timeout: 40 * time.Second}
func Batch(strings []string, target, source string) ([]string, error) {
body, err := json.Marshal(payload{Text: strings, TargetLang: target, SourceLang: source})
if err != nil {
return nil, err
}
req, err := http.NewRequest(http.MethodPost, "https://api.langapi.xyz/v1/translate", bytes.NewReader(body))
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("LANGUAGE_API_KEY"))
req.Header.Set("Content-Type", "application/json")
res, err := httpClient.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode >= 400 {
var p problem
_ = json.NewDecoder(res.Body).Decode(&p)
return nil, fmt.Errorf("%s [%s]: %s", p.Code, p.RequestID, p.Detail)
}
var r reply
if err := json.NewDecoder(res.Body).Decode(&r); err != nil {
return nil, err
}
out := make([]string, len(r.Translations))
for i, t := range r.Translations {
out[i] = t.Text
}
return out, nil
}
Shell
#!/usr/bin/env sh
## invoke as: ./translate.sh CS "Save changes" "Discard"
target="$1"; shift
printf '%s\n' "$@" \
| jq -R . | jq -s --arg lang "$target" '{text: ., target_lang: $lang}' \
| curl -sS https://api.langapi.xyz/v1/translate \
-H "Authorization: Bearer ${LANGUAGE_API_KEY}" \
-H "Content-Type: application/json" \
--data-binary @- \
| jq -r '.translations[].text'
Using a DeepL client instead
The DeepL libraries for Python and Node accept a custom server URL and work against this API without patches. That path is supported; see Switching from DeepL.
Revisionato il 7 set 2026, 00:00