diff --git a/.github/workflows/translate-i18n-claude.yml b/.github/workflows/translate-i18n-claude.yml
index aaf51aa6064..f3fbfe60e21 100644
--- a/.github/workflows/translate-i18n-claude.yml
+++ b/.github/workflows/translate-i18n-claude.yml
@@ -1,10 +1,10 @@
name: Translate i18n Files with Claude Code
+# Note: claude-code-action doesn't support push events directly.
+# Push events are bridged by trigger-i18n-sync.yml via repository_dispatch.
on:
- push:
- branches: [main]
- paths:
- - 'web/i18n/en-US/*.json'
+ repository_dispatch:
+ types: [i18n-sync]
workflow_dispatch:
inputs:
files:
@@ -30,7 +30,7 @@ permissions:
concurrency:
group: translate-i18n-${{ github.event_name }}-${{ github.ref }}
- cancel-in-progress: ${{ github.event_name == 'push' }}
+ cancel-in-progress: false
jobs:
translate:
@@ -67,19 +67,20 @@ jobs:
}
" web/i18n-config/languages.ts | sed 's/[[:space:]]*$//')
- if [ "${{ github.event_name }}" = "push" ]; then
- BASE_SHA="${{ github.event.before }}"
- if [ -z "$BASE_SHA" ] || [ "$BASE_SHA" = "0000000000000000000000000000000000000000" ]; then
- BASE_SHA=$(git rev-parse HEAD~1 2>/dev/null || true)
- fi
- HEAD_SHA="${{ github.sha }}"
- if [ -n "$BASE_SHA" ]; then
- CHANGED_FILES=$(git diff --name-only "$BASE_SHA" "$HEAD_SHA" -- 'web/i18n/en-US/*.json' 2>/dev/null | sed -n 's@^.*/@@p' | sed 's/\.json$//' | tr '\n' ' ' | sed 's/[[:space:]]*$//')
- else
- CHANGED_FILES=$(find web/i18n/en-US -maxdepth 1 -type f -name '*.json' -print | sed -n 's@^.*/@@p' | sed 's/\.json$//' | sort | tr '\n' ' ' | sed 's/[[:space:]]*$//')
- fi
+ if [ "${{ github.event_name }}" = "repository_dispatch" ]; then
+ BASE_SHA="${{ github.event.client_payload.base_sha }}"
+ HEAD_SHA="${{ github.event.client_payload.head_sha }}"
+ CHANGED_FILES="${{ github.event.client_payload.changed_files }}"
TARGET_LANGS="$DEFAULT_TARGET_LANGS"
- SYNC_MODE="incremental"
+ SYNC_MODE="${{ github.event.client_payload.sync_mode || 'incremental' }}"
+
+ if [ -n "${{ github.event.client_payload.diff_base64 }}" ]; then
+ printf '%s' '${{ github.event.client_payload.diff_base64 }}' | base64 -d > /tmp/i18n-diff.txt
+ DIFF_AVAILABLE="true"
+ else
+ : > /tmp/i18n-diff.txt
+ DIFF_AVAILABLE="false"
+ fi
else
BASE_SHA=""
HEAD_SHA=$(git rev-parse HEAD)
@@ -104,6 +105,18 @@ jobs:
else
CHANGED_FILES=""
fi
+
+ if [ "$SYNC_MODE" = "incremental" ] && [ -n "$BASE_SHA" ]; then
+ git diff "$BASE_SHA" "$HEAD_SHA" -- 'web/i18n/en-US/*.json' > /tmp/i18n-diff.txt 2>/dev/null || : > /tmp/i18n-diff.txt
+ else
+ : > /tmp/i18n-diff.txt
+ fi
+
+ if [ -s /tmp/i18n-diff.txt ]; then
+ DIFF_AVAILABLE="true"
+ else
+ DIFF_AVAILABLE="false"
+ fi
fi
FILE_ARGS=""
@@ -123,6 +136,7 @@ jobs:
echo "CHANGED_FILES=$CHANGED_FILES"
echo "TARGET_LANGS=$TARGET_LANGS"
echo "SYNC_MODE=$SYNC_MODE"
+ echo "DIFF_AVAILABLE=$DIFF_AVAILABLE"
echo "FILE_ARGS=$FILE_ARGS"
echo "LANG_ARGS=$LANG_ARGS"
} >> "$GITHUB_OUTPUT"
@@ -156,6 +170,7 @@ jobs:
- Head SHA: `${{ steps.context.outputs.HEAD_SHA }}`
- Scoped file args: `${{ steps.context.outputs.FILE_ARGS }}`
- Scoped language args: `${{ steps.context.outputs.LANG_ARGS }}`
+ - Full English diff available: `${{ steps.context.outputs.DIFF_AVAILABLE }}`
Tool rules:
- Use Read for repository files.
@@ -173,6 +188,9 @@ jobs:
- Do not touch unrelated i18n files.
- Do not modify `${{ github.workspace }}/web/i18n/en-US/`.
3. Detect English changes per file.
+ - Treat the current English JSON files under `${{ github.workspace }}/web/i18n/en-US/` plus the scoped `i18n:check` result as the primary source of truth.
+ - Use `/tmp/i18n-diff.txt` only as supporting context to understand what changed between `Base SHA` and `Head SHA`.
+ - Never rely on diff alone when deciding final keys or values.
- Read the current English JSON file for each file in scope.
- If sync mode is `incremental` and `Base SHA` is not empty, run:
`git -C ${{ github.workspace }} show :web/i18n/en-US/.json`
@@ -182,7 +200,7 @@ jobs:
- ADD: key only in current
- UPDATE: key exists in both and the English value changed
- DELETE: key only in previous
- - Do not rely on a truncated diff file.
+ - If `/tmp/i18n-diff.txt` is available, read it before translating so wording changes are grounded in the full English patch, but resolve any ambiguity by trusting the actual English files and scoped checks.
4. Run a scoped pre-check before editing:
- `pnpm --dir ${{ github.workspace }}/web run i18n:check ${{ steps.context.outputs.FILE_ARGS }} ${{ steps.context.outputs.LANG_ARGS }}`
- Use this command as the source of truth for missing and extra keys inside the current scope.
diff --git a/.github/workflows/trigger-i18n-sync.yml b/.github/workflows/trigger-i18n-sync.yml
new file mode 100644
index 00000000000..ee44fbb0c05
--- /dev/null
+++ b/.github/workflows/trigger-i18n-sync.yml
@@ -0,0 +1,81 @@
+name: Trigger i18n Sync on Push
+
+on:
+ push:
+ branches: [main]
+ paths:
+ - 'web/i18n/en-US/*.json'
+
+permissions:
+ contents: write
+
+concurrency:
+ group: trigger-i18n-sync-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ trigger:
+ if: github.repository == 'langgenius/dify'
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ fetch-depth: 0
+
+ - name: Detect changed files and generate full diff
+ id: detect
+ shell: bash
+ run: |
+ BASE_SHA="${{ github.event.before }}"
+ if [ -z "$BASE_SHA" ] || [ "$BASE_SHA" = "0000000000000000000000000000000000000000" ]; then
+ BASE_SHA=$(git rev-parse HEAD~1 2>/dev/null || true)
+ fi
+ HEAD_SHA="${{ github.sha }}"
+
+ if [ -n "$BASE_SHA" ]; then
+ CHANGED_FILES=$(git diff --name-only "$BASE_SHA" "$HEAD_SHA" -- 'web/i18n/en-US/*.json' 2>/dev/null | sed -n 's@^.*/@@p' | sed 's/\.json$//' | tr '\n' ' ' | sed 's/[[:space:]]*$//')
+ git diff "$BASE_SHA" "$HEAD_SHA" -- 'web/i18n/en-US/*.json' > /tmp/i18n-diff.txt 2>/dev/null || : > /tmp/i18n-diff.txt
+ else
+ CHANGED_FILES=$(find web/i18n/en-US -maxdepth 1 -type f -name '*.json' -print | sed -n 's@^.*/@@p' | sed 's/\.json$//' | sort | tr '\n' ' ' | sed 's/[[:space:]]*$//')
+ : > /tmp/i18n-diff.txt
+ fi
+
+ if [ -n "$CHANGED_FILES" ]; then
+ echo "has_changes=true" >> "$GITHUB_OUTPUT"
+ else
+ echo "has_changes=false" >> "$GITHUB_OUTPUT"
+ fi
+
+ echo "base_sha=$BASE_SHA" >> "$GITHUB_OUTPUT"
+ echo "head_sha=$HEAD_SHA" >> "$GITHUB_OUTPUT"
+ echo "changed_files=$CHANGED_FILES" >> "$GITHUB_OUTPUT"
+
+ - name: Trigger i18n sync workflow
+ if: steps.detect.outputs.has_changes == 'true'
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ env:
+ BASE_SHA: ${{ steps.detect.outputs.base_sha }}
+ HEAD_SHA: ${{ steps.detect.outputs.head_sha }}
+ CHANGED_FILES: ${{ steps.detect.outputs.changed_files }}
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ script: |
+ const fs = require('fs')
+
+ const diffBase64 = fs.readFileSync('/tmp/i18n-diff.txt').toString('base64')
+
+ await github.rest.repos.createDispatchEvent({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ event_type: 'i18n-sync',
+ client_payload: {
+ changed_files: process.env.CHANGED_FILES,
+ diff_base64: diffBase64,
+ sync_mode: 'incremental',
+ base_sha: process.env.BASE_SHA,
+ head_sha: process.env.HEAD_SHA,
+ },
+ })