This is an automated email from the ASF dual-hosted git repository.
morningman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris-website.git
The following commit(s) were added to refs/heads/master by this push:
new 6fd15d57043 [ci](i18n) Scope admission checks to the locales a change
can affect (#4405)
6fd15d57043 is described below
commit 6fd15d57043268a92f31ea1670aed14d37d5e175
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Mon Oct 19 18:47:31 2026 +0100
[ci](i18n) Scope admission checks to the locales a change can affect (#4405)
## Why
Admission checks ran without looking at what a PR actually changed. On
the Korean-only translation PR #4395, the five checks used ~22m30s of
runner time (on top of ~90 minutes of runner queueing), and a large part
of that could not be affected by the diff:
- `japanese-coverage-audit` scanned the 3338-page English inventory and
produced a Japanese coverage snapshot. A diff confined to `i18n/ko/**`
cannot change it.
- `check-md-links` ended with `No Markdown files added or modified in
this PR. Skipping.` — it only checks `docs/**`, zh-CN current and `ja`.
- Build Check's translation gate ran `pr-preflight` for every managed
locale. The `ja` iteration reported `checked: 0` after 2m28s, because
`check-translations.cjs` loads the full Docusaurus inventory before
selecting changed pages.
## What changed
### 1. Coverage audits only run for their own locale
`japanese-coverage-audit.yml` and `korean-coverage-audit.yml` used
`i18n/**` and `translations/**`, so every Korean PR triggered the
Japanese audit (and vice versa). The Japanese workflow originally used
`i18n/ja/**`; the locale-generic refactor widened it.
Each workflow now matches its own locale inputs plus shared routing
files, and also:
- uses `fetch-depth: 1` because the audit only reads `HEAD`;
- has a concurrency group so a force-push cancels the superseded run;
- names the Japanese job `Japanese coverage audit` instead of the bare
`audit` (neither check is required).
### 2. The audit reads blobs in one process
`scripts/i18n/audit-coverage.cjs` spawned one `git show` per page: 3338
sources plus up to ~3100 current targets. Sampling the running process
showed it blocked in `posix_spawn`. It now resolves all source, target
and routing hashes through one `git cat-file --batch` process each and
keeps only the SHA-256.
Measured on 100 paths: `git show` 2.03s vs `git cat-file --batch`
0.027s.
### 3. Build Check only gates locales that changed
`Translation gate for changed locales` runs `pr-preflight` for a locale
when:
- its own translation inputs changed (`i18n/<locale>/**`,
`translations/{state,scope,glossary,inventory,protection-exceptions}/<locale>...`),
or
- a shared input changed (`scripts/i18n/`, `translations/config.json`,
`config/i18n.js`, `docusaurus.config.js`, `versions.json`, the sidebars,
`versioned_sidebars/`).
On #4395 this skips the `ja` iteration: 2m28s out of the 5m05s step. An
English-only docs PR skips both admission gates.
Build Check also switches to `actions/checkout@v4` with `filter:
blob:none` (full history metadata, no 1.7GB of historical blobs) and
reuses the yarn cache instead of clearing it.
### 4. The required lightweight checks stay, but cheap
`Build Check`, `check-md-links` and `check-file-sizes` are required
status checks, so their names and triggers are unchanged.
- `check-md-links`: blobless checkout; the changed-file lists are
collected before `setup-python`; when there is nothing to check the job
exits right after the diff. On #4395 this turns a 37s no-op into a short
checkout plus one diff. The resolver unit test now runs when its script
changes rather than on every PR.
- `check-file-sizes`: `fetch-depth: 1`, then fetch the base commit with
`--depth=1 --filter=blob:none`; `--name-only` only needs the base tree.
- Both get `cancel-in-progress` concurrency groups.
### 5. Docs
`translations/README.md` now describes the changed-locale gate instead
of the old “once per configured locale” loop.
## Expected effect (Korean-only PR like #4395)
| check | before | after |
| --- | --- | --- |
| Build Check | 11m29s | ~9m |
| Korean coverage audit | 5m10s | ~3m10–3m30s |
| Japanese coverage audit | 4m38s | not triggered |
| check-md-links | 37s | ~20–30s |
| check-file-sizes | 36s | ~20s |
| jobs / queue slots | 5 | 4 |
The queue-slot reduction matters more than the per-job seconds: the
90-minute delay on #4395 was backlog from ~13 translation PRs that each
opened five jobs.
## Validation
- `node --test scripts/i18n/__tests__/multi-locale.test.cjs` — 6/6, with
new cases for both locales, `ja` only, `ko` only, no locale change, and
a shared-input change.
- `node --test scripts/i18n/__tests__/t04.test.cjs` — 12/12, including
the full-audit fixture (existing files, missing targets, routing
inputs).
- A full `I18N_LOCALE=ja node scripts/i18n/audit-coverage.cjs
--source-ref HEAD ...` run exits 0 on current master; its
DEV/4X/COMMUNITY counts match the Japanese coverage artifact from #4395
(1671/1616/51 pages, 1563/1505/51 current).
- All five workflow files parse, and the three required job names are
unchanged.
## Not changed
- `check-md-links` and `check-file-sizes` could be folded into Build
Check to save another queue slot, but they are required status checks;
removing their workflow would leave the required check pending.
- `check-md-links` still does not check `i18n/ko/**` (pre-existing);
Build Check's `pr-preflight` checks links in changed Korean files. I
left that behavior alone.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: morningman <[email protected]>
Co-authored-by: Claude Code <[email protected]>
---
.github/workflows/build-check.yml | 29 +++++++++----
.github/workflows/check-md-links.yml | 32 +++++++++++----
.github/workflows/check-pr-file-sizes.yml | 11 ++++-
.github/workflows/japanese-coverage-audit.yml | 21 ++++++++--
.github/workflows/korean-coverage-audit.yml | 20 +++++++--
scripts/i18n/__tests__/multi-locale.test.cjs | 59 +++++++++++++++++++++++----
scripts/i18n/audit-coverage.cjs | 58 +++++++++++++++++++-------
translations/README.md | 18 ++++----
8 files changed, 194 insertions(+), 54 deletions(-)
diff --git a/.github/workflows/build-check.yml
b/.github/workflows/build-check.yml
index 7562b330357..deccbbae576 100644
--- a/.github/workflows/build-check.yml
+++ b/.github/workflows/build-check.yml
@@ -32,11 +32,15 @@ jobs:
environment: Production
steps:
- name: Checkout ${{ github.ref }} ( ${{
github.event.pull_request.head.sha }} )
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
-
+ # Full history is needed for merge-base/check_move, but the
+ # object database is ~1.7GB. Trees and commits are enough;
+ # checkout fetches the blobs the working tree needs.
+ filter: blob:none
+
- name: Set up Python
uses: actions/setup-python@v4
with:
@@ -46,6 +50,7 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: 20
+ cache: yarn
- name: Refactor PR commits and check move
run: |
@@ -91,7 +96,6 @@ jobs:
NODE_OPTIONS: --max-old-space-size=8192
run: |
npm install -g yarn
- yarn cache clean
yarn
yarn key-features:generate
@@ -121,12 +125,23 @@ jobs:
node --test
scripts/i18n/__tests__/translate-document.test.cjs
node --test
scripts/i18n/__tests__/incremental-document.test.cjs
- - name: Translation gate for every managed locale
+ - name: Translation gate for changed locales
run: |
BASE_SHA=$(git merge-base origin/${{ github.base_ref }} HEAD)
- MANAGED_LOCALES=$(node scripts/i18n/managed-locales.cjs
--all)
- for LOCALE in $MANAGED_LOCALES; do
- I18N_LOCALE="$LOCALE" node scripts/i18n/pr-preflight.cjs
--base "$BASE_SHA"
+ CHANGED=$(git diff --name-only "$BASE_SHA" HEAD)
+ # A shared input can change every locale's admission result;
+ # otherwise only locales whose own translation inputs changed
+ # need the full inventory load.
+ SHARED=false
+ if printf '%s\n' "$CHANGED" | grep -qE
'^(scripts/i18n/|translations/config\.json$|config/i18n\.js$|docusaurus\.config\.js$|versions\.json$|sidebars[^/]*\.(ts|json)$|versioned_sidebars/)';
then
+ SHARED=true
+ fi
+ for LOCALE in $(node scripts/i18n/managed-locales.cjs
--all); do
+ if [ "$SHARED" = true ] || printf '%s\n' "$CHANGED" |
grep -qE
"^i18n/$LOCALE/|^translations/(state|scope|glossary|inventory|protection-exceptions)/$LOCALE([/.]|$)";
then
+ I18N_LOCALE="$LOCALE" node
scripts/i18n/pr-preflight.cjs --base "$BASE_SHA"
+ else
+ echo "No $LOCALE translation changes; skipping the
admission gate for this locale."
+ fi
done
- name: Publish artifact optimization tests
diff --git a/.github/workflows/check-md-links.yml
b/.github/workflows/check-md-links.yml
index d345d7cc2d4..30ef3f90256 100644
--- a/.github/workflows/check-md-links.yml
+++ b/.github/workflows/check-md-links.yml
@@ -4,6 +4,10 @@ on:
pull_request:
types: [opened, synchronize, reopened]
+concurrency:
+ group: check-md-links-${{ github.event.pull_request.number }}
+ cancel-in-progress: true
+
jobs:
check-md-links:
runs-on: ubuntu-latest
@@ -12,14 +16,9 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
-
- - name: Set up Python
- uses: actions/setup-python@v5
- with:
- python-version: '3.11'
-
- - name: Verify Japanese source fallback resolver
- run: python3 -m unittest scripts/__tests__/test_japanese_links.py
+ # The changed-file lists and link targets need trees and the working
+ # tree only; skip the historical blobs of this ~1.7GB repository.
+ filter: blob:none
- name: Collect added/modified Markdown files
id: changed
@@ -43,8 +42,25 @@ jobs:
'i18n/ja/**/*.md' 'i18n/ja/**/*.mdx'
echo EOF
} >> "$GITHUB_OUTPUT"
+ {
+ echo 'scripts<<EOF'
+ git diff --name-only "$BASE_SHA" HEAD \
+ -- 'scripts/check_md_links_single.py'
'scripts/__tests__/test_japanese_links.py'
+ echo EOF
+ } >> "$GITHUB_OUTPUT"
+
+ - name: Set up Python
+ if: steps.changed.outputs.files != '' || steps.changed.outputs.scripts
!= ''
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.11'
+
+ - name: Verify Japanese source fallback resolver
+ if: steps.changed.outputs.scripts != ''
+ run: python3 -m unittest scripts/__tests__/test_japanese_links.py
- name: Check links in changed Markdown files
+ if: steps.changed.outputs.files != ''
run: |
set -u
FILES="${{ steps.changed.outputs.files }}"
diff --git a/.github/workflows/check-pr-file-sizes.yml
b/.github/workflows/check-pr-file-sizes.yml
index 7bd3585d3ee..9046e5c874f 100644
--- a/.github/workflows/check-pr-file-sizes.yml
+++ b/.github/workflows/check-pr-file-sizes.yml
@@ -4,6 +4,10 @@ on:
pull_request:
types: [opened, synchronize, reopened]
+concurrency:
+ group: check-file-sizes-${{ github.event.pull_request.number }}
+ cancel-in-progress: true
+
jobs:
check-file-sizes:
runs-on: ubuntu-latest
@@ -11,7 +15,12 @@ jobs:
- name: Checkout PR
uses: actions/checkout@v4
with:
- fetch-depth: 0
+ fetch-depth: 1
+
+ - name: Fetch base commit
+ # Only the base tree is needed for `git diff --name-only`; its blobs
+ # would otherwise re-download the whole repository at the base commit.
+ run: git fetch --depth=1 --filter=blob:none origin ${{
github.event.pull_request.base.sha }}
- name: Get changed files in PR
id: changed
diff --git a/.github/workflows/japanese-coverage-audit.yml
b/.github/workflows/japanese-coverage-audit.yml
index 3719f4ce94e..f4d34b8abdf 100644
--- a/.github/workflows/japanese-coverage-audit.yml
+++ b/.github/workflows/japanese-coverage-audit.yml
@@ -10,8 +10,17 @@ on:
- 'docs/**'
- 'versioned_docs/version-4.x/**'
- 'community/**'
- - 'i18n/**'
- - 'translations/**'
+ - 'i18n/ja/**'
+ - 'translations/config.json'
+ - 'translations/inventory/ja/**'
+ - 'translations/scope/ja.json'
+ - 'translations/glossary/ja.json'
+ - 'translations/state/ja/**'
+ - 'translations/protection-exceptions/ja.json'
+ - 'docusaurus.config.js'
+ - 'versions.json'
+ - 'config/i18n.js'
+ - 'config/versions-plugin.js'
- 'sidebars.ts'
- 'sidebarsCommunity.json'
- 'versioned_sidebars/version-4.x-sidebars.json'
@@ -21,15 +30,21 @@ on:
permissions:
contents: read
+concurrency:
+ group: japanese-coverage-audit-${{ github.event.pull_request.number ||
github.ref }}
+ cancel-in-progress: true
+
jobs:
audit:
+ name: Japanese coverage audit
env:
I18N_LOCALE: ja
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
- fetch-depth: 0
+ # The audit only reads HEAD; its git comparisons never need history.
+ fetch-depth: 1
persist-credentials: false
- uses: actions/setup-node@v4
with:
diff --git a/.github/workflows/korean-coverage-audit.yml
b/.github/workflows/korean-coverage-audit.yml
index 6cd3e54004b..aef12a1cfe8 100644
--- a/.github/workflows/korean-coverage-audit.yml
+++ b/.github/workflows/korean-coverage-audit.yml
@@ -10,8 +10,17 @@ on:
- 'docs/**'
- 'versioned_docs/version-4.x/**'
- 'community/**'
- - 'i18n/**'
- - 'translations/**'
+ - 'i18n/ko/**'
+ - 'translations/config.json'
+ - 'translations/inventory/ko/**'
+ - 'translations/scope/ko.json'
+ - 'translations/glossary/ko.json'
+ - 'translations/state/ko/**'
+ - 'translations/protection-exceptions/ko.json'
+ - 'docusaurus.config.js'
+ - 'versions.json'
+ - 'config/i18n.js'
+ - 'config/versions-plugin.js'
- 'sidebars.ts'
- 'sidebarsCommunity.json'
- 'versioned_sidebars/version-4.x-sidebars.json'
@@ -21,6 +30,10 @@ on:
permissions:
contents: read
+concurrency:
+ group: korean-coverage-audit-${{ github.event.pull_request.number ||
github.ref }}
+ cancel-in-progress: true
+
jobs:
audit:
name: Korean coverage audit
@@ -30,7 +43,8 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
- fetch-depth: 0
+ # The audit only reads HEAD; its git comparisons never need history.
+ fetch-depth: 1
persist-credentials: false
- uses: actions/setup-node@v4
with:
diff --git a/scripts/i18n/__tests__/multi-locale.test.cjs
b/scripts/i18n/__tests__/multi-locale.test.cjs
index b7aa0130cd2..9b1e4c9b0a0 100644
--- a/scripts/i18n/__tests__/multi-locale.test.cjs
+++ b/scripts/i18n/__tests__/multi-locale.test.cjs
@@ -106,26 +106,67 @@ test('native doc metadata, browser groups and per-locale
manifests agree, includ
}
});
-test('scheduled Japanese jobs select ja and PR admission checks every
configured locale with fail-fast behavior', async t => {
+test('scheduled Japanese jobs select ja and PR admission checks only changed
locales with fail-fast behavior', async t => {
const workflow = file => yaml.load(fs.readFileSync(path.join(repoRoot,
'.github/workflows', file), 'utf8'));
const translation = workflow('japanese-translation.yml');
assert.equal(translation.on.workflow_dispatch.inputs.locale.default, 'ja');
for (const job of ['discover', 'generate'])
assert.equal(translation.jobs[job].env.I18N_LOCALE, "${{ inputs.locale || 'ja'
}}");
assert.equal(workflow('japanese-coverage-audit.yml').jobs.audit.env.I18N_LOCALE,
'ja');
- const gate =
workflow('build-check.yml').jobs['build-and-deploy'].steps.find(step =>
step.name === 'Translation gate for every managed locale');
+ const gate =
workflow('build-check.yml').jobs['build-and-deploy'].steps.find(step =>
step.name === 'Translation gate for changed locales');
const {root, write} = await createFixture(directory(t));
for (const file of ['scripts/i18n/managed-locales.cjs',
'scripts/i18n/state.cjs', 'config/i18n.js']) write(file,
fs.readFileSync(path.join(repoRoot, file), 'utf8'));
write('scripts/i18n/pr-preflight.cjs',
"require('node:fs').appendFileSync('calls', process.env.I18N_LOCALE + '\\n');
process.exit(process.env.I18N_LOCALE === process.env.FAIL_LOCALE ? 1 : 0);\n");
- execFileSync('git', ['init', '-q'], {cwd: root});
- execFileSync('git', ['-c', 'user.name=Test', '-c',
'[email protected]', 'commit', '--allow-empty', '-qm', 'base'], {cwd:
root});
- execFileSync('git', ['update-ref', 'refs/remotes/origin/master', 'HEAD'],
{cwd: root});
- for (const fail of ['', 'ko', 'ja']) {
+ const git = (...args) => execFileSync('git', args, {cwd: root, encoding:
'utf8', stdio: ['ignore', 'pipe', 'pipe']});
+ const commit = files => {
+ git('add', '-f', ...files);
+ git('-c', 'user.name=Test', '-c', '[email protected]',
'commit', '-qm', 'change');
+ };
+ const run = fail => {
write('calls', '');
const result = spawnSync('bash', ['-e', '-c', gate.run.replace('${{
github.base_ref }}', 'master')],
{cwd: root, encoding: 'utf8', env: {...process.env, FAIL_LOCALE:
fail, DORIS_ENABLE_JA: '', DORIS_ENABLE_LOCALES: ''}});
- assert.equal(result.status, fail ? 1 : 0, result.stderr);
- assert.equal(fs.readFileSync(path.join(root, 'calls'), 'utf8'), fail
=== 'ja' ? 'ja\n' : 'ja\nko\n');
- }
+ return {status: result.status, calls: fs.readFileSync(path.join(root,
'calls'), 'utf8'), stderr: result.stderr};
+ };
+ const jaTarget =
'i18n/ja/docusaurus-plugin-content-docs/current/01-shared.md';
+ const koTarget =
'i18n/ko/docusaurus-plugin-content-docs/current/01-shared.md';
+ git('init', '-q');
+ git('-c', 'user.name=Test', '-c', '[email protected]', 'commit',
'--allow-empty', '-qm', 'base');
+ git('update-ref', 'refs/remotes/origin/master', 'HEAD');
+
+ write(jaTarget, '# ja\n');
+ write(koTarget, '# ko\n');
+ commit([jaTarget, koTarget]);
+ let result = run('');
+ assert.equal(result.status, 0, result.stderr);
+ assert.equal(result.calls, 'ja\nko\n');
+ result = run('ja');
+ assert.equal(result.status, 1, result.stderr);
+ assert.equal(result.calls, 'ja\n');
+
+ git('reset', '--hard', 'origin/master');
+ write(jaTarget, '# ja\n');
+ commit([jaTarget]);
+ result = run('');
+ assert.equal(result.status, 0, result.stderr);
+ assert.equal(result.calls, 'ja\n');
+
+ git('reset', '--hard', 'origin/master');
+ write(koTarget, '# ko\n');
+ commit([koTarget]);
+ result = run('ko');
+ assert.equal(result.status, 1, result.stderr);
+ assert.equal(result.calls, 'ko\n');
+
+ git('reset', '--hard', 'origin/master');
+ result = run('');
+ assert.equal(result.status, 0, result.stderr);
+ assert.equal(result.calls, '');
+
+ fs.appendFileSync(path.join(root, 'scripts/i18n/state.cjs'), '\n// shared
change\n');
+ commit(['scripts/i18n/state.cjs']);
+ result = run('');
+ assert.equal(result.status, 0, result.stderr);
+ assert.equal(result.calls, 'ja\nko\n');
});
test('the publisher-selected locale is forwarded to the admission subprocess',
async t => {
diff --git a/scripts/i18n/audit-coverage.cjs b/scripts/i18n/audit-coverage.cjs
index 92559be88ea..8dfeeec3a93 100644
--- a/scripts/i18n/audit-coverage.cjs
+++ b/scripts/i18n/audit-coverage.cjs
@@ -3,15 +3,39 @@
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
-const {execFileSync} = require('node:child_process');
+const {execFileSync, spawnSync} = require('node:child_process');
const {hash, identity, inventoryBaseline, loadRecordsAt, pipelineLocale} =
require('./state.cjs');
const {collect, validateScope} = require('./inventory.cjs');
const keyFor = locale => page => identity({...page,locale});
-const refContent = (root,ref,file) => {
- try {return
execFileSync('git',['show',`${ref}:${file}`],{cwd:root,encoding:'utf8',stdio:['ignore','pipe','ignore'],maxBuffer:16*1024*1024});}
- catch {return null;}
-};
+// One `git show` per page is several thousand processes for a full-scope
+// audit. Read the same blobs through one `git cat-file --batch` process and
+// keep only the SHA-256 the audit compares.
+function refHashes(root, ref, files) {
+ const unique = [...new Set(files)], hashes = new Map();
+ if (!unique.length) return hashes;
+ const result = spawnSync('git', ['cat-file', '--batch'], {
+ cwd: root, maxBuffer: 512 * 1024 * 1024,
+ input: unique.map(file => `${ref}:${file}`).join('\n') + '\n',
+ });
+ if (result.error) throw result.error;
+ if (result.status !== 0)
+ throw new Error(`git cat-file --batch failed:
${result.stderr.toString().trim()}`);
+ let offset = 0;
+ for (const file of unique) {
+ const newline = result.stdout.indexOf(0x0a, offset);
+ assert(newline >= 0, `Truncated git cat-file output at ${file}`);
+ const header = result.stdout.toString('utf8', offset, newline);
+ offset = newline + 1;
+ if (header.endsWith(' missing')) {hashes.set(file, null); continue;}
+ const match = header.match(/^[0-9a-f]+ blob (\d+)$/);
+ assert(match, `Unexpected git cat-file header for ${file}: ${header}`);
+ const size = Number(match[1]);
+ hashes.set(file, hash(result.stdout.toString('utf8', offset, offset +
size)));
+ offset += size + 1;
+ }
+ return hashes;
+}
function baselineEntries(root) {
return ['dev','4x','community'].flatMap(group => {
const meta =
JSON.parse(fs.readFileSync(path.join(root,`${group}.json`),'utf8'));
@@ -62,22 +86,27 @@ async function audit({root,sourceRef,baseline,native}) {
const current = new Set(), groups = [];
const files = group =>
execFileSync('git',['ls-tree','-r','--name-only',commit,'--',group.sourceRoot],{cwd:root,encoding:'utf8'})
.split('\n').filter(file => /\.(md|mdx)$/.test(file));
- for (const group of scope.groups) {
- const pages = native.pages.filter(page => page.pluginId ===
group.pluginId && page.version === group.version);
+ const scoped = scope.groups.map(group => ({group,
+ pages:native.pages.filter(page => page.pluginId === group.pluginId &&
page.version === group.version)}));
+ const sourceHashes = refHashes(root, commit, scoped.flatMap(({pages}) =>
pages.map(page => page.englishPath)));
+ const targetHashes = refHashes(root, commit, scoped.flatMap(({pages}) =>
pages
+ .filter(page => page.status === 'current' && page.reviewStatus !==
'unverified' && page.useTarget)
+ .map(page => page.targetPath)));
+ for (const {group, pages} of scoped) {
assert(pages.length,`Missing native group ${group.id}`);
const sourceFiles = files(group), seen = new Set(), entries = [];
for (const page of pages) {
assert(page.englishPath.startsWith(group.sourceRoot+'/'),`Wrong
source root: ${page.englishPath}`);
assert(sourceFiles.includes(page.englishPath),`Native page missing
at source ref: ${page.englishPath}`);
-
assert.equal(hash(refContent(root,commit,page.englishPath)),hash(fs.readFileSync(path.join(root,page.englishPath),'utf8')),
+
assert.equal(sourceHashes.get(page.englishPath),hash(fs.readFileSync(path.join(root,page.englishPath),'utf8')),
`Source differs from ${commit}: ${page.englishPath}`);
assert(!seen.has(page.englishPath),`Duplicate native source
${page.englishPath}`);
seen.add(page.englishPath); current.add(key(page));
const previous = old.get(key(page));
const disposition = page.draft ? 'draft' :
scope.retainEnglish.some(entry => key(entry) === key(page)) ? 'retain_english'
: 'translate';
const mergedCurrent = page.status === 'current' &&
page.reviewStatus !== 'unverified' && page.useTarget
- && refContent(root,commit,page.targetPath) !== null
- && hash(refContent(root,commit,page.targetPath)) ===
page.targetHash
+ && targetHashes.get(page.targetPath) !== null
+ && targetHashes.get(page.targetPath) === page.targetHash
&& committedRecords.get(key(page))?.targetHash ===
page.targetHash;
const state = disposition !== 'translate' ? disposition :
page.status === 'current'
? mergedCurrent ? 'current' : 'unverified' : page.status;
@@ -106,14 +135,15 @@ async function audit({root,sourceRef,baseline,native}) {
const routingInputs =
['docusaurus.config.js','versions.json','sidebars.ts','sidebarsCommunity.json','versioned_sidebars/version-4.x-sidebars.json',
'translations/config.json',`translations/scope/${locale}.json`];
routingInputs.push(`translations/state/${locale}/docs.json`,
`translations/state/${locale}/resolved-deletions.json`);
- if (refContent(root, commit,
`translations/state/${locale}/shards.schema.json`) !== null)
- routingInputs.push(`translations/state/${locale}/shards.schema.json`);
+ const schemaPath = `translations/state/${locale}/shards.schema.json`;
+ if (refHashes(root, commit, [schemaPath]).get(schemaPath) !== null)
routingInputs.push(schemaPath);
routingInputs.push(...execFileSync('git', ['ls-tree', '-r', '--name-only',
commit, '--', `translations/state/${locale}/pages`],
{cwd:root,encoding:'utf8'}).split('\n').filter(Boolean));
- for (const file of routingInputs)
assert.equal(refContent(root,commit,file),fs.readFileSync(path.join(root,file),'utf8'),
+ const routingHashes = refHashes(root, commit, routingInputs);
+ for (const file of routingInputs)
assert.equal(routingHashes.get(file),hash(fs.readFileSync(path.join(root,file),'utf8')),
`Routing input differs from source ref: ${file}`);
return
{schemaVersion:1,sourceCommit:commit,baselineCommit:JSON.parse(fs.readFileSync(path.join(baseline,'summary.json'),'utf8')).sourceCommit,
- routingInputHashes:Object.fromEntries(routingInputs.map(file =>
[file,hash(refContent(root,commit,file))])),
+ routingInputHashes:Object.fromEntries(routingInputs.map(file =>
[file,routingHashes.get(file)])),
groups,removed,labels,sidebar,reports:native.reports};
}
if (require.main === module) {
diff --git a/translations/README.md b/translations/README.md
index 6e106b5b081..d940cc7cb31 100644
--- a/translations/README.md
+++ b/translations/README.md
@@ -365,15 +365,15 @@ before discovery or generation. The Japanese translation
workflow defaults its
manual input and scheduled runs to `ja`; the Japanese coverage audit always
selects `ja`. A manual translation run can select another configured locale.
-Build Check runs PR admission once per configured translation locale, including
-locales disabled for the site build:
-
-```bash
-MANAGED_LOCALES=$(node scripts/i18n/managed-locales.cjs --all)
-for LOCALE in $MANAGED_LOCALES; do
- I18N_LOCALE="$LOCALE" node scripts/i18n/pr-preflight.cjs --base "$BASE_SHA"
-done
-```
+Build Check runs PR admission for every configured translation locale,
including
+locales disabled for the site build, when a shared input changes. Otherwise it
+runs only the locales whose own translation inputs changed, so a Korean-only PR
+does not pay for the Japanese inventory load and an English-only docs PR skips
+both admission gates. The selection lives in the `Translation gate for changed
+locales` step of `.github/workflows/build-check.yml`; shared inputs are
+`scripts/i18n/`, `translations/config.json`, `config/i18n.js`,
+`docusaurus.config.js`, `versions.json`, the site sidebars, and
+`versioned_sidebars/`.
Without `--all`, `managed-locales.cjs` lists only enabled managed locales for
publication checks. The publisher passes its selected locale to PR admission.
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]