Repository navigation
Exception with Android Studio Rabbit 2026.2 #88
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Issue Translator | |
| # Translates non-English issues into English using GitHub Models (the built-in | |
| # `actions/ai-inference` action — no API key, only the repo's GITHUB_TOKEN). | |
| # | |
| # For a non-English issue it does two things: | |
| # 1. Rewrites the TITLE in place to "<original> (<English translation>)", so the | |
| # original text is preserved and the English rendering sits beside it. | |
| # 2. Posts a NEW comment containing a full English translation of the BODY, with | |
| # every image, attachment link, code block and template header kept verbatim. | |
| # | |
| # Triggers: | |
| # - issues.opened -> automatic on every new issue | |
| # - workflow_dispatch -> manual, pass an issue number to (re)translate an | |
| # existing issue (used for back-filling old issues) | |
| # | |
| # The step is idempotent: an already-appended title is not touched again, and a | |
| # second run will not post a duplicate translation comment (detected via a hidden | |
| # marker in the comment body). | |
| on: | |
| issues: | |
| types: [opened] | |
| workflow_dispatch: | |
| inputs: | |
| issue_number: | |
| description: "Issue number to (re)translate" | |
| required: true | |
| type: number | |
| permissions: | |
| # Update the issue title and post the translation comment. | |
| issues: write | |
| # Call GitHub Models for inference. | |
| models: read | |
| jobs: | |
| translate: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Resolve issue content | |
| id: issue | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| let number, title, body; | |
| if (context.eventName === 'workflow_dispatch') { | |
| number = Number(context.payload.inputs.issue_number); | |
| const { data } = await github.rest.issues.get({ | |
| ...context.repo, | |
| issue_number: number, | |
| }); | |
| title = data.title; | |
| body = data.body || ''; | |
| } else { | |
| number = context.payload.issue.number; | |
| title = context.payload.issue.title; | |
| body = context.payload.issue.body || ''; | |
| } | |
| core.setOutput('number', String(number)); | |
| core.setOutput('title', title); | |
| core.setOutput('body', body); | |
| - name: Translate via GitHub Models | |
| id: ai | |
| uses: actions/ai-inference@v1 | |
| with: | |
| model: openai/gpt-4o | |
| # The action's `max-tokens` defaults to 200, which truncates the | |
| # response. Set both the deprecated and current knobs generously. | |
| max-tokens: 16000 | |
| max-completion-tokens: 16000 | |
| system-prompt: | | |
| You are a translation assistant for GitHub issues. You receive an | |
| issue's TITLE and BODY. Detect the primary natural language. | |
| Respond with ONLY a single valid JSON object (no markdown code | |
| fences, no prose before or after) with exactly these keys: | |
| "is_english": boolean -- true if the content is already primarily English | |
| "title_en": string -- English translation of the title ("" if is_english) | |
| "body_en": string -- English translation of the body ("" if is_english) | |
| Translation rules for body_en: | |
| - Translate ONLY natural-language prose. | |
| - Keep the following EXACTLY as in the original, byte for byte, | |
| translating nothing inside them: | |
| * <img ...> and any other HTML tags | |
| * markdown image syntax  and links [text](url) | |
| * URLs and attachment links (e.g. github.com/user-attachments/...) | |
| * fenced code blocks ``` ... ``` and inline `code` | |
| * issue-template section headers that start with "###" | |
| - Do NOT drop, summarize, or reorder ANY part of the body. Every | |
| line, image, and section present in the original must be present | |
| in body_en, in the same order. | |
| - Preserve blank lines and markdown structure. | |
| prompt: | | |
| TITLE: | |
| ${{ steps.issue.outputs.title }} | |
| BODY: | |
| ${{ steps.issue.outputs.body }} | |
| - name: Apply translation | |
| uses: actions/github-script@v7 | |
| env: | |
| # Read from the response FILE, not the `response` output: a large | |
| # translation can exceed the step-output size limit and arrive | |
| # truncated (which breaks JSON parsing). | |
| AI_RESPONSE_FILE: ${{ steps.ai.outputs.response-file }} | |
| ORIG_TITLE: ${{ steps.issue.outputs.title }} | |
| ISSUE_NUMBER: ${{ steps.issue.outputs.number }} | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const raw = fs.readFileSync(process.env.AI_RESPONSE_FILE, 'utf8'); | |
| const origTitle = process.env.ORIG_TITLE || ''; | |
| const number = Number(process.env.ISSUE_NUMBER); | |
| // The model is asked for raw JSON, but strip accidental ```json fences. | |
| const jsonText = raw | |
| .trim() | |
| .replace(/^```(?:json)?\s*/i, '') | |
| .replace(/\s*```$/, '') | |
| .trim(); | |
| let parsed; | |
| try { | |
| parsed = JSON.parse(jsonText); | |
| } catch (e) { | |
| core.setFailed('Could not parse model response as JSON: ' + e.message + '\n---\n' + raw); | |
| return; | |
| } | |
| if (parsed.is_english) { | |
| core.info('Issue is already in English; nothing to do.'); | |
| return; | |
| } | |
| // 1) Title: append the English translation, keeping the original. | |
| // Idempotent -- skip if the translation is already present. | |
| if (parsed.title_en && !origTitle.includes(parsed.title_en)) { | |
| await github.rest.issues.update({ | |
| ...context.repo, | |
| issue_number: number, | |
| title: `${origTitle} (${parsed.title_en})`, | |
| }); | |
| core.info('Title updated.'); | |
| } else { | |
| core.info('Title already translated; leaving it as-is.'); | |
| } | |
| // 2) Body: post a translation comment (once). | |
| const marker = '<!-- ccg-ai-translation -->'; | |
| const comments = await github.paginate(github.rest.issues.listComments, { | |
| ...context.repo, | |
| issue_number: number, | |
| per_page: 100, | |
| }); | |
| if (comments.some((c) => c.body && c.body.includes(marker))) { | |
| core.info('Translation comment already exists; skipping.'); | |
| return; | |
| } | |
| const note = | |
| '> [!NOTE]\n' + | |
| '> This issue was written in another language. Below is an automatic ' + | |
| 'English translation (the original text is preserved above).'; | |
| const commentBody = `${marker}\n${note}\n\n---\n\n${parsed.body_en}`; | |
| await github.rest.issues.createComment({ | |
| ...context.repo, | |
| issue_number: number, | |
| body: commentBody, | |
| }); | |
| core.info('Translation comment posted.'); |