skip to content

Configure BabelDOC as Translator

6 min read

I found a very interesting and useful open-source project BableDOC.

Install BabelDOC

First install uv:

pip install uv

Then we can install BabelDOC:

uv tool install --python 3.12 BabelDOC

Configure DeepSeek API

On MacOS:

echo 'export DEEPSEEK_API_KEY="My DeepSeek API Key"' >> ~/.zshrc
echo 'export DEEPSEEK_BASE_URL="https://api.deepseek.com/v1"' >> ~/.zshrc
echo 'export DEEPSEEK_MODEL="deepseek-chat"' >> ~/.zshrc
source ~/.zshrc

On Windows:

setx DEEPSEEK_API_KEY "My DeepSeek API Key"
setx DEEPSEEK_BASE_URL "https://api.deepseek.com"
setx DEEPSEEK_MODEL "deepseek-chat"

Restart Powershell to take effect.

Create a Command Line Function to Make Translation Easie

On MacOS:

Paste following function in to ~/.zshrc:

bd() {
    # -----------------------
    # Argument Parsing
    # -----------------------
    FILE=""
    OUTDIR=""
    LANG_IN="en"
    LANG_OUT="zh"
    FULL_MODE=0     # 0 = bilingual only, 1 = mono + bilingual
    PROVIDER="deepseek"  # default
 
    # Optional performance knobs (apply to BOTH providers)
    QPS=""
    POOL=""
    TERM_POOL=""
 
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --out)
                OUTDIR="$2"
                shift 2
                ;;
            --lang-in)
                LANG_IN="$2"
                shift 2
                ;;
            --lang-out)
                LANG_OUT="$2"
                shift 2
                ;;
            --full)
                FULL_MODE=1
                shift
                ;;
            --provider)
                PROVIDER="$2"
                shift 2
                ;;
            --doubao)
                PROVIDER="doubao"
                shift
                ;;
            --deepseek)
                PROVIDER="deepseek"
                shift
                ;;
            # NEW: apply to both providers if provided
            --qps)
                QPS="$2"
                shift 2
                ;;
            --pool)
                POOL="$2"
                shift 2
                ;;
            --term-pool)
                TERM_POOL="$2"
                shift 2
                ;;
            -*)
                echo "Unknown option: $1"
                echo "Usage: bd <pdf_path> [--provider deepseek|doubao] [--deepseek|--doubao] [--full] [--out dir] [--lang-in en] [--lang-out zh] [--qps N] [--pool N] [--term-pool N]"
                return 1
                ;;
            *)
                FILE="$1"
                shift
                ;;
        esac
    done
 
    if [ -z "$FILE" ]; then
        echo "Usage: bd <pdf_path> [--provider deepseek|doubao] [--deepseek|--doubao] [--full] [--out dir] [--lang-in en] [--lang-out zh] [--qps N] [--pool N] [--term-pool N]"
        return 1
    fi
 
    if [ -z "$OUTDIR" ]; then
        OUTDIR="$(dirname "$FILE")/translated"
    fi
 
    mkdir -p "$OUTDIR"
 
    # -----------------------
    # Provider config
    # -----------------------
    PROVIDER_LC="$(printf '%s' "$PROVIDER" | tr '[:upper:]' '[:lower:]')"
 
    OPENAI_MODEL=""
    OPENAI_BASE_URL=""
    OPENAI_API_KEY=""
 
    if [ "$PROVIDER_LC" = "doubao" ]; then
        OPENAI_MODEL="$DOUBAO_MODEL"
        OPENAI_BASE_URL="$DOUBAO_BASE_URL"
        OPENAI_API_KEY="$DOUBAO_API_KEY"
    else
        PROVIDER_LC="deepseek"
        OPENAI_MODEL="$DEEPSEEK_MODEL"
        OPENAI_BASE_URL="$DEEPSEEK_BASE_URL"
        OPENAI_API_KEY="$DEEPSEEK_API_KEY"
    fi
 
    # Validate required envs
    if [ -z "$OPENAI_MODEL" ] || [ -z "$OPENAI_BASE_URL" ] || [ -z "$OPENAI_API_KEY" ]; then
        echo "Missing environment variables for provider: $PROVIDER_LC"
        if [ "$PROVIDER_LC" = "doubao" ]; then
            echo "Please set: DOUBAO_MODEL, DOUBAO_BASE_URL, DOUBAO_API_KEY"
        else
            echo "Please set: DEEPSEEK_MODEL, DEEPSEEK_BASE_URL, DEEPSEEK_API_KEY"
        fi
        return 1
    fi
 
    echo "=========================="
    echo "Provider       : $PROVIDER_LC"
    echo "Input file     : $FILE"
    echo "Output dir     : $OUTDIR"
    echo "Language       : $LANG_IN -> $LANG_OUT"
    if [ $FULL_MODE -eq 1 ]; then
        echo "Output mode    : bilingual + monolingual"
    else
        echo "Output mode    : bilingual only"
    fi
    echo "Model          : $OPENAI_MODEL"
    echo "Base URL       : $OPENAI_BASE_URL"
    if [ -n "$QPS" ] || [ -n "$POOL" ] || [ -n "$TERM_POOL" ]; then
        echo "Perf knobs     : qps=${QPS:-default} pool=${POOL:-default} term_pool=${TERM_POOL:-default}"
    else
        echo "Perf knobs     : (default; same for both providers)"
    fi
    echo "=========================="
 
    # -----------------------
    # Build BabelDOC arguments
    # -----------------------
    EXTRA_ARGS=()
    if [ $FULL_MODE -eq 0 ]; then
        EXTRA_ARGS+=(--no-mono)   # only dual-language PDF
    fi
 
    # Apply throttling args ONLY if user explicitly provided them (same behavior for doubao/deepseek)
    if [ -n "$QPS" ]; then
        EXTRA_ARGS+=(--qps "$QPS")
    fi
    if [ -n "$POOL" ]; then
        EXTRA_ARGS+=(--pool-max-workers "$POOL")
    fi
    if [ -n "$TERM_POOL" ]; then
        EXTRA_ARGS+=(--term-pool-max-workers "$TERM_POOL")
    fi
 
    # -----------------------
    # Run BabelDOC with live output
    # -----------------------
    echo ""
    echo "▶ Running BabelDOC (live output)..."
 
    LOG_FILE="$(mktemp -t babeldoc_run.XXXXXX.log)"
    cleanup() { :; }  # keep log by default
    trap cleanup EXIT INT TERM
 
    babeldoc \
        --openai \
        --openai-model "$OPENAI_MODEL" \
        --openai-base-url "$OPENAI_BASE_URL" \
        --openai-api-key "$OPENAI_API_KEY" \
        --files "$FILE" \
        --lang-in "$LANG_IN" \
        --lang-out "$LANG_OUT" \
        --output "$OUTDIR" \
        "${EXTRA_ARGS[@]}" \
        2>&1 | tee "$LOG_FILE"
 
    BABELDOC_RC=${PIPESTATUS[0]}
    if (( BABELDOC_RC != 0 )); then
        echo ""
        echo "⚠️ BabelDOC exited with error code: $BABELDOC_RC"
        echo "   Log saved at: $LOG_FILE"
        return $BABELDOC_RC
    fi
 
    echo ""
    echo "=========================="
    echo " Translation completed. Checking token usage & balance..."
    echo "=========================="
    echo "Log file: $LOG_FILE"
 
    # -----------------------
    # Parse token usage from BabelDOC log
    # -----------------------
    echo ""
    echo "Parsing token usage..."
    TOKENS_LINE=$(grep -E "Total tokens" "$LOG_FILE" | tail -n 1)
 
    if [ -n "$TOKENS_LINE" ]; then
        TOKENS=$(printf '%s\n' "$TOKENS_LINE" | sed 's/.*[=:][ ]*\([0-9][0-9]*\).*/\1/')
        if [ -n "$TOKENS" ]; then
            echo "Total tokens: $TOKENS"
        else
            echo "Found 'Total tokens' but failed to parse:"
            echo "  $TOKENS_LINE"
        fi
    else
        echo "No 'Total tokens' line found."
        grep -E "Prompt tokens|Completion tokens|Total tokens" "$LOG_FILE" | tail -n 5
    fi
 
    # -----------------------
    # Fetch balance (DeepSeek only by default)
    # -----------------------
    echo ""
    if [ "$PROVIDER_LC" = "deepseek" ]; then
        echo "Fetching DeepSeek balance..."
        BALANCE_JSON=$(curl -s -H "Authorization: Bearer $DEEPSEEK_API_KEY" https://api.deepseek.com/user/balance)
 
        if [[ "$BALANCE_JSON" == *"balance_infos"* ]]; then
            TOTAL_BALANCE=$(printf '%s\n' "$BALANCE_JSON" | sed -n 's/.*"total_balance":"\([^"]*\)".*/\1/p' | head -n 1)
            CURRENCY=$(printf '%s\n' "$BALANCE_JSON" | sed -n 's/.*"currency":"\([^"]*\)".*/\1/p' | head -n 1)
            if [ -n "$TOTAL_BALANCE" ]; then
                echo "DeepSeek balance: $TOTAL_BALANCE $CURRENCY"
            else
                echo "Balance JSON returned but parsing failed:"
                echo "  $BALANCE_JSON"
            fi
        elif [ -n "$BALANCE_JSON" ]; then
            echo "Unexpected DeepSeek balance response:"
            echo "  $BALANCE_JSON"
        else
            echo "Failed to retrieve DeepSeek balance (empty response)."
        fi
    else
        echo "Skipping balance check for Doubao (Ark)."
    fi
 
    echo ""
    echo "Done! Translated files saved to: $OUTDIR"
}
 
 
image-20251205203954771

Later I found the function was so large and make itself hard to maintain, so I put the function at folder ~/.config/bd, and add source ~/.config/bd/bd.zsh in ~/.zshrc.

mkdir -p ~/.config/bd
vim ~/.config/bd/bd.zsh

On Windows:

Create bd.ps:

notepad $HOME\bd.ps1

Write:

param(
    [Parameter(Mandatory=$true)]
    [string]$File,
 
    [string]$Out = "",
    [string]$LangIn = "en",
    [string]$LangOut = "zh",
 
    [switch]$Full
)
 
# ---------------------
# Determine output directory
# ---------------------
if ($Out -eq "") {
    $Out = Join-Path (Split-Path $File -Parent) "translated"
}
mkdir $Out -Force | Out-Null
 
Write-Host "=========================="
Write-Host "Input file: $File"
Write-Host "Output directory: $Out"
Write-Host "Language: $LangIn$LangOut"
if ($Full) {
    Write-Host "Output mode: bilingual + monolingual"
} else {
    Write-Host "Output mode: bilingual only"
}
Write-Host "=========================="
 
# ---------------------
# Build BabelDOC args
# ---------------------
$ExtraArgs = @()
if (-not $Full) {
    $ExtraArgs += "--no-mono"
}
 
# ---------------------
# Run BabelDOC with live output, also save to log
# ---------------------
Write-Host ""
Write-Host "Running BabelDOC..."
 
$cmd = $babeldocCmd + @(
    "--openai",
    "--openai-model", $env:DEEPSEEK_MODEL,
    "--openai-base-url", $env:DEEPSEEK_BASE_URL,
    "--openai-api-key", $env:DEEPSEEK_API_KEY,
    "--files", $File,
    "--lang-in", $LangIn,
    "--lang-out", $LangOut,
    "--output", $Out
) + $ExtraArgs
 
$logFile = Join-Path $Out "babeldoc_run.log"
 
# live output + log to file
& $cmd[0] $cmd[1..($cmd.Count-1)] 2>&1 | Tee-Object -FilePath $logFile
 
Write-Host "`n=========================="
Write-Host " Translation completed. Checking token usage & balance..."
Write-Host "=========================="
 
# ---------------------
# Parse token usage
# ---------------------
Write-Host "`n📘 Parsing token usage..."
 
$TokenLine = $Output | Select-String -Pattern "Total tokens"
if ($TokenLine) {
    $Token = ($TokenLine -replace ".*:\s*", "")
    Write-Host "🔢 Token usage: $Token"
} else {
    Write-Host "⚠️ No token info found."
}
 
# ---------------------
# Fetch DeepSeek balance
# ---------------------
Write-Host "`n📡 Fetching DeepSeek API balance..."
 
$BalanceJson = Invoke-RestMethod -Headers @{Authorization = "Bearer $env:DEEPSEEK_API_KEY"} `
    -Uri "https://api.deepseek.com/user/balance" `
    -Method GET `
    -ErrorAction SilentlyContinue
 
if ($BalanceJson) {
    $Balance = $BalanceJson.balance_infos[0].total_balance
    $Currency = $BalanceJson.balance_infos[0].currency
    Write-Host "💰 DeepSeek Balance: $Balance $Currency"
} else {
    Write-Host "⚠️ Failed to fetch balance."
}
 
Write-Host "`n🎉 Done! Files saved to $Out"
 

Allow powershell to run the .ps1 script:

Set-ExecutionPolicy -Scope CurrentUser RemoteSigned

Use bd anywhere:

notepad $PROFILE

Add bd to profile:

function bd {
    & "$HOME\bd.ps1" @args
}

Then:

. $PROFILE

Usage

Simplest (default: en → zh, default output directory)

bd paper.pdf

Specify output directory

bd paper.pdf --out ./translated

Both Mono and Dual language

bd paper.pdf --full

Specify languages

bd paper.pdf --lang-in en --lang-out ja

Use DOUBAO API:

bd paper.pdf --doubao