clawdie-ai/scripts/crowdin-sync.sh

299 lines
11 KiB
Bash
Executable file

#!/bin/sh
# Crowdin Sync Script
# Pushes English docs to Crowdin and pulls translations
#
# Usage:
# ./crowdin-sync.sh --push # Upload English sources to Crowdin
# ./crowdin-sync.sh --pull # Download all translations
# ./crowdin-sync.sh --pull sl # Download Slovenian only
# ./crowdin-sync.sh --status # Check sync status
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
DOCS_DIR="${REPO_DIR}/docs/public"
ENV_FILE="${REPO_DIR}/.env"
# Config (from .crowdin.yml)
PROJECT_ID="883714"
API_BASE="https://api.crowdin.com/api/v2"
# All target languages configured in Crowdin
ALL_LANGUAGES="sl de hr sr ru el it mk sk bs"
# Source .env first (if exists)
if [ -f "$ENV_FILE" ]; then
. "$ENV_FILE"
fi
# Token from environment or file
if [ -n "$CROWDIN_PERSONAL_TOKEN" ]; then
API_TOKEN="$CROWDIN_PERSONAL_TOKEN"
elif [ -f ~/.config/clawdie/crowdin-token ]; then
API_TOKEN=$(cat ~/.config/clawdie/crowdin-token)
else
echo "ERROR: No API token found"
echo "Set CROWDIN_PERSONAL_TOKEN in .env or create ~/.config/clawdie/crowdin-token"
exit 1
fi
AUTH_HEADER="Authorization: Bearer $API_TOKEN"
CONTENT_TYPE="Content-Type: application/json"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log_info() { echo "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo "${YELLOW}[WARN]${NC} $1"; }
log_err() { echo "${RED}[ERROR]${NC} $1" >&2; }
# ────────────────────────────────────────────────────────────────────────────
# API Helper Functions
# ────────────────────────────────────────────────────────────────────────────
crowdin_api() {
local method="$1"
local endpoint="$2"
local data="$3"
local response
if [ -n "$data" ]; then
response=$(curl -s -X "$method" \
"${API_BASE}${endpoint}" \
-H "$AUTH_HEADER" \
-H "$CONTENT_TYPE" \
-d "$data")
else
response=$(curl -s -X "$method" \
"${API_BASE}${endpoint}" \
-H "$AUTH_HEADER" \
-H "$CONTENT_TYPE")
fi
echo "$response"
}
# ────────────────────────────────────────────────────────────────────────────
# Push: Upload English sources to Crowdin
# ────────────────────────────────────────────────────────────────────────────
push_sources() {
log_info "Uploading English sources to Crowdin..."
cd "$DOCS_DIR" || { log_err "Docs directory not found: $DOCS_DIR"; exit 1; }
local count=0
local failed=0
# Find all markdown files (excluding translation directories)
for doc_file in $(find . -name "*.md" -type f 2>/dev/null | grep -v -E '^\./(de|hr|ru|sr|sl|el|it|mk|sk|bs)/'); do
# Remove leading ./
doc_file="${doc_file#./}"
if [ ! -f "$doc_file" ]; then
continue
fi
count=$((count + 1))
echo " [$count] Uploading: $doc_file"
# Read file content
local file_content
file_content=$(base64 -w 0 < "$doc_file")
# Step 1: Upload to storage (requires Crowdin-API-FileName header, no / allowed)
local crowdin_filename=$(echo "$doc_file" | tr '/' '_')
local storage_response
storage_response=$(curl -s -X POST "${API_BASE}/storages" \
-H "$AUTH_HEADER" \
-H "Crowdin-API-FileName: ${crowdin_filename}" \
-T "$doc_file")
local storage_id
storage_id=$(echo "$storage_response" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2)
if [ -z "$storage_id" ]; then
log_warn " Failed to upload to storage: $doc_file"
log_warn " Response: $storage_response"
failed=$((failed + 1))
continue
fi
# Step 2: Add/update file in project
local file_data="{\"storageId\":${storage_id},\"name\":\"/${doc_file}\"}"
local file_response
file_response=$(crowdin_api "POST" "/projects/${PROJECT_ID}/files" "$file_data")
local file_id
file_id=$(echo "$file_response" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2)
if [ -z "$file_id" ]; then
# File might already exist, try update
log_warn " File might exist, trying update..."
else
echo " ${GREEN}${NC} Uploaded (file ID: $file_id)"
fi
done
echo ""
log_info "Upload complete: $count files processed, $failed failed"
if [ "$failed" -gt 0 ]; then
exit 1
fi
}
# ────────────────────────────────────────────────────────────────────────────
# Pull: Download translations from Crowdin
# ────────────────────────────────────────────────────────────────────────────
pull_translations() {
local languages="${1:-$ALL_LANGUAGES}"
log_info "Downloading translations from Crowdin..."
log_info "Languages: $languages"
local count=0
for lang in $languages; do
echo ""
log_info "Language: $lang"
# Create language directory if missing
mkdir -p "${DOCS_DIR}/${lang}"
# Build translations export
local export_data="{\"targetLanguageId\":\"${lang}\",\"format\":\"md\"}"
local export_response
export_response=$(crowdin_api "POST" "/projects/${PROJECT_ID}/translations/exports" "$export_data")
local export_url
export_url=$(echo "$export_response" | grep -o '"url":"[^"]*"' | cut -d'"' -f4)
if [ -z "$export_url" ]; then
log_warn " No translations available for $lang"
log_warn " Response: $export_response"
continue
fi
# Download and extract
local tmp_file="/tmp/crowdin-${lang}-$(date +%s).zip"
if curl -s -o "$tmp_file" "$export_url"; then
log_info " Downloaded: $tmp_file"
# Extract to language directory
if unzip -q -o "$tmp_file" -d "${DOCS_DIR}/${lang}" 2>/dev/null; then
local file_count
file_count=$(unzip -l "$tmp_file" | tail -1 | awk '{print $2}')
log_info " ${GREEN}${NC} Extracted $file_count files to ${lang}/"
count=$((count + file_count))
else
log_warn " Failed to extract: $tmp_file"
fi
rm -f "$tmp_file"
else
log_err " Failed to download from: $export_url"
fi
done
echo ""
log_info "Pull complete: $count translation files downloaded"
}
# ────────────────────────────────────────────────────────────────────────────
# Status: Check project status
# ────────────────────────────────────────────────────────────────────────────
check_status() {
log_info "Checking Crowdin project status..."
# Get project info
local project_info
project_info=$(crowdin_api "GET" "/projects/${PROJECT_ID}")
local project_name
project_name=$(echo "$project_info" | grep -o '"name":"[^"]*"' | head -1 | cut -d'"' -f4)
echo ""
echo "Project: $project_name (ID: $PROJECT_ID)"
echo "Languages: $ALL_LANGUAGES"
echo ""
# Get files list
local files_list
files_list=$(crowdin_api "GET" "/projects/${PROJECT_ID}/files?limit=500")
local file_count
file_count=$(echo "$files_list" | grep -o '"id":' | wc -l | tr -d ' ')
log_info "Source files: $file_count"
# List first 10 files
echo "$files_list" | grep -o '"name":"[^"]*"' | head -10 | while read line; do
name=$(echo "$line" | cut -d'"' -f4)
echo " - $name"
done
if [ "$file_count" -gt 10 ]; then
echo " ... and $((file_count - 10)) more"
fi
# Get translation progress for all languages
echo ""
log_info "Translation progress:"
for lang in $ALL_LANGUAGES; do
local progress
progress=$(crowdin_api "GET" "/projects/${PROJECT_ID}/languages/${lang}/progress")
local total phrases translated
total=$(echo "$progress" | grep -o '"total":*[0-9]*' | head -1 | cut -d: -f2)
translated=$(echo "$progress" | grep -o '"translated":*[0-9]*' | head -1 | cut -d: -f2)
if [ -n "$total" ] && [ "$total" -gt 0 ]; then
local percent=$((translated * 100 / total))
printf " %-4s %3d/%-3d (%2d%%)\n" "$lang" "$translated" "$total" "$percent"
else
printf " %-4s No translations yet\n" "$lang"
fi
done
}
# ────────────────────────────────────────────────────────────────────────────
# Main
# ────────────────────────────────────────────────────────────────────────────
case "${1:-}" in
--push)
push_sources
;;
--pull)
pull_translations "$2"
;;
--pull-sl)
pull_translations "sl"
;;
--status)
check_status
;;
*)
echo "Usage: $0 {--push|--pull|--pull-sl|--status}"
echo ""
echo "Commands:"
echo " --push Upload English sources to Crowdin"
echo " --pull Download all translations from Crowdin"
echo " --pull sl Download specific language (sl, de, hr, sr, ru, el, it, mk, sk, bs)"
echo " --pull-sl Download Slovenian only (shortcut)"
echo " --status Check project status and translation progress"
echo ""
echo "Prerequisites:"
echo " - Set CROWDIN_PERSONAL_TOKEN in .env, or"
echo " - Create ~/.config/clawdie/crowdin-token with your API token"
exit 1
;;
esac