Skip to content

ci: add translation-sync guardrails for READMEs and docs#455

Merged
lizhengfeng101 merged 3 commits into
alibaba:mainfrom
youdie006:feat/translation-sync-guardrails
Jul 23, 2026
Merged

ci: add translation-sync guardrails for READMEs and docs#455
lizhengfeng101 merged 3 commits into
alibaba:mainfrom
youdie006:feat/translation-sync-guardrails

Conversation

@youdie006

Copy link
Copy Markdown
Contributor

Adds the translation-sync guardrails from #419.

  • Blocking: check-translation-sync.js readmes verifies all five README.<locale>.md files share an identical level-2 heading structure. It compares structure, not text (the headings are translated - README.ko-KR.md even keeps English for some - so a text compare would wrongly fail), and is code-fence-aware so bash # comment lines are not misread as headings.
  • Non-blocking: check-translation-sync.js docs warns (never fails) when a pages/src/content/docs/en/** page changes without its zh/ja counterparts.
  • Wired both into .github/workflows/ci.yml (new translation-sync job) following the existing post-review-comments.js pattern; package.json test:github-actions runs both suites.

Verified: node check-translation-sync.test.js (17 tests) pass; the blocking check passes on the current 5 READMEs and correctly exits 1 on an injected divergence; the docs warn path emits annotations without failing.


AI disclosure: drafted with Claude Code (AI-assisted); verified with the unit tests and against the real repo READMEs.

Add a check-translation-sync script + tests wired into CI: a blocking
check that all five README.<locale>.md files share an identical level-2
heading structure (compared by structure not translated text, and
code-fence-aware), and a non-blocking warning when a docs/en page changes
without its zh/ja counterparts. Addresses alibaba#419.
@CLAassistant

CLAassistant commented Jul 23, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

🔍 OpenCodeReview found 2 issue(s) in this PR.

  • ✅ Successfully posted inline: 2 comment(s)

Comment thread .github/workflows/ci.yml Outdated
runs-on: self-hosted
timeout-minutes: 10
container:
image: node:24

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The container image node:24 is a mutable major-version tag that can change unexpectedly (e.g., when Node 24.x releases a new minor/patch). Other jobs in this workflow pin to exact versions (e.g., golang:1.26.5). Consider pinning to a specific version like node:24.3.0 (or whatever the current latest 24.x is) for reproducible builds.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pinned to node:24.18.0 in deaffd6, matching the golang:1.26.5 pin used by the other jobs.

Comment on lines +59 to +68
let fenceChar = null; // '`' or '~' while inside a fence, else null
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const fence = /^\s{0,3}(`{3,}|~{3,})/.exec(line);
if (fence) {
const ch = fence[1][0];
if (fenceChar === null) fenceChar = ch; // opening fence
else if (fenceChar === ch) fenceChar = null; // matching closing fence
continue;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The fence parser only tracks the fence character (\`` or ~) but not the opening fence *length*. Per CommonMark spec, a closing fence must use the same character AND be at least as long as the opening fence. For example, if a README contains a code block opened with ````` (4 backticks) that has ``` (3 backticks) inside it, this parser would prematurely close the fence at the inner ```, causing subsequent # comment` lines in the code block to be misidentified as headings. This could produce false-positive structure divergence errors, blocking valid PRs.

Suggestion: Track the opening fence length and only close when the matching character sequence is >= the opening length.

Suggestion:

Suggested change
let fenceChar = null; // '`' or '~' while inside a fence, else null
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const fence = /^\s{0,3}(`{3,}|~{3,})/.exec(line);
if (fence) {
const ch = fence[1][0];
if (fenceChar === null) fenceChar = ch; // opening fence
else if (fenceChar === ch) fenceChar = null; // matching closing fence
continue;
}
let fenceChar = null; // '`' or '~' while inside a fence, else null
let fenceLen = 0; // length of the opening fence sequence
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const fence = /^\s{0,3}(`{3,}|~{3,})/.exec(line);
if (fence) {
const ch = fence[1][0];
const len = fence[1].length;
if (fenceChar === null) {
fenceChar = ch;
fenceLen = len; // record opening fence length
} else if (fenceChar === ch && len >= fenceLen) {
fenceChar = null; // only close if >= opening length
fenceLen = 0;
}
continue;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch - fixed in deaffd6 by tracking the opening fence length and only closing on a same-char run that is at least as long (per CommonMark). Added a regression test (testExtractHeadingsInnerShorterFenceDoesNotClose) that reproduces the premature-close case.

@lizhengfeng101 lizhengfeng101 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review feedback

Consider a separate workflow file

The translation-sync job checks both README structure and docs translation sync — it's a content validation concern, not a code quality one. I'd suggest moving it to its own workflow file (e.g. .github/workflows/translation-sync.yml) instead of appending to ci.yml:

  • Separation of concerns: ci.yml focuses on build/test/lint; translation sync is content validation.
  • Independent triggers: you can scope it with paths: filters (only run when README*.md or pages/src/content/docs/** change), avoiding unnecessary runs.
  • No blocking the main pipeline: if the container pull is slow or the check has issues, it won't delay core CI feedback.

Minor suggestions

  1. Remove dead variables in testtestReorderedHeadingFails declares en and zh then immediately discards them with void. Just remove the unused declarations and keep only en2/zh2.

  2. Code fence closing is length-unaware — The fence parser matches by character type (` vs ~) but doesn't track the opening fence length. Per CommonMark, a closing fence must be at least as long as the opening one. Practically this won't bite you in real READMEs, but worth a TODO comment for correctness.

  3. missing files still allow structure comparison to proceed — In runReadmeCheck, if a README file is missing, the remaining files are still compared. This could mask divergence (e.g. if the reference file is missing, the comparison runs against the second file as reference). Consider short-circuiting with exit code 1 if any expected file is absent.

Overall the code is clean, well-tested, and follows existing project conventions. Nice work.

- Move the translation-sync job out of ci.yml into its own
  .github/workflows/translation-sync.yml, scoped with paths: filters
  (README*.md, pages/src/content/docs/**, the checker scripts) so it
  only runs on translation changes and never blocks the core pipeline.
- runReadmeCheck: short-circuit with exit 1 when any expected README*.md
  is missing, before the structure comparison, so a missing file can no
  longer mask a real divergence.
- extractHeadings: add a TODO(commonmark) note that closing fences are
  matched by fence char only, not by length.
- test: drop the dead en/zh fixtures and the void statements in
  testReorderedHeadingFails; keep the en2/zh2 pair that actually drives
  the assertion.
Address the automated review comments beyond the maintainer's minor note:

- extractHeadings: track the opening fence length and only close on a
  same-char run that is at least as long (per CommonMark). Previously a
  block opened with ```` and containing an inner ``` closed early, so
  lines inside the block could be misread as headings (or real headings
  dropped), producing false-positive structure-divergence errors. Adds a
  regression test covering the inner-shorter-fence case.
- translation-sync.yml: pin node:24.18.0 instead of the mutable node:24
  major tag, matching the golang:1.26.5 pin used by the other jobs.
@youdie006

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review, @lizhengfeng101 - all points addressed.

Separate workflow file (5718464): moved the job to .github/workflows/translation-sync.yml and removed it from ci.yml. It is scoped with paths: filters (README*.md, pages/src/content/docs/**, and the checker scripts) on both push and pull_request, so it only runs when a translation or the checker changes and never gates the core build/test/lint pipeline.

Minor suggestions:

  1. Dead test variables - removed the unused en/zh and the void statements in testReorderedHeadingFails; only en2/zh2 (which drive the assertion) remain.
  2. Length-unaware code fences - rather than just a TODO, I implemented the real fix (deaffd6): extractHeadings now tracks the opening fence length and only closes on a same-char run that is at least as long, per CommonMark. This also resolves the automated bot comment on the same line. Added a regression test (testExtractHeadingsInnerShorterFenceDoesNotClose) that fails without the fix.
  3. Missing files still comparing - runReadmeCheck now short-circuits with exit 1 when any expected README*.md is absent, before the structure comparison, so a missing reference can no longer be silently promoted.

Also pinned the container to node:24.18.0 (was the mutable node:24 tag) to match the golang:1.26.5 pin used by the other jobs, per the bot's note.

All unit tests pass locally (node scripts/github-actions/check-translation-sync.test.js).

@lizhengfeng101

Copy link
Copy Markdown
Collaborator

Suggestion: consider removing the push trigger from .github/workflows/translation-sync.yml and keeping only pull_request.

Since our main branch only accepts changes via PRs, the pull_request event (which fires on opened, synchronize, and reopened by default) already covers all the cases we need. The push trigger would just cause a redundant run after the PR merges into main.

@lizhengfeng101 lizhengfeng101 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@lizhengfeng101
lizhengfeng101 merged commit 0c97538 into alibaba:main Jul 23, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants