mirror of
https://github.com/forkless/Piperless.git
synced 2026-08-25 03:52:37 +02:00
Initial public commit
This commit is contained in:
Executable
+176
@@ -0,0 +1,176 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# json2po.sh — Convert a per-locale JSON translation file into a WordPress
|
||||
# .po file, using the original .pot for file-reference comments.
|
||||
#
|
||||
# Usage:
|
||||
# ./tools/json2po.sh <locale_json> [pot_path] [output_po]
|
||||
#
|
||||
# Defaults:
|
||||
# pot_path = languages/piperless.pot
|
||||
# output_po = languages/piperless-<locale>.po
|
||||
#
|
||||
# The script only emits entries with non-empty translations.
|
||||
# Untranslated strings are skipped — WordPress falls back to the English
|
||||
# msgid at runtime.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
LOCALE_JSON="${1:?Usage: $0 <locale_json> [pot_path] [output_po]}"
|
||||
POT="${2:-languages/piperless.pot}"
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
# Resolve paths relative to project root.
|
||||
[[ "$LOCALE_JSON" != /* ]] && LOCALE_JSON="${PROJECT_DIR}/${LOCALE_JSON}"
|
||||
[[ "$POT" != /* ]] && POT="${PROJECT_DIR}/${POT}"
|
||||
|
||||
if [[ ! -f "$LOCALE_JSON" ]]; then
|
||||
echo "Error: locale JSON not found: $LOCALE_JSON" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -f "$POT" ]]; then
|
||||
echo "Error: .pot file not found: $POT" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v jq &>/dev/null; then
|
||||
echo "Error: jq is required." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# Helper: escape a string for PO msgid/msgstr double-quoted format.
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
po_escape() {
|
||||
local s="$1"
|
||||
s="${s//\\/\\\\}"
|
||||
s="${s//\"/\\\"}"
|
||||
printf '%s' "$s"
|
||||
}
|
||||
|
||||
# ── Determine locale and output path ──────────────────────────────────
|
||||
LOCALE=$(jq -r '._meta.locale // "unknown"' "$LOCALE_JSON")
|
||||
|
||||
if [[ -n "${3:-}" ]]; then
|
||||
OUT_PO="${3}"
|
||||
[[ "$OUT_PO" != /* ]] && OUT_PO="${PROJECT_DIR}/${OUT_PO}"
|
||||
else
|
||||
OUT_PO="${LOCALE_JSON%.json}.po"
|
||||
fi
|
||||
|
||||
# ── Build msgid → references map from the .pot ────────────────────────
|
||||
# Format: msgid<TAB>#: file:line file:line
|
||||
declare -A refs
|
||||
current_refs=""
|
||||
current_msgid=""
|
||||
in_msgid=false
|
||||
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
if [[ "$line" =~ ^#:\ (.*) ]]; then
|
||||
current_refs="#: ${BASH_REMATCH[1]}"
|
||||
elif [[ "$line" =~ ^#:\ ]]; then
|
||||
# Continuation of references (e.g. "#: piperless.php:47 piperless.php:56")
|
||||
: # handled by the regex above
|
||||
elif [[ "$line" =~ ^msgid\ \"(.*)\"$ ]]; then
|
||||
current_msgid="${BASH_REMATCH[1]}"
|
||||
in_msgid=true
|
||||
elif $in_msgid && [[ "$line" =~ ^\"(.*)\"$ ]]; then
|
||||
current_msgid+="${BASH_REMATCH[1]}"
|
||||
elif [[ "$line" =~ ^msgstr ]]; then
|
||||
in_msgid=false
|
||||
if [[ -n "$current_msgid" && -n "$current_refs" ]]; then
|
||||
refs["$current_msgid"]="$current_refs"
|
||||
fi
|
||||
current_refs=""
|
||||
current_msgid=""
|
||||
fi
|
||||
done < "$POT"
|
||||
|
||||
# ── Counters ──────────────────────────────────────────────────────────
|
||||
TOTAL=$(jq '._meta.total_strings // 0' "$LOCALE_JSON")
|
||||
TRANSLATED=$(jq '[.strings[] | select(. != "")] | length' "$LOCALE_JSON")
|
||||
TODAY=$(date +%Y-%m-%d)
|
||||
|
||||
# ── PO header ─────────────────────────────────────────────────────────
|
||||
LANG_CODE="${LOCALE%_*}" # e.g. "de" from "de_DE"
|
||||
LANG_NAME="${LOCALE}" # full locale as fallback
|
||||
|
||||
# Try to get a human-readable language name.
|
||||
case "$LANG_CODE" in
|
||||
de) LANG_DISPLAY="German" ;;
|
||||
fr) LANG_DISPLAY="French" ;;
|
||||
es) LANG_DISPLAY="Spanish" ;;
|
||||
ja) LANG_DISPLAY="Japanese" ;;
|
||||
nl) LANG_DISPLAY="Dutch" ;;
|
||||
*) LANG_DISPLAY="$LANG_CODE" ;;
|
||||
esac
|
||||
|
||||
# ── Generate .po ──────────────────────────────────────────────────────
|
||||
{
|
||||
cat <<HEADER
|
||||
# Piperless — Audio Transcripts
|
||||
# Copyright (C) 2024 Piperless Team
|
||||
# This file is distributed under the MIT license.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Piperless 1.0.0\\n"
|
||||
"Report-Msgid-Bugs-To: https://github.com/example/piperless/issues\\n"
|
||||
"POT-Creation-Date: 2024-01-01 00:00+0000\\n"
|
||||
"PO-Revision-Date: ${TODAY} 00:00+0000\\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n"
|
||||
"Language-Team: ${LANG_DISPLAY} <LL@li.org>\\n"
|
||||
"Language: ${LOCALE}\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n"
|
||||
"Content-Transfer-Encoding: 8bit\\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\\n"
|
||||
|
||||
HEADER
|
||||
|
||||
# ── Emit translated entries ───────────────────────────────────────
|
||||
emitted=0
|
||||
while IFS=$'\t' read -r key val; do
|
||||
# Skip empty translations.
|
||||
[[ -z "$val" ]] && continue
|
||||
|
||||
# Emit file-reference comment if known.
|
||||
ref="${refs["$key"]:-}"
|
||||
if [[ -n "$ref" ]]; then
|
||||
echo "$ref"
|
||||
fi
|
||||
|
||||
# msgid — escape for PO format.
|
||||
printf 'msgid "%s"\n' "$(po_escape "$key")"
|
||||
|
||||
# msgstr — escape for PO format.
|
||||
printf 'msgstr "%s"\n' "$(po_escape "$val")"
|
||||
echo ""
|
||||
emitted=$((emitted + 1))
|
||||
done < <(jq -r '.strings | to_entries[] | "\(.key)\t\(.value)"' "$LOCALE_JSON")
|
||||
|
||||
} > "$OUT_PO"
|
||||
|
||||
# ── Compile .po → .mo ─────────────────────────────────────────────────
|
||||
# WordPress loads .mo files at runtime, not .po.
|
||||
OUT_MO="${OUT_PO%.po}.mo"
|
||||
if command -v msgfmt &>/dev/null; then
|
||||
msgfmt -o "$OUT_MO" "$OUT_PO" 2>/dev/null
|
||||
MO_OK=true
|
||||
else
|
||||
MO_OK=false
|
||||
fi
|
||||
|
||||
# ── Report ────────────────────────────────────────────────────────────
|
||||
echo "json2po: ${LOCALE} → ${OUT_PO}"
|
||||
echo " translated : ${TRANSLATED} / ${TOTAL}"
|
||||
echo " emitted : ${emitted:-0} entries"
|
||||
if [[ "$MO_OK" == true ]]; then
|
||||
echo " compiled : ${OUT_MO}"
|
||||
else
|
||||
echo " compiled : skipped (msgfmt not found)"
|
||||
fi
|
||||
if [[ ${emitted:-0} -eq 0 ]]; then
|
||||
echo " WARNING: no translations found. .po file contains headers only."
|
||||
fi
|
||||
Executable
+208
@@ -0,0 +1,208 @@
|
||||
#!/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
|
||||
Executable
+82
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# pot2json.sh — Extract translatable strings from a WordPress .pot file
|
||||
# and produce a clean JSON keyed by msgid.
|
||||
#
|
||||
# Usage:
|
||||
# ./tools/pot2json.sh [pot_path] [output_json]
|
||||
#
|
||||
# Defaults:
|
||||
# pot_path = languages/piperless.pot
|
||||
# output_json = languages/translations.json
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
POT="${1:-languages/piperless.pot}"
|
||||
OUT="${2:-languages/translations.json}"
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
POT="${PROJECT_DIR}/${POT#./}"
|
||||
OUT="${PROJECT_DIR}/${OUT#./}"
|
||||
|
||||
if [[ ! -f "$POT" ]]; then
|
||||
echo "Error: .pot file not found at $POT" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── Extract msgid/msgstr pairs into a tmp file ────────────────────────
|
||||
TMP="$(mktemp)"
|
||||
trap 'rm -f "$TMP"' EXIT
|
||||
|
||||
current_msgid=""
|
||||
current_msgstr=""
|
||||
in_msgid=false
|
||||
in_msgstr=false
|
||||
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
[[ "$line" =~ ^# ]] && continue
|
||||
|
||||
if [[ "$line" =~ ^msgid\ \"(.*)\"$ ]]; then
|
||||
current_msgid="${BASH_REMATCH[1]}"
|
||||
in_msgid=true
|
||||
in_msgstr=false
|
||||
current_msgstr=""
|
||||
elif [[ "$line" =~ ^msgstr\ \"(.*)\"$ ]]; then
|
||||
current_msgstr="${BASH_REMATCH[1]}"
|
||||
in_msgid=false
|
||||
in_msgstr=true
|
||||
if [[ -n "$current_msgid" ]]; then
|
||||
# Write key<tab>value to tmp file (sorted later).
|
||||
printf '%s\t%s\n' "$current_msgid" "$current_msgstr" >> "$TMP"
|
||||
fi
|
||||
elif $in_msgid && [[ "$line" =~ ^\"(.*)\"$ ]]; then
|
||||
current_msgid+="${BASH_REMATCH[1]}"
|
||||
elif $in_msgstr && [[ "$line" =~ ^\"(.*)\"$ ]]; then
|
||||
current_msgstr+="${BASH_REMATCH[1]}"
|
||||
fi
|
||||
done < "$POT"
|
||||
|
||||
# ── Build JSON from sorted pairs ──────────────────────────────────────
|
||||
{
|
||||
echo "{"
|
||||
first=true
|
||||
sort "$TMP" | while IFS=$'\t' read -r key val; do
|
||||
# Escape for JSON.
|
||||
esc_key=$(printf '%s' "$key" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
||||
esc_val=$(printf '%s' "$val" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
||||
|
||||
if $first; then
|
||||
first=false
|
||||
else
|
||||
echo ","
|
||||
fi
|
||||
printf ' "%s": "%s"' "$esc_key" "$esc_val"
|
||||
done
|
||||
echo ""
|
||||
echo "}"
|
||||
} > "$OUT"
|
||||
|
||||
count=$(wc -l < "$TMP")
|
||||
echo "Extracted ${count} strings → ${OUT}"
|
||||
Executable
+108
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# sync-translations.sh — Generate / update per-locale JSON translation files
|
||||
# from the master translations.json.
|
||||
#
|
||||
# Usage:
|
||||
# ./tools/sync-translations.sh [locale...]
|
||||
#
|
||||
# With no arguments: syncs all known locales.
|
||||
# With one or more locale codes: syncs only those (e.g. de_DE fr_FR).
|
||||
#
|
||||
# The master file (languages/translations.json) keys on the original English
|
||||
# string. Each locale file gets a _meta block and a "strings" object.
|
||||
# Existing translations are preserved; new keys from master are added with
|
||||
# empty values. Removed keys are dropped.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
LANG_DIR="${PROJECT_DIR}/languages"
|
||||
MASTER="${LANG_DIR}/translations.json"
|
||||
|
||||
# ── Known target locales ──────────────────────────────────────────────
|
||||
DEFAULT_LOCALES=("de_DE" "fr_FR" "es_ES" "ja" "nl_NL")
|
||||
|
||||
if [[ $# -gt 0 ]]; then
|
||||
LOCALES=("$@")
|
||||
else
|
||||
LOCALES=("${DEFAULT_LOCALES[@]}")
|
||||
fi
|
||||
|
||||
# ── Validate master ───────────────────────────────────────────────────
|
||||
if [[ ! -f "$MASTER" ]]; then
|
||||
echo "Error: master translations.json not found. Run pot2json.sh first." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v jq &>/dev/null; then
|
||||
echo "Error: jq is required. Install with: sudo apt install jq" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TODAY=$(date +%Y-%m-%d)
|
||||
|
||||
# ── Count keys in master ──────────────────────────────────────────────
|
||||
MASTER_COUNT=$(jq 'keys | length' "$MASTER")
|
||||
|
||||
for LOCALE in "${LOCALES[@]}"; do
|
||||
LOCALE_FILE="${LANG_DIR}/piperless-${LOCALE}.json"
|
||||
|
||||
if [[ -f "$LOCALE_FILE" ]]; then
|
||||
# ── Merge: keep existing translations, add new keys, drop removed ──
|
||||
OLD_TRANSLATED=$(jq '._meta.translated // 0' "$LOCALE_FILE")
|
||||
OLD_LOCKED=$(jq '._meta.locked // false' "$LOCALE_FILE")
|
||||
|
||||
# Build a new strings object: for every key in master, use the
|
||||
# existing translation if present; otherwise empty string.
|
||||
jq -n --slurpfile master "$MASTER" --slurpfile local "$LOCALE_FILE" '
|
||||
($local[0].strings // {}) as $existing |
|
||||
{
|
||||
_meta: {
|
||||
locale: "'"$LOCALE"'",
|
||||
source: "translations.json",
|
||||
generated: "'"$TODAY"'",
|
||||
total_strings: ($master[0] | keys | length),
|
||||
translated: 0,
|
||||
locked: '"$OLD_LOCKED"'
|
||||
},
|
||||
strings: ($master[0] | to_entries | map({
|
||||
key: .key,
|
||||
value: ($existing[.key] // "")
|
||||
}) | from_entries)
|
||||
}
|
||||
' > "${LOCALE_FILE}.tmp"
|
||||
|
||||
# Re-count translated (non-empty values).
|
||||
NEW_TRANSLATED=$(jq '[.strings[] | select(. != "")] | length' "${LOCALE_FILE}.tmp")
|
||||
jq --argjson t "$NEW_TRANSLATED" '._meta.translated = $t' "${LOCALE_FILE}.tmp" > "$LOCALE_FILE"
|
||||
rm -f "${LOCALE_FILE}.tmp"
|
||||
|
||||
echo "Updated ${LOCALE}: ${NEW_TRANSLATED} / ${MASTER_COUNT} translated (was ${OLD_TRANSLATED})"
|
||||
|
||||
else
|
||||
# ── Create new locale file ─────────────────────────────────────
|
||||
jq -n --slurpfile master "$MASTER" '
|
||||
{
|
||||
_meta: {
|
||||
locale: "'"$LOCALE"'",
|
||||
source: "translations.json",
|
||||
generated: "'"$TODAY"'",
|
||||
total_strings: ($master[0] | keys | length),
|
||||
translated: 0,
|
||||
locked: false
|
||||
},
|
||||
strings: ($master[0] | to_entries | map({
|
||||
key: .key,
|
||||
value: .value
|
||||
}) | from_entries)
|
||||
}
|
||||
' > "$LOCALE_FILE"
|
||||
|
||||
echo "Created ${LOCALE}: 0 / ${MASTER_COUNT} translated"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "Done. Locale files in ${LANG_DIR}/"
|
||||
Reference in New Issue
Block a user