From c7e64b2d55168298a21d04aa1b8615b53128db4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=87=AA=E7=94=B1=E7=9A=84=E4=B8=96=E7=95=8C=E4=BA=BA?= <3196812536@qq.com> Date: Fri, 5 Sep 2025 19:05:42 +0800 Subject: [PATCH] update --- .github/workflows/translator.yml | 14 ++- scripts/github-translator.js | 208 +++++++++++++++++++++++++++++++ 2 files changed, 218 insertions(+), 4 deletions(-) create mode 100644 scripts/github-translator.js diff --git a/.github/workflows/translator.yml b/.github/workflows/translator.yml index 95a641af12..54e1e96a53 100644 --- a/.github/workflows/translator.yml +++ b/.github/workflows/translator.yml @@ -1,4 +1,4 @@ -name: Issue Translator +name: GitHub Issue Translator env: API_KEY: ${{ secrets.TRANSLATE_API_KEY}} @@ -24,7 +24,7 @@ jobs: translate: runs-on: ubuntu-latest if: github.repository == 'CherryHQ/cherry-studio' - name: Auto Translate Issues and Comments + name: Auto Translate GitHub Content permissions: issues: write discussions: write @@ -44,8 +44,14 @@ jobs: run: | mkdir -p /tmp/translation-deps cd /tmp/translation-deps - echo '{"dependencies": {"openai": "^5.12.2", "@octokit/rest": "^21.0.0", "cli-progress": "^3.12.0", "tsx": "^4.20.3"}}' > package.json + echo '{"dependencies": {"openai": "^5.12.2", "@octokit/rest": "^21.0.0"}}' > package.json npm install --no-package-lock echo "NODE_PATH=/tmp/translation-deps/node_modules" >> $GITHUB_ENV - - name: 🌐 Translate Content + - name: 🌐 Run Translation Script + run: node scripts/github-translator.js + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_EVENT_NAME: ${{ github.event_name }} + GITHUB_CONTEXT: ${{ toJSON(github) }} diff --git a/scripts/github-translator.js b/scripts/github-translator.js new file mode 100644 index 0000000000..3ddf23df2e --- /dev/null +++ b/scripts/github-translator.js @@ -0,0 +1,208 @@ +const { Octokit } = require('@octokit/rest') +const OpenAI = require('openai') + +class GitHubTranslator { + constructor() { + this.octokit = new Octokit({ + auth: process.env.GITHUB_TOKEN + }) + + this.openai = new OpenAI({ + apiKey: process.env.API_KEY, + baseURL: process.env.BASE_URL + }) + + this.model = process.env.MODEL || 'deepseek/deepseek-v3.1' + this.repo = process.env.GITHUB_REPOSITORY.split('/') + this.context = JSON.parse(process.env.GITHUB_CONTEXT) + } + + async translateText(text, targetLang = 'English') { + if (!text || this.isAlreadyTranslated(text) || this.isPrimarylyEnglish(text)) { + return null + } + + try { + const response = await this.openai.chat.completions.create({ + model: this.model, + messages: [ + { + role: 'system', + content: `You are a professional translator. Translate the following text to ${targetLang}. Keep the original formatting (markdown, code blocks, links) intact. If the text is already primarily in ${targetLang}, respond with "NO_TRANSLATION_NEEDED".` + }, + { + role: 'user', + content: text + } + ], + temperature: 0.3 + }) + + const translation = response.choices[0].message.content.trim() + return translation === 'NO_TRANSLATION_NEEDED' ? null : translation + } catch (error) { + console.error('Translation error:', error) + return null + } + } + + isAlreadyTranslated(text) { + return ( + text.includes('**🌐 Translation**') || + text.includes('**English Translation**') || + text.includes('') + ) + } + + isPrimarylyEnglish(text) { + // Simple heuristic: if text contains mostly English characters + const englishChars = text.match(/[a-zA-Z\s]/g) || [] + const totalChars = text.replace(/\s/g, '') + return englishChars.length / totalChars.length > 0.7 + } + + formatTranslation(originalText, translation) { + return `${originalText} + +--- + +**🌐 English Translation:** + +${translation} + +` + } + + async handleIssue() { + const issue = this.context.event.issue + if (!issue) return + + let updates = {} + + // Translate title + if (issue.title) { + const translatedTitle = await this.translateText(issue.title) + if (translatedTitle) { + updates.title = `${issue.title} / ${translatedTitle}` + } + } + + // Translate body + if (issue.body) { + const translatedBody = await this.translateText(issue.body) + if (translatedBody) { + updates.body = this.formatTranslation(issue.body, translatedBody) + } + } + + // Update issue if we have translations + if (Object.keys(updates).length > 0) { + await this.octokit.issues.update({ + owner: this.repo[0], + repo: this.repo[1], + issue_number: issue.number, + ...updates + }) + + console.log(`✅ Translated issue #${issue.number}`) + } + } + + async handleComment() { + const comment = this.context.event.comment + if (!comment) return + + const translatedBody = await this.translateText(comment.body) + if (translatedBody) { + await this.octokit.issues.updateComment({ + owner: this.repo[0], + repo: this.repo[1], + comment_id: comment.id, + body: this.formatTranslation(comment.body, translatedBody) + }) + + console.log(`✅ Translated comment #${comment.id}`) + } + } + + async handlePullRequest() { + const pr = this.context.event.pull_request + if (!pr) return + + let updates = {} + + // Translate title + if (pr.title) { + const translatedTitle = await this.translateText(pr.title) + if (translatedTitle) { + updates.title = `${pr.title} / ${translatedTitle}` + } + } + + // Translate body + if (pr.body) { + const translatedBody = await this.translateText(pr.body) + if (translatedBody) { + updates.body = this.formatTranslation(pr.body, translatedBody) + } + } + + // Update PR if we have translations + if (Object.keys(updates).length > 0) { + await this.octokit.pulls.update({ + owner: this.repo[0], + repo: this.repo[1], + pull_number: pr.number, + ...updates + }) + + console.log(`✅ Translated PR #${pr.number}`) + } + } + + async handleDiscussion() { + // Note: GitHub's GraphQL API would be needed for full discussion support + console.log('Discussion translation not implemented yet') + } + + async run() { + try { + const eventName = process.env.GITHUB_EVENT_NAME + + console.log(`🌐 Processing ${eventName} event...`) + + switch (eventName) { + case 'issues': + await this.handleIssue() + break + case 'issue_comment': + await this.handleComment() + break + case 'pull_request': + case 'pull_request_target': + await this.handlePullRequest() + break + case 'pull_request_review_comment': + await this.handleComment() + break + case 'discussion': + case 'discussion_comment': + await this.handleDiscussion() + break + default: + console.log(`Event ${eventName} not supported`) + } + } catch (error) { + console.error('Translation workflow error:', error) + process.exit(1) + } + } +} + +// Run the translator +if (require.main === module) { + const translator = new GitHubTranslator() + translator.run() +} + +module.exports = GitHubTranslator