A next-generation command-line interface for DeepL translation and writing enhancement
DeepL CLI is a comprehensive, developer-friendly command-line tool that integrates DeepL's powerful translation API and cutting-edge Write API for grammar and style enhancement. Built with TypeScript and designed for modern development workflows.
- 🌍 Translation - High-quality translation using DeepL's next-gen LLM
- 🔄 Continuous Localization (
deepl sync) - Incremental i18n file sync with 11 format parsers - 📄 Document Translation - Translate PDF, DOCX, PPTX, XLSX with formatting preservation
- 🎙️ Voice Translation (Pro/Enterprise) - Real-time speech translation via WebSocket streaming (Voice API)
- 👀 Watch Mode - Real-time file watching with auto-translation
- ✍️ Writing Enhancement - Grammar, style, and tone suggestions (DeepL Write API)
- ✅ Spelling & Grammar Correction - Fix mistakes without rewording (
deepl correct) - 💾 Smart Caching - Local SQLite cache that evicts oldest entries first
- 🎯 Context-Aware - Preserves code blocks, variables, and formatting
- 📦 Batch Processing - Translate multiple files with parallel processing
- 📖 Glossaries - Single-target and multilingual glossary management (v3 Glossary API)
- 🗂️ Translation Memories - Reuse approved translations in translate and sync runs
- 🖋️ Style Rules (Pro) - Create, apply, and manage style rules and custom instructions
- 💰 Cost Transparency - Track actual billed characters for budget planning
- 🔧 Developer Workflows - Git hooks, CI/CD integration
- 🗝️ Admin API - Organization key management and usage analytics
- 🔒 Privacy-First - Local caching, no telemetry, secure key storage
For security policy and vulnerability reporting, see SECURITY.md.
- Installation
- Upgrading from 1.x
- Quick Start
- Global Options
- Usage
- Core Commands: Translation | Writing Enhancement | Spelling and Grammar Correction | Voice Translation
- Resources: Glossaries | Translation Memories
- Workflow: Continuous Localization (sync) | Watch Mode | Git Hooks
- Configuration: Setup Wizard | Authentication | Configure Defaults | Proxy Configuration | Retry and Timeout Configuration | Cache Management | Style Rules
- Information: Usage Statistics | Language Detection | Languages | Shell Completion
- Administration: Admin API
- Development
- Architecture
- Testing
- Documentation
- Environment Variables
- Security & Privacy
- Contributing
- License
Prerequisite: Node.js 24.15.0 or later. The cache uses Node's built-in
node:sqlitemodule — no native compilation, no build toolchain needed. 24.15.0 is the release wherenode:sqlitestopped being experimental; Homebrew installs a suitable Node for you.
brew install deepl/tap/deeplThis installs the deepl command and everything it needs, including Node.
npm install -g @deepl/cli
# Verify installation
deepl --versionThe package is scoped, but the command is just deepl.
# Clone the repository
git clone https://github.com/DeepL/deepl-cli.git
cd deepl-cli
# Install dependencies
npm install
# Build the project
npm run build
# Link for global usage
npm link
# Verify installation
deepl --version2.0.0 requires Node 24.15.0, renames the package to @deepl/cli, removes several
deprecated flags, and changes a number of exit codes and output shapes that scripts
read. If you have a pipeline built on 1.x, read
docs/MIGRATION.md before upgrading — it lists every
change that needs action, with a before/after for each.
Sign up for a DeepL API account and get your authentication key.
Use the interactive setup wizard:
deepl initOr set your API key directly:
# Piping from stdin keeps the key out of process listings and shell history
echo "YOUR_API_KEY" | deepl auth set-key --from-stdin
# Passing it as an argument also works, but is deprecated and warns:
# other users can read it via `ps`
deepl auth set-key YOUR_API_KEYOr use an environment variable:
export DEEPL_API_KEY=YOUR_API_KEYdeepl translate "Hello, world!" --to es
# Output:
# ¡Hola, mundo!
# Short alias: t (and w for write)
deepl t "Hello, world!" --to esDeepL CLI supports global flags that work with all commands:
| Flag | Short | Description |
|---|---|---|
--version |
-V |
Show version number |
--quiet |
-q |
Suppress non-essential output |
--verbose |
-v |
Show extra information |
--config FILE |
-c |
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 |
Retries per failed request (default 3) |
The --verbose (or -v) flag shows extra information such as detected source language, timing, and cache status. Useful for debugging and understanding translation behavior.
$ deepl --verbose translate "Hello" --to esThe --quiet (or -q) flag suppresses all non-essential output, showing only errors and essential results. Perfect for scripts, CI/CD pipelines, and automation.
# Normal mode
$ deepl translate "Hello" --to es
Hola
# Quiet mode - same result, without the status lines on stderr
$ deepl --quiet translate "Hello" --to es
Hola
# Suppress progress indicators in batch operations
$ deepl --quiet translate docs/ --to es --output docs-es/
# Shows only final statistics, no spinners or progress updatesWhat's suppressed in quiet mode:
- ❌ Informational messages (
Note: "zz-zz" is not in the bundled language list; deferring to the API.) - ❌ Success confirmations (
✓ Cache enabled) - ❌ Warnings — including the notice that
--api-urlpoints somewhere that is not a DeepL endpoint, and thefile exceeds 100 KiB, using document API insteadfallback - ❌ Progress spinners and status updates
- ❌ Decorative output
What's always shown:
- ✅ Errors, with their
Suggestion:line - ✅ Essential command output (translation results, JSON data, statistics)
Warnings are suppressed along with everything else, so run a new pipeline once without --quiet before trusting it — that is where you would see a misdirected --api-url or a silent fallback to the document API.
Use cases:
- CI/CD pipelines: Clean output for log parsing
- Scripting: Extract just the translation result
- Automation: Reduce noise in automated workflows
- Parsing: Easier to parse machine-readable output
# Example: Use in scripts
TRANSLATION=$(deepl --quiet translate "Hello" --to es)
echo "Result: $TRANSLATION" # Result: Hola
# Example: CI/CD pipeline
deepl --quiet translate docs/ --to es,fr,de --output i18n/
# Returns exit code 0 on success, shows only errors if they occurSee docs/API.md#global-options for complete documentation.
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.The --config (or -c) 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 -c /path/to/test-config.json usageUse cases:
- Multiple API keys: Switch between free and paid accounts
- Project isolation: Different settings per project (glossaries, formality defaults, etc.)
- Team configurations: Share standardized configs via version control
- Environment separation: Separate configs for dev/staging/production
- Testing: Use test configurations without affecting default settings
Precedence: --config replaces the config file only. The cache location is unaffected — it still follows DEEPL_CONFIG_DIR > legacy ~/.deepl-cli/ > XDG directories (see below).
The CLI follows the XDG Base Directory Specification:
| 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.
See docs/API.md#global-options for more details.
# Simple translation
deepl translate "Hello world" --to ja
# こんにちは世界
# Specify source language explicitly
deepl translate "Bonjour" --from fr --to en
# Hello
# Multiple target languages (each result is prefixed with its language)
deepl translate "Good morning" --to es,fr,de
# [es] Buenos días
# [fr] Bonjour
# [de] Guten Morgen
# Read from stdin
echo "Hello world" | deepl translate --to es
cat input.txt | deepl translate --to jaText Files: .txt, .md, .html, .htm, .srt, .xlf, .xliff
Structured Files (i18n): .json, .yaml, .yml
Structured files are parsed to extract only string values. Keys, nesting, non-string values (numbers, booleans, null), indentation, and YAML comments are all preserved. Ideal for translating i18n locale files.
# Translate a JSON locale file
deepl translate en.json --to es --output es.json
# Translate a YAML locale file (comments preserved)
deepl translate en.yaml --to de --output de.yaml
# Multiple targets
deepl translate locales/en.json --to es,fr,de --output locales/Smart Caching for Text Files:
Small text-based files (under 100 KiB) automatically use the cached text translation API for faster performance and reduced API calls. Larger files automatically fall back to the document translation API (not cached).
- Cached formats:
.txt,.md,.html,.htm,.srt,.xlf,.xliff(files under 100 KiB only) - Structured formats:
.json,.yaml,.yml— parsed and translated via batch text API (no size limit) - Large file fallback: Files ≥100 KiB use document API (not cached, always makes API calls)
- Binary formats:
.pdf,.docx,.pptx,.xlsxalways use document API (not cached) - Performance: Only small text files (<100 KiB) benefit from instant cached translations
- Cost savings: Only small text files avoid repeated API calls
# Single file translation (uses cache for small text files)
deepl translate README.md --to es --output README.es.md
# Translated README.md -> README.es.md
# Output directory (names the file <name>.<lang>.<ext>)
deepl translate README.md --to es --output ./translated/
# Translated README.md -> translated/README.es.md
# Multiple target languages (creates README.es.md, README.fr.md, etc.)
deepl translate docs.md --to es,fr,de --output ./translated/
# Translated docs.md to 3 languages:
# [es] ./translated/docs.es.md
# [fr] ./translated/docs.fr.md
# [de] ./translated/docs.de.md
# With code preservation (preserves code blocks in markdown)
deepl translate tutorial.md --to ja --output tutorial.ja.md --preserve-code
# Large text file (over 100 KiB) - automatic fallback with warning
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.txtSupported Document Formats: .pdf, .docx, .doc, .pptx, .xlsx, .html, .htm, .txt, .srt, .xlf, .xliff, .jpg, .jpeg, .png
Translate complete documents while preserving formatting, structure, and layout:
# Translate PDF document
deepl translate document.pdf --to es --output document.es.pdf
# Uploading document...
# Document queued for translation...
# Translating document (est. 5s remaining)...
# Downloading translated document...
# ✔ Document translated successfully!
# Translated document.pdf -> document.es.pdf
# Billed characters: 1,234
# Translate PowerPoint presentation
deepl translate presentation.pptx --to fr --output presentation.fr.pptx
# Translate Excel spreadsheet
deepl translate report.xlsx --to de --output report.de.xlsx
# Translate HTML file
deepl translate website.html --to ja --output website.ja.html
# With formality setting
deepl translate contract.pdf --to de --formality more --output contract.de.pdf
# Specify source language
deepl translate document.pdf --from en --to es --output document.es.pdf
# Apply a glossary (needs a source language: --from, or defaults.sourceLang)
deepl translate report.docx --from en --to de --glossary tech-terms --output report.de.docx
# 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-overrides
# Convert PDF to DOCX during translation (ONLY supported conversion)
deepl translate document.pdf --to es --output-format docx --output document.es.docx
# Translates PDF to Spanish and converts to editable Word format
# Note: DeepL API only supports PDF → DOCX conversion
# All other format conversions (DOCX→PDF, HTML→TXT, etc.) are NOT supported
# See examples/06-document-format-conversion.sh for detailsDocument Translation Features:
- ✅ Preserves Formatting - Maintains fonts, styles, colors, and layout
- ✅ Format Conversion - PDF → DOCX conversion only (convert PDFs to editable Word documents)
- ✅ Progress Tracking - Real-time status updates during translation
- ✅ Large Files - Handles documents up to 30MB
- ✅ Cost Tracking - Shows billed characters after translation
- ✅ Glossaries -
--glossaryapplies to documents too, repeatable up to 5 (needs a source language:--from, ordefaults.sourceLang). Translation memories are not supported for documents. - ✅ Async Processing - Documents are translated on DeepL servers with polling
Supported Formats:
.pdf- PDF documents.docx,.doc- Microsoft Word.pptx- Microsoft PowerPoint.xlsx- Microsoft Excel.html,.htm- HTML files.txt- Plain text files.srt- Subtitle files.xlf,.xliff- XLIFF localization files.jpg,.jpeg- JPEG images.png- PNG images
Note: Document translation uses DeepL's async translation API. The CLI automatically handles upload, polling, and download. Translation time varies based on document size and complexity.
Translate multiple files in parallel with progress indicators:
# Translate all files in a directory
deepl translate ./docs --to es --output ./docs-es
# Scanning files...
# Translating files: 10/10
# ✔ Translation complete!
#
# Translation Statistics:
# Total files: 10
# ✓ Successful: 10
# Translate specific file types with glob pattern
deepl translate ./docs --to fr --output ./docs-fr --pattern "*.md"
# Only translates markdown files
# Non-recursive (current directory only)
deepl translate ./docs --to de --output ./docs-de --no-recursive
# Custom concurrency (default: 5)
deepl translate ./large-docs --to ja --output ./large-docs-ja --concurrency 10
# Faster processing with more parallel translationsPerformance Optimization:
The CLI automatically optimizes batch translations by grouping multiple texts into single API requests (up to 50 texts per batch). This significantly reduces API overhead and improves translation speed compared to translating texts individually.
- ✅ Automatic batching - Groups translations into efficient batches
- ✅ Cache-aware - Only translates uncached texts
- ✅ Smart splitting - Respects DeepL API batch size limits (50 texts/request)
- ✅ Parallel processing - Multiple batches processed concurrently
This optimization is automatic and transparent - no configuration needed.
# Preserve code blocks and variables
deepl translate tutorial.md --preserve-code --to ja --output tutorial.ja.md
# Preserve line breaks and whitespace formatting
deepl translate document.txt --preserve-formatting --to es --output document.es.txt
# Enable document minification for PPTX/DOCX (reduces file size)
deepl translate presentation.pptx --enable-minification --to de --output presentation.de.pptx
# Set formality level (default, more, less, prefer_more, prefer_less, formal, informal)
deepl translate "How are you?" --formality more --to de --output formal.txt
# More formal: Wie geht es Ihnen?
deepl translate "How are you?" --formality less --to de --output casual.txt
# Less formal: Wie geht's?
# Add context for better translation quality (helps with ambiguous terms)
deepl translate "bank" --context "This document is about financial institutions" --to es
# Translation considers financial context → "banco" (financial institution)
deepl translate "bank" --context "This document is about rivers and geography" --to es
# Translation considers geographical context → "orilla" (riverbank)
# Combine context with other options
deepl translate "How are you?" --context "Formal business email" --formality more --to de
# Choose model type for quality vs. speed trade-offs
deepl translate "Long document text..." --to ja --model-type quality_optimized
# Best translation quality
deepl translate "Real-time chat message" --to es --model-type latency_optimized
# Faster response time, slightly lower quality
deepl translate "Important email" --to de --model-type prefer_quality_optimized
# Prefer quality, fall back to latency if unavailable
# Custom instructions for tailored translations (repeatable, max 10)
deepl translate "Click Save to confirm" --to de \
--custom-instruction "This is a software UI string" \
--custom-instruction "Keep it concise"
# Combine custom instructions with other options
deepl translate "Meeting at the bank" --to es \
--custom-instruction "This is about financial institutions" \
--formality more
# Apply a style rule (Pro API only - get IDs with: deepl style-rules list)
deepl translate "Hello" --to de --style-id "abc-123-def-456"
# Custom API endpoint (for DeepL Pro accounts or testing)
deepl translate "Hello" --to es --api-url https://api.deepl.com/v2
# Track actual billed characters for cost transparency
deepl translate "Hello, world!" --to es --show-billed-characters
# Output:
# Hola, mundo!
#
# Billed characters: 13
# Table output format - structured view for multiple languages
deepl translate "Hello, world!" --to es,fr,de --format table
# ┌──────────┬──────────────────────────────────────────────────────────────────────┐
# │ Language │ Translation │
# ├──────────┼──────────────────────────────────────────────────────────────────────┤
# │ es │ ¡Hola, mundo! │
# │ fr │ Bonjour le monde! │
# │ de │ Hallo, Welt! │
# └──────────┴──────────────────────────────────────────────────────────────────────┘
# Table format with cost tracking (adds Characters column)
deepl translate "Cost analysis" --to es,fr,de --format table --show-billed-characters --no-cache
# ┌──────────┬────────────────────────────────────────────────────────────┬────────────┐
# │ Language │ Translation │ Characters │
# ├──────────┼────────────────────────────────────────────────────────────┼────────────┤
# │ es │ Análisis de costes │ 14 │
# │ fr │ Analyse des coûts │ 14 │
# │ de │ Kostenanalyse │ 14 │
# └──────────┴────────────────────────────────────────────────────────────┴────────────┘
# Preview what would be translated without making API calls (file/directory mode)
deepl translate ./docs --to es --dry-run
# Tag handling pins v2 (better structure handling); opt back into v1 explicitly
deepl translate page.html --to es --tag-handling html --tag-handling-version v1
# Advanced XML/HTML tag handling (requires --tag-handling xml)
# Control automatic XML structure detection
deepl translate "<doc><p>Hello</p></doc>" --to es --tag-handling xml --outline-detection false
# Specify tags that split sentences (like <br/> and <hr/>)
deepl translate "<div>First sentence<br/>Second sentence</div>" --to es --tag-handling xml --splitting-tags "br,hr"
# Preserve non-translatable content (code blocks, preformatted text)
deepl translate "<doc><code>let x = 1;</code><p>Text</p></doc>" --to es --tag-handling xml --non-splitting-tags "code,pre"
# Ignore specific tags and their content (scripts, styles)
deepl translate page.html --to es --tag-handling xml --ignore-tags "script,style,noscript" --output page.es.html
# Combine multiple XML tag handling options for fine-tuned control
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--format table draws the box only when stdout is a terminal. Redirected or captured (--format table > out.txt, $(…)), it falls back to plain [lang] text lines and says so on stderr, so log scrapers and screen readers get parseable output instead of box-drawing characters. Use --format json when a script needs a stable shape.
XML Tag Handling Use Cases:
Advanced XML/HTML tag handling is perfect for:
- 🌐 Localizing HTML websites while preserving structure
- 📚 Translating technical documentation with code blocks
- 📄 Processing custom XML formats with specific content rules
- 🔒 Protecting non-translatable content (scripts, styles, code)
- ✂️ Fine-tuned control over sentence splitting for better context
See examples/09-xml-tag-handling.sh for comprehensive XML tag handling examples with real-world scenarios.
Model Types:
quality_optimized- Best translation quality, standard latencyprefer_quality_optimized- Prefer quality, fallback to latency if unavailablelatency_optimized- Faster responses, slightly lower quality (ideal for real-time use)
There is no CLI default: when --model-type is omitted, the API server selects the model.
See examples/08-model-type-selection.sh for a complete example with different model types.
Improve your writing with AI-powered grammar, style, and tone suggestions using the DeepL Write API.
The --lang flag is optional. If omitted, DeepL auto-detects the language and rephrases in the original language. Generic codes en and pt are also accepted (mapped to en-us and pt-br respectively).
# Auto-detect language (--lang is optional)
deepl write "This is a sentence."
# Specify language explicitly
deepl write "This is a sentence." --lang en-us
# Use generic language code (en maps to en-us, pt maps to pt-br)
deepl write "This is a sentence." --lang en
# Apply business writing style
deepl write "We want to tell you about our new product." --lang en-us --style business
# Apply academic writing style
deepl write "This shows that the method works." --lang en-us --style academic
# Apply casual tone
deepl write "That is interesting." --lang en-us --style casual
# Use confident tone
deepl write "I think this will work." --lang en-us --tone confident
# Use diplomatic tone
deepl write "Try something else." --lang en-us --tone diplomatic
# Show all alternative improvements
deepl write "This is good." --tone enthusiastic --alternatives
# Improve files and save to output
deepl write input.txt --lang en-us --output improved.txt
# Edit file in place
deepl write document.md --lang en-us --in-place
# Interactive mode - choose from multiple style alternatives
# Generates improvements with simple, business, academic, and casual styles
deepl write "Text to improve." --lang en-us --interactive
# Interactive mode with file
deepl write document.md --lang en-us --interactive --in-place
# Interactive mode with specific style (single option)
deepl write "Text to improve." --lang en-us --style business --interactive
# Check if text needs improvement (exit code 0 if no changes needed)
deepl write document.md --lang en-us --check
# Machine-readable check result for CI (exit code unchanged)
deepl write document.md --check --format json
# {"ok":true,"mode":"write","needsChanges":true,"changes":3,"file":"/abs/path/document.md"}
# Auto-fix files in place
deepl write document.md --lang en-us --fix
# Auto-fix with backup
deepl write document.md --lang en-us --fix --backup
# Show diff between original and improved
deepl write file.txt --lang en-us --diff
# Show diff for plain text
deepl write "This text could be better." --lang en-us --diff
# Machine-readable diff (uncoloured patch)
deepl write file.txt --diff --format json
# {"ok":true,"original":"...","improved":"...","diff":"Index: text\n===...\n"}
# Every alternative the API offered, as an array
deepl write "This is good." --alternatives --format json
# {"ok":true,"original":"This is good.","alternatives":["This is fine.","This works."]}
# Bypass cache for this request
deepl write "Fresh improvement please." --lang en-us --no-cacheFailures under --format json. Every command with a JSON mode writes a single error envelope to stdout — not stderr — so one redirection captures both outcomes. Discriminate on ok:
{ "ok": false, "error": { "code": "ValidationError", "message": "…", "suggestion": "…" }, "exitCode": 6 }The non-zero exit code remains the failure signal, and stderr carries only human-facing warnings. --check is not a failure: it emits ok: true with a needsChanges verdict and exits 0 or 8.
Supported Languages:
- German (
de) - English (
en) - generic, defaults to American English - English - British (
en-gb) - English - American (
en-us) - Spanish (
es) - French (
fr) - Italian (
it) - Japanese (
ja) - Korean (
ko) - Portuguese (
pt) - generic, defaults to Brazilian Portuguese - Portuguese - Brazilian (
pt-br) - Portuguese - European (
pt-pt) - Chinese (
zh) - generic, defaults to Simplified Chinese - Chinese - Simplified (
zh-hans)
Writing Styles:
default- No style modification (API default)simple- Easy-to-read, accessible languagebusiness- Professional, formal business toneacademic- Scholarly, research-oriented stylecasual- Relaxed, conversational toneprefer_*prefix - Apply style only if language supports it
Tones:
default- No tone modification (API default)enthusiastic- Energetic and positivefriendly- Warm and approachableconfident- Assertive and certaindiplomatic- Tactful and considerateprefer_*prefix - Apply tone only if language supports it
Interactive Mode:
When using --interactive without specifying a style or tone, the CLI automatically generates 4 different alternatives by calling the DeepL Write API with different writing styles (simple, business, academic, casual). You can then choose which version works best for your needs:
? Choose an improvement (4 alternatives):
❯ Keep original - "This text could be better improved with..."
Simple - "This text needs improvement with better..."
Business - "We recommend enhancing this text through..."
Academic - "It is advisable to improve this text via..."
Casual - "You should make this text better by..."
If you specify a style or tone with --interactive, you'll get a simple confirm/reject prompt for that single suggestion.
Note: You cannot combine --style and --tone in a single request. Choose one or the other.
Fix spelling and grammar without rewording, using the Write API's dedicated correct endpoint. Unlike deepl write, which may rephrase for style, deepl correct leaves your wording alone.
# Correct a sentence (language auto-detected)
deepl correct "This is an test."
# This is a test.
# c is an alias of correct
deepl c "Their going too the store."
# Grammar gate for CI: exit code 8 if corrections are needed
deepl correct README.md --check
# Fix a file in place, keeping a backup
deepl correct essay.md --fix --backup
# Review the correction as a diff first
deepl correct document.txt --diff
# Pipe from stdin
cat notes.txt | deepl correctcorrect supports the same languages and workflow flags as write — --check, --fix, --backup, --diff, --alternatives, --interactive, --output, --in-place, --no-cache, --format json, and --to as an alias of --lang — but not --style/--tone.
Translate audio in real-time using the DeepL Voice API. Supports multiple audio formats with automatic content type detection.
# Translate an audio file
deepl voice recording.ogg --to de
# Multiple target languages (max 5)
deepl voice meeting.mp3 --to de,fr,es
# Specify source language
deepl voice audio.flac --to ja --from en
# Pipe from stdin (content-type required)
cat audio.pcm | deepl voice - --to es --content-type 'audio/pcm;encoding=s16le;rate=16000'
# JSON output
deepl voice speech.ogg --to de --format json
# Disable live streaming (plain text at end)
deepl voice speech.ogg --to de --no-stream
# Set source language detection mode (auto or fixed)
deepl voice speech.ogg --to de --from en --source-language-mode fixed
# Customize chunking behavior
deepl voice large-recording.ogg --to de --chunk-size 12800 --chunk-interval 100
# Adjust reconnection attempts (default: 3)
deepl voice speech.ogg --to de --max-reconnect-attempts 5Supported audio formats: OGG (Opus), WebM (Opus), FLAC, MP3, PCM (16kHz s16le), Matroska (Opus)
Automatic WebSocket reconnection is enabled by default (up to 3 attempts). Disable with --no-reconnect or adjust with --max-reconnect-attempts <n>.
Note: The Voice API requires a DeepL Pro or Enterprise plan.
Incremental i18n translation engine: scans your source locale files, diffs them against .deepl-sync.lock, translates only new and changed strings, and writes format-preserving target files. Supports 11 formats: JSON, YAML, TOML, Gettext PO, Android XML, iOS Strings, Xcode String Catalog (.xcstrings), ARB, XLIFF, Java Properties, and Laravel PHP arrays.
# One-time setup - generates .deepl-sync.yaml (framework auto-detection)
deepl sync init
# Translate new and changed keys across all configured locales
deepl sync
# Preview changes without making API calls
deepl sync --dry-run
# CI gate - exit 10 if translations are missing or outdated (no API calls)
deepl sync --frozen
# Per-locale coverage report
deepl sync status
# Check placeholder, format-string, and HTML-tag integrity
deepl sync validate
# Terminology-consistency audit across locales
deepl sync audit
# Export source strings to XLIFF 1.2 for CAT tool handoff
deepl sync export --output handoff.xlf
# Resolve git merge conflicts in .deepl-sync.lock
deepl sync resolve
# Optional TMS integration (requires a tms: block in .deepl-sync.yaml)
deepl sync push
deepl sync pull
# .deepl-sync.yaml picks the TMS host, so an environment-supplied TMS_API_KEY
# only goes to a host you have approved. Approve one up front:
deepl config set tms.allowedServers tms.example.comBecause tms.server is chosen by .deepl-sync.yaml in the checkout while TMS_API_KEY/TMS_TOKEN come from your environment, push/pull will not send an environment-supplied credential to a hostname you have not approved: it asks once in a terminal (recording the answer in your user config, outside the repo) and fails closed with exit 7 under --no-input or in CI. Both commands also print the destination origin on success. See TMS destination trust.
See docs/SYNC.md for configuration (.deepl-sync.yaml), the lockfile model, CI/CD recipes, and the TMS REST contract, and docs/API.md#sync for the full flag reference.
Monitor files or directories for changes and automatically translate them in real-time. Perfect for keeping documentation and localization files in sync.
# Watch a single file
deepl watch README.md --to es,fr,de
# Watch a directory (all supported files)
deepl watch docs/ --to ja --output docs-i18n/
# Watch with pattern filtering
deepl watch src/locales/ --pattern "*.json" --to es,fr,de
# Watch markdown files only
deepl watch docs/ --pattern "*.md" --to ja
# Auto-commit translations to git
deepl watch docs/ --to es --auto-commit
# Custom debounce delay (default: 500ms)
deepl watch src/ --to es --debounce 1000
# Preview what would be watched without starting the watcher
deepl watch docs/ --to es --dry-run
# Only watch git-staged files (useful in pre-commit workflows)
deepl watch . --to es --git-staged
# With formality and code preservation
deepl watch docs/ --to de --formality more --preserve-codeFeatures:
- 🔄 Real-time monitoring with debouncing
- 📁 Watch files or entire directories
- 🎯 Glob pattern filtering (e.g.,
*.md,*.json) - 🔀 Multiple target languages
- 💾 Auto-commit to git (optional)
- 📌 Git-staged filtering for pre-commit workflows
- ⚡ Smart debouncing to avoid redundant translations
- 🗂️ Output mirrors the source tree: a file in a subdirectory of the watched path is written to the matching subdirectory under
--output, so two same-named files in different directories never overwrite each other
Example output:
👀 Watching for changes...
Path: docs/
Targets: es, fr, ja
Output: docs/translations
Pattern: *.md
📝 Change detected: docs/README.md
✓ Translated docs/README.md to 3 languages
→ [es] docs/translations/README.es.md
→ [fr] docs/translations/README.fr.md
→ [ja] docs/translations/README.ja.md
Press Ctrl+C to stop
See examples/19-watch-mode.sh for a complete watch mode example with multiple scenarios.
Automate translation validation in your git workflow with pre-commit, pre-push, commit-msg, and post-commit hooks.
# Install hooks
deepl hooks install pre-commit
deepl hooks install pre-push
deepl hooks install commit-msg
deepl hooks install post-commit
# List hook installation status
deepl hooks list
# Show path to hook file
deepl hooks path pre-commit
# Uninstall a hook
deepl hooks uninstall pre-commitWhat the hooks do:
- pre-commit: Checks if staged files include translatable content (
.md,.txtfiles) and validates translations are up-to-date - pre-push: Validates all translations in the repository before pushing to remote
- commit-msg: Validates or augments commit messages with translation context
- post-commit: Runs post-commit translation tasks (e.g., auto-translate changed files)
Features:
- 🔒 Safe installation with automatic backup of existing hooks
- 🚧 Confirmation before writing an executable outside the working tree — a repository-local
core.hooksPathtravels with the repository, so an install that would land outside the checkout names the configured value and the resolved directory and asks first (--yesto accept, exit6and nothing written if declined). Paths inside the working tree, linked worktrees, and submodules are unaffected - 🎯 Only validates changed files (pre-commit)
- ⚡ Lightweight and fast
- 🔧 Customizable hook scripts
- 🗑️ Clean uninstallation with backup restoration
Hook Status Example:
$ deepl hooks list
Git Hooks Status:
✓ pre-commit installed
! pre-push installed, content does not match its recorded hash
? commit-msg installed, no hash recorded (legacy marker)
✗ post-commit not installed
The content of a hook no longer matches the hash its marker records. That
is expected if you edited the hook yourself. If you did not, replace it:
deepl hooks install <hook-type>
A recorded hash detects a change made after the marker was written. It
cannot establish that a hook came from this CLI.Every hook this CLI installs carries a marker recording the SHA-256 of its body, and hooks list checks it rather than reporting a bare "installed". A ! means the file at that path is not what its own marker says it is — you edited it, or something else wrote it. The hash is unkeyed, so a match shows 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 to agree with it. --format json reports these states as strings (installed, modified, unverified, not-installed) rather than booleans.
Note: The hooks are generated with placeholder validation logic. You can customize them based on your project's translation workflow by editing the hook files directly at .git/hooks/pre-commit or .git/hooks/pre-push. A customized hook reports as ! from then on, since its body no longer matches the hash recorded when it was installed.
See examples/20-git-hooks.sh for a complete git hooks example demonstrating installation, usage, and management.
First-time users can use the interactive setup wizard:
deepl init
# Walks through:
# - API key setup and validation
# - Default target language selection
# - Basic configurationTo configure continuous localization for an existing project, see also
deepl sync initor run the wizard directly.
# Set the API key (piping keeps it out of process listings and shell history)
echo "YOUR_API_KEY" | deepl auth set-key --from-stdin
# ✓ API key saved and validated successfully
# Store it without validating against the API (offline or proxied networks)
echo "YOUR_API_KEY" | deepl auth set-key --from-stdin --no-verify
# ✓ API key saved without validation
# Passing the key as an argument still works, but is deprecated and warns —
# other users can read it via `ps`
deepl auth set-key YOUR_API_KEY
# Show the stored key, masked (does not contact the API)
deepl auth show
# API Key: abc1...2def
# Clear API key
deepl auth clear
# ✓ API key removedOr use an environment variable:
export DEEPL_API_KEY=YOUR_API_KEYCheck your API usage to monitor character consumption:
# Show API usage statistics
deepl usage
# Character Usage:
# Used: 123,456 / 500,000 (24.7%)
# Remaining: 376,544Note: Usage statistics help you track your DeepL API character quota and avoid exceeding limits.
See examples/33-usage-monitoring.sh for a complete usage monitoring example.
Cost Transparency:
For detailed cost tracking per translation, use the --show-billed-characters flag with the translate command (see Advanced Translation Options above). This displays the actual billed character count for each translation, helping with budget planning and cost analysis.
See examples/12-cost-transparency.sh for comprehensive cost tracking examples.
Detect the language of text using the DeepL API:
# Detect language of text
deepl detect "Bonjour le monde"
# Detected language: French (fr)
# Pipe text for detection
echo "こんにちは" | deepl detect
# JSON output for scripting
deepl detect "Hola mundo" --format json
# {
# "detected_language": "es",
# "language_name": "Spanish"
# }List all 125 supported languages grouped by category:
# 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
# ...
# en-gb English (British)
# en-us English (American)
# ...
#
# Extended Languages (quality_optimized only, no formality/glossary):
# ace Acehnese
# ...
# 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):
# 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 languagesNote: Languages are grouped into three categories:
- Core (32) — Full feature support including formality and glossaries
- Regional (11) — Target-only variants:
de-ch,de-de,en-gb,en-us,es-419,fr-ca,fr-fr,pt-br,pt-pt,zh-hans,zh-hant - Extended (82) — Only support
quality_optimizedmodel, no formality or glossary
--features is finer-grained than these tiers — some extended languages do support style rules and translation memory. It needs an API key, since the local registry carries no feature data. A feature only gets its own column when support differs across the languages listed; one supported by all of them is summarised on the last line instead of repeated on every row.
The API decides what exists. GET /v3/languages is authoritative; the bundled list is a generated snapshot of it (npm run generate:languages) so that listing and validating languages works offline. Because a snapshot can lag the API, a well-formed language code it does not list is passed to the API rather than rejected locally — that is why deepl translate --to de-CH works even if your copy of the list predates Swiss German. Input that is not shaped like a language tag is still rejected immediately.
See examples/34-languages.sh for a complete example.
Configuration is stored in ~/.config/deepl-cli/config.json (or ~/.deepl-cli/config.json for legacy installations; see Configuration Paths)
# View all configuration
deepl config list
# {
# "auth": { "apiKey": "..." },
# "api": { "baseUrl": "https://api.deepl.com", "usePro": true, ... },
# "cache": { "enabled": true, "maxSize": 1073741824, "ttl": 2592000 },
# ...
# }
# Get specific value
deepl config get cache.enabled
# true
# Set a value
deepl config set defaults.targetLangs es,fr,de
# ✓ Set defaults.targetLangs = es,fr,de
# Approve TMS destinations for an environment-supplied TMS_API_KEY / TMS_TOKEN
deepl config set tms.allowedServers tms.example.com,tms2.example.com
# ✓ Set tms.allowedServers = tms.example.com,tms2.example.com
# Set cache size (in bytes)
deepl config set cache.maxSize 2147483648
# ✓ Set cache.maxSize = 2147483648
# Disable caching
deepl config set cache.enabled false
# Reset to defaults
deepl config reset
# ✓ Configuration reset to defaults
# Reset without confirmation prompt
deepl config reset --yesDeepL CLI automatically supports HTTP and HTTPS proxies through environment variables:
# Configure HTTP proxy
export HTTP_PROXY=http://proxy.example.com:8080
deepl translate "Hello" --to es
# Configure HTTPS proxy
export HTTPS_PROXY=https://proxy.example.com:8443
deepl translate "Hello" --to es
# Configure proxy with authentication
export HTTP_PROXY=http://username:password@proxy.example.com:8080
deepl translate "Hello" --to es
# Both HTTP_PROXY and HTTPS_PROXY are supported (case-insensitive)
export http_proxy=http://proxy.example.com:8080
export https_proxy=https://proxy.example.com:8443
# Exempt hosts from the proxy with NO_PROXY (comma-separated)
export NO_PROXY=localhost,127.0.0.1,.internal.example.comFeatures:
- ✅ Automatic proxy detection from environment variables
- ✅ HTTP and HTTPS proxy support
- ✅ Proxy authentication support
- ✅
NO_PROXY/no_proxybypass, including*, a leading dot or*.for subdomains, and optionalhost:portentries - ✅ Follows standard proxy environment variable conventions
- ✅ Works with all DeepL CLI commands
Note: HTTPS_PROXY takes precedence over HTTP_PROXY when both are set. The CLI automatically parses proxy URLs including authentication credentials.
DeepL CLI includes built-in retry logic and timeout handling for robust API communication:
Automatic Retry Logic:
- Idempotent requests (GET, HEAD, PUT, DELETE) are retried on 5xx responses and transient network failures
- Non-idempotent requests (POST — translations, uploads, glossary and key creation) are replayed only when the error proves the request never reached the server (connection refused, DNS failure), so a slow response can never be submitted and billed twice
- Rate limiting (429) is retried for all methods, honoring the
Retry-Afterheader when the server sends one - Other client errors (4xx — bad request, auth failures, etc.) are not retried
- Default: 3 retries with full-jitter exponential backoff (capped at 10s)
Timeout Configuration:
- Default timeout: 30 seconds per request, with an overall time budget across retries
- Applies to all API requests (translate, usage, languages, etc.)
- Override per invocation with the global
--timeout <ms>and--max-retries <n>flags
Features:
- ✅ Automatic retry on transient failures
- ✅ Exponential backoff to avoid overwhelming the API
- ✅ Retry policy aware of request idempotency
- ✅ Configurable via
--timeout/--max-retries(or library-consumer options) - ✅ Works across all DeepL API endpoints
Retry Behavior Examples:
# Network failure - automatically retries up to 3 times
deepl translate "Hello" --to es
# If API returns 503 (service unavailable), retries automatically
# Authentication failure (403) - does not retry
deepl translate "Hello" --to es
# Fails immediately without retries on auth errors
# Rate limiting (429) - retried automatically
# Honors the Retry-After header when the server sends one,
# otherwise falls back to jittered exponential backoffNote: Retry and timeout settings use sensible defaults optimized for the DeepL API. No configuration is required; --timeout and --max-retries are available when a specific invocation needs different bounds (e.g. very large documents on a slow uplink).
DeepL glossaries ensure consistent terminology across translations. The v3 Glossary API supports both single-target and multilingual glossaries (one glossary with multiple target languages).
# Create a single-target glossary from TSV file
# File format: source_term<TAB>target_term per line
echo -e "API\tAPI\nREST\tREST\nauthentication\tAuthentifizierung" > glossary.tsv
deepl glossary create tech-terms en de glossary.tsv
# ✓ Glossary created successfully
# Name: tech-terms
# ID: abc123...
# Source language: en
# Target languages: de
# Type: Single target
# Total entries: 3
# Created: 2026-08-09T12:34:56.000Z
# List all glossaries
deepl glossary list
# 📖 tech-terms (en→de) - 3 entries
# 📚 multilingual-terms (en→3 targets) - 15 entries
# Show glossary details
deepl glossary show tech-terms
# Name: tech-terms
# ID: abc123...
# Source language: en
# Target languages: de
# Type: Single target
# Total entries: 3
# Created: 2026-08-09T12:34:56.000Z
# Show glossary entries (single-target glossary - no --target flag needed)
deepl glossary entries tech-terms
# API → API
# REST → REST
# authentication → Authentifizierung
# Show entries for multilingual glossary (--target-lang required)
deepl glossary entries multilingual-terms --target-lang es
# API → API
# cache → caché
# ...
# Delete glossary (prompts for confirmation; --yes to skip)
deepl glossary delete tech-terms --yes
# ✓ Glossary deleted successfully
# Preview what would be deleted without performing the operation
deepl glossary delete tech-terms --dry-run
# List supported glossary language pairs
deepl glossary languages
# de → en
# de → fr
# de → it
# en → de
# en → es
# en → fr
# en → ja
# en → pt
# ...
# Add a new entry to an existing glossary
deepl glossary add-entry tech-terms "database" "Datenbank"
# ✓ Entry added successfully
# Add entry to multilingual glossary (requires --target-lang flag)
deepl glossary add-entry multilingual-terms "cache" "caché" --target-lang es
# Update an existing entry in a glossary
deepl glossary update-entry tech-terms "API" "API (Programmierschnittstelle)"
# ✓ Entry updated successfully
# Remove an entry from a glossary
deepl glossary remove-entry tech-terms "REST"
# ✓ Entry removed successfully
# Rename a glossary
deepl glossary rename tech-terms "Technical Terms v2"
# ✓ Glossary renamed successfully
# Update glossary name and/or dictionary entries in a single request
deepl glossary update my-terms --name "Updated Terms" --target-lang de --file updated.tsv
# Replace all entries in a glossary dictionary from a new file (v3 API only)
# Unlike individual entry updates (which merge), this replaces the entire dictionary
deepl glossary replace-dictionary multilingual-terms es updated-entries.tsv
# ✓ Dictionary replaced successfully (es)
# Note: All existing entries for the target language are removed and replaced
# The file format is the same as for 'glossary create' (TSV or CSV)
# Delete a dictionary from a multilingual glossary (v3 API only)
# Removes a specific language pair from a multilingual glossary
deepl glossary delete-dictionary multilingual-terms es
# ✓ Dictionary deleted successfully (es)
# Note: This only works with multilingual glossaries (multiple target languages)
# For single-target glossaries, use 'glossary delete' to remove the entire glossaryGlossary file format (TSV):
source_term target_term
API API
REST REST
authentication AuthentifizierungKey Features:
- Single-target glossaries - One source language → one target language (e.g., en → de)
- Multilingual glossaries - One source language → multiple target languages (e.g., en → es, fr, de)
- Direct updates - v3 API uses PATCH endpoints for efficient updates (no delete+recreate)
- Smart defaults -
--target-langonly required for multilingual glossaries - Visual indicators - 📖 for single-target, 📚 for multilingual glossaries
- Translation integration - Use
--glossaryflag in translate and watch commands to apply glossary terms (a source language is required, since the API rejects a glossary without one: pass--from, or setdefaults.sourceLang) - Several glossaries at once - Repeat
--glossaryontranslatefor up to 5 glossaries; entries are merged, and a term defined in more than one is resolved by the API, not by flag order
# Combine shared base terminology with project-specific terms
deepl translate "Hello world" --from en --to de --glossary base-terms --glossary project-overridesReuse approved translations from your account's translation memories. TMs are authored and uploaded via the DeepL web UI; the CLI lists them and applies them to translations.
# List translation memories on the account
deepl tm list
# brand-terms (en → de, fr, ja)
# legal-phrases (en → fr)
# JSON output for scripting
deepl tm list --format json
# Apply a TM to a single translation (by name or UUID)
deepl translate "Hello" --to de --translation-memory brand-terms
# Tighten the minimum match score (default: 75)
deepl translate "Hello" --to de --translation-memory brand-terms --tm-threshold 90For sync runs, configure translation.translation_memory (and optionally translation.translation_memory_threshold) in .deepl-sync.yaml — see docs/SYNC.md.
Style rules are named rule lists (configured rules plus optional custom instructions) applied to translations via their ID (Pro API only). The CLI supports the full lifecycle:
| Subcommand | Purpose |
|---|---|
list |
List style rules (--detailed, --page, --page-size, --format) |
create |
Create a style rule (--name and --language required, optional --rules) |
show <id> |
Show a single style rule (--detailed) |
update <id> |
Rename (--name) and/or replace configured rules (--rules) |
delete <id> |
Delete a style rule (--yes, --dry-run) |
instructions <id> |
List custom instructions on a style rule |
add-instruction <id> <label> <prompt> |
Add a custom instruction |
update-instruction <id> <label> <prompt> |
Update a custom instruction |
remove-instruction <id> <label> |
Remove a custom instruction (--yes, --dry-run) |
# List available style rules
deepl style-rules list
# List with detailed information
deepl style-rules list --detailed
# JSON output
deepl style-rules list --format json
# Paginate results
deepl style-rules list --page 2 --page-size 10
# Create a style rule and add a custom instruction
deepl style-rules create --name "Corporate" --language en
deepl style-rules add-instruction sr-abc123 tone "Be formal"
# Apply a style rule to a translation
deepl translate "Hello" --to de --style-id "abc-123-def-456"See docs/API.md#style-rules for the full subcommand reference, including the configured-rules JSON shape.
Manage API keys and view organization usage analytics (requires admin-level API key).
# List all API keys in the organization
deepl admin keys list
# List keys in JSON format
deepl admin keys list --format json
# Create a new API key
deepl admin keys create --label "Production Key"
# Rename an API key
deepl admin keys rename <key-id> "New Label"
# Set character usage limit
deepl admin keys set-limit <key-id> 1000000
# Remove usage limit (unlimited)
deepl admin keys set-limit <key-id> unlimited
# Deactivate an API key (permanent, cannot be undone)
deepl admin keys deactivate <key-id>
# Deactivate without confirmation prompt
deepl admin keys deactivate <key-id> --yes
# View organization usage for a date range (includes per-product breakdown)
deepl admin usage --start 2024-01-01 --end 2024-12-31
# Period: 2024-01-01 to 2024-12-31
#
# Total Usage:
# Total: 10,000
# Translation: 7,000
# Documents: 2,000
# Write: 1,000
# Usage grouped by key (shows per-product breakdown per 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 for programmatic use
deepl admin usage --start 2024-01-01 --end 2024-12-31 --format jsonKey Management:
- list - View all API keys in the organization
- create - Create a new key with an optional label
- rename - Change the label of an existing key
- set-limit - Set or remove character usage limits
- deactivate - Permanently deactivate a key (requires confirmation)
Usage Analytics:
- Per-product character breakdowns (translation, documents, write)
- Group by key or by key and day for cost allocation
- JSON output for integration with monitoring tools
Note: Admin API endpoints require an admin-level API key, not a regular developer key. Key deactivation is permanent and cannot be undone.
Generate shell completion scripts for tab-completion of commands, options, and arguments:
# Bash (system-wide; needs write access to that directory)
deepl completion bash | sudo tee /etc/bash_completion.d/deepl > /dev/null
# Zsh
deepl completion zsh > "${fpath[1]}/_deepl"
# Fish
deepl completion fish > ~/.config/fish/completions/deepl.fish
# Or source directly in your current session
source <(deepl completion bash)Bash only: the generated script uses
_init_completion, which the bash-completion package provides. Without it loaded the script is a no-op rather than an error. Install it withbrew install bash-completion@2(macOS) or your distribution'sbash-completionpackage.
The CLI uses a local SQLite database to cache translations and write improvements, reducing API calls.
# View cache statistics
deepl cache stats
# Cache Status: enabled
# Entries: 42
# Size: 1.23 MB / 1024.00 MB (0.1% used)
# Clear all cached translations
deepl cache clear
# ✓ Cache cleared successfully
# Preview what would be cleared without performing the operation
deepl cache clear --dry-run
# Enable caching
deepl cache enable
# ✓ Cache enabled
# Disable caching
deepl cache disable
# ✓ Cache disabledCache location: ~/.cache/deepl-cli/cache.db (or ~/.deepl-cli/cache.db for legacy installations)
One database is shared by every endpoint a config directory has ever talked to, so the resolved API base URL is part of every translation, write, and correct cache key. A run against --api-url http://localhost:1234 gets its own entries and can never answer a later request aimed at api.deepl.com; the free and Pro endpoints are likewise keyed apart.
- Node.js >= 24.0.0
- npm >= 9.0.0
- DeepL API key
# Clone repository
git clone https://github.com/DeepL/deepl-cli.git
cd deepl-cli
# Install dependencies
npm install
# Run tests
npm test
# Run tests in watch mode
npm test -- --watch
# Run tests with coverage
npm run test:coverage
# Lint code
npm run lint
# Type check
npm run type-check
# Build
npm run buildSee CLAUDE.md for comprehensive development guidelines.
deepl-cli/
├── src/
│ ├── cli/ # CLI interface and commands
│ ├── services/ # Business logic
│ ├── sync/ # Continuous localization engine (scan, diff, translate, write, lock)
│ ├── formats/ # i18n file format parsers (JSON, YAML, PO, XLIFF, Android XML, etc.)
│ ├── api/ # DeepL API client
│ ├── storage/ # Data persistence (cache, config)
│ ├── data/ # Static data (language registry)
│ ├── utils/ # Utility functions
│ ├── types/ # Type definitions
├── tests/
│ ├── unit/ # Unit tests
│ ├── integration/ # Integration tests
│ └── e2e/ # End-to-end tests
├── docs/ # Documentation
├── examples/ # Usage examples
├── CLAUDE.md # Development guidelines
└── README.md # This file
# Run CLI without building
npm run dev -- translate "Hello" --to es
# Or link for global usage
npm link
deepl translate "Hello" --to esDeepL CLI follows a layered architecture:
CLI Commands (translate, write, voice, sync, watch, glossary, tm, …)
↓
Service Layer (Translation, Write, Voice, Batch, Watch, Glossary,
TranslationMemory, StyleRules, Admin, Document,
GitHooks, Usage, Detect, Languages)
↓ ↓
Sync Engine (src/sync — Format Parsers (src/formats — 11 i18n formats)
incl. TmsClient)
↓ ↓
API Client (Translate, Write, Glossary, Document, Voice,
StyleRules, Admin)
↓
Storage (node:sqlite Cache, Config) + Static Data (src/data — language registry)
- Translation Service — core translation with caching and text preservation
- Write Service — grammar, style, and tone suggestions
- Voice Service — real-time speech translation over WebSocket
- Sync Engine — continuous localization: scan, diff, translate, write, lock
- Format Parsers — 11 i18n parsers (JSON, YAML, TOML, PO, Android XML, iOS Strings, xcstrings, ARB, XLIFF, Properties, Laravel PHP) with format-preserving reconstruct
- Batch Service — parallel multi-file translation
- Watch Service — file watching with debouncing
- Glossary Service — glossary management and application
- Translation Memory Service — reuse approved translations (
--translation-memory) - Cache —
node:sqlitecache, oldest-entry-first eviction (src/storage/cache.ts) - Preservation utilities —
src/utils/helpers for code blocks, variables, and ICU MessageFormat
# Run all tests
npm test
# Run specific test type
npm run test:unit
npm run test:integration
npm run test:e2e
# Run specific test file
npm test -- translation.test.ts
# Run with coverage report
npm run test:coverage
# Watch mode for TDD
npm test -- --watch
# Run all example scripts (validation)
npm run examples
# Run example scripts (fast mode, skips slow examples)
npm run examples:fast- API Reference - Complete API reference with all commands, flags, and options
- Migrating from 1.x - Removed flags, changed exit codes, and output that scripts parse
- Sync Guide - Continuous localization:
.deepl-sync.yamlconfiguration, lockfile model, CI/CD recipes, TMS integration - Troubleshooting - Common issues, solutions, and exit codes reference
- Examples - Practical usage examples for every feature
- Changelog - Release history and version notes
- Development Guidelines - TDD workflow and contribution standards
- DeepL API Docs - Official API documentation
- CLI Guidelines - Command-line best practices
| Variable | Description |
|---|---|
DEEPL_API_KEY |
API authentication key |
DEEPL_CONFIG_DIR |
Override config and cache directory |
XDG_CONFIG_HOME |
Override XDG config base (default: ~/.config) |
XDG_CACHE_HOME |
Override XDG cache base (default: ~/.cache) |
HTTP_PROXY |
HTTP proxy URL for outbound DeepL API traffic |
HTTPS_PROXY |
HTTPS proxy URL (takes precedence over HTTP_PROXY) |
TMS_API_KEY |
API key for the configured TMS server (sync push/pull) |
TMS_TOKEN |
Alternative auth token for the configured TMS server |
NO_COLOR |
Disable colored output |
FORCE_COLOR |
Force colored output even when terminal doesn't support it. Useful in CI. NO_COLOR takes priority if both are set. |
TERM=dumb |
Disables colored output and progress spinners. Automatically set by some CI environments and editors. |
See docs/API.md#environment-variables for full details.
- API key storage - Keys are stored as plaintext in
config.jsonwith0600file permissions (owner read/write only). That mode is checked on every load, not only when the file is created: aconfig.jsonthat arrives from a backup, anrsync, or another tool with a looser mode is tightened back to0600and the run says so, since the key may already have been readable. The containing directory is created0700; one that already existed and lets other users write to it is reported with thechmodthat would close it, but is not changed — the CLI does not lock other users out of a directory it did not create (a sticky directory such as/tmpis exempt, since only a file's owner can replace it there). For CI/CD or shared environments, prefer theDEEPL_API_KEYenvironment variable instead. Avoid committingconfig.jsonto version control — it is gitignored by default. - Credential redaction in diagnostics - Errors, warnings and
--verboseoutput are scrubbed before they reach stderr: auth headers andtoken=/api_key=query parameters are replaced by[REDACTED], as is the literal value of whichever credential the run is using, whether it came from the environment,config.json, or.deepl-sync.yaml. A credential value shorter than 8 characters is matched only by the header and query-parameter patterns, not by its literal value — a substring that short corrupts ordinary words in the diagnostics without protecting anything real.deepl translateoutput on stdout is never rewritten. - Local caching - All cached data stored locally in SQLite, never shared
- No telemetry - Zero usage tracking or data collection
- Environment variable support - Use
DEEPL_API_KEYenvironment variable for CI/CD - GDPR-aligned with DeepL's DPA - Follows DeepL's Data Processing Agreement terms
Contributions are welcome! See CONTRIBUTING.md for the development workflow (strict TDD), test requirements, commit conventions, and the pull request process.
This project is licensed under the MIT License - see the LICENSE file for details.
DeepL® is a registered trademark of DeepL SE.
Powered by DeepL's next-generation language model