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)
|
# Use existing translations if available (don't delete on updates)
|
||||||
translation_update: crowdin_move
|
translation_update: crowdin_move
|
||||||
|
|
||||||
# Language code mapping
|
# Language code mapping (all 10 Crowdin target languages)
|
||||||
languages_mapping:
|
languages_mapping:
|
||||||
two_letters_code:
|
two_letters_code:
|
||||||
sl: sl
|
sl: sl
|
||||||
|
|
@ -24,6 +24,11 @@ files:
|
||||||
hr: hr
|
hr: hr
|
||||||
sr: sr
|
sr: sr
|
||||||
ru: ru
|
ru: ru
|
||||||
|
el: el
|
||||||
|
it: it
|
||||||
|
mk: mk
|
||||||
|
sk: sk
|
||||||
|
bs: bs
|
||||||
|
|
||||||
# Automatic commits when translations complete
|
# Automatic commits when translations complete
|
||||||
commit_message: 'translations: update {language} translations from Crowdin'
|
commit_message: 'translations: update {language} translations from Crowdin'
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,11 @@ TAILSCALE_AUTHKEY=
|
||||||
# Channels
|
# Channels
|
||||||
TELEGRAM_BOT_TOKEN=
|
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
|
# SSH (optional) — used by Ansible when enabling sshd inside jails
|
||||||
# Example: SSH_PUBLIC_KEY="ssh-ed25519 AAAA... you@host"
|
# Example: SSH_PUBLIC_KEY="ssh-ed25519 AAAA... you@host"
|
||||||
SSH_PUBLIC_KEY=
|
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