|
| 1 | +name: 📖 Translate Documentation |
| 2 | + |
| 3 | +on: |
| 4 | + workflow_dispatch: |
| 5 | + inputs: |
| 6 | + target_locale: |
| 7 | + description: "Target locale to translate" |
| 8 | + required: false |
| 9 | + default: "all" |
| 10 | + type: choice |
| 11 | + options: |
| 12 | + - "all" |
| 13 | + - "es" |
| 14 | + - "pt-BR" |
| 15 | + |
| 16 | + force_translate: |
| 17 | + description: "Force translate all files (ignore cache)" |
| 18 | + required: false |
| 19 | + default: false |
| 20 | + type: boolean |
| 21 | + |
| 22 | + cleanup_deleted: |
| 23 | + description: "Delete translated files when English originals are removed" |
| 24 | + required: false |
| 25 | + default: true |
| 26 | + type: boolean |
| 27 | + |
| 28 | + dry_run: |
| 29 | + description: "Dry run (don't write files or update cache)" |
| 30 | + required: false |
| 31 | + default: false |
| 32 | + type: boolean |
| 33 | + |
| 34 | + single_file: |
| 35 | + description: "Single file to translate (relative to app/en, e.g. 'home/page.mdx')" |
| 36 | + required: false |
| 37 | + default: "" |
| 38 | + type: string |
| 39 | + |
| 40 | + concurrency: |
| 41 | + description: "Number of files to translate in parallel (1-10)" |
| 42 | + required: false |
| 43 | + default: 3 |
| 44 | + type: number |
| 45 | + |
| 46 | +jobs: |
| 47 | + translate: |
| 48 | + runs-on: ubuntu-latest |
| 49 | + |
| 50 | + steps: |
| 51 | + - name: 🛒 Checkout repository |
| 52 | + uses: actions/checkout@v4 |
| 53 | + with: |
| 54 | + fetch-depth: 0 |
| 55 | + token: ${{ secrets.GITHUB_TOKEN }} |
| 56 | + |
| 57 | + - name: 📦 Setup Node.js |
| 58 | + uses: actions/setup-node@v4 |
| 59 | + with: |
| 60 | + node-version: "20" |
| 61 | + cache: "pnpm" |
| 62 | + |
| 63 | + - name: 📥 Install pnpm |
| 64 | + uses: pnpm/action-setup@v4 |
| 65 | + with: |
| 66 | + version: 9 |
| 67 | + |
| 68 | + - name: 📚 Install dependencies |
| 69 | + run: pnpm install --frozen-lockfile |
| 70 | + |
| 71 | + - name: 🧹 Clean up deleted files |
| 72 | + if: inputs.cleanup_deleted |
| 73 | + run: | |
| 74 | + echo "🔍 Checking for deleted English files..." |
| 75 | + pnpm dlx tsx scripts/i18n-sync/index.ts --cleanup |
| 76 | + env: |
| 77 | + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} |
| 78 | + |
| 79 | + - name: 🌍 Run translation |
| 80 | + run: | |
| 81 | + echo "🚀 Starting translation process..." |
| 82 | +
|
| 83 | + # Build the command with dynamic inputs |
| 84 | + CMD="pnpm dlx tsx scripts/i18n-sync/index.ts" |
| 85 | +
|
| 86 | + # Add target locale if not 'all' |
| 87 | + if [ "${{ inputs.target_locale }}" != "all" ]; then |
| 88 | + CMD="$CMD --locale ${{ inputs.target_locale }}" |
| 89 | + fi |
| 90 | +
|
| 91 | + # Add force flag if enabled |
| 92 | + if [ "${{ inputs.force_translate }}" = "true" ]; then |
| 93 | + CMD="$CMD --force" |
| 94 | + fi |
| 95 | +
|
| 96 | + # Add dry-run flag if enabled |
| 97 | + if [ "${{ inputs.dry_run }}" = "true" ]; then |
| 98 | + CMD="$CMD --dry-run" |
| 99 | + fi |
| 100 | +
|
| 101 | + # Add single file if specified |
| 102 | + if [ -n "${{ inputs.single_file }}" ]; then |
| 103 | + CMD="$CMD --file '${{ inputs.single_file }}'" |
| 104 | + fi |
| 105 | +
|
| 106 | + # Add concurrency |
| 107 | + CMD="$CMD --concurrency ${{ inputs.concurrency }}" |
| 108 | +
|
| 109 | + echo "💻 Executing: $CMD" |
| 110 | + eval $CMD |
| 111 | + env: |
| 112 | + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} |
| 113 | + OPENAI_MODEL: ${{ secrets.OPENAI_MODEL || 'gpt-4o-mini' }} |
| 114 | + |
| 115 | + - name: 📊 Check translation status |
| 116 | + id: check_changes |
| 117 | + run: | |
| 118 | + if git diff --quiet && git diff --cached --quiet; then |
| 119 | + echo "No changes detected" |
| 120 | + echo "has_changes=false" >> $GITHUB_OUTPUT |
| 121 | + else |
| 122 | + echo "Changes detected" |
| 123 | + echo "has_changes=true" >> $GITHUB_OUTPUT |
| 124 | + |
| 125 | + # Count changed files |
| 126 | + CHANGED_FILES=$(git diff --name-only | wc -l) |
| 127 | + STAGED_FILES=$(git diff --cached --name-only | wc -l) |
| 128 | + TOTAL_CHANGES=$((CHANGED_FILES + STAGED_FILES)) |
| 129 | + |
| 130 | + echo "changed_files=$TOTAL_CHANGES" >> $GITHUB_OUTPUT |
| 131 | + |
| 132 | + # Get list of changed locales |
| 133 | + CHANGED_LOCALES=$(git diff --name-only | grep -E '^app/(es|pt-BR)/' | cut -d'/' -f2 | sort -u | tr '\n' ',' | sed 's/,$//') |
| 134 | + if [ -z "$CHANGED_LOCALES" ]; then |
| 135 | + CHANGED_LOCALES=$(git diff --cached --name-only | grep -E '^app/(es|pt-BR)/' | cut -d'/' -f2 | sort -u | tr '\n' ',' | sed 's/,$//') |
| 136 | + fi |
| 137 | + echo "changed_locales=$CHANGED_LOCALES" >> $GITHUB_OUTPUT |
| 138 | + fi |
| 139 | +
|
| 140 | + - name: 🏷️ Generate branch name |
| 141 | + if: steps.check_changes.outputs.has_changes == 'true' |
| 142 | + id: branch_name |
| 143 | + run: | |
| 144 | + TIMESTAMP=$(date +%Y%m%d-%H%M%S) |
| 145 | + if [ "${{ inputs.target_locale }}" != "all" ]; then |
| 146 | + BRANCH_NAME="translations/${{ inputs.target_locale }}-$TIMESTAMP" |
| 147 | + else |
| 148 | + BRANCH_NAME="translations/all-locales-$TIMESTAMP" |
| 149 | + fi |
| 150 | + echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT |
| 151 | +
|
| 152 | + - name: 🌿 Create and switch to new branch |
| 153 | + if: steps.check_changes.outputs.has_changes == 'true' |
| 154 | + run: | |
| 155 | + git config user.name "github-actions[bot]" |
| 156 | + git config user.email "github-actions[bot]@users.noreply.github.com" |
| 157 | + git checkout -b ${{ steps.branch_name.outputs.branch_name }} |
| 158 | +
|
| 159 | + - name: 📝 Commit changes |
| 160 | + if: steps.check_changes.outputs.has_changes == 'true' |
| 161 | + run: | |
| 162 | + git add . |
| 163 | +
|
| 164 | + # Create detailed commit message |
| 165 | + COMMIT_MSG="🌍 Update translations" |
| 166 | +
|
| 167 | + if [ "${{ inputs.target_locale }}" != "all" ]; then |
| 168 | + COMMIT_MSG="$COMMIT_MSG for ${{ inputs.target_locale }}" |
| 169 | + fi |
| 170 | +
|
| 171 | + COMMIT_MSG="$COMMIT_MSG (${{ steps.check_changes.outputs.changed_files }} files)" |
| 172 | +
|
| 173 | + if [ "${{ inputs.force_translate }}" = "true" ]; then |
| 174 | + COMMIT_MSG="$COMMIT_MSG [forced]" |
| 175 | + fi |
| 176 | +
|
| 177 | + if [ "${{ inputs.cleanup_deleted }}" = "true" ]; then |
| 178 | + COMMIT_MSG="$COMMIT_MSG [with cleanup]" |
| 179 | + fi |
| 180 | +
|
| 181 | + if [ "${{ inputs.dry_run }}" = "true" ]; then |
| 182 | + COMMIT_MSG="$COMMIT_MSG [dry-run]" |
| 183 | + fi |
| 184 | +
|
| 185 | + # Add configuration details to commit body |
| 186 | + COMMIT_BODY="Translation Configuration: |
| 187 | + - Target Locale: ${{ inputs.target_locale }} |
| 188 | + - Force Translate: ${{ inputs.force_translate }} |
| 189 | + - Cleanup Deleted: ${{ inputs.cleanup_deleted }} |
| 190 | + - Dry Run: ${{ inputs.dry_run }} |
| 191 | + - Single File: ${{ inputs.single_file || 'none' }} |
| 192 | + - Concurrency: ${{ inputs.concurrency }} |
| 193 | + - Changed Locales: ${{ steps.check_changes.outputs.changed_locales }} |
| 194 | +
|
| 195 | + Generated by GitHub Actions workflow" |
| 196 | +
|
| 197 | + git commit -m "$COMMIT_MSG" -m "$COMMIT_BODY" |
| 198 | +
|
| 199 | + - name: 📤 Push branch |
| 200 | + if: steps.check_changes.outputs.has_changes == 'true' |
| 201 | + run: | |
| 202 | + git push origin ${{ steps.branch_name.outputs.branch_name }} |
| 203 | +
|
| 204 | + - name: 🔧 Create Pull Request |
| 205 | + if: steps.check_changes.outputs.has_changes == 'true' |
| 206 | + uses: actions/github-script@v7 |
| 207 | + with: |
| 208 | + script: | |
| 209 | + const { data: pr } = await github.rest.pulls.create({ |
| 210 | + owner: context.repo.owner, |
| 211 | + repo: context.repo.repo, |
| 212 | + title: `🌍 Translation Update: ${{ inputs.target_locale }} (${{ steps.check_changes.outputs.changed_files }} files)`, |
| 213 | + head: '${{ steps.branch_name.outputs.branch_name }}', |
| 214 | + base: 'main', |
| 215 | + body: `## 📖 Automated Translation Update |
| 216 | + |
| 217 | + This PR contains automated translations generated by the GitHub Actions workflow. |
| 218 | + |
| 219 | + ### 📊 Translation Summary |
| 220 | + - **Target Locale**: ${{ inputs.target_locale }} |
| 221 | + - **Files Changed**: ${{ steps.check_changes.outputs.changed_files }} |
| 222 | + - **Locales Updated**: ${{ steps.check_changes.outputs.changed_locales }} |
| 223 | + |
| 224 | + ### ⚙️ Configuration Used |
| 225 | + - **Force Translate**: ${{ inputs.force_translate }} |
| 226 | + - **Cleanup Deleted Files**: ${{ inputs.cleanup_deleted }} |
| 227 | + - **Dry Run**: ${{ inputs.dry_run }} |
| 228 | + - **Single File**: ${{ inputs.single_file || 'None (all files)' }} |
| 229 | + - **Concurrency**: ${{ inputs.concurrency }} |
| 230 | + - **Model**: gpt-4o-mini |
| 231 | + |
| 232 | + ### 🔍 Review Guidelines |
| 233 | + Please review the translations for: |
| 234 | + - [ ] Accuracy and context preservation |
| 235 | + - [ ] Proper handling of technical terms |
| 236 | + - [ ] UI/Dashboard elements remain in English |
| 237 | + - [ ] Code blocks and inline code unchanged |
| 238 | + - [ ] Markdown formatting preserved |
| 239 | + - [ ] Brand names (Arcade, Arcade Engine, Control Plane) kept in English |
| 240 | + |
| 241 | + ### 🚀 Auto-generated |
| 242 | + This PR was automatically created by the \`translate-docs.yml\` GitHub Action. |
| 243 | + |
| 244 | + **Triggered by**: @${{ github.actor }} |
| 245 | + **Workflow Run**: [${{ github.run_id }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})`, |
| 246 | + draft: false |
| 247 | + }); |
| 248 | +
|
| 249 | + // Add labels |
| 250 | + await github.rest.issues.addLabels({ |
| 251 | + owner: context.repo.owner, |
| 252 | + repo: context.repo.repo, |
| 253 | + issue_number: pr.number, |
| 254 | + labels: ['🌍 translation', '🤖 automated', 'documentation'] |
| 255 | + }); |
| 256 | +
|
| 257 | + // Add specific locale label if not 'all' |
| 258 | + if ('${{ inputs.target_locale }}' !== 'all') { |
| 259 | + await github.rest.issues.addLabels({ |
| 260 | + owner: context.repo.owner, |
| 261 | + repo: context.repo.repo, |
| 262 | + issue_number: pr.number, |
| 263 | + labels: [`locale:${{ inputs.target_locale }}`] |
| 264 | + }); |
| 265 | + } |
| 266 | +
|
| 267 | + console.log(`Created PR #${pr.number}: ${pr.html_url}`); |
| 268 | +
|
| 269 | + - name: 📄 Summary |
| 270 | + if: always() |
| 271 | + run: | |
| 272 | + echo "## 🌍 Translation Workflow Summary" >> $GITHUB_STEP_SUMMARY |
| 273 | + echo "" >> $GITHUB_STEP_SUMMARY |
| 274 | +
|
| 275 | + if [ "${{ steps.check_changes.outputs.has_changes }}" = "true" ]; then |
| 276 | + echo "✅ **Translation completed successfully!**" >> $GITHUB_STEP_SUMMARY |
| 277 | + echo "" >> $GITHUB_STEP_SUMMARY |
| 278 | + echo "- **Files changed**: ${{ steps.check_changes.outputs.changed_files }}" >> $GITHUB_STEP_SUMMARY |
| 279 | + echo "- **Target locale**: ${{ inputs.target_locale }}" >> $GITHUB_STEP_SUMMARY |
| 280 | + echo "- **Branch created**: \`${{ steps.branch_name.outputs.branch_name }}\`" >> $GITHUB_STEP_SUMMARY |
| 281 | + echo "- **Changed locales**: ${{ steps.check_changes.outputs.changed_locales }}" >> $GITHUB_STEP_SUMMARY |
| 282 | + echo "" >> $GITHUB_STEP_SUMMARY |
| 283 | + echo "A Pull Request has been created for review." >> $GITHUB_STEP_SUMMARY |
| 284 | + else |
| 285 | + echo "ℹ️ **No changes detected**" >> $GITHUB_STEP_SUMMARY |
| 286 | + echo "" >> $GITHUB_STEP_SUMMARY |
| 287 | + echo "All translations are up to date. No PR was created." >> $GITHUB_STEP_SUMMARY |
| 288 | + fi |
| 289 | +
|
| 290 | + echo "" >> $GITHUB_STEP_SUMMARY |
| 291 | + echo "### Configuration Used" >> $GITHUB_STEP_SUMMARY |
| 292 | + echo "- Target Locale: ${{ inputs.target_locale }}" >> $GITHUB_STEP_SUMMARY |
| 293 | + echo "- Force Translate: ${{ inputs.force_translate }}" >> $GITHUB_STEP_SUMMARY |
| 294 | + echo "- Cleanup Deleted: ${{ inputs.cleanup_deleted }}" >> $GITHUB_STEP_SUMMARY |
| 295 | + echo "- Dry Run: ${{ inputs.dry_run }}" >> $GITHUB_STEP_SUMMARY |
| 296 | + echo "- Single File: ${{ inputs.single_file || 'None' }}" >> $GITHUB_STEP_SUMMARY |
| 297 | + echo "- Concurrency: ${{ inputs.concurrency }}" >> $GITHUB_STEP_SUMMARY |
| 298 | + echo "- Model: gpt-4o-mini" >> $GITHUB_STEP_SUMMARY |
0 commit comments