Translates texts, documents, and XLIFF files via the DeepL API.
Configuration
Required:
DEEPL_API_KEY- DeepL API key
Optional:
DEEPL_TARGET_LANG- Default target language (default:DE)
Available Commands
| Command | Description |
|---|---|
/deepl translate "<text>" |
Translate text |
/deepl file <path> |
Translate document |
/deepl xliff <path> |
Translate XLIFF file |
Common Options
--to <LANG>- Target language (e.g. EN, DE, FR)--from <LANG>- Source language (otherwise auto-detect)
API Configuration
API Key Validation
Check before every API call:
if [ -z "$DEEPL_API_KEY" ]; then
echo "❌ DEEPL_API_KEY nicht gesetzt"
echo ""
echo "Setup:"
echo " export DEEPL_API_KEY='your-api-key'"
echo ""
echo "API-Key erhältlich unter: https://www.deepl.com/pro-api"
exit 1
fi
Determine API Endpoint
DeepL Free keys end with :fx:
if [[ "$DEEPL_API_KEY" == *":fx" ]]; then
DEEPL_API_URL="https://api-free.deepl.com/v2"
else
DEEPL_API_URL="https://api.deepl.com/v2"
fi
Determine Target Language
TARGET_LANG="${DEEPL_TARGET_LANG:-DE}"
# Kann mit --to überschrieben werden
/deepl translate
Translates free text.
Syntax
/deepl translate "<text>" [--to LANG] [--from LANG]
Examples
/deepl translate "Hello, how are you?"
/deepl translate "Guten Morgen" --to EN
/deepl translate "Bonjour" --from FR --to DE
Instructions
-
Parse parameters:
- Extract text from quotation marks
--toand--fromprocess flags
-
Execute API call:
# Variablen setzen
TEXT="{{user_text}}"
TARGET="${TARGET_LANG}" # aus --to oder Environment
SOURCE_PARAM=""
if [ -n "$SOURCE_LANG" ]; then
SOURCE_PARAM="--data-urlencode \"source_lang=$SOURCE_LANG\""
fi
# API-Call
RESPONSE=$(curl -s -X POST "$DEEPL_API_URL/translate" \
-H "Authorization: DeepL-Auth-Key $DEEPL_API_KEY" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "text=$TEXT" \
--data-urlencode "target_lang=$TARGET" \
$SOURCE_PARAM)
# Ergebnis extrahieren
TRANSLATED=$(echo "$RESPONSE" | jq -r '.translations[0].text // empty')
DETECTED_LANG=$(echo "$RESPONSE" | jq -r '.translations[0].detected_source_language // empty')
if [ -z "$TRANSLATED" ]; then
ERROR=$(echo "$RESPONSE" | jq -r '.message // "Unbekannter Fehler"')
echo "❌ Übersetzung fehlgeschlagen: $ERROR"
exit 1
fi
echo "📝 Original ($DETECTED_LANG): $TEXT"
echo "✅ Übersetzung ($TARGET): $TRANSLATED"
Error Handling
| HTTP Code | Meaning | Action |
|---|---|---|
| 403 | Invalid API key | Check key |
| 429 | Rate limit | Wait 1s, retry |
| 456 | Quota exceeded | Check DeepL account |
/deepl file
Translates documents (DeepL-native formats).
Supported Formats
| Format | Extension |
|---|---|
| Microsoft Word | .docx |
| Microsoft PowerPoint | .pptx |
| .pdf (Pro only) | |
| HTML | .html, .htm |
| Plain Text | .txt |
Syntax
/deepl file <path> [--to LANG] [--from LANG] [--output <path>]
Examples
/deepl file README.txt --to EN
/deepl file document.docx --to FR --output document_fr.docx
Instructions
- Check file:
FILE_PATH="{{user_path}}"
if [ ! -f "$FILE_PATH" ]; then
echo "❌ Datei nicht gefunden: $FILE_PATH"
exit 1
fi
# Extension prüfen
EXT="${FILE_PATH##*.}"
case "$EXT" in
docx|pptx|pdf|html|htm|txt) ;;
md)
# Markdown als txt behandeln
EXT="txt"
;;
*)
echo "❌ Format nicht unterstützt: .$EXT"
echo "Unterstützt: docx, pptx, pdf, html, txt"
exit 1
;;
esac
- Upload document:
UPLOAD_RESPONSE=$(curl -s -X POST "$DEEPL_API_URL/document" \
-H "Authorization: DeepL-Auth-Key $DEEPL_API_KEY" \
-F "file=@$FILE_PATH" \
-F "target_lang=$TARGET_LANG")
DOC_ID=$(echo "$UPLOAD_RESPONSE" | jq -r '.document_id // empty')
DOC_KEY=$(echo "$UPLOAD_RESPONSE" | jq -r '.document_key // empty')
if [ -z "$DOC_ID" ]; then
ERROR=$(echo "$UPLOAD_RESPONSE" | jq -r '.message // "Upload fehlgeschlagen"')
echo "❌ $ERROR"
exit 1
fi
echo "📤 Dokument hochgeladen (ID: $DOC_ID)"
- Check status (polling):
echo "⏳ Übersetze..."
while true; do
STATUS_RESPONSE=$(curl -s -X POST "$DEEPL_API_URL/document/$DOC_ID" \
-H "Authorization: DeepL-Auth-Key $DEEPL_API_KEY" \
-d "document_key=$DOC_KEY")
STATUS=$(echo "$STATUS_RESPONSE" | jq -r '.status')
case "$STATUS" in
"done")
echo "✅ Übersetzung fertig"
break
;;
"error")
ERROR=$(echo "$STATUS_RESPONSE" | jq -r '.message')
echo "❌ Fehler: $ERROR"
exit 1
;;
"translating"|"queued")
sleep 2
;;
esac
done
- Download document:
# Output-Pfad bestimmen
if [ -z "$OUTPUT_PATH" ]; then
BASENAME="${FILE_PATH%.*}"
OUTPUT_PATH="${BASENAME}_${TARGET_LANG}.${EXT}"
fi
curl -s -X POST "$DEEPL_API_URL/document/$DOC_ID/result" \
-H "Authorization: DeepL-Auth-Key $DEEPL_API_KEY" \
-d "document_key=$DOC_KEY" \
-o "$OUTPUT_PATH"
echo "📥 Gespeichert: $OUTPUT_PATH"
/deepl xliff
Translates XLIFF files (TYPO3, Symfony, etc.).
Syntax
/deepl xliff <path> [--to LANG] [--force] [--dry-run] [--output <path>]
Options
| Option | Description |
|---|---|
--force |
Recreate all translations |
--dry-run |
Only show what would be translated |
--output <path> |
Write to new file |
Examples
/deepl xliff locallang.xlf --to DE
/deepl xliff locallang.xlf --force --to FR
/deepl xliff locallang.xlf --dry-run
Instructions
- Read and validate XLIFF file:
XLIFF_PATH="{{user_path}}"
if [ ! -f "$XLIFF_PATH" ]; then
echo "❌ Datei nicht gefunden: $XLIFF_PATH"
exit 1
fi
# XML validieren
if ! xmllint --noout "$XLIFF_PATH" 2>/dev/null; then
echo "❌ Ungültiges XML in: $XLIFF_PATH"
exit 1
fi
- Extract trans-units that need translation:
# XLIFF 1.2: <trans-unit> mit leerem/fehlendem <target>
# XLIFF 2.0: <unit>/<segment> mit leerem/fehlendem <target>
# Für XLIFF 1.2:
UNITS_TO_TRANSLATE=$(xmllint --xpath "//trans-unit[not(target) or target='']" "$XLIFF_PATH" 2>/dev/null)
# Bei --force: alle Units
if [ "$FORCE" = "true" ]; then
UNITS_TO_TRANSLATE=$(xmllint --xpath "//trans-unit" "$XLIFF_PATH" 2>/dev/null)
fi
- Collect source texts:
# Alle source-Texte extrahieren
SOURCES=$(xmllint --xpath "//trans-unit/source/text()" "$XLIFF_PATH" 2>/dev/null)
# Als Array für Batch-Übersetzung
readarray -t SOURCE_ARRAY <<< "$SOURCES"
- Batch translation via API:
# DeepL unterstützt mehrere Texte in einem Request
# Baue JSON-Array für text Parameter
TRANSLATIONS=()
for SOURCE in "${SOURCE_ARRAY[@]}"; do
RESPONSE=$(curl -s -X POST "$DEEPL_API_URL/translate" \
-H "Authorization: DeepL-Auth-Key $DEEPL_API_KEY" \
--data-urlencode "text=$SOURCE" \
--data-urlencode "target_lang=$TARGET_LANG")
TRANSLATED=$(echo "$RESPONSE" | jq -r '.translations[0].text')
TRANSLATIONS+=("$TRANSLATED")
done
- With --dry-run: Only display:
if [ "$DRY_RUN" = "true" ]; then
echo "📋 Würde übersetzen:"
echo ""
for i in "${!SOURCE_ARRAY[@]}"; do
echo " Source: ${SOURCE_ARRAY[$i]}"
echo " Target: ${TRANSLATIONS[$i]}"
echo ""
done
echo "Gesamt: ${#SOURCE_ARRAY[@]} Einheiten"
exit 0
fi
- Update XLIFF file:
Since XML manipulation in Bash is complex, use Python if available:
import xml.etree.ElementTree as ET
tree = ET.parse(xliff_path)
root = tree.getroot()
# Namespace handling für XLIFF
ns = {'xliff': 'urn:oasis:names:tc:xliff:document:1.2'}
for i, unit in enumerate(root.findall('.//xliff:trans-unit', ns)):
target = unit.find('xliff:target', ns)
if target is None:
target = ET.SubElement(unit, 'target')
if force or not target.text:
target.text = translations[i]
tree.write(output_path, encoding='utf-8', xml_declaration=True)
Fallback for pure Bash (simpler variant with sed):
# Backup erstellen
cp "$XLIFF_PATH" "${XLIFF_PATH}.bak"
# Für jede Übersetzung
for i in "${!SOURCE_ARRAY[@]}"; do
SOURCE="${SOURCE_ARRAY[$i]}"
TARGET="${TRANSLATIONS[$i]}"
# Escape für sed
SOURCE_ESC=$(printf '%s\n' "$SOURCE" | sed 's/[[\.*^$()+?{|]/\\&/g')
TARGET_ESC=$(printf '%s\n' "$TARGET" | sed 's/[&/\]/\\&/g')
# Leeres target füllen
sed -i '' "s|<source>$SOURCE_ESC</source>\s*<target></target>|<source>$SOURCE_ESC</source><target>$TARGET_ESC</target>|g" "$XLIFF_PATH"
# Fehlendes target einfügen
sed -i '' "s|<source>$SOURCE_ESC</source>\s*</trans-unit>|<source>$SOURCE_ESC</source><target>$TARGET_ESC</target></trans-unit>|g" "$XLIFF_PATH"
done
- Output result:
echo "✅ XLIFF aktualisiert: $OUTPUT_PATH"
echo " Übersetzt: ${#TRANSLATIONS[@]} Einheiten"
Error Handling
HTTP Status Codes
| Code | Meaning | Response |
|---|---|---|
| 200 | Success | Continue |
| 400 | Bad Request | Check parameters |
| 403 | Forbidden | API key invalid |
| 404 | Not Found | Check endpoint |
| 413 | Payload too large | Text/file too large |
| 429 | Too Many Requests | Wait 1s, max 3 retries |
| 456 | Quota exceeded | Check DeepL account |
| 5xx | Server Error | Retry with backoff |
Retry logic
deepl_request() {
local max_retries=3
local retry_count=0
local wait_time=1
while [ $retry_count -lt $max_retries ]; do
RESPONSE=$(curl -s -w "\n%{http_code}" "$@")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | sed '$d')
case "$HTTP_CODE" in
200) echo "$BODY"; return 0 ;;
429|5*)
retry_count=$((retry_count + 1))
echo "⏳ Retry $retry_count/$max_retries in ${wait_time}s..." >&2
sleep $wait_time
wait_time=$((wait_time * 2))
;;
403)
echo "❌ API-Key ungültig. Bitte prüfen:" >&2
echo " export DEEPL_API_KEY='your-key'" >&2
return 1
;;
456)
echo "❌ DeepL-Quota überschritten." >&2
echo " Prüfe dein Konto: https://www.deepl.com/pro-account" >&2
return 1
;;
*)
echo "❌ API-Fehler ($HTTP_CODE): $BODY" >&2
return 1
;;
esac
done
echo "❌ Max Retries erreicht" >&2
return 1
}
Validation
Always check before API calls:
DEEPL_API_KEYset?- File exists? (for file/xliff)
- File format supported?
- Target language valid?
Valid language codes
BG, CS, DA, DE, EL, EN, EN-GB, EN-US, ES, ET, FI, FR, HU, ID, IT,
JA, KO, LT, LV, NB, NL, PL, PT, PT-BR, PT-PT, RO, RU, SK, SL, SV,
TR, UK, ZH, ZH-HANS, ZH-HANT