feat: Crowdin integration - Slovenian first (Sam & Claude)
This commit is contained in:
parent
84cc452497
commit
e07a68ae92
3 changed files with 310 additions and 1 deletions
|
|
@ -16,7 +16,7 @@ files:
|
|||
# Use existing translations if available (don't delete on updates)
|
||||
translation_update: crowdin_move
|
||||
|
||||
# Language code mapping
|
||||
# Language code mapping (all 10 Crowdin target languages)
|
||||
languages_mapping:
|
||||
two_letters_code:
|
||||
sl: sl
|
||||
|
|
@ -24,6 +24,11 @@ files:
|
|||
hr: hr
|
||||
sr: sr
|
||||
ru: ru
|
||||
el: el
|
||||
it: it
|
||||
mk: mk
|
||||
sk: sk
|
||||
bs: bs
|
||||
|
||||
# Automatic commits when translations complete
|
||||
commit_message: 'translations: update {language} translations from Crowdin'
|
||||
|
|
|
|||
|
|
@ -46,6 +46,11 @@ TAILSCALE_AUTHKEY=
|
|||
# Channels
|
||||
TELEGRAM_BOT_TOKEN=
|
||||
|
||||
# Crowdin — Translation management for multi-language docs
|
||||
# Free for open source: https://crowdin.com/open-source
|
||||
# Create token at: https://crowdin.com/settings#api-key
|
||||
CROWDIN_PERSONAL_TOKEN=
|
||||
|
||||
# SSH (optional) — used by Ansible when enabling sshd inside jails
|
||||
# Example: SSH_PUBLIC_KEY="ssh-ed25519 AAAA... you@host"
|
||||
SSH_PUBLIC_KEY=
|
||||
|
|
|
|||
299
scripts/crowdin-sync.sh
Executable file
299
scripts/crowdin-sync.sh
Executable file
|
|
@ -0,0 +1,299 @@
|
|||
#!/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
|
||||
Loading…
Add table
Reference in a new issue