Skip to content

Commit 2ae4bff

Browse files
probablyanggclaude
andcommitted
fix(i18n): skip in-sync pages when translating explicit paths
discover() returned explicit paths verbatim, bypassing the source_sha freshness gate. Since CI passes every en page changed in the PR (git diff base...HEAD), each push re-translated the entire PR changeset through Opus — even pages whose cn/ko already matched — exhausting the rate limit / credit balance. Explicit paths now flow through the same missing-or-drifted check used by full discovery, so an in-sync page is a no-op. Added --force to re-translate regardless (intentional local re-runs). Verified: an in-sync page passed explicitly now reports "translating 0 page(s)" and makes no API call; drifted/missing pages are still selected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 7d6af01 commit 2ae4bff

1 file changed

Lines changed: 25 additions & 10 deletions

File tree

‎docs/lib/i18n-translate.mjs‎

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@
99
* Used by both the auto-draft CI workflow (.github/workflows/i18n-translate.yml)
1010
* and one-off local backfills.
1111
*
12-
* ANTHROPIC_API_KEY=... node docs/lib/i18n-translate.mjs <locale> [--stale] [--limit N] [page ...]
12+
* ANTHROPIC_API_KEY=... node docs/lib/i18n-translate.mjs <locale> [--stale] [--force] [--limit N] [page ...]
1313
*
1414
* <locale> cn | ko (required)
15-
* --stale also re-translate pages whose source_sha drifted (default: missing only)
15+
* --stale in a full sweep, also re-translate pages whose source_sha drifted (default: missing only)
16+
* --force re-translate every candidate even if its translation is in sync
1617
* --limit N translate at most N pages this run (default: all)
17-
* page ... explicit en-relative paths to translate (overrides discovery)
18+
* page ... explicit en-relative paths to consider; in-sync ones are still
19+
* skipped (drift is always checked) unless --force is given
1820
*
1921
* Model: claude-opus-4-8, adaptive thinking, streamed (pages can be long).
2022
*/
@@ -35,6 +37,7 @@ if (!LANGUAGE[locale]) {
3537
process.exit(2)
3638
}
3739
const includeStale = rest.includes('--stale')
40+
const force = rest.includes('--force')
3841
const relinkOnly = rest.includes('--relink')
3942
const limitIdx = rest.indexOf('--limit')
4043
const limit = limitIdx !== -1 ? Number(rest[limitIdx + 1]) : Infinity
@@ -69,17 +72,29 @@ const blobSha = (file) =>
6972
// and are intentionally left alone. Idempotent.
7073
const localizeLinks = (body, locale) => body.replaceAll('](/en/', `](/${locale}/`)
7174

72-
// Discover which en pages this locale needs.
75+
// Discover which en pages this locale actually needs translating.
76+
//
77+
// Candidates are the explicit paths if given, else every en page. A candidate
78+
// is only translated when its target is missing or its source drifted — so a
79+
// page whose translation already matches the current en `source_sha` is skipped
80+
// (no wasted API call). `--force` re-translates every candidate regardless.
81+
//
82+
// Drift is always checked for explicit paths: the caller named them because
83+
// they changed, so an in-sync one is genuinely nothing to do. For a full sweep
84+
// drift is gated behind `--stale` (otherwise the default is missing-only).
7385
function discover() {
74-
if (explicit.length) return explicit
75-
const enFiles = walk(join(PAGES, SOURCE))
76-
.map((p) => relative(join(PAGES, SOURCE), p))
77-
.sort()
86+
const candidates = explicit.length
87+
? explicit
88+
: walk(join(PAGES, SOURCE))
89+
.map((p) => relative(join(PAGES, SOURCE), p))
90+
.sort()
91+
if (force) return candidates
92+
const checkDrift = explicit.length > 0 || includeStale
7893
const needed = []
79-
for (const rel of enFiles) {
94+
for (const rel of candidates) {
8095
const target = join(PAGES, locale, rel)
8196
if (!existsSync(target)) needed.push(rel)
82-
else if (includeStale && frontmatterSha(target) !== blobSha(join(PAGES, SOURCE, rel)))
97+
else if (checkDrift && frontmatterSha(target) !== blobSha(join(PAGES, SOURCE, rel)))
8398
needed.push(rel)
8499
}
85100
return needed

0 commit comments

Comments
 (0)