diff --git a/.github/workflows/push-translations.sh b/.github/workflows/push-translations.sh new file mode 100755 index 00000000..be153eea --- /dev/null +++ b/.github/workflows/push-translations.sh @@ -0,0 +1,138 @@ +#!/bin/sh + +set -e + +I18N_DIR=resources/i18n + +# Normalize JSON for deterministic comparison: +# remove empty/null attributes, sort keys alphabetically +process_json() { + jq 'walk(if type == "object" then with_entries(select(.value != null and .value != "" and .value != [] and .value != {})) | to_entries | sort_by(.key) | from_entries else . end)' "$1" +} + +# Get list of all languages configured in the POEditor project +get_language_list() { + curl -s -X POST https://api.poeditor.com/v2/languages/list \ + -d api_token="${POEDITOR_APIKEY}" \ + -d id="${POEDITOR_PROJECTID}" +} + +# Extract language name from the language list JSON given a language code +get_language_name() { + lang_code="$1" + lang_list="$2" + echo "$lang_list" | jq -r ".result.languages[] | select(.code == \"$lang_code\") | .name" +} + +# Extract language code from a file path (e.g., "resources/i18n/fr.json" -> "fr") +get_lang_code() { + filepath="$1" + filename=$(basename "$filepath") + echo "${filename%.*}" +} + +# Export the current translation for a language from POEditor (v2 API) +export_language() { + lang_code="$1" + response=$(curl -s -X POST https://api.poeditor.com/v2/projects/export \ + -d api_token="${POEDITOR_APIKEY}" \ + -d id="${POEDITOR_PROJECTID}" \ + -d language="$lang_code" \ + -d type="key_value_json") + + url=$(echo "$response" | jq -r '.result.url') + if [ -z "$url" ] || [ "$url" = "null" ]; then + echo "Failed to export $lang_code: $response" >&2 + return 1 + fi + echo "$url" +} + +# Flatten nested JSON to POEditor languages/update format. +# POEditor uses term + context pairs, where: +# term = the leaf key name +# context = the parent path as "key1"."key2"."key3" (empty for root keys) +flatten_to_poeditor() { + jq -c '[paths(scalars) as $p | + { + "term": ($p | last | tostring), + "context": (if ($p | length) > 1 then ($p[:-1] | map("\"" + tostring + "\"") | join(".")) else "" end), + "translation": {"content": getpath($p)} + } + ]' "$1" +} + +# Update translations for a language in POEditor via languages/update API +update_language() { + lang_code="$1" + file="$2" + + flatten_to_poeditor "$file" > /tmp/poeditor_data.json + response=$(curl -s -X POST https://api.poeditor.com/v2/languages/update \ + -d api_token="${POEDITOR_APIKEY}" \ + -d id="${POEDITOR_PROJECTID}" \ + -d language="$lang_code" \ + --data-urlencode data@/tmp/poeditor_data.json) + rm -f /tmp/poeditor_data.json + + status=$(echo "$response" | jq -r '.response.status') + if [ "$status" != "success" ]; then + echo "Failed to update $lang_code: $response" >&2 + return 1 + fi + + parsed=$(echo "$response" | jq -r '.result.translations.parsed') + added=$(echo "$response" | jq -r '.result.translations.added') + updated=$(echo "$response" | jq -r '.result.translations.updated') + echo " Translations - parsed: $parsed, added: $added, updated: $updated" +} + +# --- Main --- + +if [ $# -eq 0 ]; then + echo "Usage: $0 [file2] ..." + echo "No files specified. Nothing to do." + exit 0 +fi + +lang_list=$(get_language_list) +upload_count=0 + +for file in "$@"; do + if [ ! -f "$file" ]; then + echo "Warning: File not found: $file, skipping" + continue + fi + + lang_code=$(get_lang_code "$file") + lang_name=$(get_language_name "$lang_code" "$lang_list") + + if [ -z "$lang_name" ]; then + echo "Warning: Language code '$lang_code' not found in POEditor, skipping $file" + continue + fi + + echo "Processing $lang_name ($lang_code)..." + + # Export current state from POEditor + url=$(export_language "$lang_code") + curl -sSL "$url" -o poeditor_export.json + + # Normalize both files for comparison + process_json "$file" > local_normalized.json + process_json poeditor_export.json > remote_normalized.json + + # Compare normalized versions + if diff -q local_normalized.json remote_normalized.json > /dev/null 2>&1; then + echo " No differences, skipping" + else + echo " Differences found, updating POEditor..." + update_language "$lang_code" "$file" + upload_count=$((upload_count + 1)) + fi + + rm -f poeditor_export.json local_normalized.json remote_normalized.json +done + +echo "" +echo "Done. Updated $upload_count translation(s) in POEditor." diff --git a/.github/workflows/push-translations.yml b/.github/workflows/push-translations.yml new file mode 100644 index 00000000..f7cf0062 --- /dev/null +++ b/.github/workflows/push-translations.yml @@ -0,0 +1,32 @@ +name: POEditor export + +on: + push: + branches: + - master + paths: + - 'resources/i18n/*.json' + +jobs: + push-translations: + runs-on: ubuntu-latest + if: ${{ github.repository_owner == 'navidrome' }} + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 2 + + - name: Detect changed translation files + id: changed + run: | + CHANGED_FILES=$(git diff --name-only HEAD~1 HEAD -- 'resources/i18n/*.json' | tr '\n' ' ') + echo "files=$CHANGED_FILES" >> $GITHUB_OUTPUT + echo "Changed translation files: $CHANGED_FILES" + + - name: Push translations to POEditor + if: ${{ steps.changed.outputs.files != '' }} + env: + POEDITOR_APIKEY: ${{ secrets.POEDITOR_APIKEY }} + POEDITOR_PROJECTID: ${{ secrets.POEDITOR_PROJECTID }} + run: | + .github/workflows/push-translations.sh ${{ steps.changed.outputs.files }}