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]

Reply via email to