Skip to content

Merge pull request #48 from jiahim/automation/sync-openai-docs #29

Merge pull request #48 from jiahim/automation/sync-openai-docs

Merge pull request #48 from jiahim/automation/sync-openai-docs #29

name: Translate Chinese docs
on:
workflow_dispatch:
push:
branches:
- main
paths:
- "docs/en/**"
schedule:
# 01:00 Asia/Shanghai (UTC+8), one hour after the English sync starts.
- cron: "0 17 * * *"
permissions:
contents: write
pull-requests: write
concurrency:
group: translate-openai-chinese-docs
cancel-in-progress: false
env:
BASE_BRANCH: main
TRANSLATION_BRANCH: automation/translate-openai-docs
jobs:
translate:
if: github.ref == 'refs/heads/main'
environment: translation-production
env:
DEEPSEEK_MODEL: ${{ vars.DEEPSEEK_MODEL }}
MINIMAX_MODEL: ${{ vars.MINIMAX_MODEL }}
TRANSLATION_PROVIDER: ${{ vars.TRANSLATION_PROVIDER }}
runs-on: ubuntu-latest
timeout-minutes: 180
steps:
- name: Stop while a translation PR is awaiting review
id: open-pr
uses: actions/github-script@v9
with:
script: |
const { owner, repo } = context.repo;
const pulls = await github.paginate(github.rest.pulls.list, {
owner,
repo,
base: process.env.BASE_BRANCH,
head: `${owner}:${process.env.TRANSLATION_BRANCH}`,
state: "open",
per_page: 100,
});
const exists = pulls.length > 0;
core.setOutput("exists", String(exists));
if (exists) {
core.notice(`Translation PR is awaiting review: ${pulls[0].html_url}`);
}
- name: Check out trusted main with history
if: steps.open-pr.outputs.exists != 'true'
uses: actions/checkout@v6
with:
fetch-depth: 0
ref: main
- name: Prepare the automation branch
if: steps.open-pr.outputs.exists != 'true'
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git fetch origin "refs/heads/$TRANSLATION_BRANCH:refs/remotes/origin/$TRANSLATION_BRANCH" || true
git checkout -B "$TRANSLATION_BRANCH" "origin/$BASE_BRANCH"
- name: Install pnpm
if: steps.open-pr.outputs.exists != 'true'
uses: pnpm/action-setup@v6
- name: Set up Node.js
if: steps.open-pr.outputs.exists != 'true'
uses: actions/setup-node@v7
with:
node-version: "24"
cache: pnpm
- name: Install locked dependencies without lifecycle scripts
if: steps.open-pr.outputs.exists != 'true'
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Verify trusted translation code before injecting the secret
if: steps.open-pr.outputs.exists != 'true'
run: |
pnpm typecheck
pnpm test
pnpm translate:check
- name: Translate up to 100 budgeted pages
if: steps.open-pr.outputs.exists != 'true'
id: translate
continue-on-error: true
timeout-minutes: 160
env:
DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
run: pnpm translate:auto -- --limit 100
- name: Verify completed translation pages
if: ${{ !cancelled() && steps.open-pr.outputs.exists != 'true' && steps.translate.outcome != 'skipped' }}
run: pnpm translate:check
- name: Publish the translation branch
if: steps.open-pr.outputs.exists != 'true'
id: publish
run: |
if [[ -z "$(git status --porcelain -- docs/zh)" ]]; then
echo "No translation changes were generated."
echo "has_changes=false" >> "$GITHUB_OUTPUT"
exit 0
fi
git add docs/zh
git commit -m "[AI] docs: 自动更新 OpenAI 中文翻译"
echo "has_changes=true" >> "$GITHUB_OUTPUT"
if git show-ref --verify --quiet "refs/remotes/origin/$TRANSLATION_BRANCH"; then
expected="$(git rev-parse "refs/remotes/origin/$TRANSLATION_BRANCH")"
git push \
--force-with-lease="refs/heads/$TRANSLATION_BRANCH:$expected" \
--set-upstream origin "HEAD:refs/heads/$TRANSLATION_BRANCH"
else
git push --set-upstream origin "HEAD:refs/heads/$TRANSLATION_BRANCH"
fi
- name: List translated files for the pull request
if: steps.publish.outputs.has_changes == 'true'
run: git diff --name-only "origin/$BASE_BRANCH...HEAD" -- docs/zh > "$RUNNER_TEMP/translation-paths.txt"
- name: Create the translation pull request
if: steps.publish.outputs.has_changes == 'true'
uses: actions/github-script@v9
env:
TRANSLATION_PATHS_FILE: ${{ runner.temp }}/translation-paths.txt
with:
script: |
const { readFile } = require("node:fs/promises");
const { owner, repo } = context.repo;
const head = `${owner}:${process.env.TRANSLATION_BRANCH}`;
const pulls = await github.paginate(github.rest.pulls.list, {
owner,
repo,
base: process.env.BASE_BRANCH,
head,
state: "open",
per_page: 100,
});
const paths = (await readFile(process.env.TRANSLATION_PATHS_FILE, "utf8"))
.trim()
.split("\n")
.filter(Boolean);
const body = [
"## 自动中文翻译",
"",
"本 PR 由受信任的 `main` 工作流使用环境配置的大模型供应商自动生成,每次最多处理 100 篇受预算约束的页面。",
"",
"### 文件",
"",
...paths.map((path) => `- \`${path}\``),
"",
"### 审核要求",
"",
"- 检查中文准确性、术语和可读性。",
"- 不要直接修改代码、URL、模型 ID 或 Markdown 结构。",
"- 固定译法写入术语表;单页上下文或官方原文勘误写入 `scripts/translation/review-notes.zh-CN.json`。",
"- 人工润色后运行 `pnpm translate:review -- --match <精确路径> --limit 1`。",
"- 合入仍须通过 `Quality gate` 和 `main` Ruleset。",
].join("\n");
let pull = pulls[0];
if (!pull) {
const response = await github.rest.pulls.create({
owner,
repo,
base: process.env.BASE_BRANCH,
head: process.env.TRANSLATION_BRANCH,
title: "[AI] docs: 自动更新 OpenAI 中文翻译",
body,
});
pull = response.data;
} else {
const response = await github.rest.pulls.update({
owner,
repo,
pull_number: pull.number,
title: "[AI] docs: 自动更新 OpenAI 中文翻译",
body,
});
pull = response.data;
}
core.notice(`Translation PR: ${pull.html_url}`);
- name: Report translation failure after publishing completed pages
if: ${{ !cancelled() && steps.open-pr.outputs.exists != 'true' && steps.translate.outcome == 'failure' }}
run: |
echo "Translation stopped after the recovery and publishing steps completed."
exit 1