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 bb7e86a7ff7b [feat](i18n) Add the unified content identity and 
translation manifest (#4490)
bb7e86a7ff7b is described below

commit bb7e86a7ff7b9232af24090928cfbc9a87d8a3b2
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Sat Oct 24 07:03:30 2026 +0100

    [feat](i18n) Add the unified content identity and translation manifest 
(#4490)
    
    Existing docs state, page allowlists and blog directory detection cannot
    describe partial translation availability consistently. Add a versioned
    build-only contract that joins native docs/page/blog identities and
    routes, separates navigable fallback routes from indexable translations,
    and binds every decision to the source SHA and input digests.
    
    Preserve the existing docs state, hash/ownership decisions, legacy
    manifest and browser behavior. Register the 21 ordinary page IDs and
    Slack utility, define independent manual page/blog ledgers, and expose a
    compact per-page routing payload. Manual decisions explicitly carry
    `renderIntegrated: false` until the page/blog renderers adopt them in
    T03/T14; the empty ledgers make no new translation coverage claims. Full
    blog lifecycle and SEO integration remain separate tasks.
    
    Validation:
    
    - 237 i18n tests passed; one existing opt-in production-render test
    skipped.
    - 22 workflow boundary tests passed; workflow YAML and `git diff
    --check` passed.
    - Real native input: 1,680 Dev docs, 21 pages, 206 blog posts and one
    utility. The full English manifest is 3,020,067 bytes; largest per-page
    routing payload is 1,063 bytes, with zero added global browser payload.
    - Sequential real production fixtures passed for all five languages and
    with Russian disabled, covering zero/one/partial/full manual coverage,
    native routes, emitted HTML, docs fallback, manual source drift and
    payload sizes.
    - Repository typecheck still reports the same 176 diagnostics as the
    unmodified baseline, with no new diagnostics.
    
    The shared contract and manual ledgers select the complete optimized
    five-language, all-active-version CI build. That CI/publishing
    validation is pending; this PR does not publish the global language
    switcher or whole-page fallback.
    
    ---------
    
    Co-authored-by: morningman <[email protected]>
---
 .github/workflows/build-check.yml                  |   7 +
 docusaurus.config.js                               |  26 ++--
 scripts/i18n/__tests__/content-manifest.test.cjs   | 104 +++++++++++++++
 .../i18n/__tests__/fixtures/content-manifest.cjs   |  51 ++++++++
 scripts/i18n/__tests__/fixtures/multi-locale.cjs   |   6 +
 scripts/i18n/build-scope.cjs                       |   7 +-
 scripts/i18n/content-manifest.cjs                  |  87 +++++++++++++
 scripts/i18n/content-manifest.schema.json          | 141 +++++++++++++++++++++
 scripts/i18n/docs-plugin.cjs                       |  67 +++++++---
 scripts/i18n/manual-plugins.cjs                    | 102 +++++++++++++++
 scripts/i18n/manual-state.cjs                      |  45 +++++++
 scripts/i18n/native-context.cjs                    |  12 ++
 scripts/i18n/status-plugin.cjs                     |  23 +++-
 scripts/i18n/verify-content-fixture.cjs            |  63 +++++++++
 src/utils/translation.ts                           |  23 ++++
 translations/CONTENT_MANIFEST.md                   | 133 +++++++++++++++++++
 translations/blog/state.json                       |   4 +
 translations/pages/registry.json                   | 115 +++++++++++++++++
 translations/pages/state.json                      |   4 +
 19 files changed, 986 insertions(+), 34 deletions(-)

diff --git a/.github/workflows/build-check.yml 
b/.github/workflows/build-check.yml
index 628d933204a4..d6ec352771b8 100644
--- a/.github/workflows/build-check.yml
+++ b/.github/workflows/build-check.yml
@@ -116,6 +116,7 @@ jobs:
                   node --test scripts/i18n/__tests__/marketing-pages.test.cjs
                   node --test scripts/i18n/__tests__/locale-registry.test.cjs
                   node --test scripts/i18n/__tests__/multi-locale.test.cjs
+                  node --test scripts/i18n/__tests__/content-manifest.test.cjs
                   node --test scripts/i18n/__tests__/inventory.test.cjs
                   node --test scripts/i18n/__tests__/planner.test.cjs
                   node --test 
scripts/i18n/__tests__/translation-engine.test.cjs
@@ -126,6 +127,10 @@ jobs:
                   node --test 
scripts/i18n/__tests__/translate-document.test.cjs
                   node --test 
scripts/i18n/__tests__/incremental-document.test.cjs
 
+            - name: Verify native content manifest fixture
+              if: steps.detect.outputs.optimize_publish == 'true'
+              run: node scripts/i18n/verify-content-fixture.cjs
+
             - name: Workflow security boundary tests
               run: PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s 
.github/scripts -p 'test_*.py' -v
 
@@ -229,6 +234,8 @@ jobs:
                   path: |
                       .i18n-build/publish-result.json
                       .i18n-build/build-result.json
+                      .i18n-build/content-fixture-verification.json
+                      .i18n-build/content-manifests/*.sizes.json
                       .i18n-build/ci-*.txt
                       .i18n-build/browser-*-time.txt
                       .i18n-build/browser-*.json
diff --git a/docusaurus.config.js b/docusaurus.config.js
index 6589a9d04fa0..cc4a944ff50d 100644
--- a/docusaurus.config.js
+++ b/docusaurus.config.js
@@ -10,6 +10,7 @@ const REDIRECTS_4X = require('./config/redirects-4.x.json');
 const path = require('path');
 const translationDocsPlugin = require('./scripts/i18n/docs-plugin.cjs');
 const translationStatusPlugin = require('./scripts/i18n/status-plugin.cjs');
+const manualPlugins = require('./scripts/i18n/manual-plugins.cjs');
 const contentTranslationPlugin = require('./scripts/i18n/content-plugin.cjs');
 const {translatedContentPages} = require('./scripts/i18n/source-groups.cjs');
 const searchPlugin = require('./scripts/i18n/search-plugin.cjs');
@@ -382,7 +383,18 @@ const config = {
     plugins: [
         contentTranslationPlugin,
         SKIP_DOCS ? null : [translationDocsPlugin, MAIN_DOCS_OPTIONS],
-        MANAGED_LOCALES.length ? translationStatusPlugin : null,
+        [translationStatusPlugin, {browserData: MANAGED_LOCALES.length > 0}],
+        [manualPlugins.pages, {}],
+        SKIP_BLOG ? null : [manualPlugins.blog, {
+            blogTitle: 'Apache Doris - Blog | Latest news and events ',
+            blogDescription:
+                'Explore how Doris empower lakehouse, adhoc analysis, 
customer-facing analysis and various scenarios',
+            postsPerPage: 'ALL',
+            blogSidebarCount: 0,
+            showReadingTime: false,
+            onUntruncatedBlogPosts: 'ignore',
+            onInlineAuthors: 'ignore',
+        }],
         'docusaurus-plugin-sass',
         'docusaurus-plugin-matomo',
         // Use custom blog plugin
@@ -505,16 +517,8 @@ const config = {
             /** @type {import('@docusaurus/preset-classic').Options} */
             ({
                 docs: false, // The native docs plugin is wrapped below for 
translation state.
-                blog: SKIP_BLOG ? false : {
-                    blogTitle: 'Apache Doris - Blog | Latest news and events ',
-                    blogDescription:
-                        'Explore how Doris empower lakehouse, adhoc analysis, 
customer-facing analysis and various scenarios',
-                    postsPerPage: 'ALL',
-                    blogSidebarCount: 0,
-                    showReadingTime: false,
-                    onUntruncatedBlogPosts: 'ignore',
-                    onInlineAuthors: 'ignore',
-                },
+                blog: false, // Native blog metadata is wrapped for the 
content contract.
+                pages: false,
                 theme: {
                     customCss: require.resolve('./src/scss/custom.scss'),
                 },
diff --git a/scripts/i18n/__tests__/content-manifest.test.cjs 
b/scripts/i18n/__tests__/content-manifest.test.cjs
new file mode 100644
index 000000000000..58d6c83fbfcb
--- /dev/null
+++ b/scripts/i18n/__tests__/content-manifest.test.cjs
@@ -0,0 +1,104 @@
+const test = require('node:test');
+const assert = require('node:assert/strict');
+const fs = require('node:fs');
+const path = require('node:path');
+const os = require('node:os');
+const {createContentFixture} = require('./fixtures/content-manifest.cjs');
+const {loadManual, decideManual} = require('../manual-state.cjs');
+const {sourceSha, validateManifest, buildManifest, routeData} = 
require('../content-manifest.cjs');
+const {buildScope} = require('../build-scope.cjs');
+function directory(t) {
+    const root = fs.mkdtempSync(path.join(os.tmpdir(), 
'doris-content-manifest-'));
+    t.after(() => fs.rmSync(root, {recursive: true, force: true}));
+    return root;
+}
+async function collect(root, locale, options) {
+    const {loadContext} = require('@docusaurus/core/lib/server/site.js');
+    const {loadPluginConfigs} = 
require('@docusaurus/core/lib/server/plugins/configs.js');
+    const {initPluginsConfigs} = 
require('@docusaurus/core/lib/server/plugins/init.js');
+    const context = await loadContext({siteDir: root, locale});
+    const plugins = (await initPluginsConfigs(context, await 
loadPluginConfigs(context))).map(item => item.plugin);
+    const allContent = {};
+    for (const plugin of plugins) {
+        if (!plugin.loadContent) continue;
+        const content = await plugin.loadContent();
+        if (!allContent[plugin.name]) allContent[plugin.name] = {};
+        allContent[plugin.name][plugin.options.id] = content;
+    }
+    let globalData;
+    await require('../status-plugin.cjs')(context, 
options).allContentLoaded({allContent,
+        actions: {setGlobalData: value => {globalData = value;}}});
+    return {manifest: JSON.parse(fs.readFileSync(path.join(root, 
'.i18n-build/content-manifests', locale + '.json'))), globalData};
+}
+test('real native plugin output binds stable identities, routes and all four 
coverage levels to one source', async t => {
+    const {root} = await createContentFixture(directory(t));
+    const old = process.env.NODE_ENV; process.env.NODE_ENV = 'production';
+    t.after(() => old === undefined ? delete process.env.NODE_ENV : 
process.env.NODE_ENV = old);
+    for (const locale of ['en', 'zh-CN', 'ja', 'ko', 'ru']) {
+        const {manifest, globalData} = await collect(root, locale);
+        validateManifest(manifest);
+        const validateShape = new 
(require('ajv'))().compile(require('../content-manifest.schema.json'));
+        assert(validateShape(manifest), JSON.stringify(validateShape.errors));
+        assert.equal(manifest.sourceSha, sourceSha(root));
+        const page = manifest.pages.find(page => page.contentId === 
'page:sample-0');
+        assert.deepEqual(page.availableTranslations, ['en', 'ja', 'ko', 'ru']);
+        assert.deepEqual(Object.keys(page.navigationRoutes), ['en', 'zh-CN', 
'ja', 'ko', 'ru']);
+        assert.equal(page.fallback, locale === 'zh-CN');
+        const blog = manifest.pages.find(page => page.contentId === 
'blog:2026-01-01-sample.md');
+        assert.equal(blog.state, locale === 'en' ? 'source' : locale === 
'zh-CN' ? 'missing' : 'needs-update');
+        assert.equal(blog.route, `${locale === 'en' ? '' : '/' + 
locale}/blog/native-0/`);
+        const full = manifest.pages.find(page => page.contentId === 
'blog:2026-01-03-sample.md');
+        assert.deepEqual(full.availableTranslations, ['en', 'ru']);
+        assert(!manifest.pages.find(page => page.contentId === 
'utility:slack').indexable);
+        assert(manifest.reports.some(report => report.kind === 'orphan_target' 
&& report.locale === 'ja'));
+        const ids = manifest.pages.filter(page => page.docId === 
'shared').map(page => page.contentId);
+        assert.equal(new Set(ids).size, 3, 'docs identity includes plugin and 
version');
+        const missing = manifest.pages.find(page => page.kind === 'docs' && 
page.docId === 'missing');
+        assert.equal(missing.navigationRoutes['zh-CN'], 
'/zh-CN/docs/dev/chinese-missing/');
+        assert(!JSON.stringify(globalData).includes('sourceHash'));
+        assert(!JSON.stringify(routeData(page)).includes('sourceHash'));
+        assert(Buffer.byteLength(JSON.stringify(routeData(page))) < 2048);
+    }
+    const buildOnly = await collect(root, 'en', {browserData: false});
+    assert.equal(buildOnly.globalData, undefined);
+    assert(buildOnly.manifest.pages.length > 0);
+    const previous = process.env.FIXTURE_DISABLE_RU; 
process.env.FIXTURE_DISABLE_RU = 'true';
+    try {
+        const {manifest} = await collect(root, 'ja');
+        assert(!manifest.enabledLocales.includes('ru'));
+        assert(manifest.pages.every(page => !page.navigationRoutes.ru && 
!page.availableTranslations.includes('ru')));
+        assert(!manifest.reports.some(report => report.locale === 'ru'));
+    } finally {previous === undefined ? delete process.env.FIXTURE_DISABLE_RU 
: process.env.FIXTURE_DISABLE_RU = previous;}
+});
+test('manual targets reject corruption, retain source drift, and honor blocked 
publication', async t => {
+    const {root} = await createContentFixture(directory(t));
+    const record = loadManual(root, 'blog').find(record => record.locale === 
'ja');
+    const decide = entry => decideManual({root, record: entry, sourceFiles: 
entry.sourceFiles});
+    assert.equal(decide(record).state, 'needs-update');
+    assert.equal(decide(record).fallback, false);
+    assert.equal(decide({...record, state: 'blocked'}).fallback, true);
+    fs.appendFileSync(path.join(root, record.targetFiles[0]), 'Modified 
target.');
+    assert.throws(() => decide(record), /target digest mismatch/);
+});
+test('validator rejects mixed source and fallback indexing; missing English 
has an explicit report', () => {
+    const entry = {contentId: 'page:one', kind: 'page', updatePolicy: 
'manual', renderIntegrated: false, siteLocale: 'ja', contentLocale: 'en', 
state: 'missing',
+        route: '/ja/one/', englishRoute: '/one/', fallback: true, indexable: 
false};
+    const base = {sourceSha: 'a'.repeat(40), locale: 'ja', enabledLocales: 
['en', 'ja']};
+    const absent = buildManifest({...base, entries: [entry]});
+    assert.equal(absent.pages.length, 0);
+    assert.equal(absent.reports[0].kind, 'missing_english_identity');
+    const manifest = buildManifest({...base, entries: [entry, {...entry, 
siteLocale: 'en', route: '/one/', fallback: false, indexable: true}]});
+    const corrupt = structuredClone(manifest); corrupt.pages[0].sourceSha = 
'b'.repeat(40);
+    assert.throws(() => validateManifest(corrupt), /Mixed source/);
+    manifest.pages[0].indexable = true;
+    assert.throws(() => validateManifest(manifest), /fallback/);
+});
+test('CI recognizes the new contract and manual state as shared inputs', () => 
{
+    for (const file of ['scripts/i18n/content-manifest.cjs', 
'scripts/i18n/manual-state.cjs', 'scripts/i18n/manual-plugins.cjs',
+        'scripts/i18n/native-context.cjs', 'translations/pages/state.json', 
'translations/pages/registry.json', 'translations/blog/state.json']) {
+        const scope = buildScope([file], ['4.x'], ['ja', 'ko', 'ru']);
+        assert.deepEqual(scope.locales, ['en', 'zh-CN', 'ja', 'ko', 'ru'], 
file);
+        assert.equal(scope.optimize_publish, true);
+        assert.equal(scope.docs_versions, '');
+    }
+});
diff --git a/scripts/i18n/__tests__/fixtures/content-manifest.cjs 
b/scripts/i18n/__tests__/fixtures/content-manifest.cjs
new file mode 100644
index 000000000000..a109a1f6e94b
--- /dev/null
+++ b/scripts/i18n/__tests__/fixtures/content-manifest.cjs
@@ -0,0 +1,51 @@
+const fs = require('node:fs');
+const path = require('node:path');
+const {execFileSync} = require('node:child_process');
+const {createFixture} = require('./multi-locale.cjs');
+const {digestFiles} = require('../../manual-state.cjs');
+const repo = path.resolve(__dirname, '../../../..');
+async function createContentFixture(root) {
+    const fixture = await createFixture(root);
+    const {write} = fixture;
+    const registry = [{id: 'home', kind: 'page', sourcePath: 
'src/pages/index.tsx'},
+        {id: 'slack', kind: 'utility', sourcePath: 'src/pages/slack.tsx'}];
+    write('src/pages/slack.tsx', "import React from 'react'; export default 
function Slack(){return <main>Slack utility</main>;}\n");
+    const states = {pages: [], blog: []};
+    const counts = {'zh-CN': 0, ja: 1, ko: 2, ru: 3};
+    for (let i = 0; i < 3; i++) {
+        const file = `src/pages/sample-${i}.tsx`;
+        registry.push({id: `sample-${i}`, kind: 'page', sourcePath: file});
+        write(file, `import React from 'react'; import Translate from 
'@docusaurus/Translate';
+export default function Page(){return <main><Translate 
id="fixture.${i}">English sample ${i}</Translate></main>;}\n`);
+        write(`blog/2026-01-0${i + 1}-sample.md`, `---\ntitle: English article 
${i}\nslug: /native-${i}\n---\n\nEnglish article ${i}.\n`);
+    }
+    for (const [locale, count] of Object.entries(counts)) {
+        const messages = Object.fromEntries(Array.from({length: count}, (_, i) 
=> [`fixture.${i}`, {message: `${locale} translated sample ${i}`} ]));
+        write(`i18n/${locale}/code.json`, JSON.stringify(messages));
+        for (let i = 0; i < count; i++) {
+            const sourceFiles = [`src/pages/sample-${i}.tsx`];
+            const targetFiles = [`i18n/${locale}/code.json`];
+            states.pages.push({contentId: `page:sample-${i}`, locale, state: 
'ready', messageNamespaces: [`fixture.${i}`], sourceFiles, targetFiles,
+                sourceHash: digestFiles(root, sourceFiles), targetHash: 
digestFiles(root, targetFiles), reviewStatus: 'unverified'});
+            const blogFile = `2026-01-0${i + 1}-sample.md`;
+            const target = 
`i18n/${locale}/docusaurus-plugin-content-blog/${blogFile}`;
+            write(target, fs.readFileSync(path.join(root, 'blog', blogFile), 
'utf8').replaceAll('English article', `${locale} translated article`));
+            states.blog.push({contentId: `blog:${blogFile}`, locale, state: 
'ready', sourceFiles: [`blog/${blogFile}`], targetFiles: [target],
+                sourceHash: digestFiles(root, [`blog/${blogFile}`]), 
targetHash: digestFiles(root, [target]), reviewStatus: 'unverified'});
+        }
+    }
+    // Source drift preserves the published manual target; docs drift above 
falls back.
+    fs.appendFileSync(path.join(root, 'blog/2026-01-01-sample.md'), '\nSource 
update.\n');
+    write('translations/pages/registry.json', JSON.stringify({schemaVersion: 
1, pages: registry}));
+    for (const kind of ['pages', 'blog']) 
write(`translations/${kind}/state.json`, JSON.stringify({schemaVersion: 1, 
pages: states[kind]}));
+    write('i18n/ja/docusaurus-plugin-content-blog/2026-01-10-orphan.md', 
'---\ntitle: Orphan\n---\n\nOrphan.\n');
+    let config = fs.readFileSync(path.join(root, 'docusaurus.config.js'), 
'utf8');
+    config = config.replace(" presets: 
[['classic',{docs:false,blog:false}]],", ` presets: 
[['classic',{docs:false,blog:false,pages:false}]],`)
+        .replace(" plugins: [", ` plugins: 
[[require(${JSON.stringify(path.join(repo, 
'scripts/i18n/manual-plugins.cjs'))}).pages, {}],
+ [require(${JSON.stringify(path.join(repo, 
'scripts/i18n/manual-plugins.cjs'))}).blog, {onUntruncatedBlogPosts:'ignore', 
onInlineAuthors:'ignore'}],`);
+    write('docusaurus.config.js', config);
+    execFileSync('git', ['add', '.'], {cwd: root});
+    execFileSync('git', ['-c', 'user.name=Fixture', '-c', 
'[email protected]', 'commit', '-qm', 'Five-language content 
source'], {cwd: root});
+    return fixture;
+}
+module.exports = {createContentFixture};
diff --git a/scripts/i18n/__tests__/fixtures/multi-locale.cjs 
b/scripts/i18n/__tests__/fixtures/multi-locale.cjs
index ae30c2b6588d..6f29dbf12eb9 100644
--- a/scripts/i18n/__tests__/fixtures/multi-locale.cjs
+++ b/scripts/i18n/__tests__/fixtures/multi-locale.cjs
@@ -80,6 +80,12 @@ export default function Layout(props) {
  function fixtureCache(){return 
{name:'fixture-cache',configureWebpack:()=>({cache:false})}}],
  presets: [['classic',{docs:false,blog:false}]],
 });\n`);
+    write('.gitignore', 'node_modules\n.i18n-build/\n.docusaurus/\nbuild/\n');
+    const {execFileSync} = require('node:child_process');
+    const git = (...args) => execFileSync('git', args, {cwd: root, stdio: 
'pipe'});
+    git('init', '-q');
+    git('add', '.');
+    git('-c', 'user.name=Fixture', '-c', '[email protected]', 
'commit', '-qm', 'Native fixture source');
     return {root, write};
 }
 module.exports = {createFixture};
diff --git a/scripts/i18n/build-scope.cjs b/scripts/i18n/build-scope.cjs
index 12d47e7c51bf..3ecb8cc04d4c 100644
--- a/scripts/i18n/build-scope.cjs
+++ b/scripts/i18n/build-scope.cjs
@@ -40,7 +40,8 @@ function buildScope(files, versions, managed = []) {
         else if (/^sidebars(Community|Releases|Course)\./.test(file)) shared();
         else if (/^(src|config)\//.test(file) || 
['docusaurus.config.js','package.json','yarn.lock','tailwind.config.js','tsconfig.json'].includes(file)
             || /^scripts\/(key-features|last-update|publish)\//.test(file) || 
/^scripts\/(patch-search-tokenize.js|build-locales.cjs|build-locale.cjs)$/.test(file)
-            || 
/^scripts\/i18n\/(docs-plugin|status-plugin|state|structure|content-plugin|content-loader|source-groups|code-prose|protection)\.cjs$/.test(file))
 shared();
+            || 
/^scripts\/i18n\/(docs-plugin|status-plugin|state|structure|content-plugin|content-loader|source-groups|code-prose|protection|content-manifest|manual-state|manual-plugins|native-context)\.cjs$/.test(file))
 shared();
+        else if (/^translations\/(pages|blog)\//.test(file)) {shared(); if 
(file.endsWith('.json')) json.add(file);}
         else if ((match = 
file.match(/^translations\/(scope|glossary)\/([^/]+)\.json$/))) {
             if (!locales.includes(match[2])) throw new Error(`Unconfigured 
locale: ${match[2]}`);
             json.add(file); // Offline configuration checks cover these 
without a site build.
@@ -68,7 +69,9 @@ function buildScope(files, versions, managed = []) {
         || 
/^src\/utils\/(searchVersion|searchContexts|searchInput|searchNormalization|tokenize|smartQueries|smartTerms)\.[jt]s$/.test(file));
     const navigationChanged = files.some(file => 
/^src\/theme\/NavbarItem\/(LocaleDropdownNavbarItem\/index|DocsVersionDropdownNavbarItem)\.tsx$/.test(file)
         || /^scripts\/i18n\/verify-(doc-navigation|search)\.cjs$/.test(file));
-    const optimizePublish = searchChanged || navigationChanged || 
files.some(file => 
/^(scripts\/publish\/|config\/(custom-docusaurus-plugin|ssrTemplate)\.js$|scripts\/build-locales\.cjs$|static\/js\/(brand-theme|course-rail)-bootstrap\.js$|\.github\/workflows\/(cron|manual)-deploy-website\.yml$)/.test(file));
+    const contractChanged = files.some(file => 
/^scripts\/i18n\/(content-manifest|manual-state|manual-plugins|native-context|docs-plugin|status-plugin)\.cjs$/.test(file)
+        || /^translations\/(pages|blog)\//.test(file));
+    const optimizePublish = contractChanged || searchChanged || 
navigationChanged || files.some(file => 
/^(scripts\/publish\/|config\/(custom-docusaurus-plugin|ssrTemplate)\.js$|scripts\/build-locales\.cjs$|static\/js\/(brand-theme|course-rail)-bootstrap\.js$|\.github\/workflows\/(cron|manual)-deploy-website\.yml$)/.test(file));
     // Publication changes need the same complete artifact as deployment, on
     // the CI runner. Keep local checks scoped to fixtures and Dev versions.
     if (optimizePublish) {tier = 'full'; shared();}
diff --git a/scripts/i18n/content-manifest.cjs 
b/scripts/i18n/content-manifest.cjs
new file mode 100644
index 000000000000..48aa0859dfb4
--- /dev/null
+++ b/scripts/i18n/content-manifest.cjs
@@ -0,0 +1,87 @@
+// Build-only contract. Keep source hashes, ownership and reports out of 
browser data.
+const assert = require('node:assert/strict');
+const {execFileSync} = require('node:child_process');
+const {hash} = require('./state.cjs');
+const schemaVersion = 1;
+const docsId = ({pluginId, version, docId}) => 
`docs:${JSON.stringify([pluginId, version, docId])}`;
+const key = entry => JSON.stringify([entry.contentId, entry.siteLocale]);
+const isRoute = route => typeof route === 'string' && route.startsWith('/') && 
!route.startsWith('//') && !/[?#]/.test(route);
+
+function sourceSha(root) {
+    const sha = execFileSync('git', ['rev-parse', 'HEAD'], {cwd: root, 
encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe']}).trim();
+    assert(/^[a-f0-9]{40}$/.test(sha), 'Manifest requires a Git source SHA');
+    return sha;
+}
+
+// Automatic docs state has already made the hash/technical/ownership decision.
+// This adapter never reclassifies stale_prose, human protection, or conflicts.
+function adaptDoc(page) {
+    return {contentId: docsId(page), kind: 'docs', renderIntegrated: true, 
pluginId: page.pluginId, version: page.version, docId: page.docId,
+        siteLocale: page.locale, contentLocale: page.contentLocale, state: 
page.status,
+        route: page.permalink, englishRoute: page.englishPermalink, fallback: 
!page.useTarget,
+        indexable: page.indexable, sourceHash: page.sourceHash, targetHash: 
page.targetHash,
+        englishPath: page.englishPath, sourcePath: page.sourcePath, 
targetPath: page.targetPath,
+        sourceLocale: page.sourceLocale, reviewStatus: page.reviewStatus, 
updatePolicy: 'automatic-docs'};
+}
+
+function buildManifest({sourceSha: sha, locale, enabledLocales, entries, 
reports = []}) {
+    assert(enabledLocales.includes('en') && enabledLocales.includes(locale), 
'English and current locale must be enabled');
+    const selected = entries.filter(entry => 
enabledLocales.includes(entry.siteLocale));
+    const seen = new Set(), routes = new Set(), groups = new Map();
+    for (const entry of selected) {
+        assert(!seen.has(key(entry)), `Duplicate content identity: 
${key(entry)}`);
+        assert(!routes.has(entry.siteLocale + ':' + entry.route), `Duplicate 
content route: ${entry.route}`);
+        seen.add(key(entry)); routes.add(entry.siteLocale + ':' + entry.route);
+        if (!groups.has(entry.contentId)) groups.set(entry.contentId, []);
+        groups.get(entry.contentId).push(entry);
+    }
+    const pages = [];
+    for (const group of groups.values()) {
+        const english = group.find(entry => entry.siteLocale === 'en');
+        if (!english) {
+            reports.push({kind: 'missing_english_identity', contentId: 
group[0].contentId});
+            continue;
+        }
+        const navigationRoutes = Object.fromEntries(group.map(entry => 
[entry.siteLocale, entry.route]));
+        const availableTranslations = group.filter(entry => entry.indexable && 
!entry.fallback).map(entry => entry.siteLocale);
+        for (const entry of group.filter(item => item.siteLocale === locale)) {
+            assert(entry.englishRoute === english.route, `English route 
mismatch: ${entry.contentId}`);
+            pages.push({...entry, sourceSha: sha, navigationRoutes, 
availableTranslations});
+        }
+    }
+    pages.sort((a, b) => a.contentId.localeCompare(b.contentId));
+    const manifest = {schemaVersion, sourceSha: sha, locale, enabledLocales, 
pages,
+        reports: reports.filter(report => !report.locale || 
enabledLocales.includes(report.locale))};
+    manifest.snapshotHash = hash(JSON.stringify({pages, reports: 
manifest.reports}));
+    validateManifest(manifest);
+    return manifest;
+}
+
+function validateManifest(manifest) {
+    assert(manifest.schemaVersion === schemaVersion && 
/^[a-f0-9]{40}$/.test(manifest.sourceSha), 'Invalid manifest version/source 
SHA');
+    assert(new Set(manifest.enabledLocales).size === 
manifest.enabledLocales.length && 
manifest.enabledLocales.includes(manifest.locale), 'Invalid enabled locales');
+    const ids = new Set();
+    for (const page of manifest.pages) {
+        assert(['docs', 'page', 'blog', 'utility'].includes(page.kind) && 
page.contentId && !ids.has(page.contentId), 'Invalid/duplicate content 
identity');
+        ids.add(page.contentId);
+        assert(page.siteLocale === manifest.locale && page.sourceSha === 
manifest.sourceSha, 'Mixed source/locale manifest');
+        assert(isRoute(page.route) && isRoute(page.englishRoute), 'Invalid 
native route');
+        assert(typeof page.state === 'string' && typeof page.indexable === 
'boolean' && typeof page.fallback === 'boolean' && typeof page.renderIntegrated 
=== 'boolean', 'Invalid content decision');
+        assert(manifest.enabledLocales.includes(page.contentLocale) && 
(page.fallback ? page.contentLocale === 'en' && !page.indexable : 
page.contentLocale === page.siteLocale), 'Invalid fallback decision');
+        assert(page.kind !== 'utility' || !page.indexable, 'Utility must not 
be indexed');
+        assert(['automatic-docs', 'native-manual-docs', 
'manual'].includes(page.updatePolicy), 'Invalid update policy');
+        assert(Object.entries(page.navigationRoutes).every(([locale, route]) 
=> manifest.enabledLocales.includes(locale) && isRoute(route)), 
'Disabled/invalid navigation route');
+        assert(page.navigationRoutes[page.siteLocale] === page.route && 
page.navigationRoutes.en === page.englishRoute, 'Missing self/English 
navigation route');
+        assert(new Set(page.availableTranslations).size === 
page.availableTranslations.length && page.availableTranslations.every(locale => 
page.navigationRoutes[locale]), 'Invalid available translations');
+        assert(page.availableTranslations.includes(page.siteLocale) === 
(page.indexable && !page.fallback), 'Fallback advertised as a translation');
+    }
+    return manifest;
+}
+
+// Consumers request one content identity; the full registry never enters 
globalData.
+function routeData(page) {
+    return {schemaVersion, sourceSha: page.sourceSha, contentId: 
page.contentId,
+        siteLocale: page.siteLocale, contentLocale: page.contentLocale, 
fallback: page.fallback, renderIntegrated: page.renderIntegrated,
+        indexable: page.indexable, navigationRoutes: page.navigationRoutes, 
availableTranslations: page.availableTranslations};
+}
+module.exports = {schemaVersion, docsId, sourceSha, adaptDoc, buildManifest, 
validateManifest, routeData};
diff --git a/scripts/i18n/content-manifest.schema.json 
b/scripts/i18n/content-manifest.schema.json
new file mode 100644
index 000000000000..86bd762249aa
--- /dev/null
+++ b/scripts/i18n/content-manifest.schema.json
@@ -0,0 +1,141 @@
+{
+  "$schema": "http://json-schema.org/draft-07/schema#";,
+  "title": "Doris build content manifest v1",
+  "type": "object",
+  "required": [
+    "schemaVersion",
+    "sourceSha",
+    "snapshotHash",
+    "locale",
+    "enabledLocales",
+    "pages",
+    "reports"
+  ],
+  "properties": {
+    "schemaVersion": {
+      "const": 1
+    },
+    "sourceSha": {
+      "type": "string",
+      "pattern": "^[a-f0-9]{40}$"
+    },
+    "snapshotHash": {
+      "type": "string",
+      "pattern": "^[a-f0-9]{64}$"
+    },
+    "locale": {
+      "type": "string"
+    },
+    "enabledLocales": {
+      "type": "array",
+      "uniqueItems": true,
+      "items": {
+        "type": "string"
+      }
+    },
+    "pages": {
+      "type": "array",
+      "items": {
+        "type": "object",
+        "required": [
+          "contentId",
+          "kind",
+          "siteLocale",
+          "contentLocale",
+          "state",
+          "route",
+          "englishRoute",
+          "fallback",
+          "indexable",
+          "sourceSha",
+          "navigationRoutes",
+          "availableTranslations",
+          "updatePolicy",
+          "renderIntegrated"
+        ],
+        "properties": {
+          "contentId": {
+            "type": "string",
+            "minLength": 1
+          },
+          "kind": {
+            "enum": [
+              "docs",
+              "page",
+              "blog",
+              "utility"
+            ]
+          },
+          "siteLocale": {
+            "type": "string"
+          },
+          "contentLocale": {
+            "type": "string"
+          },
+          "state": {
+            "type": "string"
+          },
+          "route": {
+            "type": "string",
+            "pattern": "^/(?!/)"
+          },
+          "englishRoute": {
+            "type": "string",
+            "pattern": "^/(?!/)"
+          },
+          "fallback": {
+            "type": "boolean"
+          },
+          "indexable": {
+            "type": "boolean"
+          },
+          "sourceSha": {
+            "type": "string",
+            "pattern": "^[a-f0-9]{40}$"
+          },
+          "navigationRoutes": {
+            "type": "object",
+            "required": [
+              "en"
+            ],
+            "additionalProperties": {
+              "type": "string",
+              "pattern": "^/(?!/)"
+            }
+          },
+          "availableTranslations": {
+            "type": "array",
+            "uniqueItems": true,
+            "items": {
+              "type": "string"
+            }
+          },
+          "updatePolicy": {
+            "enum": [
+              "automatic-docs",
+              "native-manual-docs",
+              "manual"
+            ]
+          },
+          "renderIntegrated": {
+            "type": "boolean"
+          }
+        }
+      }
+    },
+    "reports": {
+      "type": "array",
+      "items": {
+        "type": "object",
+        "required": [
+          "kind"
+        ],
+        "properties": {
+          "kind": {
+            "type": "string"
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/scripts/i18n/docs-plugin.cjs b/scripts/i18n/docs-plugin.cjs
index 99bcd84d0efc..e332642699f1 100644
--- a/scripts/i18n/docs-plugin.cjs
+++ b/scripts/i18n/docs-plugin.cjs
@@ -3,7 +3,7 @@ const path = require('node:path');
 const native = require('@docusaurus/plugin-content-docs');
 const {readVersionsMetadata} = 
require('@docusaurus/plugin-content-docs/lib/versions/index.js');
 const {processDocMetadata} = 
require('@docusaurus/plugin-content-docs/lib/docs.js');
-const {Globby, getPluginI18nPath, normalizeUrl, readLastUpdateData, 
aliasedSitePath, resolveMarkdownLinkPathname} = require('@docusaurus/utils');
+const {Globby, getPluginI18nPath, readLastUpdateData, aliasedSitePath, 
resolveMarkdownLinkPathname} = require('@docusaurus/utils');
 const {applyTrailingSlash} = require('@docusaurus/utils-common');
 const {hash, relative, identity, loadConfig, managedLocales, loadRecords, 
sourceLocaleFor, decide} = require('./state.cjs');
 const {mergeAlternates} = require('./alternates.cjs');
@@ -11,15 +11,8 @@ const {structure, alignHeadings} = 
require('./structure.cjs');
 
 async function exists(file) { try { await fs.access(file); return true; } 
catch { return false; } }
 async function read(file) { return await exists(file) ? fs.readFile(file, 
'utf8') : undefined; }
-function localizedContext(context, locale) {
-    const defaultLocale = context.i18n.defaultLocale;
-    const rootBaseUrl = context.i18n.currentLocale === defaultLocale ? 
context.baseUrl
-        : context.baseUrl.replace(`/${context.i18n.currentLocale}/`, '/');
-    const baseUrl = locale === context.i18n.currentLocale ? context.baseUrl
-        : normalizeUrl([rootBaseUrl, locale === defaultLocale ? '' : locale, 
'/']);
-    return {...context, baseUrl, localizationDir: path.join(context.siteDir, 
'i18n', locale),
-        i18n: {...context.i18n, currentLocale: locale}};
-}
+const {localizedContext} = require('./native-context.cjs');
+const {adaptDoc, docsId} = require('./content-manifest.cjs');
 
 async function metadata(context, options, versionMetadata, source, filePath, 
content) {
     return processDocMetadata({context, options: {...options, 
showLastUpdateTime: false, showLastUpdateAuthor: false},
@@ -32,10 +25,15 @@ async function metadata(context, options, versionMetadata, 
source, filePath, con
 // to its generated view and adds translation state.
 module.exports = async function translationDocsPlugin(context, options) {
     // Managed locales come from the translation configuration, restricted to 
the
-    // locales this build serves. With none of them active the adapter is inert
-    // and the native plugin runs untouched.
+    // locales this build serves. Without managed locales the native render 
path
+    // stays untouched, while English/Chinese identities still enter the 
registry.
+    const metadataCache = new Map();
+    const readMetadata = (ctx, opts, version, source, file, content) => {
+        const key = JSON.stringify([ctx.i18n.currentLocale, 
version.versionName, file, hash(content)]);
+        if (!metadataCache.has(key)) metadataCache.set(key, metadata(ctx, 
opts, version, source, file, content));
+        return metadataCache.get(key);
+    };
     const managed = managedLocales(context.siteDir, context.i18n.locales);
-    if (!managed.length) return native.default(context, options);
     const settings = loadConfig(context.siteDir).locales;
     const records = Object.fromEntries(managed.map(locale => [locale, 
loadRecords(context.siteDir, locale)]));
     // Source locales are always present; a managed locale that repeats one of
@@ -58,6 +56,7 @@ module.exports = async function 
translationDocsPlugin(context, options) {
         if (await exists(originalPluginDir(activeLocale))) await 
fs.cp(originalPluginDir(activeLocale), viewPluginDir(activeLocale), {recursive: 
true});
     }
     const pages = [];
+    const sourceEntries = [];
     const reports = [];
     const route = permalink => applyTrailingSlash(permalink, {trailingSlash: 
context.siteConfig.trailingSlash, baseUrl: context.baseUrl});
     const versionInputs = await Promise.all(versions.en.map(async 
(englishVersion, index) => {
@@ -65,6 +64,35 @@ module.exports = async function 
translationDocsPlugin(context, options) {
         return {index, englishVersion, filenames, registered: new 
Set(filenames),
             versionDir: englishVersion.versionName === 'current' ? 'current' : 
`version-${englishVersion.versionName}`};
     }));
+    // Native source metadata also exists when all optional locales are 
disabled.
+    // It supplies real Chinese slugs and per-file fallback, independent of 
docs state.
+    for (const {index, englishVersion, filenames} of versionInputs) {
+        for (const filename of filenames) {
+            const englishFile = path.join(englishVersion.contentPath, 
filename);
+            const englishContent = await fs.readFile(englishFile, 'utf8');
+            const englishDoc = await readMetadata(contexts.en, options, 
englishVersion, filename, englishFile, englishContent);
+            if (englishDoc.draft) continue;
+            for (const locale of ['en', 'zh-CN'].filter(lang => 
context.i18n.locales.includes(lang))) {
+                const file = locale === 'en' ? englishFile : 
path.join(versions[locale][index].contentPathLocalized, filename);
+                const content = locale === 'en' ? englishContent : await 
read(file);
+                const doc = locale === 'en' ? englishDoc : await 
readMetadata(contexts[locale], options, versions[locale][index], filename,
+                    content === undefined ? englishFile : file, content ?? 
englishContent);
+                if (doc.draft) continue;
+                if (doc.id !== englishDoc.id) {
+                    reports.push({kind: 'source_identity_conflict', pluginId: 
options.id, version: englishVersion.versionName,
+                        sourceLocale: locale, docId: doc.id, englishDocId: 
englishDoc.id, sourcePath: relative(context.siteDir, file)});
+                    continue;
+                }
+                sourceEntries.push({contentId: docsId({pluginId: options.id, 
version: englishVersion.versionName, docId: englishDoc.id}),
+                    kind: 'docs', renderIntegrated: true, pluginId: 
options.id, version: englishVersion.versionName, docId: englishDoc.id, 
siteLocale: locale,
+                    contentLocale: content === undefined ? 'en' : locale, 
state: content === undefined ? 'untranslated' : 'source',
+                    fallback: content === undefined, indexable: content !== 
undefined && !doc.unlisted && !versions[locale][index].noIndex,
+                    route: route(doc.permalink), englishRoute: 
route(englishDoc.permalink),
+                    englishPath: relative(context.siteDir, englishFile), 
sourcePath: relative(context.siteDir, content === undefined ? englishFile : 
file),
+                    sourceHash: hash(content ?? englishContent), targetHash: 
null, updatePolicy: 'native-manual-docs'});
+            }
+        }
+    }
     for (const locale of managed) {
     for (const {index, englishVersion, versionDir, filenames, registered} of 
versionInputs) {
         const targetDir = path.join(originalPluginDir(locale), versionDir);
@@ -72,7 +100,7 @@ module.exports = async function 
translationDocsPlugin(context, options) {
         for (const filename of filenames) {
             const englishFile = path.join(englishVersion.contentPath, 
filename);
             const englishContent = await fs.readFile(englishFile, 'utf8');
-            const englishDoc = await metadata(contexts.en, options, 
englishVersion, filename, englishFile, englishContent);
+            const englishDoc = await readMetadata(contexts.en, options, 
englishVersion, filename, englishFile, englishContent);
             seenIds.add(englishDoc.id);
             const page = {pluginId: options.id, version: 
englishVersion.versionName, docId: englishDoc.id, locale};
             const requestedSourceLocale = sourceLocaleFor(settings[locale], 
page);
@@ -96,12 +124,12 @@ module.exports = async function 
translationDocsPlugin(context, options) {
             }
             // Parse all counterparts with the framework, including custom IDs,
             // number prefixes and localized slugs. Never invent a missing 
node.
-            const chineseDoc = await metadata(contexts['zh-CN'], options, 
versions['zh-CN'][index], filename,
+            const chineseDoc = await readMetadata(contexts['zh-CN'], options, 
versions['zh-CN'][index], filename,
                 chineseContent === undefined ? englishFile : chineseFile, 
chineseContent ?? englishContent);
             const nativeSourceDoc = sourceLocale === 'en' ? englishDoc : 
chineseDoc;
             let localizedContent;
             if (state.useTarget) {
-                const targetDoc = await metadata(contexts[locale], options, 
versions[locale][index], filename, targetFile, target).catch(() => null);
+                const targetDoc = await readMetadata(contexts[locale], 
options, versions[locale][index], filename, targetFile, target).catch(() => 
null);
                 if (!targetDoc || targetDoc.id !== englishDoc.id || 
targetDoc.slug !== englishDoc.slug || targetDoc.draft !== englishDoc.draft
                     || targetDoc.unlisted !== englishDoc.unlisted) {
                     state = {...state, status: 'state_conflict', useTarget: 
false, indexable: false, contentLocale: 'en'};
@@ -119,7 +147,7 @@ module.exports = async function 
translationDocsPlugin(context, options) {
                 reports.push({kind: 'source_divergence', ...page, reason: 
'source_route_conflict'});
             }
             const selectedContent = state.useTarget ? localizedContent : 
englishContent;
-            const localizedDoc = await metadata(contexts[locale], options, 
versions[locale][index], filename, targetFile, selectedContent);
+            const localizedDoc = await readMetadata(contexts[locale], options, 
versions[locale][index], filename, targetFile, selectedContent);
             const indexable = state.indexable && !localizedDoc.draft && 
!localizedDoc.unlisted && !versions[locale][index].noIndex;
             state = {...state, indexable, draft: localizedDoc.draft, unlisted: 
localizedDoc.unlisted,
                 permalink: route(localizedDoc.permalink), englishPermalink: 
route(englishDoc.permalink),
@@ -201,11 +229,12 @@ module.exports = async function 
translationDocsPlugin(context, options) {
                     }
                 }
             }
-            return {...content, translationManifest: {schemaVersion: 1, 
pluginId: options.id, locale: context.i18n.currentLocale, pages, reports}};
+            return {...content, contentIdentity: {entries: [...sourceEntries, 
...pages.filter(page => !page.draft).map(adaptDoc)], reports},
+                ...(managed.length && {translationManifest: {schemaVersion: 1, 
pluginId: options.id, locale: context.i18n.currentLocale, pages, reports}})};
         },
         translateContent({content, translationFiles}) {
             // Docusaurus's native translateLoadedContent returns only 
versions.
-            return {...plugin.translateContent({content, translationFiles}), 
translationManifest: content.translationManifest};
+            return {...plugin.translateContent({content, translationFiles}), 
translationManifest: content.translationManifest, contentIdentity: 
content.contentIdentity};
         },
         async contentLoaded(args) {
             await plugin.contentLoaded(args);
diff --git a/scripts/i18n/manual-plugins.cjs b/scripts/i18n/manual-plugins.cjs
new file mode 100644
index 000000000000..c28b959920a7
--- /dev/null
+++ b/scripts/i18n/manual-plugins.cjs
@@ -0,0 +1,102 @@
+// Read routes from native plugins. Rendering, blog lifecycle and SEO remain
+// native until the page/blog consumers adopt the contract in T03/T14.
+const path = require('node:path');
+const {Globby, getPluginI18nPath} = require('@docusaurus/utils');
+const {applyTrailingSlash} = require('@docusaurus/utils-common');
+const {relative} = require('./state.cjs');
+const {readJson, loadManual, decideManual} = require('./manual-state.cjs');
+const {localizedContext} = require('./native-context.cjs');
+
+function wrap(kind, native) {
+    async function adapter(context, options) {
+        const plugin = await native.default(context, options);
+        return {...plugin,
+            getPathsToWatch() {return [...plugin.getPathsToWatch(), 
path.join(context.siteDir, `translations/${kind}/**/*.json`),
+                ...loadManual(context.siteDir, kind).flatMap(record => 
[...record.sourceFiles, ...record.targetFiles])
+                    .map(file => path.join(context.siteDir, file))];},
+            async loadContent() {
+                const snapshots = new Map();
+                for (const locale of context.i18n.locales) {
+                    const ctx = localizedContext(context, locale);
+                    const instance = locale === context.i18n.currentLocale ? 
plugin : await native.default(ctx, options);
+                    snapshots.set(locale, await instance.loadContent());
+                }
+                const records = loadManual(context.siteDir, kind);
+                const registry = readJson(context.siteDir, 
'translations/pages/registry.json', {schemaVersion: 1, pages: []});
+                if (registry.schemaVersion !== 1 || 
!Array.isArray(registry.pages)) throw new Error('Invalid page registry');
+                const registeredIds = new Set(), registeredPaths = new Set();
+                for (const page of registry.pages) {
+                    if (!page.id || !page.sourcePath || !['page', 
'utility'].includes(page.kind)
+                        || registeredIds.has(page.id) || 
registeredPaths.has(page.sourcePath)) throw new Error('Invalid/duplicate page 
registry identity');
+                    registeredIds.add(page.id); 
registeredPaths.add(page.sourcePath);
+                }
+                const report = [], entries = [];
+                const list = value => kind === 'blog' ? 
value?.blogPosts.map(post => post.metadata) ?? [] : value ?? [];
+                const route = permalink => applyTrailingSlash(permalink, 
{trailingSlash: context.siteConfig.trailingSlash, baseUrl: context.baseUrl});
+                function sourcePath(item, locale) {
+                    const source = path.resolve(context.siteDir, 
item.source.replace(/^@site\//, ''));
+                    const localized = getPluginI18nPath({localizationDir: 
path.join(context.siteDir, 'i18n', locale),
+                        pluginName: `docusaurus-plugin-content-${kind}`, 
pluginId: options.id});
+                    const base = source.startsWith(localized + path.sep) ? 
localized : path.resolve(context.siteDir, options.path);
+                    return relative(base, source);
+                }
+                const english = new Map(list(snapshots.get('en')).map(item => 
[sourcePath(item, 'en'), item]));
+                const locales = new Map([...snapshots].map(([locale, value]) 
=> [locale,
+                    new Map(list(value).map(item => [sourcePath(item, locale), 
item]))]));
+                const known = new Set();
+                for (const [file, source] of english) {
+                    const englishPath = relative(context.siteDir, 
path.resolve(context.siteDir, options.path, file));
+                    const registration = kind === 'pages' ? 
registry.pages.find(page => page.sourcePath === englishPath) : undefined;
+                    const contentId = kind === 'blog' ? `blog:${options.id === 
'default' ? '' : options.id + ':'}${file}`
+                        : `${registration?.kind ?? 'page'}:${registration?.id 
?? englishPath}`;
+                    known.add(contentId);
+                    if (kind === 'pages' && !registration) report.push({kind: 
'unregistered_page_identity', contentId, englishPath});
+                    for (const locale of context.i18n.locales) {
+                        const item = locales.get(locale).get(file);
+                        if (!item) {report.push({kind: 'missing_native_route', 
contentId, locale}); continue;}
+                        const record = records.find(page => page.contentId === 
contentId && page.locale === locale);
+                        if (record && 
(!record.sourceFiles.includes(englishPath) || (kind === 'blog'
+                            && 
!record.targetFiles.includes(`i18n/${locale}/docusaurus-plugin-content-blog${options.id
 === 'default' ? '' : '-' + options.id}/${file}`)))) {
+                            throw new Error(`Manual state does not cover 
native source/target: ${contentId}/${locale}`);
+                        }
+                        const decision = decideManual({root: context.siteDir, 
record: locale === 'en' ? undefined : record, sourceFiles: [englishPath]});
+                        if (locale === 'en') Object.assign(decision, {state: 
'source', contentLocale: 'en', fallback: false, indexable: true});
+                        if (decision.state === 'needs-update') 
report.push({kind: 'manual_source_changed', contentId, locale,
+                            sourceHash: decision.sourceHash, 
publishedSourceHash: record.sourceHash});
+                        const utility = registration?.kind === 'utility';
+                        entries.push({contentId, kind: utility ? 'utility' : 
kind === 'pages' ? 'page' : 'blog',
+                            pluginId: options.id, englishPath, 
renderIntegrated: locale === 'en', siteLocale: locale, englishRoute: 
route(source.permalink), route: route(item.permalink),
+                            ...decision, indexable: !utility && !item.unlisted 
&& decision.indexable, updatePolicy: 'manual'});
+                    }
+                }
+                for (const [locale, items] of locales) {
+                    for (const file of items.keys()) if (!english.has(file)) 
report.push({kind: 'orphan_target', locale, targetPath: file});
+                    if (locale === 'en') continue;
+                    const ctx = localizedContext(context, locale);
+                    const dir = getPluginI18nPath({localizationDir: 
ctx.localizationDir, pluginName: `docusaurus-plugin-content-${kind}`, pluginId: 
options.id});
+                    for (const file of await Globby(options.include, {cwd: 
dir, ignore: options.exclude})) {
+                        if (!english.has(file)) report.push({kind: 
'orphan_target_file', locale, targetPath: relative(context.siteDir, 
path.join(dir, file))});
+                    }
+                }
+                if (kind === 'pages') {
+                    const englishPaths = new Set(entries.map(entry => 
entry.englishPath));
+                    for (const page of registry.pages) if 
(!englishPaths.has(page.sourcePath)) report.push({
+                        kind: 'missing_english_identity', contentId: 
`${page.kind}:${page.id}`, englishPath: page.sourcePath});
+                }
+                for (const record of records) if 
(!known.has(record.contentId)) report.push({kind: 'source_deleted', ...record});
+                const content = snapshots.get(context.i18n.currentLocale);
+                if (content) content.contentIdentity = {entries, reports: 
report};
+                return content;
+            },
+            ...(plugin.translateContent && {translateContent(args) {
+                const result = plugin.translateContent(args);
+                result.contentIdentity = args.content.contentIdentity;
+                return result;
+            }}),
+        };
+    }
+    adapter.validateOptions = native.validateOptions;
+    return adapter;
+}
+module.exports = {pages: wrap('pages', 
require('@docusaurus/plugin-content-pages')),
+    blog: wrap('blog', require('@docusaurus/plugin-content-blog'))};
diff --git a/scripts/i18n/manual-state.cjs b/scripts/i18n/manual-state.cjs
new file mode 100644
index 000000000000..f1e8508c123a
--- /dev/null
+++ b/scripts/i18n/manual-state.cjs
@@ -0,0 +1,45 @@
+const fs = require('node:fs');
+const path = require('node:path');
+const assert = require('node:assert/strict');
+const {hash} = require('./state.cjs');
+const safePath = value => typeof value === 'string' && value && 
!path.isAbsolute(value) && !value.split(/[\\/]/).includes('..');
+function readJson(root, file, fallback) {
+    const full = path.join(root, file);
+    return fs.existsSync(full) ? JSON.parse(fs.readFileSync(full, 'utf8')) : 
fallback;
+}
+function loadManual(root, kind) {
+    const data = readJson(root, `translations/${kind}/state.json`, 
{schemaVersion: 1, pages: []});
+    assert(data.schemaVersion === 1 && Array.isArray(data.pages), `Invalid 
manual ${kind} state`);
+    const seen = new Set();
+    for (const page of data.pages) {
+        const key = JSON.stringify([page.contentId, page.locale]);
+        assert(page.contentId && page.locale !== 'en' && page.locale && 
!seen.has(key), 'Invalid/duplicate manual identity');
+        assert(['missing', 'ready', 'needs-update', 
'blocked'].includes(page.state), 'Invalid manual state');
+        assert(Array.isArray(page.sourceFiles) && page.sourceFiles.length && 
page.sourceFiles.every(safePath), 'Manual source dependencies required');
+        assert(Array.isArray(page.targetFiles) && 
page.targetFiles.every(safePath), 'Invalid manual target dependencies');
+        if (['ready', 'needs-update'].includes(page.state)) {
+            assert(page.targetFiles.length && 
/^[a-f0-9]{64}$/.test(page.sourceHash) && 
/^[a-f0-9]{64}$/.test(page.targetHash), 'Published manual content needs both 
digests');
+            assert(['machine', 'human', 
'unverified'].includes(page.reviewStatus), 'Manual review identity required');
+        }
+        seen.add(key);
+    }
+    return data.pages;
+}
+function digestFiles(root, files) {
+    return hash(JSON.stringify([...new Set(files)].sort().map(file => {
+        assert(safePath(file), `Invalid dependency: ${file}`);
+        return [file, hash(fs.readFileSync(path.join(root, file), 'utf8'))];
+    })));
+}
+function decideManual({root, record, sourceFiles, targetFiles = []}) {
+    const sourceHash = digestFiles(root, record?.sourceFiles ?? sourceFiles);
+    if (!record || ['missing', 'blocked'].includes(record.state)) return 
{state: record?.state ?? 'missing', sourceHash,
+        targetHash: null, contentLocale: 'en', fallback: true, indexable: 
false, reviewStatus: record?.reviewStatus ?? 'unverified'};
+    const targetHash = digestFiles(root, record.targetFiles ?? targetFiles);
+    // A broken/edited target needs an explicit new publication decision. Do 
not
+    // silently mask invalid translations with English during a release build.
+    assert(record.targetHash === targetHash, `Manual target digest mismatch: 
${record.contentId}/${record.locale}`);
+    return {state: record.sourceHash === sourceHash ? record.state : 
'needs-update', sourceHash, targetHash,
+        contentLocale: record.locale, fallback: false, indexable: true, 
reviewStatus: record.reviewStatus};
+}
+module.exports = {readJson, loadManual, digestFiles, decideManual};
diff --git a/scripts/i18n/native-context.cjs b/scripts/i18n/native-context.cjs
new file mode 100644
index 000000000000..356da738716d
--- /dev/null
+++ b/scripts/i18n/native-context.cjs
@@ -0,0 +1,12 @@
+const path = require('node:path');
+const {normalizeUrl} = require('@docusaurus/utils');
+function localizedContext(context, locale) {
+    const defaultLocale = context.i18n.defaultLocale;
+    const rootBaseUrl = context.i18n.currentLocale === defaultLocale ? 
context.baseUrl
+        : context.baseUrl.replace(`/${context.i18n.currentLocale}/`, '/');
+    const baseUrl = locale === context.i18n.currentLocale ? context.baseUrl
+        : normalizeUrl([rootBaseUrl, locale === defaultLocale ? '' : locale, 
'/']);
+    return {...context, baseUrl, siteConfig: {...context.siteConfig, baseUrl}, 
localizationDir: path.join(context.siteDir, 'i18n', locale),
+        i18n: {...context.i18n, currentLocale: locale}};
+}
+module.exports = {localizedContext};
diff --git a/scripts/i18n/status-plugin.cjs b/scripts/i18n/status-plugin.cjs
index c60d03713bf3..192f9bfbe7f1 100644
--- a/scripts/i18n/status-plugin.cjs
+++ b/scripts/i18n/status-plugin.cjs
@@ -2,8 +2,9 @@ const fs = require('node:fs/promises');
 const path = require('node:path');
 const {managedLocales} = require('./state.cjs');
 const {mergeAlternates} = require('./alternates.cjs');
+const {sourceSha, buildManifest, routeData} = 
require('./content-manifest.cjs');
 
-module.exports = function translationStatusPlugin(context) {
+module.exports = function translationStatusPlugin(context, {browserData = 
true} = {}) {
     const locale = context.i18n.currentLocale;
     // The browser needs to know which locales carry translation state, so the
     // UI can decide from data instead of naming a language.
@@ -34,7 +35,25 @@ module.exports = function translationStatusPlugin(context) {
             // localized documentation, search, or other plugin-owned UI.
             const pageRoutes = [...new 
Set(Object.values(allContent['docusaurus-plugin-content-pages'] ?? {})
                 .flatMap(content => content ?? []).map(page => 
page.permalink.replace(/\/$/, '') + '/'))];
-            actions.setGlobalData({alternates, prefixes, locales, pageRoutes});
+            if (browserData) actions.setGlobalData({alternates, prefixes, 
locales, pageRoutes});
+            const inputs = Object.values(allContent).flatMap(instances => 
Object.values(instances))
+                .map(content => content?.contentIdentity).filter(Boolean);
+            if (inputs.length) {
+                const manifest = buildManifest({sourceSha: 
sourceSha(context.siteDir), locale,
+                    enabledLocales: context.i18n.locales, entries: 
inputs.flatMap(input => input.entries),
+                    reports: inputs.flatMap(input => input.reports)});
+                const directory = path.join(context.siteDir, 
'.i18n-build/content-manifests');
+                await fs.mkdir(directory, {recursive: true});
+                const serialized = JSON.stringify(manifest, null, 2) + '\n';
+                await fs.writeFile(path.join(directory, `${locale}.json`), 
serialized);
+                // A separate per-page payload is available to T02/T03 
consumers;
+                // no full content registry or source digests are put in 
globalData.
+                const sizes = manifest.pages.map(page => ({contentId: 
page.contentId,
+                    bytes: 
Buffer.byteLength(JSON.stringify(routeData(page)))}));
+                await fs.writeFile(path.join(directory, 
`${locale}.sizes.json`), JSON.stringify({
+                    sourceSha: manifest.sourceSha, fullBytes: 
Buffer.byteLength(serialized), pages: sizes,
+                    maxRouteBytes: Math.max(0, ...sizes.map(page => 
page.bytes)), clientGlobalBytesAdded: 0}, null, 2) + '\n');
+            }
             // Reports that belong to no single locale describe the repository,
             // so they stay in every locale's manifest.
             const reports = manifests.flatMap(manifest => manifest.reports)
diff --git a/scripts/i18n/verify-content-fixture.cjs 
b/scripts/i18n/verify-content-fixture.cjs
new file mode 100644
index 000000000000..ccf874697eeb
--- /dev/null
+++ b/scripts/i18n/verify-content-fixture.cjs
@@ -0,0 +1,63 @@
+// Real sequential builds: five languages, then Russian disabled. The manifests
+// come from plugin loadContent output, never from hand-written result JSON.
+const assert = require('node:assert/strict');
+const fs = require('node:fs/promises');
+const path = require('node:path');
+const {spawnSync, execFileSync} = require('node:child_process');
+const {hash} = require('./state.cjs');
+const cheerio = require('cheerio');
+const {createContentFixture} = 
require('./__tests__/fixtures/content-manifest.cjs');
+const {validateManifest, sourceSha, routeData} = 
require('./content-manifest.cjs');
+const root = path.resolve(__dirname, '../..');
+async function main() {
+    const implementation = () => ({sha: sourceSha(root), trackedDiffHash: 
hash(execFileSync('git', ['diff', 'HEAD', '--binary'], {cwd: root, encoding: 
'utf8'}))});
+    const initialImplementation = implementation();
+    const results = [];
+    for (const disabled of [false, true]) {
+        const fixture = path.join(root, '.i18n-build', disabled ? 
'content-disabled-fixture' : 'content-fixture');
+        await fs.rm(fixture, {recursive: true, force: true});
+        await createContentFixture(fixture);
+        const result = spawnSync(process.execPath, [path.join(root, 
'scripts/build-locales.cjs'), '--site-dir', fixture],
+            {stdio: 'inherit', env: {...process.env, FIXTURE_DISABLE_RU: 
disabled ? 'true' : '', NODE_OPTIONS: '--max-old-space-size=4096'}});
+        assert.equal(result.status, 0, 'Content fixture build failed');
+        const locales = ['en', 'zh-CN', 'ja', 'ko', ...(disabled ? [] : 
['ru'])];
+        const manifests = [];
+        for (const locale of locales) {
+            const manifest = validateManifest(JSON.parse(await 
fs.readFile(path.join(fixture, '.i18n-build/content-manifests', locale + 
'.json'), 'utf8')));
+            assert.equal(manifest.sourceSha, sourceSha(fixture));
+            let checked = 0;
+            for (const page of manifest.pages) {
+                for (const route of Object.values(page.navigationRoutes)) 
await fs.access(path.join(fixture, 'build', decodeURIComponent(route), 
'index.html'));
+                if (disabled) assert(!page.navigationRoutes.ru && 
!page.availableTranslations.includes('ru'));
+                const html = await fs.readFile(path.join(fixture, 'build', 
decodeURIComponent(page.route), 'index.html'), 'utf8');
+                const $ = cheerio.load(html);
+                if (page.kind === 'docs' && ['ja', 'ko', 
'ru'].includes(locale)) assert.equal($('.theme-doc-markdown').attr('lang'), 
page.contentLocale);
+                if (page.contentId.startsWith('page:sample-')) {
+                    const i = page.contentId.slice(-1);
+                    assert($('main').text().includes(page.fallback || locale 
=== 'en' ? `English sample ${i}` : `${locale} translated sample ${i}`));
+                }
+                if (page.kind === 'blog') {
+                    const i = page.contentId.match(/01-0(\d)/)[1] - 1;
+                    assert($('article').text().includes(page.fallback || 
locale === 'en' ? `English article ${i}` : `${locale} translated article 
${i}`));
+                }
+                checked++;
+            }
+            const sizes = JSON.parse(await fs.readFile(path.join(fixture, 
'.i18n-build/content-manifests', locale + '.sizes.json'), 'utf8'));
+            assert(sizes.maxRouteBytes < 2048);
+            assert.equal(sizes.clientGlobalBytesAdded, 0);
+            const first = manifest.pages.find(page => page.contentId === 
'blog:2026-01-01-sample.md');
+            assert.deepEqual(first.availableTranslations, locales.filter(lang 
=> lang !== 'zh-CN'));
+            manifests.push({locale, checked, fullBytes: sizes.fullBytes, 
maxRouteBytes: sizes.maxRouteBytes,
+                fallback: manifest.pages.filter(page => page.fallback).length,
+                reportKinds: [...new Set(manifest.reports.map(report => 
report.kind))]});
+            // One page payload can be consumed independently of the full 
manifest.
+            assert.equal(routeData(first).contentId, first.contentId);
+        }
+        if (disabled) await assert.rejects(fs.access(path.join(fixture, 
'build/ru/index.html')));
+        assert.deepEqual(implementation(), initialImplementation, 
'Implementation changed during fixture builds');
+        results.push({disabled, fixture, sourceSha: sourceSha(fixture), 
implementation: initialImplementation, manifests});
+    }
+    await fs.writeFile(path.join(root, 
'.i18n-build/content-fixture-verification.json'), JSON.stringify(results, null, 
2) + '\n');
+    console.log(JSON.stringify(results, null, 2));
+}
+if (require.main === module) main().catch(error => {console.error(error); 
process.exitCode = 1;});
diff --git a/src/utils/translation.ts b/src/utils/translation.ts
index 2b51ef1a7b18..651e23f5fe9c 100644
--- a/src/utils/translation.ts
+++ b/src/utils/translation.ts
@@ -48,3 +48,26 @@ export function useTranslationRoutes() {
         managed: ordinaryPage || !!data?.prefixes.some(prefix => route === 
prefix || route.startsWith(prefix.replace(/\/$/, '') + '/')),
         pageFallback};
 }
+
+// T01 consumers accept one page's routing payload. The build-only registry
+// (source/target digests, state and reports) must not be imported by the 
browser.
+export type ContentRouteData = {
+    schemaVersion: 1;
+    renderIntegrated: boolean;
+    sourceSha: string;
+    contentId: string;
+    siteLocale: string;
+    contentLocale: string;
+    fallback: boolean;
+    indexable: boolean;
+    navigationRoutes: Record<string, string>;
+    availableTranslations: string[];
+};
+
+export function contentRouteGroups(page: ContentRouteData) {
+    return {
+        navigationRoutes: page.navigationRoutes,
+        indexableTranslations: Object.fromEntries(page.availableTranslations
+            .map(locale => [locale, page.navigationRoutes[locale]])),
+    };
+}
diff --git a/translations/CONTENT_MANIFEST.md b/translations/CONTENT_MANIFEST.md
new file mode 100644
index 000000000000..0714a8ff3818
--- /dev/null
+++ b/translations/CONTENT_MANIFEST.md
@@ -0,0 +1,133 @@
+# Content manifest v1
+
+The build contract joins native content identity, routes, translation 
availability,
+body language and indexing decisions. It does not replace `translations/state/`
+or change existing page rendering. The status plugin writes
+`.i18n-build/content-manifests/<locale>.json` and `<locale>.sizes.json`.
+The legacy `.i18n-build/manifests/<locale>.json` and its browser API remain 
intact.
+
+## Identity and routing
+
+| Kind | Stable identity | Route authority |
+| --- | --- | --- |
+| docs | `docs:` + JSON `[pluginId, version, docId]` | Native docs metadata, 
including localized Chinese slugs |
+| page | `page:<registry-id>` | Native pages metadata |
+| blog | `blog:<English-source-relative-path>` | Native blog metadata, 
including date/custom permalink |
+| utility | `utility:<registry-id>` | Native pages metadata; always excluded 
from indexing |
+
+`pages/registry.json` pins the 21 existing page IDs P01–P21 and the Slack 
utility.
+Keep the ID when moving a source file. Update its source path and redirects in 
the
+same change. Unregistered native pages get a source-path ID and an explicit 
report;
+assign a registry ID before adopting them into the stable public contract. 
Search
+and 404 belong to their native plugins; add their utility identities when those
+plugins expose route metadata in T05. They are not inferred from URL strings.
+Blog plugin instances other than `default` namespace their source paths with 
the
+plugin ID. An English source move needs a deliberate migration and redirects in
+T14. A localized-only file does not establish an English identity.
+
+Each page records `sourceSha` (the checkout's Git HEAD), `siteLocale`,
+`contentLocale`, `state`, `route`, `englishRoute`, `fallback`, `indexable` and 
an
+`updatePolicy`. All pages in an output share the source SHA. Source/target 
hashes
+bind the selected input bytes; `snapshotHash` binds the complete output 
decision
+set. Local uncommitted work still has the checkout's HEAD: retain the patch and
+its digest with validation evidence. The publishing builder also rejects 
tracked
+source changes between locale builds.
+
+`navigationRoutes` contains only enabled, native routes for the same identity,
+including English fallback routes. `availableTranslations` contains only
+indexable, non-fallback members. Draft docs are absent from this registry;
+unlisted/noIndex content remains navigable but is excluded from translations.
+Unknown identities and orphan targets are reported rather than assigned an
+invented counterpart. Chinese-only docs remain explicit unsupported source
+reports. Disabled languages are removed from routes, translation lists and
+locale-specific reports.
+
+## Rendering adoption
+
+`renderIntegrated` is true for the docs adapter and English sources. The manual
+page/blog decisions have `renderIntegrated: false` until T03/T14 connects their
+renderers. Those decisions describe declared publication availability, not an
+assertion that the existing renderer already applies whole-page fallback. Do 
not
+use pending manual decisions to change live SEO. In particular, the old seven
+marketing translations have not been promoted to `ready` by this task. Their
+manual audit and migration remain in T10. No translated content is generated 
here.
+
+## Independent update policies
+
+The docs adapter copies the existing state decision without changing its hash,
+technical-content, source override, review or ownership rules. `current`,
+`stale_prose`, conflicts and other states retain their existing meaning. 
Chinese
+manual docs keep native per-file selection. English fallback is never converted
+to `current` or `ready` by a compatibility reader.
+
+`pages/state.json` and `blog/state.json` contain `{schemaVersion: 1, pages: 
[]}`.
+Each manual record uses:
+
+```json
+{
+  "contentId": "page:P01",
+  "locale": "ja",
+  "state": "ready",
+  "sourceFiles": ["src/pages/index.tsx", "src/components/home/index.tsx"],
+  "targetFiles": ["i18n/ja/code.json"],
+  "messageNamespaces": ["homepage"],
+  "sourceHash": "<digestFiles result at publication>",
+  "targetHash": "<digestFiles result at publication>",
+  "reviewStatus": "unverified"
+}
+```
+
+This illustrates the shape; it is not a valid ready record until paths exist 
and
+both hashes are computed. Use `digestFiles(root, files)` from
+`scripts/i18n/manual-state.cjs`: it sorts unique relative paths, hashes each 
file
+with the existing CRLF normalization, then hashes the ordered path/hash pairs.
+Explicit dependencies must include the entry module, shared body components,
+English messages/data, target messages/data and resources that affect 
publication.
+The adapter requires the native English entry in `sourceFiles`; blog records 
must
+also include the native localized file in `targetFiles`. Whole dictionaries are
+valid dependencies but produce broader drift reports. `messageNamespaces` and
+reviewer metadata may document the publication unit; neither grants automatic
+ownership or human review. These records never enter the docs scheduler.
+
+| State / event | Selected body | Index policy |
+| --- | --- | --- |
+| no record / missing / blocked | English | Not indexable in the target locale 
|
+| ready, matching hashes | Published target | Indexable unless native metadata 
excludes it |
+| source hash changed / needs-update | Published target; report pending update 
| Preserve indexability |
+| missing dependency / changed target hash / invalid native metadata | Fail 
build | Require explicit repair or publication update |
+
+A source update must not overwrite the old `sourceHash` just to clear drift.
+Re-review, update the target as needed, then record both current digests and 
the
+actual review identity. A target edit requires a new explicit publication 
record;
+it cannot silently fall back. The initial empty ledgers deliberately make no 
new
+claims about translated body coverage. Complete blog route, draft, tag, author,
+feed, anchor and lifecycle validation remains in T14.
+
+## Consumers and validation
+
+`scripts/i18n/content-manifest.schema.json` describes the serialized shape.
+`validateManifest()` checks identity uniqueness, source/locale consistency,
+native routes, enabled-language closure and fallback/indexing invariants.
+`buildManifest()` joins all native inputs before producing the current locale.
+Reports cover deleted English sources, isolated targets, conflicting Chinese
+identities and unregistered page IDs. They do not delete files or change URLs.
+
+Full manifests are build artifacts. Do not import them into React or put them 
in
+`setGlobalData`. `routeData(page)` produces one compact payload;
+`ContentRouteData` and `contentRouteGroups()` in `src/utils/translation.ts` 
keep
+navigation and indexing consumers distinct. T02/T03 will attach only the 
current
+page or required family to its route. The current compatibility browser data is
+unchanged; source-only builds use `browserData: false`, preserving the previous
+absence of status globals when optional locales are disabled. The size report 
records full bytes, maximum per-page route bytes and
+zero added global payload bytes. Existing global alternate data can be retired
+when T02 consumers migrate.
+
+Run `node --test scripts/i18n/__tests__/content-manifest.test.cjs` for native
+metadata and state boundaries, then `node 
scripts/i18n/verify-content-fixture.cjs`
+for sequential five-language and disabled-Russian production builds. The 
fixture
+uses real docs (two versions/two plugins), React pages, native blog posts and a
+utility route; target coverage is zero/one/partial/full. It checks emitted 
HTML,
+route existence, body selection, source binding and payload sizes. This fixture
+does not certify full production publishing, page-wide SEO or blog lifecycle.
+CI selects the complete optimized five-language, all-active-version build for
+changes to the contract, adapters or manual ledgers, and archives size reports.
diff --git a/translations/blog/state.json b/translations/blog/state.json
new file mode 100644
index 000000000000..f2350ea88165
--- /dev/null
+++ b/translations/blog/state.json
@@ -0,0 +1,4 @@
+{
+  "schemaVersion": 1,
+  "pages": []
+}
diff --git a/translations/pages/registry.json b/translations/pages/registry.json
new file mode 100644
index 000000000000..86f3e3365bc1
--- /dev/null
+++ b/translations/pages/registry.json
@@ -0,0 +1,115 @@
+{
+  "schemaVersion": 1,
+  "pages": [
+    {
+      "id": "P01",
+      "kind": "page",
+      "sourcePath": "src/pages/index.tsx"
+    },
+    {
+      "id": "P02",
+      "kind": "page",
+      "sourcePath": "src/pages/why-doris/compare/index.tsx"
+    },
+    {
+      "id": "P03",
+      "kind": "page",
+      "sourcePath": "src/pages/why-doris/benchmarks/index.tsx"
+    },
+    {
+      "id": "P04",
+      "kind": "page",
+      "sourcePath": "src/pages/why-doris/key-features/index.tsx"
+    },
+    {
+      "id": "P05",
+      "kind": "page",
+      "sourcePath": "src/pages/why-doris/users/index.tsx"
+    },
+    {
+      "id": "P06",
+      "kind": "page",
+      "sourcePath": "src/pages/use-cases/customer-facing-analytics/index.tsx"
+    },
+    {
+      "id": "P07",
+      "kind": "page",
+      "sourcePath": "src/pages/use-cases/data-warehousing/index.tsx"
+    },
+    {
+      "id": "P08",
+      "kind": "page",
+      "sourcePath": "src/pages/use-cases/observability/index.tsx"
+    },
+    {
+      "id": "P09",
+      "kind": "page",
+      "sourcePath": "src/pages/use-cases/ai/index.tsx"
+    },
+    {
+      "id": "P10",
+      "kind": "page",
+      "sourcePath": "src/pages/download/index.tsx"
+    },
+    {
+      "id": "P11",
+      "kind": "page",
+      "sourcePath": "src/pages/brand-assets/index.tsx"
+    },
+    {
+      "id": "P12",
+      "kind": "page",
+      "sourcePath": "src/pages/events/index.tsx"
+    },
+    {
+      "id": "P13",
+      "kind": "page",
+      "sourcePath": "src/pages/community/roadmap/index.tsx"
+    },
+    {
+      "id": "P14",
+      "kind": "page",
+      "sourcePath": "src/pages/community-report/index.tsx"
+    },
+    {
+      "id": "P15",
+      "kind": "page",
+      "sourcePath": "src/pages/course/index.tsx"
+    },
+    {
+      "id": "P16",
+      "kind": "page",
+      "sourcePath": "src/pages/course/hackathon/a1-hybrid-search.tsx"
+    },
+    {
+      "id": "P17",
+      "kind": "page",
+      "sourcePath": "src/pages/course/hackathon/a2-mcp.tsx"
+    },
+    {
+      "id": "P18",
+      "kind": "page",
+      "sourcePath": "src/pages/course/hackathon/a3-log-search.tsx"
+    },
+    {
+      "id": "P19",
+      "kind": "page",
+      "sourcePath": "src/pages/course/hackathon/a4-agent-traces.tsx"
+    },
+    {
+      "id": "P20",
+      "kind": "page",
+      "sourcePath": "src/pages/course/hackathon/track-b.tsx"
+    },
+    {
+      "id": "P21",
+      "kind": "page",
+      "sourcePath": "src/pages/profile-analysis/index.tsx"
+    },
+    {
+      "id": "slack",
+      "kind": "utility",
+      "sourcePath": "src/pages/slack.tsx"
+    }
+  ]
+}
diff --git a/translations/pages/state.json b/translations/pages/state.json
new file mode 100644
index 000000000000..f2350ea88165
--- /dev/null
+++ b/translations/pages/state.json
@@ -0,0 +1,4 @@
+{
+  "schemaVersion": 1,
+  "pages": []
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to