Merge pull request #48 from jiahim/automation/sync-openai-docs #29
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: 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 |