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 a0ebb5427cd ci(i18n): label translation PRs, use ten-page batches, and
validate before build (#4331)
a0ebb5427cd is described below
commit a0ebb5427cd0e4820f06f369c9a80846a40fa3f8
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Fri Oct 16 15:37:20 2026 +0800
ci(i18n): label translation PRs, use ten-page batches, and validate before
build (#4331)
Generated translation PRs currently have no locale label, and normal
generation defaults to two target pages. Add `translate-<locale>`
(`translate-ja` for Japanese) and raise the normal default to at most
ten target pages. Live `validate` remains limited to one exact source
page; the planner still considers up to twenty tasks.
Build Check now installs dependencies and runs unit tests before the
translation gate, then builds the selected website scope and verifies
static pages. The gate reads native source metadata and state, so it can
reject invalid translations before a site build. Keep its full-scope
environment separate from the build's version and locale filters.
Validation:
- Node 20.20.2: all 106 i18n tests pass. The new regression supplies
eleven eligible pages and verifies that only ten targets/state records
are written.
- Both workflow files parse; the normal/validation limits and
dependency-test-gate-build-static ordering are checked.
- A fresh checkout of the real machine candidate PR is running the gate
with no site build or build directory.
- The local publication simulation will verify that the actual PR
command carries `translate-ja` before this PR leaves draft.
The label already exists in this repository. There are no new token
permissions, automatic-generation switches, source edits or
translation-state changes in this PR.
## Versions
- [x] dev
- [x] 4.x
- [ ] 3.x
- [ ] 2.1 or older (not covered by version/language sync gate)
The managed translation scope stays Dev, 4.x and community. No
documentation routes or navigation change.
## Languages
- [ ] Chinese
- [x] English
Only the tooling README changes; there are no localized product-doc
counterparts.
## Docs Checklist
- [x] Checked by AI
- [x] Test Cases Built
- [x] Updated required version and language counterparts, or explained
why not
- [x] If only one language changed, confirmed whether source/translation
counterparts need sync
Co-authored-by: morningman <[email protected]>
---
.github/workflows/build-check.yml | 44 ++++++++++++++++++------------
.github/workflows/japanese-translation.yml | 2 +-
scripts/i18n/__tests__/workflow.test.cjs | 41 ++++++++++++++++++++++++++++
scripts/i18n/simulate-workflow.cjs | 5 ++--
scripts/i18n/workflow-generate.cjs | 4 +--
scripts/i18n/workflow-publish.cjs | 3 +-
translations/README.md | 10 +++++++
7 files changed, 86 insertions(+), 23 deletions(-)
diff --git a/.github/workflows/build-check.yml
b/.github/workflows/build-check.yml
index 0ab7003af0d..b1217ad1d0d 100644
--- a/.github/workflows/build-check.yml
+++ b/.github/workflows/build-check.yml
@@ -84,17 +84,20 @@ jobs:
sudo mkswap /mnt/japanese-build-check-swap
sudo swapon /mnt/japanese-build-check-swap
- - name: Build
+ # Dependencies are required even when the scoped site build is
skipped.
+ - name: Install dependencies
env:
- DOCS_VERSIONS: ${{ steps.detect.outputs.docs_versions }}
- DORIS_ENABLE_LOCALES: ${{
steps.detect.outputs.enabled_locales }}
- PWA_SERVICE_WORKER_URL: https://doris.apache.org/sw.js
+ NODE_OPTIONS: --max-old-space-size=8192
run: |
npm install -g yarn
yarn cache clean
- export NODE_OPTIONS=--max-old-space-size=8192
yarn
yarn key-features:generate
+
+ - name: Japanese translation unit tests
+ env:
+ NODE_OPTIONS: --max-old-space-size=8192
+ run: |
node --test scripts/i18n/__tests__/t04.test.cjs
node --test scripts/i18n/__tests__/heading-ids.test.cjs
node --test scripts/i18n/__tests__/search-input.test.cjs
@@ -105,9 +108,18 @@ jobs:
node --test scripts/i18n/__tests__/workflow.test.cjs
node --test scripts/i18n/__tests__/codex-adapter.test.cjs
- # Dependencies are installed unconditionally above because
the
- # governance steps below need node_modules; only the site
build
- # itself is scoped by the detect step.
+ - name: Japanese translation gate
+ run: |
+ BASE_SHA=$(git merge-base origin/${{ github.base_ref }} HEAD)
+ node scripts/i18n/check-translations.cjs --base "$BASE_SHA"
+
+ - name: Build
+ env:
+ DOCS_VERSIONS: ${{ steps.detect.outputs.docs_versions }}
+ DORIS_ENABLE_LOCALES: ${{
steps.detect.outputs.enabled_locales }}
+ PWA_SERVICE_WORKER_URL: https://doris.apache.org/sw.js
+ NODE_OPTIONS: --max-old-space-size=8192
+ run: |
if [ "${{ steps.detect.outputs.skip_build }}" = "true" ];
then
echo "No site-affecting changes detected — skipping the
docusaurus build."
exit 0
@@ -118,12 +130,15 @@ jobs:
PWA_SERVICE_WORKER_URL=https://doris.apache.org/sw.js
yarn docusaurus build ${{ steps.detect.outputs.locale_args }}
else
# Every locale gets a separate Node process and its real
prefix.
- # Changes that can only affect some locales build just
those.
yarn build:locales --locales "${{
steps.detect.outputs.locale_csv }}"
fi
- # A locale subset cannot render the English or Chinese
- # counterparts, so cross-locale assertions are reported as
- # skipped here. Publishing still verifies the whole site.
+
+ - name: Verify localized static pages
+ if: steps.detect.outputs.skip_build != 'true'
+ env:
+ NODE_OPTIONS: --max-old-space-size=8192
+ run: |
+ # A locale subset cannot prove cross-locale reciprocity.
VERIFY_PARTIAL=""
if [ "${{ steps.detect.outputs.partial_build }}" = "true" ];
then VERIFY_PARTIAL="--partial"; fi
for LOCALE in $(echo "${{
steps.detect.outputs.enabled_locales }}" | tr ',' ' '); do
@@ -131,11 +146,6 @@ jobs:
done
rm -rf build
- - name: Japanese translation gate
- run: |
- BASE_SHA=$(git merge-base origin/${{ github.base_ref }} HEAD)
- node scripts/i18n/check-translations.cjs --base "$BASE_SHA"
-
- name: Docs changed link gate
continue-on-error: true
run: |
diff --git a/.github/workflows/japanese-translation.yml
b/.github/workflows/japanese-translation.yml
index 2ff3c62f108..29b89b2005c 100644
--- a/.github/workflows/japanese-translation.yml
+++ b/.github/workflows/japanese-translation.yml
@@ -203,7 +203,7 @@ jobs:
I18N_LIVE_VERIFIED: ${{ vars.I18N_LIVE_VERIFIED }}
I18N_VALIDATION_RUN: ${{ github.event_name == 'workflow_dispatch' &&
inputs.mode == 'validate' }}
I18N_PATH: ${{ inputs.path }}
- I18N_MAX_PAGES: ${{ inputs.mode == 'validate' && '1' || '2' }}
+ I18N_MAX_PAGES: ${{ inputs.mode == 'validate' && '1' || '10' }}
GITHUB_REPOSITORY: ${{ github.repository }}
run: node scripts/i18n/workflow-generate.cjs
"$RUNNER_TEMP/japanese-translation"
- uses: actions/create-github-app-token@v2
diff --git a/scripts/i18n/__tests__/workflow.test.cjs
b/scripts/i18n/__tests__/workflow.test.cjs
index 3b5bf8fc995..fc2825f1cf7 100644
--- a/scripts/i18n/__tests__/workflow.test.cjs
+++ b/scripts/i18n/__tests__/workflow.test.cjs
@@ -413,3 +413,44 @@ test('publication deduplicates shared state and rejects an
unrelated page shard'
assert.deepEqual(expectedFiles({schemaVersion: 1, provider: 'codex',
candidates: [record, second]}, scope),
[record.targetPath, second.targetPath,
'translations/state/ja/docs.json'].sort());
});
+
+test('normal generation defaults to ten target pages and leaves the next task
untouched', async t => {
+ const root = fs.mkdtempSync(path.join(os.tmpdir(),
'translation-ten-page-batch-'));
+ t.after(() => fs.rmSync(root, {recursive: true, force: true}));
+ const put = (file, text) => {
+ fs.mkdirSync(path.dirname(path.join(root, file)), {recursive: true});
+ fs.writeFileSync(path.join(root, file), text);
+ };
+ put('translations/scope/ja.json', JSON.stringify(scope));
+ put('translations/state/ja/docs.json', JSON.stringify({schemaVersion: 1,
pages: []}));
+ put('translations/state/ja/shards.schema.json',
JSON.stringify({schemaVersion: 1, format: 'page-record-v1'}));
+ const tasks = Array.from({length: 11}, (_, index) => {
+ const source = `# Guide ${index}\n\nA guide.\n`;
+ const sourcePath = `docs/page-${index}.md`;
+ put(sourcePath, source);
+ return {pluginId: 'default', version: 'current', locale: 'ja',
groupId: 'DEV',
+ id: `T06-DEV-${String(index + 1).padStart(15, '0')}`, docId:
`page-${index}`,
+ sourcePath, sourceHash: hash(source), kind: 'translate_new',
+ targetPath:
`i18n/ja/docusaurus-plugin-content-docs/current/page-${index}.md`,
+ requiresHumanReview: false, activePr: null};
+ });
+ const git = (...args) => execFileSync('git', args, {cwd: root, encoding:
'utf8'}).trim();
+ git('init', '-q'); git('add', '.');
+ git('-c', 'user.name=Test', '-c', '[email protected]', 'commit',
'-qm', 'eleven pending pages');
+ const sourceCommit = git('rev-parse', 'HEAD');
+ let calls = 0;
+ const adapter = {model: 'fixture', usedTokens: 0, translate: async
({units}) => {
+ calls++;
+ return {translations: units.map(unit => ({id: unit.id, text: '訳' +
unit.text}))};
+ }};
+ const manifest = await generate({rootDir: root, target, output:
path.join(root, 'output'),
+ plan: {schemaVersion: 1, locale: 'ja', sourceCommit, selected:
tasks.map(task => task.id), tasks, exceptions: []},
+ reconciliation: {botPrs: [], botDetails: [], blockedTargets: []},
ensureFresh: () => {},
+ adapter, glossary: require('../../../translations/glossary/ja.json')});
+ assert.equal(manifest.candidates.length, 10);
+ assert.equal(manifest.failures.length, 0);
+ assert.equal(calls, 10);
+ assert.equal(loadRecords(root, 'ja').size, 10);
+ assert(!fs.existsSync(path.join(root, tasks[10].targetPath)));
+ assert(!fs.existsSync(path.join(root, stateFileFor(tasks[10]))));
+});
diff --git a/scripts/i18n/simulate-workflow.cjs
b/scripts/i18n/simulate-workflow.cjs
index dec7944379a..9422b3a3c70 100644
--- a/scripts/i18n/simulate-workflow.cjs
+++ b/scripts/i18n/simulate-workflow.cjs
@@ -53,9 +53,10 @@ if (args[0] === 'api' && args.some(arg =>
arg.startsWith('repos/apache/doris-web
assert(args.includes('--draft'));
assert.equal(value('--base'), 'master');
assert.equal(value('--repo'), 'apache/doris-website');
+ assert.equal(value('--label'), 'translate-ja');
const body = fs.readFileSync(value('--body-file'), 'utf8');
assert.match(body, /japanese-translation-automation:v1
bot-head=[a-f0-9]{40}/);
- const record = {draft: true, base: value('--base'), head: value('--head'),
body,
+ const record = {draft: true, base: value('--base'), head: value('--head'),
labels: [value('--label')], body,
url: 'https://example.invalid/apache/doris-website/pull/1'};
fs.writeFileSync(process.env.I18N_SIM_PR_RECORD, JSON.stringify(record,
null, 2) + '\\n');
process.stdout.write(record.url + '\\n');
@@ -145,7 +146,7 @@ function main() {
sourceCommit: plan.sourceCommit, selected: plan.selected, sourcePath,
invalidModel: {exitCode: 1, error: failure.failures[0].error,
usedTokens: failure.usedTokens},
generation: {candidates: generation.candidates.length, usedTokens:
generation.usedTokens},
- draftPr: {url: pr.url, branch: pr.head, commit: branch, files:
committedFiles}};
+ draftPr: {url: pr.url, branch: pr.head, commit: branch, files:
committedFiles, labels: pr.labels}};
fs.writeFileSync(path.join(output, 'report.json'), JSON.stringify(report,
null, 2) + '\n');
process.stdout.write(JSON.stringify(report, null, 2) + '\n');
}
diff --git a/scripts/i18n/workflow-generate.cjs
b/scripts/i18n/workflow-generate.cjs
index fbf319e8a3d..35276255640 100644
--- a/scripts/i18n/workflow-generate.cjs
+++ b/scripts/i18n/workflow-generate.cjs
@@ -81,7 +81,7 @@ function bodyFor(manifest) {
'', `${markerPrefixFor(manifest.language)}PENDING -->`, ''].join('\n');
}
-async function generate({plan, reconciliation, maxPages = 2, target,
+async function generate({plan, reconciliation, maxPages = 10, target,
adapter, glossary, rootDir = root, ensureFresh = ensureMasterFresh,
output}) {
assert(Number.isInteger(maxPages) && maxPages > 0 && maxPages <= 20,
'Invalid page limit');
assert(plan.schemaVersion === 1 && plan.sourceCommit ===
execFileSync('git', ['rev-parse', 'HEAD'],
@@ -173,7 +173,7 @@ async function main() {
assert(process.env.CODEX_BIN && fs.existsSync(process.env.CODEX_BIN),
'Pinned Codex runtime is required');
const adapter = codexAdapter({model: process.env.I18N_CODEX_MODEL ||
'gpt-6-sol', language: languageName(root, locale), locale,
codexBin: process.env.CODEX_BIN, codexHome: process.env.CODEX_HOME});
- const manifest = await generate({plan, reconciliation, maxPages:
Number(process.env.I18N_MAX_PAGES || '2'),
+ const manifest = await generate({plan, reconciliation, maxPages:
Number(process.env.I18N_MAX_PAGES || '10'),
target, adapter, glossary, output});
if (process.env.GITHUB_OUTPUT) fs.appendFileSync(process.env.GITHUB_OUTPUT,
`created=${manifest.candidates.length > 0}\n`);
diff --git a/scripts/i18n/workflow-publish.cjs
b/scripts/i18n/workflow-publish.cjs
index 3584ee178d1..5b26f5b8fe2 100644
--- a/scripts/i18n/workflow-publish.cjs
+++ b/scripts/i18n/workflow-publish.cjs
@@ -91,7 +91,8 @@ function main() {
}
git('push', 'origin', `HEAD:refs/heads/${branch}`);
const url = execFileSync('gh', ['pr', 'create', '--repo',
'apache/doris-website', '--base', 'master',
- '--head', branch, '--draft', '--title', `docs(i18n): machine
${language} translation candidates`,
+ '--head', branch, '--draft', '--label', `translate-${locale}`,
+ '--title', `docs(i18n): machine ${language} translation candidates`,
'--body-file', bodyFile], {cwd: root, encoding: 'utf8'}).trim();
if (process.env.GITHUB_STEP_SUMMARY)
fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY,
`\nOpened draft translation PR: ${url}; ${manifest.candidates.length}
candidate pages.\n`);
diff --git a/translations/README.md b/translations/README.md
index a87bbb8f768..668878fa167 100644
--- a/translations/README.md
+++ b/translations/README.md
@@ -18,6 +18,16 @@ registered by the English docs plugin. A Chinese-only file
is reported as
`community`) to the label that locale's search page shows; a locale without it
shows the raw context path.
+The translation workflow's `validate` mode selects one exact source page for a
+live check. Normal `generate` runs produce at most 10 target pages per batch,
+including any reused Dev/4.x partner; the planner can consider up to 20 tasks.
+Automated draft PRs receive the `translate-<locale>` repository label, such as
+`translate-ja`. Create that label before enabling publication for a new locale.
+
+Build Check installs dependencies and runs the i18n unit tests before the
+translation gate. The gate checks source metadata and state without a site
+build; the scoped website build and static-page verification run afterward.
+
## State schema, version 1
`state/ja/docs.json` is the legacy shared ledger. New Japanese records can live
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]