mirror of
https://github.com/forkless/Piperless.git
synced 2026-08-16 00:46:38 +02:00
209 lines
6.5 KiB
Bash
Executable File
209 lines
6.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# lock-translations.sh — Acquire / release / inspect translation locks.
|
|
#
|
|
# A lock marks a locale file as "being translated" so no two translators
|
|
# work on the same file simultaneously. The lock is recorded in two places:
|
|
# 1. .translations-lock.json (project-level registry)
|
|
# 2. Each locale JSON's _meta.locked flag
|
|
#
|
|
# Usage:
|
|
# ./tools/lock-translations.sh lock <locale> [who]
|
|
# ./tools/lock-translations.sh unlock <locale>
|
|
# ./tools/lock-translations.sh lock-all [who]
|
|
# ./tools/lock-translations.sh unlock-all
|
|
# ./tools/lock-translations.sh status
|
|
#
|
|
# Exit codes:
|
|
# 0 — success
|
|
# 1 — general error
|
|
# 2 — locale already locked
|
|
# 4 — locale not locked (on unlock)
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
LOCKFILE="${PROJECT_DIR}/.translations-lock.json"
|
|
LANG_DIR="${PROJECT_DIR}/languages"
|
|
|
|
# ── Ensure lock file exists ───────────────────────────────────────────
|
|
init_lockfile() {
|
|
if [[ ! -f "$LOCKFILE" ]]; then
|
|
cat > "$LOCKFILE" <<'EOF'
|
|
{
|
|
"version": 1,
|
|
"locks": {
|
|
"de_DE": null,
|
|
"fr_FR": null,
|
|
"es_ES": null,
|
|
"ja": null,
|
|
"nl_NL": null
|
|
}
|
|
}
|
|
EOF
|
|
fi
|
|
}
|
|
|
|
# ── Read lock state for a locale ──────────────────────────────────────
|
|
get_lock_state() {
|
|
local locale="$1"
|
|
# jq -r outputs the literal string "null" for JSON null.
|
|
jq -r ".locks[\"$locale\"]" "$LOCKFILE"
|
|
}
|
|
|
|
# ── Set lock state ────────────────────────────────────────────────────
|
|
set_lock_state() {
|
|
local locale="$1"
|
|
local who="$2"
|
|
local ts
|
|
ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
|
|
if [[ "$who" == "null" ]]; then
|
|
jq --arg loc "$locale" '.locks[$loc] = null' "$LOCKFILE" > "${LOCKFILE}.tmp"
|
|
else
|
|
jq --arg loc "$locale" --arg who "$who" --arg ts "$ts" \
|
|
'.locks[$loc] = {locked_by: $who, locked_at: $ts}' \
|
|
"$LOCKFILE" > "${LOCKFILE}.tmp"
|
|
fi
|
|
mv "${LOCKFILE}.tmp" "$LOCKFILE"
|
|
}
|
|
|
|
# ── Sync _meta.locked in the locale JSON ──────────────────────────────
|
|
sync_locale_meta() {
|
|
local locale="$1"
|
|
local locked="$2" # "true" or "false"
|
|
local file="${LANG_DIR}/piperless-${locale}.json"
|
|
|
|
if [[ -f "$file" ]]; then
|
|
jq --argjson l "$locked" '._meta.locked = $l' "$file" > "${file}.tmp"
|
|
mv "${file}.tmp" "$file"
|
|
fi
|
|
}
|
|
|
|
# ── Status table ──────────────────────────────────────────────────────
|
|
cmd_status() {
|
|
init_lockfile
|
|
|
|
printf "%-8s %-10s %-6s %s\n" "LOCALE" "TRANS" "LOCKED" "HELD BY"
|
|
printf "%-8s %-10s %-6s %s\n" "------" "------" "------" "-------"
|
|
|
|
for locale in de_DE fr_FR es_ES ja nl_NL; do
|
|
local file="${LANG_DIR}/piperless-${locale}.json"
|
|
local translated=0 total=0
|
|
if [[ -f "$file" ]]; then
|
|
translated=$(jq -r '._meta.translated // 0' "$file")
|
|
total=$(jq -r '._meta.total_strings // 0' "$file")
|
|
fi
|
|
|
|
local lock_info
|
|
lock_info=$(jq -r ".locks[\"$locale\"]" "$LOCKFILE")
|
|
local locked="no"
|
|
local who="-"
|
|
|
|
if [[ "$lock_info" != "null" ]]; then
|
|
locked="YES"
|
|
who=$(echo "$lock_info" | jq -r '.locked_by // "?"')
|
|
fi
|
|
|
|
printf "%-8s %3s/%-6s %-6s %s\n" \
|
|
"$locale" "$translated" "$total" "$locked" "$who"
|
|
done
|
|
}
|
|
|
|
# ── Lock ──────────────────────────────────────────────────────────────
|
|
cmd_lock() {
|
|
local locale="$1"
|
|
local who="${2:-${USER:-unknown}}"
|
|
init_lockfile
|
|
|
|
# Validate locale exists.
|
|
local file="${LANG_DIR}/piperless-${locale}.json"
|
|
if [[ ! -f "$file" ]]; then
|
|
echo "Error: locale file not found: $file" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Check if already locked.
|
|
local current
|
|
current=$(get_lock_state "$locale")
|
|
if [[ "$current" != "null" ]]; then
|
|
local by
|
|
by=$(echo "$current" | jq -r '.locked_by // "?"')
|
|
echo "Error: $locale is already locked by $by." >&2
|
|
echo "Use 'unlock $locale' first, or contact $by." >&2
|
|
exit 2
|
|
fi
|
|
|
|
set_lock_state "$locale" "$who"
|
|
sync_locale_meta "$locale" true
|
|
echo "Locked $locale → held by $who"
|
|
}
|
|
|
|
# ── Unlock ────────────────────────────────────────────────────────────
|
|
cmd_unlock() {
|
|
local locale="$1"
|
|
init_lockfile
|
|
|
|
local current
|
|
current=$(get_lock_state "$locale")
|
|
if [[ "$current" == "null" ]]; then
|
|
echo "Error: $locale is not locked." >&2
|
|
exit 4
|
|
fi
|
|
|
|
set_lock_state "$locale" "null"
|
|
sync_locale_meta "$locale" false
|
|
echo "Unlocked $locale"
|
|
}
|
|
|
|
# ── Lock all ──────────────────────────────────────────────────────────
|
|
cmd_lock_all() {
|
|
local who="${1:-${USER:-unknown}}"
|
|
init_lockfile
|
|
|
|
for locale in de_DE fr_FR es_ES ja nl_NL; do
|
|
local current
|
|
current=$(get_lock_state "$locale")
|
|
if [[ "$current" != "null" ]]; then
|
|
local by
|
|
by=$(echo "$current" | jq -r '.locked_by // "?"')
|
|
echo "Skipping $locale (already locked by $by)"
|
|
continue
|
|
fi
|
|
set_lock_state "$locale" "$who"
|
|
sync_locale_meta "$locale" true
|
|
echo "Locked $locale"
|
|
done
|
|
}
|
|
|
|
# ── Unlock all ────────────────────────────────────────────────────────
|
|
cmd_unlock_all() {
|
|
init_lockfile
|
|
|
|
for locale in de_DE fr_FR es_ES ja nl_NL; do
|
|
set_lock_state "$locale" "null"
|
|
sync_locale_meta "$locale" false
|
|
echo "Unlocked $locale"
|
|
done
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
# Main
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
|
|
CMD="${1:-status}"
|
|
shift || true
|
|
|
|
case "$CMD" in
|
|
status) cmd_status "$@";;
|
|
lock) cmd_lock "$@";;
|
|
unlock) cmd_unlock "$@";;
|
|
lock-all) cmd_lock_all "$@";;
|
|
unlock-all) cmd_unlock_all "$@";;
|
|
*)
|
|
echo "Usage: $0 {status|lock|unlock|lock-all|unlock-all} [locale] [who]" >&2
|
|
exit 1
|
|
;;
|
|
esac
|