diff --git a/.github/workflows/translate.yml b/.github/workflows/translate.yml index bad9a9e..470068d 100644 --- a/.github/workflows/translate.yml +++ b/.github/workflows/translate.yml @@ -36,3 +36,152 @@ jobs: with: IS_MODIFY_TITLE: true APPEND_TRANSLATION: true + + # English maintainer replies → OP language. Same unofficial Google endpoint as lizheming. + reverse: + if: > + github.actor != 'github-actions[bot]' && + contains(fromJSON('["issue_comment","discussion_comment","pull_request_review_comment"]'), github.event_name) + runs-on: ubuntu-latest + continue-on-error: true + steps: + - name: Reverse-translate English comment + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + with: + script: | + const DIV = ''; + const TITLE_DIV = ' || '; + const MAX = 4500; + + const isEn = (code) => !code || /^en\b/i.test(String(code)); + const origin = (text) => String(text || '').split(DIV)[0].trim(); + const originTitle = (text) => String(text || '').split(TITLE_DIV)[0].trim(); + + async function gtx(text, sl, tl) { + const q = text.length > MAX ? text.slice(0, MAX) : text; + const params = new URLSearchParams({ client: 'gtx', sl, tl, dt: 't', dj: '1', q }); + const res = await fetch(`https://translate.googleapis.com/translate_a/single?${params}`, { + headers: { 'User-Agent': 'Mozilla/5.0 NVM-Windows-Translate' } + }); + if (!res.ok) { + throw new Error(`gtx HTTP ${res.status}`); + } + const data = await res.json(); + let translated = ''; + let src = ''; + if (data && Array.isArray(data.sentences)) { + translated = data.sentences.map((s) => s.trans || '').join(''); + src = data.src || ''; + } else if (Array.isArray(data)) { + const chunks = data[0] || []; + translated = chunks.map((c) => (Array.isArray(c) ? c[0] : '')).join(''); + src = data[2] || ''; + } + return { text: String(translated || '').trim(), src: String(src || '').trim() }; + } + + const payload = context.payload; + const comment = payload.comment; + if (!comment || !comment.body) { + core.info('No comment body'); + return; + } + if (comment.user && comment.user.type === 'Bot') { + core.info('Skip bot comment'); + return; + } + + const parent = payload.issue || payload.pull_request || payload.discussion; + if (!parent) { + core.info('No parent thread'); + return; + } + const op = (parent.user && parent.user.login) || ''; + if (op && context.actor === op) { + core.info('Skip OP comment'); + return; + } + + const parentText = `${originTitle(parent.title)}\n${origin(parent.body)}`.trim(); + if (parentText.length < 8) { + core.info('Parent text too short to detect language'); + return; + } + + const commentText = origin(comment.body); + if (commentText.length < 3) { + core.info('Comment too short'); + return; + } + + let parentDet; + let commentDet; + try { + parentDet = await gtx(parentText, 'auto', 'en'); + commentDet = await gtx(commentText, 'auto', 'en'); + } catch (err) { + core.warning(`Detect failed: ${err.message}`); + return; + } + + const opLang = parentDet.src; + const commentLang = commentDet.src; + core.info(`OP lang=${opLang || 'und'} comment lang=${commentLang || 'und'}`); + if (isEn(opLang)) { + core.info('OP is English; skip reverse'); + return; + } + if (!isEn(commentLang)) { + core.info('Comment is not English; skip reverse'); + return; + } + + let translated; + try { + const out = await gtx(commentText, 'en', opLang); + translated = out.text; + } catch (err) { + core.warning(`Translate failed: ${err.message}`); + return; + } + if (!translated || translated === commentText) { + core.info('Empty or unchanged translation'); + return; + } + + const body = `${commentText}\n${DIV}\n---\n${translated}\n`; + const event = context.eventName; + if (event === 'discussion_comment') { + const id = comment.node_id; + if (!id) { + core.warning('Discussion comment missing node_id'); + return; + } + await github.graphql( + `mutation ($id: ID!, $body: String!) { + updateDiscussionComment(input: { commentId: $id, body: $body }) { + comment { id } + } + }`, + { id, body } + ); + core.info('Updated discussion comment'); + return; + } + if (event === 'pull_request_review_comment') { + await github.rest.pulls.updateReviewComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: comment.id, + body + }); + core.info('Updated review comment'); + return; + } + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: comment.id, + body + }); + core.info('Updated issue comment');