Version: 2.0.0 Last Updated: August 9, 2026
Complete reference for all DeepL CLI commands, options, and configuration.
Options that work with all commands:
--version, -V Show version number
--help Show help message
--quiet, -q Suppress all non-essential output (errors and results only)
--verbose, -v Show extra information (source language, timing, cache status)
--config, -c FILE Use alternate configuration file
--no-input Disable all interactive prompts (abort instead of prompting)
--timeout MS HTTP request timeout in milliseconds (default: 30000)
--max-retries N Maximum automatic retries for retryable requests (default: 3)Automatic retries apply to idempotent requests, and to rate-limited (429) responses for every method; a request that submits work is otherwise never replayed. Retries share a wall-clock budget of twice --timeout, so raising --max-retries alone does not extend how long the CLI waits on an unresponsive endpoint.
--timeout is an enforced wall-clock deadline per attempt, not merely a socket inactivity timeout: an endpoint that keeps a response body trickling is aborted at the deadline just like one that never answers at all. Responses are additionally capped at 32 MiB, raised to 128 MiB for the document download, which must accommodate a whole translated file. Exceeding either bound is a NetworkError (exit code 5); a size-cap rejection is deterministic and is never retried.
Examples:
# Show version
deepl --version
# Get help
deepl --help
deepl translate --help
# Quiet mode - suppress informational messages, keep errors and results
deepl --quiet translate "Hello" --to es
# Output: Hola (no "Translation (ES):" label)
deepl -q cache stats
# Shows cache statistics without decorative output
# Quiet mode with batch operations (no spinners or progress indicators)
deepl --quiet translate docs/ --to es --output docs-es/
# Shows final statistics only, no progress updates
# Use custom config file
deepl --config ~/.deepl-work.json translate "Hello" --to es
# Give a large document more time, and disable automatic retries
deepl --timeout 120000 --max-retries 0 translate report.pdf --to de
# Use custom config directory (via environment variable)
export DEEPL_CONFIG_DIR=/path/to/config
deepl translate "Hello" --to es
# Disable cache
deepl cache disable
deepl translate "Hello" --to es
deepl cache enableQuiet Mode Behavior:
- ✅ Always shown: Errors and their
Suggestion:remediation lines, essential output (translation results, JSON data, command output) - ❌ Suppressed: Warnings, informational messages, success confirmations, progress spinners, status updates
- 🎯 Use cases: CI/CD pipelines, scripting, parsing output, quiet automation
Non-Interactive Mode (--no-input):
# Abort instead of prompting for confirmation
deepl --no-input cache clear
# Output: Aborted.
# Combine with --yes to skip prompts and proceed
deepl --no-input cache clear --yes
# Interactive-only commands error with exit code 6
deepl --no-input init
deepl --no-input write "text" --interactive- Commands that require confirmation (e.g.,
cache clear) abort with "Aborted." instead of prompting --yestakes precedence over--no-input— the combination proceeds without prompting- Interactive-only commands (
init,write --interactive) exit with code 6 (InvalidInput) - Use cases: CI/CD pipelines, scripts, automation where a forgotten prompt would hang
Example comparison:
# Normal mode
$ deepl cache enable
✓ Cache enabled
# Quiet mode
$ deepl --quiet cache enable
(no output - command succeeded silently)
# Normal mode with errors
$ deepl translate "Hello" --to invalid
Error: Invalid target language: invalid
# Quiet mode with errors (errors always shown)
$ deepl --quiet translate "Hello" --to invalid
Error: Invalid target language: invalidCustom Configuration Files:
The --config flag allows you to use alternate configuration files for different projects, environments, or accounts:
# Use work configuration
deepl --config ~/.deepl-work.json translate "Hello" --to es
# Use project-specific configuration
deepl --config ./project/.deepl.json translate docs/ --to fr --output docs-fr/
# Use test environment configuration
deepl --config /path/to/test-config.json usageUse cases:
- Multiple API keys: Switch between free and paid accounts
- Project isolation: Different settings per project (glossaries, formality, etc.)
- Environment separation: Separate configs for dev/staging/production
- Testing: Use test configurations without affecting default settings
Precedence: --config replaces the config file only, overriding DEEPL_CONFIG_DIR for configuration. The cache location is unaffected — it still follows DEEPL_CONFIG_DIR > legacy ~/.deepl-cli/ > XDG resolution.
Command Suggestions:
Mistype a command? The CLI suggests the closest match:
$ deepl transalte "Hello" --to es
Unknown command: transalte
Did you mean: deepl translate?
Run deepl --help to see available commands.Commands are organized into six groups, matching the deepl --help output:
| Group | Commands | Description |
|---|---|---|
| Core Commands | translate, write, correct, voice |
Translation, writing enhancement, spelling and grammar correction, and speech translation |
| Resources | glossary, tm |
Manage translation glossaries and translation memory |
| Workflow | watch, sync, hooks |
File watching, project sync, and git hook automation |
| Configuration | init, auth, config, cache, style-rules |
Setup wizard, authentication, settings, caching, and style rules |
| Information | usage, languages, detect, completion |
API usage, supported languages, language detection, and shell completions |
| Administration | admin |
Organization key management and usage analytics |
Translate text, files, or directories.
deepl translate [OPTIONS] [TEXT|FILE|DIRECTORY]
deepl t [OPTIONS] [TEXT|FILE|DIRECTORY] # aliasTranslate text directly, from stdin, from files, or entire directories. Supports multiple target languages, code preservation, and context-aware translation.
Input Sources:
- Direct text argument:
deepl translate "Hello" --to es - From stdin:
echo "Hello" | deepl translate --to es - Single file:
deepl translate README.md --to es --output README.es.md - Directory:
deepl translate docs/ --to es --output docs-es/
Note: When reading from stdin or translating files, omit the text argument.
Target Language:
--to, -t LANGS- Target language(s), comma-separated (e.g.,es,es,fr,de). Required unlessdefaults.targetLangsis configured.
Source Options:
--from, -f LANG- Source language (auto-detect if omitted)--context TEXT- Additional context for better translation
Output Options:
--output, -o PATH- Output file or directory (required for file/directory translation, optional for text). A directory receives<name>.<lang>.<ext>—deepl translate README.md --to es --output docs/writesdocs/README.es.md, and a trailing slash creates the directory if it does not exist. Use-for stdout (text-based files only)--output-format FORMAT- Convert PDF to DOCX during translation. Valid choices:docx(only supported conversion)--enable-minification- Enable document minification for PPTX/DOCX files (reduces file size)--format FORMAT- Output format:text,json,table(default:text)
Translation Options:
--formality LEVEL- Formality:default,more,less,prefer_more,prefer_less,formal,informal--model-type TYPE- Model type:quality_optimized,prefer_quality_optimized,latency_optimized(no CLI default; the API server selects the model when omitted)--preserve-code- Preserve code blocks (markdown, etc.)- Known limitation: code blocks and variable placeholders (
{name},%s, etc.; always preserved) are protected internally with__CODE_n__/__VAR_n__tokens, so source text that literally contains such tokens may be rewritten during restoration.
- Known limitation: code blocks and variable placeholders (
--preserve-formatting- Preserve line breaks and whitespace formatting--split-sentences LEVEL- Sentence splitting:on(default),off,nonewlines--tag-handling MODE- XML tag handling:xml,html--outline-detection BOOL- Control automatic XML structure detection:true(default),false(requires--tag-handling xml)--splitting-tags TAGS- Comma-separated XML tags that split sentences (requires--tag-handling xml)--non-splitting-tags TAGS- Comma-separated XML tags that should not be used to split sentences (requires--tag-handling xml)--ignore-tags TAGS- Comma-separated XML tags with content to ignore (requires--tag-handling xml)--tag-handling-version VERSION- Tag handling version:v1,v2. v2 improves XML/HTML structure handling (requires--tag-handling). Defaults tov2, sent explicitly on every--tag-handlingrequest rather than left to the API's own default, which is documented as moving from v1 to v2 at some point — pinning keeps output from shifting on DeepL's timetable. Pass--tag-handling-version v1for the older behaviour, which DeepL documents as heading for deprecation--glossary NAME-OR-ID- Use glossary by name or ID for consistent terminology. Repeatable, up to 5 per request; entries are merged, so terms unique to each glossary all apply. When several glossaries define the same source term, which mapping wins is the API's choice and does not follow flag order, so position is not a way to override a term. Passing a 6th exits 6 (ValidationError). A source language is required, because the API rejects a glossary without one: supply--from, or setdefaults.sourceLangand it is used automatically. With neither, the command exits 6 before any request.--translation-memory NAME-OR-UUID- Use translation memory by name or UUID (forcesquality_optimizedmodel). Requires--frombecause TMs are pinned to a specific source→target language pair. Invalid use exits 6 (ValidationError); unresolvable/misconfigured TM exits 7 (ConfigError).--tm-threshold N- Minimum match score 0–100 (default 75, requires--translation-memory). Invalid use exits 6 (ValidationError); unresolvable/misconfigured TM exits 7 (ConfigError).--custom-instruction INSTRUCTION- Custom instruction for translation (repeatable, max 10, max 300 chars each). Forcesquality_optimizedmodel. Cannot be used withlatency_optimized.--style-id UUID- Style rule ID for translation (Pro API only). Forcesquality_optimizedmodel. Cannot be used withlatency_optimized. Usedeepl style-rules listto see available IDs.--no-cache- Bypass cache for this translation (useful for testing/forcing fresh translation)--dry-run- Show what would be translated without performing the operation
API Options:
--api-url URL- Custom API endpoint URL (for testing or private instances). Any host other thanapi.deepl.comandapi-free.deepl.comprints a warning to stderr naming the origin the key is about to be sent to — see "Non-standard endpoint notice" below--show-billed-characters- Request and display actual billed character count for cost transparency
Non-standard endpoint notice. The API key is attached to every request, so whatever host the CLI is pointed at receives it along with the text being translated. Whenever the resolved endpoint is not one of the two standard DeepL hosts, the CLI prints an unconditional warning to stderr naming the origin (scheme, host, port — never a path or query, which the redirect may control) and stating where the redirect came from: set by --api-url, or set by api.baseUrl in <config file> with the actual file path, since several config locations are searched and -c can override them. Regional endpoints such as api-jp.deepl.com are included, as is loopback — a co-tenant process listening on 127.0.0.1 receives the key just as a remote host does, the same reasoning the TMS destination-trust gate applies. The notice is not gated behind --verbose, because it is the user's only signal that a substituted config file has redirected their key; it is suppressed by --quiet like every other warning, and it goes to stderr so deepl translate ... > out.txt still captures only translation output. It is printed once per run, not once per API client. Under --verbose, each request line also names the resolved origin ([verbose] HTTP POST https://api.deepl.com/v2/translate completed in 12ms (status 200)).
Batch Options (for directories):
--no-recursive- Do not recurse into subdirectories (recursive is the default)--pattern GLOB- File pattern (e.g.,*.md,**/*.txt)--concurrency N- Number of parallel translations (default: 5)
Basic text translation:
# Single language
deepl translate "Hello, world!" --to es
# Multiple languages
deepl translate "Hello, world!" --to es,fr,de
# With source language
deepl translate "Bonjour" --from fr --to enFrom stdin:
# Pipe text
echo "Hello" | deepl translate --to es
# From file via stdin
cat README.md | deepl translate --to frFile translation:
# Single file
deepl translate README.md --to es --output README.es.md
# Multiple languages
deepl translate README.md --to es,fr,de --output translations/
# With code preservation
deepl translate tutorial.md --to es --output tutorial.es.md --preserve-codeOutput to stdout:
# Pipe translated file content to stdout
deepl translate README.md --to es --output -
# Combine with shell tools
deepl translate README.md --to es --output - | wc -l
# Redirect to a file
deepl translate README.md --to es --output - > translated.mdNote: --output - only works with text-based files (.txt, .md, .html, .srt, .xlf). Binary documents (PDF, DOCX, etc.), structured files (JSON, YAML), and multi-target translations do not support stdout output.
Smart caching for text-based files:
Small text-based files are automatically routed to the cached text API for faster, more efficient translations:
# Text files under 100 KiB are automatically cached
deepl translate README.md --to es --output README.es.md
# First translation: Makes API call
# Subsequent identical translations: Instant (from cache)
# HTML files also benefit from caching
deepl translate index.html --to fr --output index.fr.html
# Subtitle files
deepl translate subtitles.srt --to ja --output subtitles.ja.srt
# XLIFF localization files
deepl translate strings.xlf --to de --output strings.de.xlfStructured file formats (i18n):
The following structured formats are parsed to extract only string values, translated via the batch text API, then reassembled preserving keys, nesting, non-string values, indentation, and YAML comments:
.json- JSON files (i18n locale files, config files).yaml,.yml- YAML files (Rails i18n, config files)
An empty string value is copied to the output unchanged and is never sent to the API, so a placeholder key you have not written copy for yet costs nothing and stays in the file. Requests are capped at 50 strings each; if one of them fails, no output file is written and the command exits with that failure's own code (for example 3 for a rate limit), because a partly translated file is indistinguishable from a complete one.
Size ceiling: 10 MiB. A structured file above that is refused with a ValidationError (exit 6) naming its size, before it is read, and a directory run fails just that file. The limit is lower than the 30 MB document ceiling because these two routes cost very different amounts of memory: a document is streamed to the API once, while a structured file is parsed into an object graph — roughly 7-13x its size resident — and the multi-target path (--to de,fr,es) builds a fresh copy per language, up to 5 at a time. 10 MiB matches the ceiling sync.limits.max_file_bytes can never be configured above, so nothing deepl sync accepts is refused here. For anything larger, split the file or use deepl sync, which walks a locale directory file by file and translates only the keys that changed.
# Translate JSON locale file
deepl translate en.json --to es --output es.json
# Translate YAML locale file (comments preserved)
deepl translate en.yaml --to de --output de.yamlCached text-based formats:
The following formats use the cached text API when files are under 100 KiB:
.txt- Plain text files.md- Markdown files.html,.htm- HTML files.srt- Subtitle files.xlf,.xliff- XLIFF localization files
Large file automatic fallback:
When text-based files exceed 100 KiB, they automatically fall back to the document API:
# Large text file (>100 KiB) - uses document API
deepl translate large-document.txt --to es --output large-document.es.txt
# ⚠ File exceeds 100 KiB limit for cached translation (150.5 KiB), using document API instead
# Translated large-document.txt -> large-document.es.txtBenefits of smart caching:
- Performance: Only small text files (<100 KiB) benefit from instant cached translations
- Efficiency: Reduces API calls and character usage for small text files
- Cost savings: Only small text files avoid repeated API quota consumption
- Automatic: No configuration needed - works out of the box
- Transparent: Warning shown when falling back to document API
Important: Large text files (≥100 KiB) and all binary documents use the document API, which is NOT cached. Repeated translations of large files always make fresh API calls.
Document translation:
# Translate PDF document
deepl translate document.pdf --to es --output document.es.pdf
# Translate PowerPoint with formality
deepl translate presentation.pptx --to de --formality more --output presentation.de.pptx
# Translate Excel spreadsheet
deepl translate report.xlsx --to fr --output report.fr.xlsx
# Translate HTML file
deepl translate website.html --to ja --output website.ja.html
# Convert format during translation (PDF to DOCX - only supported conversion)
deepl translate document.pdf --to es --output document.es.docx --output-format docx
# Enable document minification for smaller file size (PPTX/DOCX only)
deepl translate presentation.pptx --to de --output presentation.de.pptx --enable-minification
deepl translate report.docx --to fr --output report.fr.docx --enable-minification
# Apply a glossary (--from is required for document glossaries)
deepl translate report.docx --from en --to de --output report.de.docx --glossary tech-terms
# Repeat --glossary for up to 5; entries merge, and a term defined in
# several is resolved by the API, not by flag order
deepl translate report.docx --from en --to de --output report.de.docx \
--glossary base-terms --glossary project-overridesSupported Document Formats:
.pdf- PDF documents (up to 30MB) - Document API only.docx,.doc- Microsoft Word - Document API only.pptx- Microsoft PowerPoint - Document API only.xlsx- Microsoft Excel - Document API only.jpg,.jpeg- JPEG images - Document API only.png- PNG images - Document API only.html,.htm- HTML files - Smart routing (cached text API <100 KiB, document API ≥100 KiB).txt- Plain text files (up to 30MB) - Smart routing (cached text API <100 KiB, document API ≥100 KiB).srt- Subtitle files - Smart routing (cached text API <100 KiB, document API ≥100 KiB).xlf,.xliff- XLIFF localization files - Smart routing (cached text API <100 KiB, document API ≥100 KiB).md- Markdown files - Cached text API (all sizes).json- JSON files - Structured file API (string extraction + batch translation).yaml,.yml- YAML files - Structured file API (string extraction + batch translation, comments preserved)
Document Translation Notes:
- Structured files:
.json,.yaml,.ymlare parsed to extract string values, translated via batch text API, and reassembled preserving structure - Smart routing: Text-based files (
.txt,.md,.html,.srt,.xlf,.xliff) under 100 KiB automatically use the cached text API for better performance - Binary formats (PDF, DOCX, PPTX, XLSX) and image formats (JPEG, PNG) always use the document API regardless of size
- Documents are translated on DeepL servers using async processing
- Progress updates show status (queued → translating → done)
- Billed characters are displayed after completion
- Formatting, structure, and layout are automatically preserved
- Large documents may take several seconds to translate
- Maximum file sizes: 30MB (document API, all formats), 100 KiB (cached text API)
- Document minification (
--enable-minification): Reduces file size for PPTX and DOCX files only. Useful for large presentations and documents. - Glossaries:
--glossaryapplies to documents and is repeatable up to 5, resolved exactly as for text translation — entries are merged, and when several glossaries define the same source term which mapping wins is the API's choice and does not follow flag order. A source language is required — the API rejects a document glossary without one ("source_lang has to be specified in order to use a glossary") — so pass--from, or setdefaults.sourceLangand it is used automatically. Glossary matching is context-dependent exactly as it is for text: a term may be applied in one sentence and left alone in another, and a bare newline-separated word list often gets few terms applied.--translation-memoryremains unsupported for documents.
Directory translation:
# Translate all supported files
deepl translate docs/ --to es --output docs-es/
# With glob pattern
deepl translate docs/ --to es --output docs-es/ --pattern "*.md"
# Recursive (default) with custom concurrency
deepl translate src/ --to es,fr --output translations/ --concurrency 10Context-aware translation:
# Add context for better disambiguation
deepl translate "Bank" --to es --context "Financial institution"
# → "Banco" (not "Orilla" for riverbank)
deepl translate app.json --to es --context "E-commerce checkout flow"Formality levels:
# Formal
deepl translate "How are you?" --to de --formality more
# → "Wie geht es Ihnen?" (formal)
# Informal
deepl translate "How are you?" --to de --formality less
# → "Wie geht es dir?" (informal)Sentence splitting:
# Default behavior (sentences split on punctuation and newlines)
deepl translate "Hello. How are you?" --to es
# → "Hola. ¿Cómo estás?"
# Disable sentence splitting (treat as one unit)
deepl translate "Hello. How are you?" --to es --split-sentences off
# → May produce different translation
# Split only on punctuation, not newlines
deepl translate "Line 1\nLine 2" --to es --split-sentences nonewlines
# → Preserves line breaks while splitting sentencesTag handling (XML/HTML):
# Basic XML tag preservation
deepl translate "<p>Hello world</p>" --to es --tag-handling xml
# → "<p>Hola mundo</p>"
# Translate HTML content
deepl translate "<div><span>Welcome</span></div>" --to de --tag-handling html
# → "<div><span>Willkommen</span></div>"
# Useful for localizing markup files
deepl translate content.html --to fr --tag-handling html --output content.fr.html
# Advanced XML tag handling: Disable automatic structure detection
deepl translate "<doc><p>Text</p></doc>" --to es --tag-handling xml --outline-detection false
# Forces manual tag handling instead of automatic detection
# Specify tags that split sentences (useful for custom XML formats)
deepl translate "<article><br/>Content<hr/>More</article>" --to es --tag-handling xml --splitting-tags "br,hr"
# Treats <br/> and <hr/> as sentence boundaries
# Specify tags for non-translatable content (like code blocks)
deepl translate "<doc><code>let x = 1;</code><p>Text</p></doc>" --to es --tag-handling xml --non-splitting-tags "code,pre"
# Content in <code> and <pre> tags won't be split into sentences
# Ignore specific tags and their content (e.g., scripts, styles)
deepl translate file.html --to es --tag-handling xml --ignore-tags "script,style,noscript" --output file.es.html
# Content in <script>, <style>, and <noscript> tags is not translated
# Combine multiple XML tag handling options
deepl translate complex.xml --to de --tag-handling xml \
--outline-detection false \
--splitting-tags "br,hr,div" \
--non-splitting-tags "code,pre,kbd" \
--ignore-tags "script,style" \
--output complex.de.xml
# Fine-tuned control for complex XML/HTML documentsGlossary usage:
# Use glossary for consistent terminology
deepl translate "API documentation" --from en --to es --glossary tech-terms
# Use glossary by ID
deepl translate README.md --from en --to fr --glossary abc-123-def-456 --output README.fr.mdMultiple glossaries on one request:
Repeat --glossary to apply up to 5 glossaries to a single request. Their entries are merged, so terms unique to each glossary all apply. When more than one glossary defines the same source term, which mapping wins is the API's choice and does not follow flag order — position is not a way to override a term, so avoid relying on one glossary to shadow another. The order is still sent as given and never sorted, because it is part of the cache key: reordering the flags is a different request with its own cache entry. Names and UUIDs can be mixed; each value is resolved independently. A 6th --glossary exits 6 (ValidationError). A name that cannot be resolved — unknown, ambiguous, or covering a different language pair than the one requested — exits 7 (ConfigError) without sending a translation request.
# Shared base terminology combined with project-specific terms; terms unique
# to each apply, and a term defined in both is resolved by the API
deepl translate "Hello world" --from en --to de --glossary base-terms --glossary project-overrides
# Names and UUIDs can be mixed
deepl translate README.md --from en --to fr --output README.fr.md \
--glossary abc-123-def-456 --glossary house-styleA single --glossary is still sent as the API's glossary_id, so existing commands and their cached results are unaffected.
Translation memory usage:
Translation memories (TMs) are pinned to a source→target language pair, so --from is required. Passing --translation-memory forces quality_optimized model type; combining it with --model-type latency_optimized (or prefer_quality_optimized) exits 6 (ValidationError). TM files are authored and uploaded via the DeepL web UI; this CLI resolves the name-or-UUID against GET /v3/translation_memories and caches the resolution per run.
# Use translation memory by name (requires --from for pair resolution)
deepl translate "Welcome to our product." --from en --to de --translation-memory my-tm
# Use translation memory by UUID with a custom threshold
deepl translate "Welcome to our product." --from en --to de \
--translation-memory 3f2504e0-4f89-41d3-9a0c-0305e82c3301 --tm-threshold 80
# Combine glossary and translation memory on a single call
deepl translate "Welcome to our product." --from en --to de \
--glossary tech-terms --translation-memory my-tm --tm-threshold 85Multi-target file translation with glossary / TM:
Both --glossary and --translation-memory apply to multi-target file translation (e.g. --to en,fr,es) and in that mode --from is required. Resolving either by name checks that the resource covers every requested language pair before any translation request goes out, and exits 7 (ConfigError) naming what it does cover if not:
deepl translate "hello" --from en --to de --glossary "EN-ES Test Glossary"
# Error: Glossary "EN-ES Test Glossary" does not support the requested language pair
# Suggestion: Glossary covers en→es; requested en→de.A multilingual glossary satisfies this when it holds a dictionary for each requested pair; matching is per dictionary, so a glossary holding en→es and de→fr does not count as covering en→fr. Each translation memory in DeepL is scoped to one source→target pair, so a TM name with differing multi-targets cannot satisfy it at all — pass the TM UUID for multi-target TM use.
Passing a UUID skips the check and lets the API decide, which is the escape hatch if the check is ever wrong: the API answers with No dictionary found for language pair EN-DE in glossary <id>.
# Glossary across multiple targets (name resolution works for all targets)
deepl translate README.md --from en --to fr,es,it --glossary tech-terms --output ./out
# Translation memory across multiple targets: pass a UUID to avoid the
# single-pair name-resolution constraint
deepl translate README.md --from en --to fr,es,it --output ./out \
--translation-memory 3f2504e0-4f89-41d3-9a0c-0305e82c3301Cache control:
# Bypass cache for fresh translation
deepl translate "Hello" --to es --no-cache
# Useful for testing or when you need the latest translation
deepl translate document.md --to es --output document.es.md --no-cacheCost transparency:
# Show actual billed character count (Pro API only)
deepl translate "Hello, world!" --to es --show-billed-characters
# Hola, mundo!
#
# Billed characters: 13
# Use with multiple languages
deepl translate "Hello" --to es,fr,de --show-billed-characters
# [es] Hola
# [fr] Bonjour
# [de] Hallo
#
# Billed characters: 15
# Useful for budget tracking and cost analysis
deepl translate document.md --to es --output document.es.md --show-billed-charactersNote: The --show-billed-characters feature is only available with Pro API accounts. Free API accounts will display "N/A" for character counts.
JSON output:
# Get machine-readable JSON output
deepl translate "Hello" --to es --format json
# {"text":"Hola","detectedSourceLang":"en","targetLang":"es","cached":false}
# JSON output may include modelTypeUsed when the API reports which model was used
deepl translate "Hello" --to es --format json --no-cache
# {"text":"Hola","detectedSourceLang":"en","targetLang":"es","modelTypeUsed":"quality_optimized"}
# Useful for scripting and automation
deepl translate "Test" --to es,fr,de --format jsonTable output:
# Display translations in structured table format (multiple languages)
deepl translate "Hello, world!" --to es,fr,de --format table
# ┌──────────┬──────────────────────────────────────────────────────────────────────┐
# │ Language │ Translation │
# ├──────────┼──────────────────────────────────────────────────────────────────────┤
# │ es │ ¡Hola mundo! │
# │ fr │ Bonjour le monde! │
# │ de │ Hallo Welt! │
# └──────────┴──────────────────────────────────────────────────────────────────────┘
# Add --show-billed-characters to display the Characters column
deepl translate "Cost tracking" --to es,fr,de --format table --show-billed-characters --no-cache
# ┌──────────┬────────────────────────────────────────────────────────────────┬────────────┐
# │ Language │ Translation │ Characters │
# ├──────────┼────────────────────────────────────────────────────────────────┼────────────┤
# │ es │ Seguimiento de costes │ 16 │
# │ fr │ Suivi des coûts │ 16 │
# │ de │ Kostenverfolgung │ 16 │
# └──────────┴────────────────────────────────────────────────────────────────┴────────────┘
# Long translations automatically wrap in the Translation column
deepl translate "This is a very long sentence that demonstrates word wrapping." --to es,fr --format table
# Wider Translation column (70 chars) when Characters column is not shown
# Useful for:
# - Comparing translations side-by-side across multiple languages
# - Monitoring billed characters per translation for cost transparency (with --show-billed-characters)
# - Human-readable output for reports and documentation
# - Quality assurance - spot-checking consistency across languagesNotes:
- Table format is only available when translating to multiple target languages. For single language translations, use default plain text or JSON format.
- The Characters column is only shown when using
--show-billed-charactersflag. - Without
--show-billed-characters, the Translation column is wider (70 characters vs 60) for better readability. - Model type, when the API reports it, is carried in JSON output as
modelTypeUsed. Billed characters are not: a single-target--format jsonrun emits{text, targetLang, detectedSourceLang?, modelTypeUsed?, cached?}and no character count, whatever--show-billed-charactersis set to. Multi-target JSON (--to es,fr,de --format json) does carrybilledCharactersper translation. In plain text output both are appended below the translated text.
Improve text with DeepL Write API (grammar, style, tone enhancement).
deepl write [OPTIONS] TEXT
deepl w [OPTIONS] TEXT # aliasEnhance text quality with AI-powered grammar checking, style improvement, and tone adjustment. Supports 14 target languages.
File Detection: The command automatically detects if the text argument is a file path. If a file exists at that path, it operates on the file; otherwise, it treats the argument as text to improve.
Language:
--lang, -l LANG- Target language:de,en,en-gb,en-us,es,fr,it,ja,ko,pt,pt-br,pt-pt,zh,zh-hans. Optional — omit to auto-detect the language and rephrase in the original language.--to LANG- Long-only alias of--lang. Accepts the same language values. Provided for muscle-memory consistency withdeepl translate --to; the short form-tis intentionally not bound here (it would collide withdeepl translate -t, --to). Specifying both--toand--langwith different values exits with aValidationError.
Style Options (mutually exclusive with tone):
--style STYLE- Writing style:default- No style modification (API default)simple- Simpler, more accessible languagebusiness- Professional business languageacademic- Formal academic languagecasual- Conversational, informal languageprefer_simple,prefer_business, etc. - Soft preferences
Tone Options (mutually exclusive with style):
--tone TONE- Tone:default- No tone modification (API default)enthusiastic- More enthusiastic and positivefriendly- Warmer, more approachableconfident- More assertive and certaindiplomatic- More careful and tactfulprefer_enthusiastic,prefer_friendly, etc. - Soft preferences
Supported target-language / style-and-tone combinations:
| Target language | --style |
--tone |
|---|---|---|
en, en-gb, en-us, de |
✓ | ✓ |
es, fr, it, pt, pt-br, pt-pt |
✓ | ✓ |
ja, ko, zh, zh-hans |
— | — |
When --style or --tone is set for a target language that does not support it, the server returns a 4xx; the CLI converts that response into a ValidationError (exit code 6) that names the unsupported combination and points back to this table.
This table is maintained by hand and reflects what the API accepts, which is not always what its metadata reports: GET /v3/languages?resource=write omits writing_style for en, yet --style with --lang en succeeds and returns what --lang en-us returns. Do not narrow this table to the metadata without re-checking behaviour.
Where the target-language list comes from:
The 14 languages are generated from GET /v3/languages?resource=write into src/data/language-entries.ts by npm run generate:languages, alongside the translation list, and npm run check:languages reports drift in either. The WriteLanguage type is derived from that same list, so a language added upstream widens it on regenerate rather than needing a second hand edit.
write and correct check --lang against this list locally and name every valid option, because the set is small enough to enumerate in an error. The list is a snapshot, though, so a code that is shaped like a language tag but absent from it is sent to the API with the list as a warning rather than rejected — otherwise a language DeepL adds is unreachable until the snapshot is regenerated. Input that is not shaped like a language tag still exits 6 locally.
Output Options:
--alternatives, -a- Show all improvement alternatives. With--format json, emits them as an array--interactive, -i- Interactive mode: choose from multiple alternatives--diff, -d- Show diff between original and improved text. With--format json, emits the diff payload with an uncoloured patch--check- Check if text needs improvement without modifying (exits with 0 if no changes, 8 if improvements suggested). With--format json, emits the check result payload on stdout--fix- Auto-fix files in place--output, -o FILE- Write output to file--in-place- Edit file in place--backup, -b- Create backup before fixing (use with--fix)--format FORMAT- Output format:text,json(default:text)
Advanced:
--no-cache- Bypass cache for this request (always call API)
de- Germanen- English (generic, defaults to American English)en-gb- British Englishen-us- American Englishes- Spanishfr- Frenchit- Italianja- Japaneseko- Koreanpt- Portuguese (generic, defaults to Brazilian Portuguese)pt-br- Brazilian Portuguesept-pt- European Portuguesezh- Chinese (generic, defaults to Simplified Chinese)zh-hans- Simplified Chinese
Basic improvement (auto-detect language):
deepl write "Me and him went to store."
# → "He and I went to the store."With explicit language:
deepl write "Me and him went to store." --lang en-us
# → "He and I went to the store."With writing style:
# Business style
deepl write "We want to tell you about our product." --lang en-us --style business
# → "We are pleased to inform you about our product."
# Casual style
deepl write "The analysis demonstrates significant findings." --lang en-us --style casual
# → "The analysis shows some pretty big findings."With tone:
# Confident tone
deepl write "I think this might work." --lang en-us --tone confident
# → "This will work."
# Diplomatic tone
deepl write "Your approach is wrong." --lang en-us --tone diplomatic
# → "Perhaps we could consider an alternative approach."Show alternatives:
deepl write "This is good." --lang en-us --alternativesFile operations:
# Improve file and save to new location
deepl write document.txt --lang en-us --output improved.txt
# Edit file in place
deepl write document.txt --lang en-us --in-place
# Auto-fix with backup
deepl write document.txt --lang en-us --fix --backupInteractive mode:
# Choose from multiple alternatives interactively
deepl write "Text to improve." --lang en-us --interactiveCheck mode:
# Check if file needs improvement (exit code 8 if changes needed)
deepl write document.md --lang en-us --check--check reports a result rather than an error, so --format json gives it a
success shape rather than the error envelope:
deepl write document.md --check --format json
# {"ok":true,"mode":"write","needsChanges":true,"changes":3,"file":"/abs/path/document.md"}
deepl write "This is fine." --check --format json
# {"ok":true,"mode":"write","needsChanges":false,"changes":0}| Field | Meaning |
|---|---|
ok |
Always true — the check ran. A failure emits the error envelope. |
mode |
write or correct, so one parser serves both commands. |
needsChanges |
The verdict, repeated by the exit code (0 clean, 8 needs changes). |
changes |
Number of word-level changes the API would make. |
file |
Absolute path of the checked file; absent when the input was text. |
The payload goes to stdout and replaces the human report — the File: … line
and the ⚠ Text needs improvement / ✓ Text looks good verdict stay on stderr in
text mode only. The exit code is unchanged either way, so a CI job may branch on
the code, the payload, or both.
Diff view:
# Show differences between original and improved
deepl write file.txt --lang en-us --diffJSON output:
# Get machine-readable JSON output
deepl write "This are good." --lang en-us --format json
# {"original":"This are good.","improved":"This is good.","changes":1,"language":"en-us"}Each output mode has its own JSON shape, because each answers a different
question. --check and the two below carry ok: true, marking them as results
rather than the ok: false error envelope; the plain improvement payload above
predates the envelope and keeps its shape, so it has no ok field.
# --diff: the same three things the text report shows, with an uncoloured patch
deepl write "This are good." --diff --format json
# {"ok":true,"original":"This are good.","improved":"This is good.",
# "diff":"Index: text\n===...\n-This are good.\n+This is good.\n"}
# --alternatives: every improvement the API offered, as an array
deepl write "This are good." --alternatives --format json
# {"ok":true,"original":"This are good.","alternatives":["This is good.","These are good."]}--diff's diff field is the unified patch with no colour escapes, whatever the
terminal — the text report colours the same patch for a human, and that colouring
never reaches the payload. --diff ignores --output/--in-place, so its payload
only ever goes to stdout.
--output <file> and --in-place receive whatever the improvement renders to, so
under --format json they write the payload rather than the improved text — that
is true of the plain payload and of --alternatives alike. To put improved text
in a file, leave --format at its default.
Bypass cache:
# Force a fresh API call, skipping cached results
deepl write "Improve this text." --lang en-us --no-cacheCorrect spelling and grammar with the DeepL Write API, without rewording.
deepl correct [OPTIONS] TEXT
deepl c [OPTIONS] TEXT # aliasFixes spelling and grammar only, avoiding the broader rewording that deepl write performs. Uses the Write API's /v2/write/correct endpoint. Supports the same 14 target languages as write.
File Detection: The command automatically detects if the text argument is a file path. If a file exists at that path, it operates on the file; otherwise, it treats the argument as text to correct.
correct accepts the same options as write except --style and --tone (the correct endpoint does not restyle text):
Language:
--lang, -l LANG- Target language:de,en,en-gb,en-us,es,fr,it,ja,ko,pt,pt-br,pt-pt,zh,zh-hans. Optional — omit to auto-detect the language and correct in the original language.--to LANG- Long-only alias of--lang, as onwrite.
Output Modes:
--alternatives, -a- Show all alternative corrections. With--format json, emits them as an array--output, -o FILE- Write corrected text to file--in-place- Edit file in place (use with file input)--interactive, -i- Review the correction before accepting--diff, -d- Show diff between original and corrected text. With--format json, emits the diff payload with an uncoloured patch
Fix Operations:
--check- Check if text needs correction (exit 0 if clean, exit 8 if corrections needed). With--format json, emits the check result payload on stdout--fix- Automatically fix file in place--backup, -b- Create backup file before fixing (use with--fix)
Advanced:
--no-cache- Bypass cache for this request--format FORMAT- Output format:text,json(default:text)
Basic correction:
deepl correct "This is an test."
# This is a test.
# Using the alias
deepl c "Their going too the store."Grammar gate in CI (exit code 8 if corrections needed):
deepl correct README.md --checkWith --format json the check emits the same result payload write --check
does, with mode set to correct (the field table is under write's Check
mode above):
deepl correct README.md --check --format json
# {"ok":true,"mode":"correct","needsChanges":true,"changes":2,"file":"/abs/path/README.md"}Fix a file in place with a backup:
deepl correct essay.md --fix --backupDiff view:
deepl correct document.txt --diffPipe via stdin:
cat notes.txt | deepl correctJSON output:
deepl correct "Their going too the store." --format json
# {"original":"Their going too the store.","improved":"They're going to the store.","changes":1,"language":"auto-detected"}--check, --diff and --alternatives each carry their own payload, identical to
write's (see JSON output under write above); only --check's differs, in
that its mode reads correct.
Translate audio using the DeepL Voice API with real-time WebSocket streaming.
deepl voice [options] <file>
| Argument | Description |
|---|---|
file |
Audio file to translate. Use - for stdin. |
| Option | Short | Description | Default |
|---|---|---|---|
--to <languages> |
-t |
Target language(s), comma-separated, max 5 (required) | - |
--from <language> |
-f |
Source language (auto-detect if not specified) | auto |
--formality <level> |
Formality level: default, formal, more, informal, less, prefer_more, prefer_less |
default |
|
--glossary <name-or-id> |
Use glossary by name or ID (single glossary; not repeatable) | - | |
--content-type <type> |
Audio content type (auto-detected from file extension) | auto | |
--chunk-size <bytes> |
Audio chunk size in bytes | 6400 |
|
--chunk-interval <ms> |
Interval between audio chunks in milliseconds | 200 |
|
--no-stream |
Disable live streaming output, collect and print at end | - | |
--no-reconnect |
Disable automatic reconnection on WebSocket drop | - | |
--max-reconnect-attempts <n> |
Maximum reconnect attempts on WebSocket drop | 3 |
|
--source-language-mode <mode> |
Source language detection mode: auto, fixed |
- | |
--format <format> |
Output format: text, json |
text |
Note: All formality values (
default,formal,informal,more,less,prefer_more,prefer_less) are accepted. The voice API natively usesformal/informal(in addition tomore/less), while the translate API usesprefer_more/prefer_less.
Note: If the server ends the stream after transcribing the audio but without sending a translation for one of
--to's languages, the command fails with exit code 9 and names the languages, rather than printing an empty translation line and exiting 0. Audio containing no speech transcribes to nothing and is translated to nothing, which is not an error and still exits 0.
Note: the Voice API supports a smaller language set than
translate, and the CLI checks--to/--fromagainst it locally — an unlisted code exits 6 before any request, naming every valid option.Targets (39):
ar,bg,cs,da,de,el,en,en-gb,en-us,es,et,fi,fr,he,hu,id,it,ja,ko,lt,lv,nb,nl,pl,pt,pt-br,pt-pt,ro,ru,sk,sl,sv,th,tr,uk,vi,zh,zh-hans,zh-hantSources (30):
ar,bg,cs,da,de,el,en,es,et,fi,fr,hu,id,it,ja,ko,lt,lv,nb,nl,pl,pt,ro,ru,sk,sl,sv,tr,uk,zhCodes are matched case-insensitively, so the lowercase spelling
deepl languagesprints is accepted; the CLI canonicalizes to the casing the Voice API expects before sending.
| Extension | Content Type |
|---|---|
.ogg, .opus |
audio/opus;container=ogg |
.webm |
audio/opus;container=webm |
.mka |
audio/opus;container=matroska |
.flac |
audio/flac |
.mp3 |
audio/mpeg |
.pcm, .raw |
audio/pcm;encoding=s16le;rate=16000 |
# Basic audio translation
deepl voice recording.ogg --to de
# Multiple target languages
deepl voice meeting.mp3 --to de,fr,es
# With source language and formality
deepl voice audio.flac --to ja --from en --formality more
# Pipe from ffmpeg
ffmpeg -i video.mp4 -f ogg - | deepl voice - --to es --content-type 'audio/opus;container=ogg'
# Pipe raw PCM from stdin
cat audio.pcm | deepl voice - --to es --content-type 'audio/pcm;encoding=s16le;rate=16000'
# JSON output for scripting
deepl voice speech.ogg --to de --format json | jq .targets[0].text
# Disable live streaming
deepl voice speech.ogg --to de --no-stream{
"sessionId": "session-abc123",
"source": {
"lang": "en",
"text": "Hello world",
"segments": [{ "text": "Hello world", "startTime": 0, "endTime": 1.5 }]
},
"targets": [
{
"lang": "de",
"text": "Hallo Welt",
"segments": [{ "text": "Hallo Welt", "startTime": 0, "endTime": 1.5 }]
}
]
}- The Voice API requires a DeepL Pro or Enterprise plan.
- Maximum 5 target languages per session.
- Maximum audio chunk size: 100KB, recommended pacing: 200ms between chunks.
- Sessions have a 30-second inactivity timeout and 1-hour maximum duration.
- The Voice API uses the same endpoint resolution as other commands:
:fxkeys useapi-free.deepl.com, others useapi.deepl.com, and custom regional URLs are always honored.
Watch files or directories for changes and auto-translate.
deepl watch [OPTIONS] PATHMonitor files or directories for changes and automatically translate them. Supports debouncing, glob patterns, and multiple target languages.
Behavior:
- Runs continuously until interrupted (Ctrl+C)
- Shows translation statistics on exit, and exits 12 rather than 0 when the session recorded any failed translation or failed auto-commit
- Detects file changes using filesystem watch
- Debounces rapid changes to avoid duplicate translations
- Translates one version of a file at a time. An edit arriving while a file's translation is still running queues exactly one re-translation, which starts after the running one has written. Two translations of the same file never overlap, so a slower translation of older content cannot overwrite a newer one, and an edit storm costs two translations rather than one per event
- Skips files inside the output directory whose name carries a target-language segment (
doc.es.mdwith--to es), so the CLI's own output does not re-trigger the watcher. The check is limited to the output directory, so a source file named that way —pricing.es.mdtranslated toes— is translated normally as long as it lives outside it. When such a file is skipped and the CLI did not write it, the reason is printed once per file - Mirrors each source file's directory under the output directory, relative to the watched path: watching
docs/writesdocs/a/index.mdto<output>/a/index.es.mdanddocs/b/index.mdto<output>/b/index.es.md. This is the same layoutdeepl translate <dir> --output <dir>produces, and it is what keeps two same-named files in different directories from writing one output path. A file at the top of the watched directory, and a watched path that is a single file, write straight into the output directory as before
Watch Options:
--to, -t LANGS- Target language(s), comma-separated (uses configureddefaults.targetLangsif omitted)--output, -o DIR- Output directory (default:<path>/translationsfor directories, same dir for files)--pattern GLOB- File pattern filter (e.g.,*.md,**/*.json)--debounce MS- Debounce delay in milliseconds. The flag wins, then the configuredwatch.debounceMs, then the default of 500--concurrency NUM- Maximum parallel translations across different files (default: 5). A single file is always translated one version at a time, whatever this is set to
Translation Options:
--from, -f LANG- Source language (auto-detect if omitted)--formality LEVEL- Formality level:default,more,less,prefer_more,prefer_less,formal,informal--preserve-code- Preserve code blocks--preserve-formatting- Preserve line breaks and whitespace formatting--glossary NAME-OR-ID- Use glossary by name or ID for consistent terminology. A source language is required, because the API refuses a glossary without one ("Use of a glossary requires the source_lang parameter to be specified"): pass--from, or setdefaults.sourceLangand it is used automatically. With neither, the command exits 6 before the watcher starts rather than failing on every file change. Unliketranslate,watchtakes a single glossary.
Git Integration:
--auto-commit- Auto-commit translations to git after each change. One commit per translated source file, queued one at a time: git holds.git/index.lockfor the duration of anaddor acommit, so parallel translations cannot commit in parallel. A failed commit is reported and counted, and the session exits 12 rather than 0 (see below). The commit goes to the repository holding the output directory, whatever directory the CLI was started in — git can only commit files inside its own working tree, so that is the only repository the translations can reach. When the output directory is in no repository at all, the command exits 6 before the watcher starts rather than warning once per translated file--git-staged- Only watch git-staged files (snapshot taken once at startup). The index read is the one belonging to the watched path's repository, so the CLI need not be started from its root--dry-run- Show what would be watched without starting the watcher
Watch single file:
# Basic watching
deepl watch README.md --to es
# With custom output
deepl watch README.md --to es,fr --output translations/
# With options
deepl watch tutorial.md --to es --preserve-code --formality moreWatch directory:
# Watch all supported files
deepl watch docs/ --to es
# Watch with pattern
deepl watch docs/ --to es,fr --pattern "*.md"
# With custom debounce (wait 1 second after changes)
deepl watch docs/ --to es --debounce 1000With auto-commit:
# Automatically commit translations
deepl watch docs/ --to es --auto-commitWith git-staged filtering:
# Only translate files currently staged in git
deepl watch . --to es --git-staged
# Combine with dry-run to preview staged files
deepl watch . --to es,fr --git-staged --dry-run
# Pre-commit workflow: stage files, then watch only those
git add docs/guide.md docs/faq.md
deepl watch docs/ --to de,ja --git-staged --auto-commitNote:
--git-stagedtakes a one-time snapshot of staged files at startup. Files staged after the watcher starts are not included. The watched path must be inside a git repository — the command exits with an error otherwise — and it is that repository's index that is read, not the one belonging to the directory the CLI was started in.
Continuous localization engine for i18n file translation.
deepl sync [OPTIONS]
deepl sync init [OPTIONS]
deepl sync status [OPTIONS]
deepl sync validate [OPTIONS]
deepl sync audit [OPTIONS]
deepl sync export [OPTIONS]
deepl sync resolve [OPTIONS]
deepl sync push [OPTIONS]
deepl sync pull [OPTIONS]Scan, translate, and sync i18n resource files. The sync engine reads .deepl-sync.yaml for project configuration, diffs source strings against .deepl-sync.lock to detect changes, translates only new and modified strings via the DeepL API, and writes properly formatted target files.
Supported formats: JSON, YAML, TOML, Gettext PO, Android XML, iOS Strings, Xcode String Catalog (.xcstrings), ARB, XLIFF, Java Properties, Laravel PHP arrays (.php).
Behavior:
- Reads configuration from
.deepl-sync.yamlin the current directory - Tracks translation state in
.deepl-sync.lockfor incremental sync - Preserves format-specific structure (indentation, comments, metadata)
- Displays per-locale progress as each translation completes
- Exits with code 10 when
--frozendetects translation drift - Bounds
context.scan_pathsatsync.max_scan_filesfiles (default 50,000) to prevent a misconfigured glob from wedging the CLI on huge source trees. Exceeding the cap throws aValidationErrorwith a suggestion to narrow the pattern or raise the cap; see docs/SYNC.md.
Sync Mode:
-
--dry-run- Preview changes without translating -
--frozen- Fail (exit 10) if translations are missing or outdated; no API calls -
--ci- Alias for--frozen -
--force- Re-translate all strings, ignoring the lockfile. WARNING: also bypasses thesync.max_characterscost-cap preflight in.deepl-sync.yaml, so a forced run can re-bill every translated key and incur unexpected API costs. Rundeepl sync --dry-runfirst to see the character estimate before forcing.Billing safety guards:
--watch --forceis rejected at startup with aValidationError(exit 6) to prevent unbounded billing from a forced re-translation on every file save.- In an interactive terminal,
--forceprompts for confirmation before bypassing the cost cap. Pass--yes(-y) to skip the prompt in scripts. - In CI environments (
CI=true),--forcerequires an explicit--yes; otherwise the process exits 6 with an actionable hint naming the missing flag. - Anywhere else the prompt cannot be shown — a git hook, cron job,
maketarget, container entrypoint,deepl sync --force < /dev/null, or--no-input—--forceis refused with exit 6 rather than assumed to be confirmed.--yesis the only way to run it unattended.--forceoverwrites every target file, including translations edited by hand, and no.deepl.baksurvives a successful run, so there is nothing to recover from afterwards.
-
--yes, -y- Skip the--forceconfirmation prompt (required whenCI=true, and whenever there is no terminal to prompt on) -
--break-lock- Take the sync lock even when.deepl-sync.lock.pidfilenames a process that looks alive, reporting the holder it removed. Also accepted bysync pullandsync resolve, which take the same lock. Use it only when that run is definitely not running: two concurrent syncs write the same target files and the same lockfile. Not carried into--watchcycles — it applies to the run you asked for, then the lock arbitrates normally again. See Concurrent sync
Filtering:
--locale LANGS- Sync only specific target locales (comma-separated). Note the split:sync --localeis a filter over locales already declared in.deepl-sync.yaml#target_locales— it narrows which configured targets a run acts on.deepl translate --tois an invocation-time specifier — it names the target languages for a one-shot text translation. The sync engine owns the locale mapping via.deepl-sync.yaml;translatedoes not. A locale not intarget_localespassed tosync --localeexits with aConfigError; an unrecognized code passed totranslate --toexits withInvalidInput.
Translation Quality:
--formality LEVEL- Override formality:default,more,less,prefer_more,prefer_less,formal,informal--model-type TYPE- Override model type:quality_optimized,prefer_quality_optimized,latency_optimized--glossary NAME-OR-ID- Override glossary name or ID--scan-context/--no-scan-context- Enable or disable source-code context scanning. Matches both string literal and template literalt()calls. When enabled, key paths are parsed into natural-language context descriptions, and HTML element types are detected from surrounding source code. Element types feed intoinstruction_templates(configured in.deepl-sync.yaml) for auto-generatedcustom_instructions. Scope: these flags overridecontext.enabledin.deepl-sync.yamlonly; all othercontext.*settings (include,exclude,max_files, etc.) continue to apply when scanning is enabled. Note: bare--context/--no-contextondeepl syncis rejected with a ValidationError (exit 6) — the string-valued--context "<text>"flag only applies todeepl translate; sync's boolean toggle was renamed to--scan-contextto avoid the collision.
Note: deepl sync deliberately exposes no --translation-memory / --tm-threshold CLI override; configure translation memory via translation.translation_memory (and optional translation.translation_memory_threshold) in .deepl-sync.yaml, with per-locale overrides under translation.locale_overrides.
Performance:
--concurrency NUM- Max parallel locale translations (default: 5)--batch- Force plain batch mode (fastest, no context or instructions). All keys in batch API calls.--no-batch- Force per-key mode (slowest, individual context per key). Default: section-batched context (~3.4x faster than per-key while preserving disambiguation context).
Git:
--auto-commit- Auto-commit translated files after sync (requires git)
Review:
--flag-for-review- Mark translations asmachine_translatedin lock file for human review
Watch:
--watch- Watch source files and auto-sync on changes--debounce MS- Debounce delay for watch mode (default: 500ms)
Output:
--format FORMAT- Output format:text(default),json
Config:
--sync-config PATH- Path to.deepl-sync.yaml(default: auto-detect)
Interactive setup wizard that creates .deepl-sync.yaml by scanning the project for i18n files.
Auto-detected project types: i18next / react-intl / vue-i18n / next-intl (JSON under locales/ or i18n/), Rails (config/locales/en.yml), generic YAML i18n, Django / generic gettext (locale/*/LC_MESSAGES/*.po), Android (res/values/strings.xml), iOS / macOS (*.lproj/Localizable.strings), Xcode String Catalog (Localizable.xcstrings / *.xcstrings), Flutter (pubspec.yaml + l10n/app_en.arb or *_en.arb), Angular / CAT tools (XLIFF under src/locale/ or root), go-i18n (locales/en.toml or i18n/en.toml), Java / Spring (src/main/resources/messages_en.properties), and Laravel (composer.json + lang/en/*.php or resources/lang/en/*.php). Detection is filesystem-only — no package manifests are parsed. See docs/SYNC.md for the full detection matrix. Layouts outside these conventions need the four flags above.
Options:
--source-locale CODE- Source locale code--target-locales CODES- Target locales (comma-separated)--file-format TYPE- File format:json,yaml,toml,po,android_xml,ios_strings,xcstrings,arb,xliff,properties,laravel_php--path GLOB- Source file path or glob pattern--format FORMAT- Output format:text(default),json. Underjson, success emits the envelope described below and failure emits the shared error envelope, both on stdout--sync-config PATH- Path to.deepl-sync.yaml
--source-lang and --target-langs were accepted as deprecated aliases during 1.x and were removed in 2.0.0; use --source-locale / --target-locales. deepl translate --target-lang is unchanged — it operates on strings and stays aligned with the DeepL API's wire name.
Examples:
# Interactive auto-detection
deepl sync init
# Non-interactive
deepl sync init --source-locale en --target-locales de,fr,es --file-format json --path "locales/en.json"Show translation coverage for all target locales.
Options:
--locale LANGS- Show status for specific locales only--format FORMAT- Output format:text(default),json--sync-config PATH- Path to.deepl-sync.yaml
JSON output contract (stable within a major version):
{
"sourceLocale": "en",
"totalKeys": 142,
"skippedKeys": 1,
"locales": [
{
"locale": "de",
"complete": 140,
"missing": 2,
"outdated": 0,
"unwritten": 0,
"needsReview": 0,
"coverage": 98
}
],
"unwrittenByLocale": []
}unwritten, needsReview and unwrittenByLocale are always present, even when zero or empty — see the two paragraphs at the end of this subcommand for what each counts.
skippedKeys counts entries the parser tagged as untranslatable and excluded from the translation batch — currently only Laravel pipe-pluralization values (|{n}, |[n,m], |[n,*]). Included in totalKeys.
stdout/stderr split (stable contract): The JSON payload — the success result, or the error envelope below when the command fails — is written to stdout, so deepl sync status --format json > status.json produces a parseable file in both cases. Diagnostic/progress logs and warnings stay on stderr, which means stdout parses even when the CLI or the Node runtime had something to say. The same stdout/stderr split applies to deepl sync --format json, deepl sync validate --format json, and deepl sync audit --format json.
Error envelope (shared across every command with a JSON mode): On failure, --format json emits the following JSON envelope to stdout — the envelope is the command's result in the failure case; the non-zero exit code is the failure signal — and exits with the typed exit code:
{
"ok": false,
"error": {
"code": "ConfigError",
"message": ".deepl-sync.yaml not found in current directory or any parent",
"suggestion": "Run `deepl sync init` to create one."
},
"exitCode": 7
}The error.code field matches the error class name (ConfigError, ValidationError, SyncConflict, AuthError, etc.). error.suggestion is present when the underlying DeepLCLIError carries one. exitCode matches the process exit code, so a caller can branch on either field.
Which commands emit it: every command whose effective --format is json — the nine sync subcommands plus translate, write, correct, voice, usage, languages, detect, and those subcommands of glossary, tm, cache, config, hooks, admin and style-rules that declare the flag. The shape and the stream are identical everywhere, so a script wrapping several deepl commands needs one failure path. Notes on the edges:
- A command with no
--formatflag, and any run intextortablemode, is unchanged:Error:/Suggestion:prose on stderr. config get/config listdefault tojson, so their failures carry the envelope with no flag passed.- A result that is not an error keeps its own shape:
write --check/correct --checkexit8to report that text needs changes, and emit their ownok: trueresult payload rather than an envelope;--diffand--alternativescarryok: truepayloads of their own too. Discriminate onok:falseis a failure,trueis a result whose detail is in the payload. The one exception iswrite/correct's plain improvement payload ({original, improved, changes, language}), which predates the envelope and carries nookfield. - A malformed invocation commander rejects before the command runs (
unknown option, a missing argument) still prints commander's own message on stderr and exits6. The envelope covers command failures, not parse failures.
sync init --format json success envelope: For scripted project bootstrap, deepl sync init --format json emits a success envelope on stdout instead of the plain text confirmation:
{
"ok": true,
"created": {
"configPath": "/absolute/path/.deepl-sync.yaml",
"sourceLocale": "en",
"targetLocales": ["de", "fr"],
"keys": 128
}
}keys is present only when the wizard counted the source strings while scanning; a fully non-interactive sync init omits it. Treat it as optional.
Casing convention: every sync JSON payload uses camelCase, while the on-disk .deepl-sync.lock and .deepl-sync.yaml use snake_case. The two are deliberately kept separate — JSON output is a consumer contract; the files are authored configuration. Outside sync, commands that pass a DeepL API response straight through keep the API's own snake_case (glossary, tm), as does detect (detected_language, language_name). The two are deliberately kept separate — JSON output is a consumer contract; the files are authored configuration.
Examples:
deepl sync status
# JSON output
deepl sync status --format jsonSample output:
Source: en (142 keys)
de [###################.] 98% (2 missing, 0 outdated)
fr [####################] 100% (0 missing, 0 outdated)
es [###################.] 97% (4 missing, 0 outdated)
ja [##################..] 91% (12 missing, 0 outdated)
A locale also reports needsReview keys — a translation is present but its format marks it as not ready to ship, which means a gettext #, fuzzy msgstr or an XLIFF review state such as needs-review-translation (1.2) or initial (2.0). msgfmt leaves a fuzzy entry out of the compiled catalog, so the application shows the source string; counting such a key complete would report a project as done while it ships untranslated text. An XLIFF unit with no state attribute, or one carrying a value outside the recognised sets, counts as complete — see docs/SYNC.md for the two tables. Such a key is never re-translated and the file is never rewritten for it — the value is a reviewer's draft — so the count is a report, not a repair, and nothing is billed. The suffix , N needs review and its explanatory line appear only when the count is non-zero, and --format json always carries needsReview on each locale. deepl sync push skips such a key under a needs_review skip reason rather than uploading it as an approved translation. sync --frozen deliberately does not treat it as drift: such a marker is a normal, transient state in a review workflow that only a human clears, and nothing the CLI did caused it.
A locale also reports unwritten keys — recorded as translated in .deepl-sync.lock but not in that locale's target file — when there are any, followed by a line naming the file and the keys. The suffix is omitted when the count is zero, so the line above is unchanged for a healthy project. --format json always carries unwritten on each locale plus a top-level unwrittenByLocale array of {locale, file, keys}, each entry gaining an unusable field when the target file could not be read or parsed at all. See docs/SYNC.md for what is and is not counted.
Check translations for placeholder integrity and format consistency.
Options:
--locale LANGS- Validate specific locales only--format FORMAT- Output format:text(default),json--sync-config PATH- Path to.deepl-sync.yaml
Examples:
deepl sync validate
# Validate only German
deepl sync validate --locale deSample output:
$ deepl sync validate
Checked 4 translations
ERROR de/greeting: Missing placeholders in translation: {name}
WARN de/bye: Translation is identical to source text
1 error(s), 1 warning(s)
One line per issue, ERROR or WARN followed by <locale>/<key> and the check's message. A run with no issues prints the header and All translations passed validation.
JSON output contract (stable within a major version):
{
"totalChecked": 3,
"passed": 2,
"warnings": 0,
"errors": 1,
"issues": [
{
"locale": "de",
"file": "locales/de.json",
"key": "errors.count",
"source": "Hi %s",
"translation": "Hi",
"severity": "error",
"issues": [
{
"check": "placeholders",
"severity": "error",
"message": "Missing placeholders in translation: %s",
"details": { "expected": ["%s"], "actual": [] }
}
]
}
]
}totalChecked counts source/target pairs that were validated; a key absent from the target file is not a pair and is not counted. passed is totalChecked minus the number of pairs carrying any issue, so a pair with only warnings is not "passed". errors is what drives the exit code — see 8 — CheckFailed for which check values are error severity and which are warn-only. A file-level unusable_target issue counts toward errors but not toward totalChecked, and its key/file are both the target path rather than a translation key.
Analyze translation consistency and detect terminology inconsistencies across target locales. "Audit" here means translation-consistency audit (detecting term divergence across locales), not security audit in the npm audit sense.
Options:
--format FORMAT- Output format:text(default),json--sync-config PATH- Path to.deepl-sync.yaml
Note: Prior to the 1.1.0 release, this subcommand was prototyped as glossary-report; it never shipped in a tagged release under that name. The old name is rejected with an error pointing at the new form; no alias is kept.
JSON output sample:
{
"totalTerms": 1,
"inconsistencies": [
{
"sourceText": "Dashboard",
"locale": "de",
"translations": ["Armaturenbrett", "Dashboard"],
"files": ["locales/en/common.json", "locales/en/admin.json"]
}
],
"missingTargets": []
}missingTargets lists target files that could not be read or parsed and were therefore excluded from the comparison. A non-empty array means the audit's verdict covers fewer locales than the project has; the text output prints the same list under N target(s) could not be read and were excluded from the comparison:.
The translations array contains the actual translated strings read from target files. If a target file is missing, the content hash falls back in its place.
Export source strings to XLIFF 1.2 for CAT tool handoff.
Options:
--locale LANGS- Filter by locale (comma-separated)--output PATH- Write to file instead of stdout. Path must stay within the project root; intermediate directories are created automatically--overwrite- Required to overwrite an existing--outputfile. Without it, an existing file causes a non-zero exit and no write occurs--format FORMAT- Output format:text(default),json. Success output is always XLIFF 1.2 regardless of format;jsonaffects the error envelope on stdout (matching other sync subcommands) so script consumers can parse failure shape uniformly--sync-config PATH- Path to.deepl-sync.yaml
Examples:
# Print XLIFF to stdout (pipe to CAT tool, clipboard, etc.)
deepl sync export
# Write to a file (creates reports/ if needed)
deepl sync export --output reports/handoff.xlf
# Overwrite an existing file
deepl sync export --output reports/handoff.xlf --overwrite
# Rejected: path escapes the project root
deepl sync export --output ../elsewhere.xlfResolve git merge conflicts in .deepl-sync.lock.
Options:
--format FORMAT- Output format:text(default),json--dry-run- Preview conflict decisions without writing the lockfile--sync-config PATH- Path to.deepl-sync.yaml--break-lock- Take the sync lock even when.deepl-sync.lock.pidfilenames a process that looks alive
JSON success envelope (stable within a major version): { "ok": true, "resolved": <n>, "decisions": [...] }
Push local translations to a TMS for human review.
Options:
--locale LANGS- Push specific locales only--format FORMAT- Output format:text(default),json--sync-config PATH- Path to.deepl-sync.yaml
Requires TMS integration. Add a tms: block to .deepl-sync.yaml (at minimum enabled: true, server, project_id) and supply credentials via the TMS_API_KEY or TMS_TOKEN environment variable. Running push without a configured tms: block exits 7 (ConfigError). See docs/SYNC.md#tms-rest-contract for the full field reference and REST contract.
TMS destination trust. tms.server is chosen by .deepl-sync.yaml, which lives in the checkout, while TMS_API_KEY / TMS_TOKEN come from your environment. Before an environment-supplied credential is attached to a request, the destination hostname must be one you have approved:
- Approved if the hostname appears in the user-level
tms.allowedServerslist (deepl config set tms.allowedServers tms.example.com, comma-separate several). - Otherwise, in an interactive terminal, the CLI names the host and what would be sent and asks once. Answering yes records the hostname in user config (
~/.config/deepl-cli/config.json), never in the repository. - Otherwise — under
--no-input, or on a non-TTY such as CI — the run fails closed with exit 7 (ConfigError) naming the host and the exactdeepl config set tms.allowedServers ...command. No credential and no translated string is sent.
The gate applies only to environment-supplied credentials. A credential inlined as tms.api_key / tms.token in .deepl-sync.yaml is not gated: it belongs to the same file that chose the destination, so nothing of yours leaks. Loopback hosts (localhost, 127.0.0.1) are not exempt — a co-tenant process listening locally is still an exfiltration sink.
Both commands print the resolved destination origin on success, in text and JSON output, so a redirected destination is visible in logs even when the host was already approved.
JSON success envelope (stable within a major version): { "ok": true, "pushed": <n>, "skipped": [...], "server": "<origin>" }
Each skipped entry carries a reason. push emits untranslated (the target file lists the key but holds no translation for it — pushing it would upload source text as the locale's approved translation), needs_review (the translation is marked as needing review, a gettext #, fuzzy msgstr or an XLIFF review state, so it is a reviewer's draft rather than an approved translation), pipe_pluralization (Laravel pipe-pluralization, never sent to a TMS), and target_missing (the target file does not exist yet, common on a first push). push never emits pull's reasons and pull never emits these.
Pull approved translations from a TMS back into local files.
Options:
--locale LANGS- Pull specific locales only--format FORMAT- Output format:text(default),json--sync-config PATH- Path to.deepl-sync.yaml--dry-run- Preview what the pull would change without writing any file--break-lock- Take the sync lock even when.deepl-sync.lock.pidfilenames a process that looks alive
Requires TMS integration. Add a tms: block to .deepl-sync.yaml (at minimum enabled: true, server, project_id) and supply credentials via the TMS_API_KEY or TMS_TOKEN environment variable. Running pull without a configured tms: block exits 7 (ConfigError). See docs/SYNC.md#tms-rest-contract for the full field reference and REST contract.
TMS destination trust. tms.server is chosen by .deepl-sync.yaml, which lives in the checkout, while TMS_API_KEY / TMS_TOKEN come from your environment. Before an environment-supplied credential is attached to a request, the destination hostname must be one you have approved:
- Approved if the hostname appears in the user-level
tms.allowedServerslist (deepl config set tms.allowedServers tms.example.com, comma-separate several). - Otherwise, in an interactive terminal, the CLI names the host and what would be sent and asks once. Answering yes records the hostname in user config (
~/.config/deepl-cli/config.json), never in the repository. - Otherwise — under
--no-input, or on a non-TTY such as CI — the run fails closed with exit 7 (ConfigError) naming the host and the exactdeepl config set tms.allowedServers ...command. No credential and no translated string is sent.
The gate applies only to environment-supplied credentials. A credential inlined as tms.api_key / tms.token in .deepl-sync.yaml is not gated: it belongs to the same file that chose the destination, so nothing of yours leaks. Loopback hosts (localhost, 127.0.0.1) are not exempt — a co-tenant process listening locally is still an exfiltration sink.
Both commands print the resolved destination origin on success, in text and JSON output, so a redirected destination is visible in logs even when the host was already approved.
JSON success envelope (stable within a major version): { "ok": true, "pulled": <n>, "replaced": <n>, "skipped": [...], "server": "<origin>", "dryRun": <bool> }
replaced counts keys whose existing local translation the pull overwrote with the TMS version — check it before trusting a pull that ran over hand-edited files. dryRun is true when --dry-run was passed, in which case nothing was written and pulled/replaced are what a real run would do.
Each skipped entry carries a reason. pull emits unusable_target (the target file could not be read), key_collision (the target file's keys collide), shared_target (a target file another sync configuration's .deepl-sync.lock accounts for keys in, left untouched rather than rebuilt from this configuration's keys alone), plural_entry (one exported string cannot fill a gettext msgstr[N] or Android <plurals> entry's forms, so the entry is carried forward as it stands), and no_matches (no matching keys). See docs/SYNC.md#two-configurations-writing-one-file.
Basic sync:
# Sync all configured locales
deepl sync
# Preview what would be translated
deepl sync --dry-runCI/CD (frozen mode):
# Fail if translations are out of date (exit code 10)
deepl sync --frozenLocale filtering:
# Sync only German and French
deepl sync --locale de,frForce re-translation:
# Re-translate everything, ignoring the lockfile
deepl sync --forceJSON output for scripting:
deepl sync --format json
deepl sync status --format jsondeepl sync --format json output contract (stable within a major version):
The success payload is written to stdout as a single JSON object. The following fields are guaranteed stable and will not be renamed or removed within the same major version:
| Field | Type | Description |
|---|---|---|
ok |
boolean |
true if the sync completed without errors |
totalKeys |
number |
Total translation keys discovered across all source files |
translated |
number |
Keys translated during this run (summed across all locales) |
skipped |
number |
Keys skipped (already up-to-date, summed across all locales) |
failed |
number |
Keys that could not be translated (summed across all locales) |
targetLocaleCount |
number |
Number of target locales processed |
estimatedCharacters |
number |
Characters estimated for billing this run |
estimatedCost |
string | undefined |
Human-readable cost estimate at Pro rate (e.g. ~$0.05), omitted when zero |
rateAssumption |
"pro" |
Always "pro" — cost estimate uses the DeepL Pro per-character rate |
dryRun |
boolean |
true when --dry-run was passed; no translations were written |
perLocale |
Array<{locale, translated, skipped, failed}> |
Per-locale breakdown; each entry aggregates all files for that locale |
No other fields appear in the output. Fields not listed above are internal and may change without notice.
- The
--frozenflag makes no API calls. It compares the lockfile against source files, and each target file against what the lockfile claims about it, and exits with code 10 if any translations are missing, outdated, or recorded as translated while absent from the target file. This is the recommended mode for CI/CD pipelines. - The
--dry-runflag makes no API calls and writes nothing — no target file, no lockfile, no backup. It still requires an API key, though: the client is constructed before the run decides it has nothing to send, sodeepl sync --dry-runwithout a key exits 2. It reads each target file the lockfile claims translations for, so its estimate covers the same work the real run does: keys the lockfile calls translated that the target file no longer holds are included (the run re-translates and re-bills them, andunwrittenKeysreports the count), and a locale whose target file is on disk and unreadable is excluded and named in a warning, because the run refuses that locale, bills nothing for it and exits 12. Reading the target files is what makes the estimate faithful and is the flag's main cost: measured on a 20,000-key, 6-locale project (2.83 MiB source, 17.3 MiB of target files, 24 MiB lockfile),--dry-runwent from 0.44 s to 0.75 s, alongside 0.77 s forsync statusand 0.79 s forsync --frozen. The read is skipped entirely for a locale the lockfile claims nothing for, so a project mid-first-sync pays nothing. - The
sync.max_characterscost cap quotes from the same estimate as--dry-run, so a run the preview prices above the cap is a run the cap refuses. That costs one pass over the target files before the cap decides: on the fixture above, a translating run took 1.54 s with no cap configured and 1.91 s with one. The cap is unaffected for projects that do not set it. - The lockfile (
.deepl-sync.lock) should be committed to version control. It enables incremental sync by tracking content hashes. - The
pushandpullsubcommands require a TMS that implements the REST contract documented in docs/SYNC.md. All other commands work with the standard DeepL Translation API. - By default, keys with extracted context are grouped by i18n section and translated in section batches. Use
--no-batchto force individual per-key context translation. Use--batchto force all keys into plain batch calls (no context). - See docs/SYNC.md for the complete sync guide including configuration schema, CI/CD recipes, and troubleshooting.
Manage git hooks for translation workflow automation.
deepl hooks <SUBCOMMAND>Install, uninstall, and manage git hooks that validate translations before committing or pushing.
Install a git hook.
Arguments:
hook-type- Hook type:pre-commit,pre-push,commit-msg,post-commit
Options:
-y, --yes- Skip the confirmation prompt when this repository sends hooks outside the working tree
Hooks directory outside the repository: the hook is written wherever git reads hooks from, which a repository-local core.hooksPath can point anywhere — including an absolute path outside the checkout. Because that setting travels with the repository rather than coming from you, an install that would land outside the working tree names the configured value and the resolved directory and asks first. Declining, or running without a terminal, exits 6 and writes nothing; --yes installs there and prints the same notice to stderr. A core.hooksPath that stays inside the working tree (.husky/_, for example) is not affected, and neither are linked worktrees or submodules, where git legitimately reads hooks from the repository that owns them.
Examples:
deepl hooks install pre-commit
deepl hooks install pre-push
deepl hooks install commit-msg
deepl hooks install post-commit
# Accept a hooks directory outside the working tree without prompting
deepl hooks install pre-commit --yesUninstall a git hook.
Examples:
deepl hooks uninstall pre-commit
deepl hooks uninstall pre-push
deepl hooks uninstall commit-msg
deepl hooks uninstall post-commitList all hooks and what the file at each hook path is.
Each hook installed by this CLI carries a marker line recording the SHA-256 of
its body, and list checks it. Four states are reported:
| State | Meaning |
|---|---|
installed |
A versioned marker is present and the body hashes to the value it records |
modified |
A marker is present and the body does not hash to it — an edit made after installation, or a forged marker |
unverified |
A legacy (pre-1.0) marker with no recorded hash to check against |
not-installed |
No hook file, or a file carrying no DeepL marker |
The hash is unkeyed, so installed establishes that a hook has not changed
since its marker was written — not that this CLI wrote it. Anyone who can write
the hook can write a marker that agrees with their own content. Treat the marker
as detection of changes to a hook, not as proof of its origin.
Options:
--format <format>- Output format:text,json(default:text)
Examples:
deepl hooks list
# JSON output for CI/CD scripting
deepl hooks list --format json
# { "pre-commit": "installed", "pre-push": "modified", "commit-msg": "unverified", "post-commit": "not-installed" }The JSON values are these state strings, not booleans. Gate on
state === "installed"; a truthiness test passes for every state, including
"not-installed".
Show the path to a hook file.
Examples:
deepl hooks path pre-commitManage translation glossaries using the DeepL v3 Glossary API.
The v3 API supports both single-target glossaries (one source → one target language) and multilingual glossaries (one source → multiple target languages).
deepl glossary <SUBCOMMAND>Create a new glossary from a TSV or CSV file.
Arguments:
name- Glossary namesource-lang- Source language code (e.g.,en,de,fr)target-lang- Target language code, comma-separated for multiple (e.g.,es,de,fr,es)file- Path to TSV or CSV file with term pairs
File Format:
- TSV (Tab-Separated Values):
source_term<TAB>target_term - CSV (Comma-Separated Values):
source_term,target_term - One term pair per line
- No header row required
Example file (glossary.tsv):
API API
authentication autenticación
cache caché
Examples:
# Create single-target glossary from TSV file
deepl glossary create tech-terms en es glossary.tsv
# ✓ Glossary created: tech-terms (ID: abc123...)
# Source language: en
# Target languages: es
# Type: Single target
# Total entries: 3
# Create multilingual glossary with comma-separated target languages
deepl glossary create tech-terms en de,fr,es glossary.tsv
# Create glossary from CSV file
deepl glossary create product-names en fr terms.csvList all glossaries with their IDs, language pairs, and entry counts.
Options:
--format <format>- Output format:text,json(default:text)
Output Format (text):
- Single-target glossaries:
📖 name (source→target) - N entries - Multilingual glossaries:
📚 name (source→N targets) - N entries
Example:
deepl glossary list
# 📖 tech-terms (en→de) - 3 entries
# 📚 multilingual-terms (en→3 targets) - 15 entries
# JSON output for CI/CD scripting
deepl glossary list --format json
# [{ "glossary_id": "abc123...", "name": "tech-terms", ... }]Show glossary details including name, ID, languages, creation date, and entry count.
Options:
--format <format>- Output format:text,json(default:text)
Output includes:
- Name and ID
- Source language
- Target languages (comma-separated for multilingual glossaries)
- Type (Single target or Multilingual)
- Total entry count
- Language pairs (for multilingual glossaries)
- Creation timestamp
Example:
deepl glossary show tech-terms
# Name: tech-terms
# ID: abc123...
# Source language: en
# Target languages: de
# Type: Single target
# Total entries: 3
# Created: 2024-10-07T12:34:56.000Z
# Multilingual glossary example
deepl glossary show multilingual-terms
# Name: multilingual-terms
# ID: def456...
# Source language: en
# Target languages: es, fr, de
# Type: Multilingual
# Total entries: 15
#
# Language pairs:
# en → es: 5 entries
# en → fr: 5 entries
# en → de: 5 entries
# Created: 2024-10-08T10:00:00.000ZDelete a glossary by name or ID.
Options:
-y, --yes- Skip confirmation prompt--dry-run- Show what would be deleted without performing the operation
Example:
deepl glossary delete tech-terms
deepl glossary delete abc-123-def-456
deepl glossary delete tech-terms --dry-runGet glossary entries in TSV format (suitable for backup or editing).
Arguments:
name-or-id- Glossary name or ID
Options:
--target-lang <lang>- Target language (required for multilingual glossaries, optional for single-target)--format <format>- Output format:text,json(default:text)
Behavior:
- For single-target glossaries:
--target-langflag is optional (automatically uses the single target language) - For multilingual glossaries:
--target-langflag is required to specify which language pair to retrieve
Example:
# Single-target glossary (no --target-lang needed)
deepl glossary entries tech-terms > backup.tsv
# View entries
deepl glossary entries tech-terms
# API → API
# REST → REST
# authentication → Authentifizierung
# Multilingual glossary (--target-lang required)
deepl glossary entries multilingual-terms --target-lang es
# API → API
# cache → caché
# ...
deepl glossary entries multilingual-terms --target-lang fr
# API → API
# cache → cache
# ...List all supported glossary language pairs.
Description: Shows which source-target language combinations are available for glossary creation. Not all language pairs supported by DeepL translation are available for glossaries.
Example:
deepl glossary languages
# en → de
# en → es
# en → fr
# de → en
# ...Add a new entry to an existing glossary.
Arguments:
name-or-id- Glossary name or IDsource- Source language termtarget- Target language translation
Options:
--target-lang <lang>- Target language (required for multilingual glossaries, optional for single-target)
Behavior:
- Uses v3 PATCH endpoint for efficient updates (no delete+recreate)
- Glossary ID remains unchanged
- Preserves all other entries
- Fails if entry already exists
Examples:
# Add entry to single-target glossary
deepl glossary add-entry tech-terms "database" "Datenbank"
# Add entry to multilingual glossary (--target-lang required)
deepl glossary add-entry multilingual-terms "cache" "caché" --target-lang es
deepl glossary add-entry multilingual-terms "cache" "cache" --target-lang fr
# Add phrase
deepl glossary add-entry tech-terms "user interface" "Schnittstelle"Note: v3 API uses PATCH for efficient updates. The glossary ID remains unchanged.
Update an existing entry in a glossary.
Arguments:
name-or-id- Glossary name or IDsource- Source language term to updatenew-target- New target language translation
Options:
--target-lang <lang>- Target language (required for multilingual glossaries, optional for single-target)
Behavior:
- Updates existing entry's target text using v3 PATCH endpoint
- Glossary ID remains unchanged
- Fails if entry doesn't exist
Examples:
# Update entry in single-target glossary
deepl glossary update-entry tech-terms "API" "API (Programmierschnittstelle)"
# Update entry in multilingual glossary (--target-lang required)
deepl glossary update-entry multilingual-terms "API" "API (Interfaz)" --target-lang es
deepl glossary update-entry multilingual-terms "API" "API (Interface)" --target-lang frNote: v3 API uses PATCH for efficient updates. The glossary ID remains unchanged.
Remove an entry from a glossary.
Arguments:
name-or-id- Glossary name or IDsource- Source language term to remove
Options:
--target-lang <lang>- Target language (required for multilingual glossaries, optional for single-target)
Behavior:
- Removes entry from glossary using v3 PATCH endpoint
- Glossary ID remains unchanged
- Fails if entry doesn't exist
- Fails if removing the last entry (delete glossary instead)
Examples:
# Remove entry from single-target glossary
deepl glossary remove-entry tech-terms "obsolete-term"
# Remove entry from multilingual glossary (--target-lang required)
deepl glossary remove-entry multilingual-terms "deprecated" --target-lang esNote: You cannot remove the last entry from a glossary. If you need to remove all entries, use deepl glossary delete instead.
Rename a glossary.
Arguments:
name-or-id- Glossary name or IDnew-name- New name for the glossary
Behavior:
- Changes glossary name using v3 PATCH endpoint
- Glossary ID remains unchanged
- Preserves all entries and language pairs
- Fails if new name matches current name
Examples:
# Rename by glossary name
deepl glossary rename tech-terms "Technical Terminology v2"
# Rename by glossary ID
deepl glossary rename abc-123-def-456 "Product Names 2024"Note: v3 API uses PATCH for efficient rename. The glossary ID remains unchanged and all entries are preserved.
Update a glossary's name and/or dictionary entries in a single request.
Arguments:
name-or-id- Glossary name or ID
Options:
--name <name>- New glossary name--target-lang <lang>- Target language for dictionary update (required when using--file)--file <path>- TSV/CSV file with entries for dictionary update
Behavior:
- At least one of
--nameor--file(with--target-lang) must be provided - When both
--nameand--fileare given, the rename and dictionary update happen in a single PATCH request --target-langis required when--fileis specified- Uses v3 PATCH endpoint for efficient updates
- Glossary ID remains unchanged
Examples:
# Rename only
deepl glossary update my-terms --name "Updated Terms"
# Update dictionary entries only
deepl glossary update my-terms --target-lang de --file updated.tsv
# Rename and update dictionary in one request
deepl glossary update my-terms --name new-name --target-lang de --file updated.tsvReplace all entries in a glossary dictionary from a TSV/CSV file (v3 API only). Unlike updating individual entries (which merges), this replaces the entire dictionary contents.
Arguments:
name-or-id- Glossary name or IDtarget-lang- Target language of the dictionary to replace (e.g.,es,fr,de)file- TSV/CSV file path with replacement entries
Examples:
# Replace all Spanish entries from a new file
deepl glossary replace-dictionary my-glossary es new-entries.tsv
# ✓ Dictionary replaced successfully (es)
# Replace by glossary ID
deepl glossary replace-dictionary abc-123-def-456 fr updated-fr.tsvNotes:
- Replaces the entire dictionary via the v3 PUT endpoint (not a merge)
- All existing entries for the specified language pair are removed and replaced with the file contents
- The file format is the same as for
glossary create(TSV or CSV)
Delete a specific language pair from a multilingual glossary (v3 API only).
Arguments:
name-or-id- Glossary name or IDtarget-lang- Target language of the dictionary to delete (e.g.,es,fr,de)
Options:
-y, --yes- Skip confirmation prompt
Behavior:
- Removes a specific language pair from a multilingual glossary using v3 DELETE endpoint
- Glossary ID remains unchanged
- Other language pairs in the glossary are preserved
- Fails if glossary is single-target (use
glossary deleteinstead) - Fails if this would be the last dictionary in the glossary (use
glossary deleteinstead) - Fails if the dictionary doesn't exist in the glossary
Examples:
# Delete Spanish dictionary from multilingual glossary
deepl glossary delete-dictionary multilingual-terms es
# ✓ Dictionary deleted successfully (es)
# Other language pairs (fr, de) remain intact
# Delete by glossary ID
deepl glossary delete-dictionary abc-123-def-456 fr
# ✓ Dictionary deleted successfully (fr)Notes:
- Multilingual glossaries only: This command only works with multilingual glossaries that have multiple target languages. For single-target glossaries, use
deepl glossary deleteto remove the entire glossary. - Preserves glossary: Unlike
glossary delete, this command preserves the glossary and only removes one language pair. - Cannot delete last dictionary: If the glossary would have zero dictionaries after deletion, the command fails. Use
glossary deleteto remove the entire glossary instead.
Manage translation memories. TM files are authored and uploaded via the DeepL web UI; this command surfaces the account's TMs so you can copy a name or UUID into a translate or sync invocation without leaving the terminal.
deepl tm list [options]List all translation memories on the account.
Options:
--format <format>- Output format:text,json(default:text)
Output Format (text):
- Per-TM:
name (source → target[, target...])— e.g.brand-terms (en → de, fr, ja). Control chars and zero-width codepoints are stripped from the rendered name to prevent a malicious API-returned name from corrupting the terminal via ANSI escape sequences. - Empty list:
No translation memories found
Output Format (JSON):
Raw TranslationMemory[] as returned by GET /v3/translation_memories — fields: translation_memory_id, name, source_language, target_languages (array).
Example:
deepl tm list
# brand-terms (en → de, fr, ja)
# legal-phrases (en → fr)
deepl tm list --format json | jq '.[] | select(.name == "brand-terms") | .translation_memory_id'
# "3f2504e0-4f89-41d3-9a0c-0305e82c3301"Related:
deepl translate --translation-memory <name-or-uuid>— use a listed TM on a single translate call..deepl-sync.yamltranslation.translation_memory— configure a TM for a sync run (see sync).
Manage translation cache.
deepl cache <SUBCOMMAND>Show cache statistics (status, entries count, size, percentage used).
Options:
--format <format>- Output format:text,json,table(default:text). In non-TTY output,tablefalls back totextwith aWARNline on stderr.
Clear all cache entries (displays: "✓ Cache cleared successfully").
Options:
-y, --yes- Skip confirmation prompt--dry-run- Show cache stats that would be cleared without performing the operation
Enable cache (displays: "✓ Cache enabled").
Options:
--max-size <size>- Maximum cache size (e.g.,100M,1G,500MB)
Examples:
# Enable cache with default size
deepl cache enable
# Enable cache with custom size
deepl cache enable --max-size 100M
deepl cache enable --max-size 1GNote: You can also configure max cache size separately: deepl config set cache.maxSize <bytes>
Disable cache (displays: "✓ Cache disabled").
Manage CLI configuration.
deepl config <SUBCOMMAND>List all configuration values (same as get without arguments).
Options:
--format <format>- Output format:text,json(default:json)
Examples:
# JSON output (default)
deepl config list
# Human-readable key=value pairs
deepl config list --format text
# auth.apiKey = "xxxx...xxxx"
# cache.enabled = true
# cache.maxSize = 1073741824Get a specific configuration value, or all values if key is omitted.
Arguments:
key(optional) - Configuration key in dot notation (e.g.,cache.maxSize,auth.apiKey)
Options:
--format <format>- Output format:text,json(default:json)
Examples:
# Get all configuration (JSON)
deepl config get
# Get specific value
deepl config get cache.maxSize
# Human-readable output
deepl config get cache.maxSize --format text
# cache.maxSize = 1073741824Set a configuration value.
Arguments:
key- Configuration key in dot notationvalue- Value to set
Examples:
deepl config set cache.maxSize 52428800
deepl config set defaults.formality moreReset configuration to defaults (keeps API key).
Options:
-y, --yes- Skip confirmation prompt
Show API usage statistics.
deepl usage [OPTIONS]Display your DeepL API character usage and remaining quota. Helps you monitor consumption and avoid exceeding your account limits.
--format FORMAT- Output format:text,json,table(default:text). In non-TTY output,tablefalls back totextwith aWARNline on stderr.
# Show usage statistics (Free account)
deepl usage
# Character Usage:
# Used: 123,456 / 500,000 (24.7%)
# Remaining: 376,544
# Pro account output (additional sections)
deepl usage
# Character Usage:
# Used: 2,150,000 / 20,000,000 (10.8%)
# Remaining: 17,850,000
#
# Billing Period:
# 2025-04-24 to 2025-05-24
#
# API Key Usage:
# Used: 1,880,000 / unlimited
#
# Product Breakdown:
# translate: 900,000 characters (API key: 880,000)
# write: 1,250,000 characters (API key: 1,000,000)
# speech_to_text: 12m 34s (API key)Output Fields:
- Used: Number of characters translated this billing period
- Limit: Total character limit for your account
- Percentage: Usage as a percentage of total quota
- Remaining: Characters remaining in your quota
Pro accounts show additional fields:
- Billing Period: Start and end dates of the current billing cycle
- API Key Usage: Characters used by this specific API key (vs. the whole account)
- Product Breakdown: Per-product character counts (translate, write) and durations (speech_to_text) with the API-key-level figure alongside. A duration-billed product shows
(API key)when the response carries no account-wide total, rather than repeating the key's own figure as if it were one.
Notes:
- Usage resets monthly for most accounts
- Free tier: typically 500,000 characters/month
- Pro accounts: varies by subscription level; additional sections shown automatically
- Shows warning when usage exceeds 80%
List supported source and target languages.
deepl languages [OPTIONS]Display all 125 supported languages grouped by category. Core and regional languages are shown first, followed by extended languages. When an API key is configured, language names are fetched from the DeepL API; otherwise, the local language registry is used.
You can filter to show only source languages, only target languages, or both (default).
--source, -s- Show only source languages--target- Show only target languages--features- Show which features each language supports (requires an API key; the local registry carries no feature data)--format FORMAT- Output format:text,json,table(default:text). In non-TTY output,tablefalls back totextwith aWARNline on stderr.
# Show all supported languages (both source and target)
deepl languages
# Source Languages:
# ar Arabic
# bg Bulgarian
# ...
# zh Chinese
#
# Extended Languages (quality_optimized only, no formality/glossary):
# ace Acehnese
# af Afrikaans
# ...
#
# Target Languages:
# ar Arabic [F]
# ...
# en-gb English (British)
# en-us English (American)
# ...
#
# Extended Languages (quality_optimized only, no formality/glossary):
# ace Acehnese
# ...
#
# [F] = supports formality parameter
# Show only source languages
deepl languages --source
# Show only target languages
deepl languages --target
# Show which features each language supports
deepl languages --target --features
# Target Languages:
# de German — formality, glossary, style rules, translation memory, auto detection
# pt Portuguese — formality, glossary, auto detection
# en-gb English (British) — glossary, style rules, translation memory
# ...
# Extended Languages (quality_optimized only, no formality/glossary):
# hi Hindi — auto detection
# th Thai — style rules, translation memory, auto detection
# ...
#
# All listed languages also support: tag handling.
# The same matrix as columns
deepl languages --target --features --format table
# Works without API key (shows local registry data)
deepl languages
# Note: No API key configured. Showing local language registry only.Output Format:
- Languages are grouped: core/regional first, then extended in a separate section
- Extended languages are annotated with "quality_optimized only, no formality/glossary"
- Target languages that support the
--formalityparameter are marked with[F](requires API key) - Language codes are left-aligned and padded for readability
Feature matrix (--features):
- Feature support comes from
GET /v3/languages; a feature is supported when the API reports it for that language - Which features are shown is derived from the response, not a fixed list. A feature only appears when its support differs across the languages listed; one supported by all of them is reported once as
All listed languages also support: ...instead of being repeated on every row - Because of that, the columns differ between listings:
auto detectionappears under--target(target-only variants lack it) but is uniform under--source - A feature that is not yet generally available shows its status instead of
yes, e.g.glossary (beta) --featuresreplaces the[F]shorthand, since formality is one of the reported features--format jsonincludes a rawfeaturesobject with each feature's status, but only when--featuresis passed
Where the language list comes from:
GET /v3/languagesis the authority on which languages exist. The CLI bundles a snapshot of it so that listing and validating languages works offline and without an API key- The snapshot is generated, not hand-maintained (
npm run generate:languages;npm run check:languagesreports drift). Tiers are derived from the response — glossary support separates extended from the rest, source usability separates core from regional — so they are not a separate judgement that can disagree with the API - Because the snapshot can lag the API, a well-formed language code it does not list is accepted and sent to the API, which accepts or rejects it authoritatively. Input that is not shaped like a language tag is still rejected locally, with a pointer to
deepl languages. This applies totranslate,syncand to language values in the config file - The listing itself is the union of the API response and the snapshot, so a language DeepL offers is never hidden even if the snapshot predates it
Notes:
- Source and target language lists differ: 11 regional variants (de-ch, de-de, en-gb, en-us, es-419, fr-ca, fr-fr, pt-br, pt-pt, zh-hans, zh-hant) are target-only
- Extended languages (82 codes) only support
quality_optimizedmodel type and do not support formality or glossary features - The API reports the same display name for a bare code and its explicit-region variant — both
deandde-deare "German", bothfrandfr-frare "French". The CLI mirrors the API rather than inventing distinct names; the code column distinguishes them - The extended tier is a coarser signal than the feature matrix: some extended languages do support style rules and translation memory even though they support neither formality nor glossary
- Without an API key, the command shows all languages from the local registry with a warning;
--featuresadditionally warns that it needs a key
Detect the language of text using DeepL API.
deepl detect [OPTIONS] [text]Detect the language of the given text. Under the hood, this command calls the DeepL translate API with a dummy target language and returns only the detected_source_language field from the response.
Text can be provided as a positional argument or piped via stdin.
--format <format>- Output format:text,json(default:text)
# Detect language of French text
deepl detect "Bonjour le monde"
# Detected language: French (fr)
# Detect language with JSON output
deepl detect "Hallo Welt" --format json
# {
# "detected_language": "de",
# "language_name": "German"
# }
# Pipe text via stdin
echo "Ciao mondo" | deepl detect
# Detected language: Italian (it)
# Use in a script
LANG=$(deepl detect "Hola" --format json | jq -r '.detected_language')
echo "$LANG" # esNotes:
- Requires an API key (the detection uses a translate API call)
- Each detection call consumes character quota (the text is translated to produce the detection)
- Very short text (single characters or words) may produce unreliable detection results
- Supports all 125 languages recognized by the DeepL API (core, regional, and extended)
Generate shell completion scripts for bash, zsh, or fish.
deepl completion <shell>shell- Shell type:bash,zsh,fish
# Generate and install bash completions
deepl completion bash > /etc/bash_completion.d/deepl
# Generate and install zsh completions
deepl completion zsh > "${fpath[1]}/_deepl"
# Generate and install fish completions
deepl completion fish > ~/.config/fish/completions/deepl.fish
# Or source directly in your current session:
source <(deepl completion bash)
eval "$(deepl completion zsh)"
deepl completion fish | sourceInteractive setup wizard for first-time users.
deepl initGuides through API key setup, default target language selection, and basic configuration. Validates the API key against the DeepL API before saving.
# Run the interactive setup wizard
deepl init
# Output:
# Welcome to DeepL CLI! Let's get you set up.
# Enter your DeepL API key: ****
# ✓ API key validated (DeepL API Free)
# Select default target language: es
# ✓ Configuration saved
# You're ready! Try: deepl translate "Hello" --to esManage API authentication.
deepl auth <SUBCOMMAND>Set your DeepL API key and validate it with the DeepL API.
Arguments:
api-key(optional) - Your DeepL API authentication key. If omitted, reads from stdin.
Options:
--from-stdin- Read API key from stdin--no-verify- Store the key without validating it against the API. Use on offline or proxied networks, where validation cannot reach the API and the key would otherwise be discarded. Exports ofDEEPL_API_KEYalso bypass validation entirely.
Examples:
# Pipe key from stdin (recommended - avoids exposing key in process listings)
echo "YOUR-API-KEY" | deepl auth set-key --from-stdin
# Read from file
deepl auth set-key --from-stdin < ~/.deepl-api-key
# Provide key as argument
deepl auth set-key YOUR-API-KEY-HERE
# ✓ API key saved and validated successfully
# Store without a network round-trip (offline, or behind an unconfigured proxy)
echo "YOUR-API-KEY" | deepl auth set-key --from-stdin --no-verify
# ✓ API key saved without validationSecurity Note: Prefer --from-stdin over passing the key as a command argument. Command arguments are visible to other users via process listings (ps aux).
Deprecation: Passing the API key as a positional argument is deprecated and will emit a warning. Use
--from-stdininstead for secure key input.
Show current API key (masked for security).
Output Format: API Key: abcd...xyz1 (first 4 and last 4 characters visible)
Examples:
deepl auth show
# API Key: 1234...abcd
deepl auth show
# No API key setClear stored API key from configuration.
Examples:
deepl auth clear
# ✓ API key removedManage DeepL style rules (Pro API only). Style rules carry a list of configured rule ids and optional custom instructions, and apply to translations via their style id.
deepl style-rules <SUBCOMMAND>List all available style rules.
Options:
--detailed- Show detailed information including configured rules and custom instructions--page NUMBER- Page number for pagination--page-size NUMBER- Number of results per page (1-25)--format FORMAT- Output format:text,json,table(default:text). In non-TTY output,tablefalls back totextwith aWARNline on stderr.
Examples:
deepl style-rules list
deepl style-rules list --detailed
deepl style-rules list --format json
deepl style-rules list --format table
deepl style-rules list --page 1 --page-size 10Create a new style rule list.
Options:
--name NAME- Style rule name (required)--language LANG- Target language (required)--rules JSON- Configured rules as a JSON object of category → settings (optional). The DeepL API models configured rules as a two-level dictionary; arrays are not accepted. See "Configured rules shape" below.--format FORMAT- Output format:text,json(default:text)
Examples:
deepl style-rules create --name "Corporate" --language en
deepl style-rules create --name "Québécois" --language fr \
--rules '{"punctuation":{"quotation_mark":"use_guillemets"}}'Configured rules shape:
The DeepL API expects configured_rules as a nested object — a category name maps to a settings object, and each setting maps to a string value. Empty rules are {}. Example for fr-CA:
{
"punctuation": {
"quotation_mark": "use_guillemets",
"spacing_and_punctuation": "do_not_use_space"
},
"spelling_and_grammar": {
"accents_and_cedillas": "use_even_on_capital_letters"
}
}The available categories, settings, and accepted values are defined by the DeepL API; consult DeepL's API documentation for the current rule schema.
Show a single style rule.
Arguments:
<id>- Style rule id (required)
Options:
--detailed- Include configured rules and custom instructions--format FORMAT- Output format:text,json(default:text)
Examples:
deepl style-rules show sr-abc123
deepl style-rules show sr-abc123 --detailed --format jsonUpdate a style rule — rename and/or replace configured rules. At least one of --name / --rules is required. When both are provided, the rename (PATCH) runs first, then the rules replacement (PUT /configured_rules).
Arguments:
<id>- Style rule id (required)
Options:
--name NAME- New name--rules JSON- Replace configured rules with a JSON object of category → settings (see "Configured rules shape" undercreate)--format FORMAT- Output format:text,json(default:text)
Examples:
deepl style-rules update sr-abc123 --name "Renamed"
deepl style-rules update sr-abc123 --rules '{"punctuation":{"quotation_mark":"use_guillemets"}}'
deepl style-rules update sr-abc123 --name "New" --rules '{"spelling_and_grammar":{"accents_and_cedillas":"use_even_on_capital_letters"}}'Delete a style rule. Prompts for confirmation on a TTY; use --yes to skip or --dry-run to preview.
Arguments:
<id>- Style rule id (required)
Options:
-y,--yes- Skip confirmation prompt--dry-run- Show what would be deleted without performing the operation
Examples:
deepl style-rules delete sr-abc123
deepl style-rules delete sr-abc123 --yes
deepl style-rules delete sr-abc123 --dry-runList custom instructions attached to a style rule. Synthesized from the detailed show response.
Arguments:
<style-id>- Style rule id (required)
Options:
--format FORMAT- Output format:text,json,table(default:text). In non-TTY output,tablefalls back totextwith aWARNline on stderr.
Examples:
deepl style-rules instructions sr-abc123
deepl style-rules instructions sr-abc123 --format json
deepl style-rules instructions sr-abc123 --format tableAdd a custom instruction to a style rule.
Arguments:
<style-id>- Style rule id (required)<label>- Instruction label, unique within the rule (required)<prompt>- Instruction prompt text (required)
Options:
--source-language LANG- Source language code (optional)--format FORMAT- Output format:text,json(default:text)
Examples:
deepl style-rules add-instruction sr-abc123 tone "Be formal"
deepl style-rules add-instruction sr-abc123 register "Use first person" --source-language enUpdate the prompt and/or source language of an existing custom instruction. The label cannot be changed (it is the identifier); rename by removing and re-adding.
Arguments:
<style-id>- Style rule id (required)<label>- Instruction label (required)<prompt>- New instruction prompt text (required)
Options:
--source-language LANG- Source language code (optional)--format FORMAT- Output format:text,json(default:text)
Examples:
deepl style-rules update-instruction sr-abc123 tone "Be friendlier"Remove a custom instruction from a style rule. Prompts for confirmation on a TTY.
Arguments:
<style-id>- Style rule id (required)<label>- Instruction label (required)
Options:
-y,--yes- Skip confirmation prompt--dry-run- Show what would be removed without performing the operation
Examples:
deepl style-rules remove-instruction sr-abc123 tone --yes
deepl style-rules remove-instruction sr-abc123 tone --dry-run- Style rules are Pro API only and datacenter-specific (EU and US rules don't cross).
- Use the style id with
deepl translate --style-id <uuid>to apply a rule at translation time. - Style rules force the
quality_optimizedmodel type. - Text-format output of stored user strings (rule names, instruction labels, instruction prompts) passes through a terminal-escape sanitizer to prevent injection of control characters from the API response. JSON-format output preserves raw strings (JSON-escaped at the encoding layer).
Admin API for managing API keys and viewing organization usage analytics. Requires an admin-level API key.
deepl admin <SUBCOMMAND>List all API keys in the organization.
Options:
--format FORMAT- Output format:text,json(default:text)
Examples:
# List all API keys
deepl admin keys list
# JSON output
deepl admin keys list --format jsonCreate a new API key.
Options:
--label LABEL- Label for the new key--format FORMAT- Output format:text,json(default:text)
Examples:
# Create a key with a label
deepl admin keys create --label "Production Key"
# Create a key without a label
deepl admin keys create
# JSON output
deepl admin keys create --label "CI Key" --format jsonDeactivate an API key (permanent, cannot be undone).
Arguments:
<key-id>- Key ID to deactivate (required)
Options:
-y, --yes- Skip confirmation prompt
Examples:
deepl admin keys deactivate abc123-def456
deepl admin keys deactivate abc123-def456 --yesRename an API key.
Arguments:
<key-id>- Key ID to rename (required)<label>- New label (required)
Examples:
deepl admin keys rename abc123-def456 "New Label"Set character usage limit for an API key.
Arguments:
<key-id>- Key ID (required)<characters>- Character limit (number or "unlimited") (required)
Options:
--stt-limit <milliseconds>- Speech-to-text milliseconds limit (number orunlimited)
Examples:
# Set a limit of 1 million characters
deepl admin keys set-limit abc123-def456 1000000
# Remove the limit
deepl admin keys set-limit abc123-def456 unlimited
# Set character limit and speech-to-text limit together
deepl admin keys set-limit abc123-def456 1000000 --stt-limit 3600000View organization usage analytics with per-product character breakdowns.
Options:
--start DATE- Start date in YYYY-MM-DD format (required)--end DATE- End date in YYYY-MM-DD format (required)--group-by GROUPING- Group results:key,key_and_day--format FORMAT- Output format:text,json(default:text)
Output includes:
- Total characters across all products
- Text translation characters — characters used for
/v2/translate - Document translation characters — characters used for document translation
- Text improvement characters — characters used for DeepL Write
Examples:
# View total usage for a date range
deepl admin usage --start 2024-01-01 --end 2024-12-31
# Group usage by key
deepl admin usage --start 2024-01-01 --end 2024-12-31 --group-by key
# Daily usage per key
deepl admin usage --start 2024-01-01 --end 2024-01-31 --group-by key_and_day
# JSON output
deepl admin usage --start 2024-01-01 --end 2024-12-31 --format jsonExample output:
Period: 2024-01-01 to 2024-01-31
Total Usage:
Total: 10,000
Translation: 7,000
Documents: 2,000
Write: 1,000
Per-Key Usage (2 entries):
Staging Key
Total: 6,000
Translation: 4,000
Documents: 1,500
Write: 500
Production Key
Total: 4,000
Translation: 3,000
Documents: 500
Write: 500
- Admin API endpoints require an admin-level API key (not a regular developer key)
- Key deactivation is permanent and cannot be undone
- Usage analytics show per-product character breakdowns (translation, documents, write)
- The
--group-byoption provides granular breakdowns for cost allocation
Configuration file location:
The CLI resolves configuration and cache paths using the following priority order:
| Priority | Condition | Config path | Cache path |
|---|---|---|---|
| 1 | DEEPL_CONFIG_DIR set |
$DEEPL_CONFIG_DIR/config.json |
$DEEPL_CONFIG_DIR/cache.db |
| 2 | ~/.deepl-cli/ exists |
~/.deepl-cli/config.json |
~/.deepl-cli/cache.db |
| 3 | XDG env vars set | $XDG_CONFIG_HOME/deepl-cli/config.json |
$XDG_CACHE_HOME/deepl-cli/cache.db |
| 4 | Default | ~/.config/deepl-cli/config.json |
~/.cache/deepl-cli/cache.db |
Existing ~/.deepl-cli/ installations continue to work with no changes needed.
{
"auth": {
"apiKey": "your-api-key"
},
"api": {
"baseUrl": "https://api.deepl.com",
"usePro": true
},
"defaults": {
"sourceLang": null,
"targetLangs": [],
"formality": "default",
"preserveFormatting": true
},
"cache": {
"enabled": true,
"maxSize": 1073741824,
"ttl": 2592000
},
"output": {
"format": "text",
"verbose": false,
"color": true
},
"watch": {
"debounceMs": 500,
"autoCommit": false,
"pattern": "*.md"
},
"tms": {
"allowedServers": []
}
}Configuration Notes:
baseUrl— when set to a custom/regional endpoint (e.g.https://api-jp.deepl.com), it overrides all auto-detection. Standard DeepL URLs (api.deepl.com,api-free.deepl.com) are treated as tier defaults and do not override key-based auto-detection. By default, the endpoint is auto-detected from the API key: keys ending with:fxuse the Free API (api-free.deepl.com), all others use the Pro API (api.deepl.com). TheuseProflag serves as a backward-compatible fallback for non-:fxkeys.tms.allowedServers— hostnames approved as TMS destinations for an environment-suppliedTMS_API_KEY/TMS_TOKEN. Empty by default, so no destination is trusted implicitly. Entries must be bare hostnames (no scheme, port, path, or wildcard) because they are matched against a parsed URL hostname, exactly and case-insensitively — a listedexample.comdoes not approvetms.example.com. Set it withdeepl config set tms.allowedServers a.example.com,b.example.com; a single host is still stored as a one-element array. Seesync pushfor how the gate behaves.- Most users configure settings via
deepl config setcommand rather than editing the file directly.
Translations, i18n keys, glossary entries and API error messages can all contain bytes the CLI did not author. Terminal control sequences hidden in that text can set the window title, write the clipboard (OSC 52), erase the screen, or forge a plausible-looking result line. The CLI neutralizes them, replacing each with ?:
| Stream | When | Behavior |
|---|---|---|
| stderr (progress, warnings, errors) | Always | Control sequences replaced. Non-TTY stderr is still rendered by CI log viewers that interpret ANSI. |
| stdout, when it is a terminal | Always | Control sequences replaced. |
| stdout, when redirected to a file, a pipe or command substitution | Never | Byte-for-byte identical to what the API returned, so deepl translate ... > out.txt is lossless. |
Report lines that interpolate untrusted values (for example the key in deepl sync validate) |
Always | Control sequences replaced whether or not stdout is a terminal, since these are diagnostics, not data. |
Colour (SGR) sequences are preserved so deepl output stays readable; use NO_COLOR to turn colour off. Tabs, newlines, carriage returns and Unicode formatting characters that are legitimate translation content — zero-width joiners, and the bidi marks used in Arabic, Hebrew and Persian text — are never altered on stdout.
deepl sync export writes XLIFF rather than terminal output. There, tab, newline and carriage return are emitted as the character references 	, and so a key survives XML attribute-value normalization unchanged, and the remaining C0 control characters — which XML 1.0 cannot represent at all — are replaced with ?.
Set your API key via environment variable.
export DEEPL_API_KEY="your-api-key"
deepl translate "Hello" --to esOverride config and cache directory. Takes highest priority over all other path resolution.
export DEEPL_CONFIG_DIR="/custom/path"Override XDG config base directory (default: ~/.config). Config is stored at $XDG_CONFIG_HOME/deepl-cli/config.json. Only used when DEEPL_CONFIG_DIR is unset and legacy ~/.deepl-cli/ does not exist.
export XDG_CONFIG_HOME="$HOME/.config"Override XDG cache base directory (default: ~/.cache). Cache is stored at $XDG_CACHE_HOME/deepl-cli/cache.db. Only used when DEEPL_CONFIG_DIR is unset and legacy ~/.deepl-cli/ does not exist.
export XDG_CACHE_HOME="$HOME/.cache"Disable colored output.
export NO_COLOR=1Force colored output even when the terminal doesn't appear to support it. Note: NO_COLOR takes priority if both are set. Useful in CI environments.
export FORCE_COLOR=1When set to dumb, disables colored output and progress spinners. This is automatically set by some CI environments and editors.
export TERM=dumbRoute outbound DeepL API requests through an HTTP proxy. Accepts a full URL including optional username:password@ credentials. Also recognized as lowercase http_proxy.
export HTTP_PROXY="http://proxy.example.com:3128"Route outbound DeepL API requests through an HTTPS proxy. Takes precedence over HTTP_PROXY when both are set. Also recognized as lowercase https_proxy.
export HTTPS_PROXY="http://proxy.example.com:3128"Comma-separated list of hosts that bypass HTTP_PROXY / HTTPS_PROXY. Standard semantics apply: * bypasses everything, a leading dot or *. matches subdomains, and an entry may carry a host:port that must agree with the target port. Also recognized as lowercase no_proxy.
export NO_PROXY="localhost,127.0.0.1,.internal.example.com"API key used by deepl sync push and deepl sync pull to authenticate against the external translation management system configured under tms.server in .deepl-sync.yaml. See docs/SYNC.md for setup details.
export TMS_API_KEY="your-tms-api-key"Bearer token alternative to TMS_API_KEY. Used by deepl sync push and deepl sync pull when the configured TMS server expects token-based auth. See docs/SYNC.md for setup details.
export TMS_TOKEN="your-tms-token"Every deepl command returns a specific exit code so CI/CD pipelines and shell scripts can react programmatically to failure modes. This appendix is the single source of truth for every code the CLI emits; per-command sections above surface exit codes inline where a flag has a code-specific contract (for example, sync --frozen exits 10 on drift).
Exit codes come from three paths:
- Typed errors thrown in services, API clients, and commands subclass
DeepLCLIError, each carrying a fixedexitCode. The CLI's top-levelhandleErroruses that value directly. - HTTP responses from the DeepL API are mapped to typed errors inside the HTTP client (401 →
AuthError, 429 →RateLimitError, 456 →QuotaError, 503 →NetworkError). - Untyped errors (plain
Errorinstances that escape service boundaries) are classified by message against a curated list of substrings. When nothing matches, the CLI returns1(general error).
Retryable codes are 3 (rate limit) and 5 (network); everything else should be treated as fatal by calling scripts.
| Code | Name | Meaning | Retryable |
|---|---|---|---|
| 0 | Success | Command completed successfully | N/A |
| 1 | GeneralError | Unclassified failure (error escaped every typed handler and matched no classifier heuristic) | No |
| 2 | AuthError | Authentication failed or API key missing | No |
| 3 | RateLimitError | Rate limit exceeded (HTTP 429) | Yes |
| 4 | QuotaError | Monthly character quota exhausted (HTTP 456) | No |
| 5 | NetworkError | Connection timeout, refused, reset, truncated response body, or 503 Service Unavailable | Yes |
| 6 | InvalidInput | Missing or malformed arguments, unsupported format | No |
| 7 | ConfigError | Configuration file or value invalid | No |
| 8 | CheckFailed | A check-style command found actionable issues | No |
| 9 | VoiceError | Voice API unavailable or session failed | No |
| 10 | SyncDrift | sync --frozen detected translations out of date |
No |
| 11 | SyncConflict | sync resolve could not auto-resolve lockfile conflicts |
No |
| 12 | PartialFailure | deepl sync completed with at least one failed key, or a deepl watch session ended with a failed translation or auto-commit |
Yes (per-locale retry) |
Command completed without error. Every deepl subcommand uses this code on success. Do not rely on stdout being non-empty — successful commands may emit only a status line (e.g., deepl cache clear).
Unclassified failure: emitted when an error escapes every typed handler and matches none of the message-classification heuristics in src/utils/exit-codes.ts. Any command can surface this. Treat it as "unknown failure — inspect stderr." Typically indicates an unexpected CLI bug or an error from a third-party dependency.
CI branching. Exit 1 now means exactly "unclassified failure." Partial sync failure has its own code — see #12 — PartialFailure. A CI script can safely treat exit 1 as "CLI crashed, investigate" without misreading a partial-locale outcome.
Authentication failed or no API key is available. Emitted by:
deepl auth set-keywhen the key cannot be validated- Every command that touches the API (
translate,write,voice,glossary,usage,sync,tm list,admin, etc.) whenDEEPL_API_KEYis unset and no key is in the config file - HTTP 401/403 responses from the DeepL API
Remediation: run deepl init or deepl auth set-key <your-api-key>, or export DEEPL_API_KEY.
Too many requests in too short a window. Emitted when the DeepL API returns HTTP 429 from any endpoint (/v2/translate, /v2/write, /v3/glossaries, /v3/translation_memories, document upload/download, voice session). The CLI honors the Retry-After header when the server sends one, otherwise it falls back to exponential backoff for in-process retries. When all internal retries are exhausted, this code is returned to the caller.
Remediation: wait and retry, or lower concurrency with --concurrency (batch translation, sync).
Monthly character quota has been exhausted (HTTP 456). Emitted by any command that consumes characters: translate, write, voice, and sync. Unlike rate limits, quota is not retryable within the billing window.
Remediation: run deepl usage to see remaining characters, or upgrade the plan at https://www.deepl.com/pro.
Connection-layer failure or transient server outage. Covers TCP errors (ECONNREFUSED, ENOTFOUND, ECONNRESET, ETIMEDOUT, socket hang up), timeouts, proxy misconfigurations, and HTTP 503 responses. Also emitted for malformed or empty API responses thrown from src/api/translation-client.ts and src/api/write-client.ts, and from document/structured-file translation when the polling response is unparseable.
A response that came back without the placeholder tokens the CLI substituted for your variables counts as malformed here too: translate names the variables it lost ({username}, %s) and writes nothing rather than leaving its own internal tokens in your text. A directory run fails only the affected files and reports each one in the summary, so its exit code follows the usual batch mapping (1 when nothing translated, 12 when some did).
A response the endpoint cuts off mid-body is exit 5 as well, even though the status line said 200. The HTTP status is what decides it: a rejection carrying a 2xx response means the exchange was accepted and then failed while the body was read — truncated, or undecodable — so the message names the status and what went wrong (Network error: the API answered HTTP 200 but its response body did not arrive intact: stream has been aborted) rather than reporting an API error your input could fix. The replay policy is unchanged by this. A 200 says the server accepted, and may have billed, the request, so a truncated response to a POST (translate, write, a sync batch) is still sent exactly once; an idempotent GET is still replayed up to --max-retries times.
A document upload response whose document_id is not a document identifier is rejected here as well. That value is interpolated into the path of every follow-up request (POST /v2/document/{id}, POST /v2/document/{id}/result), so an ID containing .. or / would send this client's own requests to a different route on the endpoint. Anything outside [A-Za-z0-9_-]+ is refused before the next request is sent, the ID is quoted back in the message, and no output file is written.
A TMS that cannot be reached is exit 5 too, however it fails to answer. deepl sync push / pull used to report a refused connection or an unresolvable hostname as Error: fetch failed at exit 1 — the unclassified code — because fetch rejects with a bare TypeError and puts the errno on cause. Both now name the request and the code (TMS request failed: PUT https://tms.example.com/api/projects/p/keys/greeting: ECONNREFUSED), matching the client-side timeout, which was already exit 5. The replay policy is unchanged by this: retry eligibility is decided before the error is classified, so a refusal is still retried up to tms.retry.max_attempts and an unresolvable name still fails on the first attempt.
Remediation: check connectivity and HTTPS_PROXY / HTTP_PROXY env vars, then retry.
User-supplied input was rejected by client-side validation before any API call. This is the most commonly emitted non-zero code. Sites include:
translate: empty text, missing--to, unsupported file format, invalid--tm-thresholdrange,--tm-thresholdwithout--translation-memory,--translation-memorywithout--from, mutually exclusive flagswrite: empty text,--styleand--toneused together,--fixwithout a file path, unsupported language for the Write APIvoice: unsupported target or source language code, unsupported--content-type, more than 5 target languages. A plan that does not include the Voice API is exit 9, not 6 — see 9 — VoiceErrorglossary: missing name/entries, entry not found on deletesync:--frozencombined with--force,--watchcombined with--force,--forcewithout--yesanywhere the confirmation prompt cannot be shown (piped stdin, cron, a git hook,--no-input,CI=true), missing.deepl-sync.yaml(beforeConfigErrorhands off)hooks,watch,detect,admin,init,completion,cache: argument parsing, unknown subcommand, bad path, bad size
Remediation: re-read the command's --help and the relevant section of this API reference.
The configuration file or a configuration value is invalid. Emitted by:
deepl config setwith a key that is not in the schema, or a value that fails validation (invalid language code, invalid formality, invalid output format, non-positive cache size, non-HTTPSbaseUrl, path-traversal attempts)deepl config getwith a malformed key- Any command that loads the config file when the file fails to parse, is missing a required field, or specifies an unsupported version
syncwhen.deepl-sync.yamlis missing required fields, has invalid locales, or declares an unsupported versionsync push/sync pullwhen the remote TMS returns 401/403 (surfaced asConfigErrorwith a hint to checkTMS_API_KEY/TMS_TOKENand the relevant YAML fields)sync push/sync pullwhen.deepl-sync.yamlnames atms.serverhostname that is not intms.allowedServersand the approval prompt is unavailable (--no-input, non-TTY) or declinedglossarywhen a named glossary cannot be resolved
Remediation: run deepl config get to inspect the current config, or edit the file directly and re-run.
A check-style command ran successfully but found actionable issues. Emitted by:
deepl write --check <text|file>anddeepl correct --check <text|file>when the Write API would suggest changes (needsImprovement === true). Under--format jsonthe same run also emits theok: truecheck result payload on stdout, so the count and the file are readable without parsing prose. Exit is soft here —process.exitCodeis set so cleanup still runsdeepl sync validatewhen validation surfaces one or moreerror-severity issues: a placeholder present in the source and missing from the translation, a mismatched ICU bracket nesting depth (icu-brackets), or a translated ICU argument name, format type or selector keyword (icu-structure) — plus a target file on disk that could not be read, reported under anunusable_targetcheck while every other locale is still validated. Issues the validator raises atwarnseverity never affect the exit code: missing HTML tags (html-tags), extra placeholders the source does not have, a translation identical to its source (untranslated), and an outlying length ratio (length-ratio); a run with warnings and no errors exits 0. Exit is hard here —process.exit(8)is called as soon as the report is written, so do not rely on later cleanup running
This code is specifically designed for CI use: a check step can block a merge without requiring try/catch wrappers in the calling script. It does not indicate a CLI failure.
Voice API call failed for a reason other than authentication, rate limiting, or generic network trouble. Emitted by:
deepl voicewhen the plan does not include the Voice API (pre-flight check in the voice client)- Voice streaming URL validation failures (
src/api/voice-client.ts: non-wss://scheme, unparseable URL, disallowed host) - Voice session lifecycle errors (failed to open, unexpected close)
deepl voicewhen the stream ends with the source transcribed but no translation for a requested target language
Remediation: confirm Pro/Enterprise plan, verify the session configuration, and retry.
deepl sync --frozen (alias --ci) detected that lockfile-tracked translations are out of date with the source strings. Emitted only from src/cli/commands/sync/register-sync-root.ts when the sync run completes and result.driftDetected === true. No other command returns this code.
Use this in CI to fail a pull request when a contributor edits source strings without running deepl sync. Exit is soft — process.exitCode is set so in-flight writes, auto-commit steps, and any --watch event loop drain cleanly before the process exits.
Remediation: run deepl sync locally and commit the updated translations and lockfile.
deepl sync resolve found git merge conflict markers in .deepl-sync.lock but could not automatically resolve every region. Emitted only by sync resolve when auto-resolution leaves residual conflict markers or produces invalid JSON (typically because the conflict region split a JSON entry in two and neither side parses in isolation).
Distinct from exit code 1 (GeneralError): a pipeline that runs deepl sync resolve in CI can now branch on 11 to route the lockfile to a human for manual merge without masking real CLI crashes under the same code.
Remediation: open .deepl-sync.lock, resolve the remaining <<<<<<< / ======= / >>>>>>> regions by hand, save, and run deepl sync to fill any gaps.
deepl sync completed, but at least one key failed to translate. This covers both a locale that failed entirely and a locale that translated some of its keys and failed the rest — a failed key is absent from the written target file, so any failure count above zero means the run did not produce a complete result. The successful translations' target files and lockfile entries are written; a locale that failed entirely has its file left untouched. Emitted only by deepl sync (the root command).
A key whose translation failed placeholder/ICU validation counts as failed here too: it is withheld from the target file rather than written corrupt. See validation in the sync configuration reference. With validation.fail_on_error: true the same run raises ValidationError (exit 6) instead.
A key that translated successfully but that the target file's format could not be given a slot for also counts as failed. Every key is read back out of the content just written before the lockfile is updated, so the run reports it rather than recording a translation the file does not contain. See A string added after the first sync.
A locale whose target file is on disk but could not be read or parsed fails here as well, and fails before any translation is requested, so nothing is billed for it and the file is not written. That file is the only copy of its locale's translations, since the lockfile records source hashes rather than translated text. See A target file that cannot be read.
deepl watch uses the same code when the session ends: on Ctrl+C (SIGINT/SIGTERM) it exits 12 rather than 0 if it recorded any failed translation or any failed --auto-commit, so a script driving a watch session can tell a clean run from one that lost work. The counts are printed beside the translation total before the process exits.
Authentication failures (401/403) abort the entire run and surface as exit code 2 (AuthError) instead of 12. Network / rate-limit / quota failures bubble up as 5 / 3 / 4 respectively. Code 12 specifically means "the run proceeded far enough to attempt per-locale work, and the result was mixed."
Retry model. Re-running deepl sync (optionally with --locale <failed,comma,separated>) will retry only the locales whose lockfile entries weren't written. This is the canonical CI recovery path: branch on $? -eq 12, inspect the per-locale error report, and re-run with the failed locales as the filter.
The paired typed error is SyncPartialFailureError in src/utils/errors.ts; the JSON error envelope emits code: "SyncPartialFailure" to match the SyncConflict/SyncDrift naming convention.
When an error reaches the top-level handler without being a DeepLCLIError, the CLI inspects the error message (lowercased) and maps it to a code. These substring matches live in classifyByMessage in src/utils/exit-codes.ts:
- 2 — AuthError:
authentication failed,invalid api key,api key not set,api key is required - 3 — RateLimitError:
rate limit exceeded,too many requests,\b429\b - 4 — QuotaError:
quota exceeded,character limit reached,\b456\b - 5 — NetworkError:
econnrefused,enotfound,econnreset,etimedout,socket hang up,network error,network timeout,connection refused,connection reset,connection timed out,service temporarily unavailable,\b503\b - 9 — VoiceError:
voice api,voice session - 7 — ConfigError (checked before 6 because config messages may contain "invalid"):
config file,config directory,configuration file,configuration error,failed to load config,failed to save config,failed to read config - 6 — InvalidInput:
cannot be empty,file not found,path not found,directory not found,not found in glossary,unsupported format,unsupported language,not supported for,not supported in,invalid target language,invalid source language,invalid language code,invalid glossary,invalid hook,invalid url,invalid size,is required,cannot specify both - 1 — GeneralError: anything not matched above
The list is in the order the classifier actually tests, which is what determines the code when a message matches more than one pattern.
API error messages include the DeepL X-Trace-ID header when available, which is useful when contacting DeepL support:
deepl translate "Hello" --to es
# Error: Authentication failed: Invalid API key (Trace ID: abc123-def456-ghi789)The trace ID is also accessible programmatically via DeepLClient.lastTraceId after any API call.
Retry only on retryable codes (3 and 5):
#!/bin/bash
deepl translate "Hello" --to es
case $? in
0) echo "Success" ;;
3|5) sleep 5 && deepl translate "Hello" --to es ;;
*) echo "Non-retryable error ($?)"; exit $? ;;
esacFail a CI build when translations drift:
deepl sync --frozen || {
code=$?
[ $code -eq 10 ] && echo "::error::Translation drift — run 'deepl sync' locally" >&2
exit $code
}Block a merge when deepl write --check flags a file:
deepl write --check README.md
[ $? -eq 8 ] && echo "Write suggests improvements; run: deepl write --fix README.md" >&2- Migrating from 1.x to 2.0.0 — removed flags, exit codes that moved, output that moved to stdout
- Sync configuration reference
- Troubleshooting
- Examples
- DeepL API Documentation
Last Updated: August 9, 2026 DeepL CLI Version: 2.0.0