Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 149 additions & 0 deletions .github/workflows/translate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '<!--This is a translation content dividing line, the content below is generated by machine, please do not modify the content below-->';
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');
Loading