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 072ace699d2 [feat](i18n) Publish Korean docs with correct fallback 
page SEO (#4361)
072ace699d2 is described below

commit 072ace699d23a2c10050650326dc7b867e1aacbf
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Sun Oct 18 21:29:37 2026 +0100

    [feat](i18n) Publish Korean docs with correct fallback page SEO (#4361)
    
    Korean documentation is ready behind an opt-in flag, but production
    deployment does not enable it. Untranslated ordinary pages also
    currently declare Korean or Japanese with their own canonical URLs and
    remain indexable, even when their content is English.
    
    This PR enables Korean in both deployment workflows and fixes
    ordinary-page fallback metadata before publication. Native Docusaurus
    page routes identify ordinary pages without capturing documentation,
    search, or other plugin-owned UI. English fallback pages declare
    English, use the English canonical/Open Graph URL, stay out of the
    sitemap with noindex, and are excluded from translated hreflang groups
    on both localized and source pages. Existing Japanese product-page
    translations retain their language and alternates. Metadata resets
    correctly during SPA navigation.
    
    The static verifier now checks ordinary-page metadata and source-page
    alternates in addition to document state. Build Check also runs the
    existing marketing-page test suite with the new regressions.
    
    Validation completed:
    
    - Node 20: 198 tests passed, zero failures, one existing Apache test
    skipped; scoped TypeScript check and git diff checks passed.
    - A native four-language fixture built in separate sequential processes.
    Strict static checks passed for Japanese and Korean, including ordinary
    fallbacks, translated Japanese pages, and full cross-language
    counterparts.
    - Chromium verified SSR/hydrated metadata, Korean fallback-to-document
    navigation, Japanese fallback-to-translated-page navigation, and back
    navigation, with no runtime errors.
    - The branch includes merged PR #4360, preserving its English benchmark
    entry, page-language controls, and full English footer.
    
    The complete four-language Dev optimized build passed in 410 seconds.
    Each managed locale passed checks for 1,810 document pages, 22 ordinary
    pages, and 1,721 native navigation routes with zero skipped
    counterparts. Korean and Japanese browser searches passed; Korean checks
    also covered English/Chinese source search, eight themes at
    desktop/mobile sizes, and the iframe theme bridge. Full-page browser
    smoke passed without runtime or local resource failures.
    All-active-version CI is still running. Production deployment and live
    URL acceptance remain pending. The merged Korean coverage is five
    machine translations out of 3,336 required documents; remaining
    documents use the existing same-version English fallback and Korean
    notices. T12 Korean product pages are still pending.
    
    Korean translation generation remains manual-only; this PR does not add
    a schedule. All enabled locales must build and pass strict verification
    before the existing OSS/ASF deployment steps. No document URLs,
    sidebars, translation state, theme colors, permissions, or secrets are
    changed.
    
    ---------
    
    Co-authored-by: morningman <[email protected]>
---
 .github/workflows/build-check.yml               |  1 +
 .github/workflows/cron-deploy-website.yml       |  1 +
 .github/workflows/manual-deploy-website.yml     |  1 +
 scripts/i18n/__tests__/locale-registry.test.cjs |  4 +-
 scripts/i18n/__tests__/marketing-pages.test.cjs | 80 ++++++++++++++++++++++---
 scripts/i18n/status-plugin.cjs                  |  8 ++-
 scripts/i18n/verify-static.cjs                  | 32 +++++++++-
 src/theme/SiteMetadata/index.tsx                | 18 ++++--
 src/utils/localization.ts                       |  3 +-
 src/utils/translation.ts                        | 10 +++-
 10 files changed, 136 insertions(+), 22 deletions(-)

diff --git a/.github/workflows/build-check.yml 
b/.github/workflows/build-check.yml
index dc6f3208719..7562b330357 100644
--- a/.github/workflows/build-check.yml
+++ b/.github/workflows/build-check.yml
@@ -108,6 +108,7 @@ jobs:
                   node --test 
scripts/i18n/__tests__/search-verification.test.cjs
                   node --test scripts/i18n/__tests__/korean-tokenizer.test.cjs
                   node --test scripts/i18n/__tests__/doc-navigation.test.cjs
+                  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__/inventory.test.cjs
diff --git a/.github/workflows/cron-deploy-website.yml 
b/.github/workflows/cron-deploy-website.yml
index b7b221b4f39..99845ddfecc 100644
--- a/.github/workflows/cron-deploy-website.yml
+++ b/.github/workflows/cron-deploy-website.yml
@@ -25,6 +25,7 @@ jobs:
         environment: Production
         env:
             DORIS_ENABLE_JA: 'true'
+            DORIS_ENABLE_KO: 'true'
         steps:
             - name: Checkout
               # v4 (the pinned @master commit predates the `filter` input) with
diff --git a/.github/workflows/manual-deploy-website.yml 
b/.github/workflows/manual-deploy-website.yml
index 0a40bda4915..cd65e786de5 100644
--- a/.github/workflows/manual-deploy-website.yml
+++ b/.github/workflows/manual-deploy-website.yml
@@ -21,6 +21,7 @@ jobs:
         environment: Production
         env:
             DORIS_ENABLE_JA: 'true'
+            DORIS_ENABLE_KO: 'true'
         steps:
             - name: Checkout
               uses: actions/checkout@master
diff --git a/scripts/i18n/__tests__/locale-registry.test.cjs 
b/scripts/i18n/__tests__/locale-registry.test.cjs
index 33c8ea46ace..190a03c425d 100644
--- a/scripts/i18n/__tests__/locale-registry.test.cjs
+++ b/scripts/i18n/__tests__/locale-registry.test.cjs
@@ -71,13 +71,15 @@ test('route groups span every managed locale while a 
manifest carries one', asyn
         default: {translationManifest: {pages: [page('ja', 'a'), page('ja', 
'b', false)], reports: [
                 {kind: 'unsupported_source_route', sourcePath: 
'i18n/zh-CN/x.md'},
                 {kind: 'source_deleted', locale: 'ja', docId: 'gone'}]}, 
loadedVersions: [{path: '/ja/docs/current'}]},
-    }};
+    }, 'docusaurus-plugin-content-pages': {default: [{permalink: '/ja/'}, 
{permalink: '/ja/download'}]}};
     for (const locale of ['en', 'ja']) {
         let globalData;
         const plugin = translationStatusPlugin({siteDir, i18n: {currentLocale: 
locale}});
         await plugin.allContentLoaded({allContent, actions: {setGlobalData: 
data => {globalData = data;}}});
         const manifest = JSON.parse(fs.readFileSync(path.join(siteDir, 
'.i18n-build/manifests', `${locale}.json`), 'utf8'));
         assert.equal(manifest.locale, locale);
+        assert.deepEqual(globalData.pageRoutes, ['/ja/', '/ja/download/']);
+        assert.deepEqual(manifest.pageRoutes, globalData.pageRoutes);
         // Both locales group the indexable page; the non-indexable one 
contributes none.
         assert.deepEqual(Object.keys(globalData.alternates).sort(),
             ['/docs/current/a/', '/ja/docs/current/a/', 
'/zh-CN/docs/current/a/']);
diff --git a/scripts/i18n/__tests__/marketing-pages.test.cjs 
b/scripts/i18n/__tests__/marketing-pages.test.cjs
index 543c9dc3748..d35944a789e 100644
--- a/scripts/i18n/__tests__/marketing-pages.test.cjs
+++ b/scripts/i18n/__tests__/marketing-pages.test.cjs
@@ -12,20 +12,34 @@ const {generate} = require('../../key-features/generate');
 const root = path.resolve(__dirname, '../../..');
 const pagePaths = localeRegistry.find(locale => locale.code === 
'ja').translatedPages;
 
-function modules(locale, pathname, enabled = true) {
+function modules(locale, pathname, enabled = true, translatedPages = {ja: 
pagePaths}) {
     const cache = new Map();
-    const i18n = {currentLocale: locale, defaultLocale: 'en', locales: enabled 
? ['en', 'zh-CN', 'ja'] : ['en', 'zh-CN'],
-        localeConfigs: Object.fromEntries(localeRegistry.map(record => 
[record.code, record]))};
+    const locales = enabled ? ['en', 'zh-CN', 'ja', 'ko'] : ['en', 'zh-CN'];
+    const i18n = {currentLocale: locale, defaultLocale: 'en', locales,
+        localeConfigs: Object.fromEntries(localeRegistry.filter(record => 
locales.includes(record.code)).map(record => [record.code, record]))};
+    const config = {url: 'https://doris.apache.org', trailingSlash: true, 
baseUrl: locale === 'en' ? '/' : `/${locale}/`,
+        customFields: {defaultLocalePageLocales: enabled ? ['ja','ko'] : [], 
translatedPagePaths: enabled ? translatedPages : {}}};
+    const prefix = locale === 'en' ? '' : `/${locale}`;
     const location = {pathname, search: '?from=test', hash: '#tech'};
     const imports = {
         '@generated/i18n': i18n,
-        '@generated/docusaurus.config': {baseUrl: locale === 'en' ? '/' : 
`/${locale}/`,
-            customFields: {defaultLocalePageLocales: ['ja'], 
translatedPagePaths: enabled ? {ja: pagePaths} : {}}},
+        '@generated/docusaurus.config': config,
         '@docusaurus/Translate': {translate: ({message}) => message},
-        '@docusaurus/useDocusaurusContext': () => ({i18n}),
+        '@docusaurus/useDocusaurusContext': () => ({i18n, siteConfig: config}),
         '@docusaurus/useBaseUrl': source => source,
-        '@docusaurus/useGlobalData': () => ({}),
+        '@docusaurus/useGlobalData': () => ({'doris-translation-status': 
{default: {
+            alternates: {}, locales: enabled ? ['ja','ko'] : [], prefixes: 
[`${prefix}/docs/dev`,`${prefix}/community`],
+            pageRoutes: ['/', '/download/', '/community-report/', 
...pagePaths.map(page => `${page}/`)].map(page => prefix + page),
+        }}}),
         '@docusaurus/router': {useLocation: () => location},
+        '@docusaurus/Head': ({children}) => 
React.createElement(React.Fragment, null, children),
+        '@docusaurus/theme-common': {useThemeConfig: () => ({metadata: []})},
+        '@docusaurus/theme-common/internal': {DEFAULT_SEARCH_TAG: 'default', 
keyboardFocusedClassName: 'keyboard',
+            useAlternatePageUtils: () => ({createUrl: ({locale, 
fullyQualified}) => {
+                const plain = pathname.replace(/^\/(en|zh-CN|ja|ko)(?=\/|$)/, 
'') || '/';
+                return (fullyQualified ? config.url : '') + (locale === 'en' ? 
'' : `/${locale}`) + plain;
+            }})},
+        '@theme/SearchMetadata': () => null,
         '@docusaurus/Link': React.forwardRef(({to, href, autoAddBaseUrl = 
true, children, ...props}, ref) => {
             const target = (to || href).replace(/^pathname:\/\//, '');
             const baseUrl = locale === 'en' ? '' : `/${locale}`;
@@ -60,6 +74,58 @@ function modules(locale, pathname, enabled = true) {
     return load;
 }
 
+function metadata(locale, pathname, enabled = true, translatedPages) {
+    const {default: SiteMetadata} = modules(locale, pathname, enabled, 
translatedPages)('src/theme/SiteMetadata/index.tsx');
+    return 
require('cheerio').load(renderToStaticMarkup(React.createElement(SiteMetadata)));
+}
+
+test('ordinary English fallbacks declare English and stay out of translated 
SEO groups', () => {
+    for (const locale of ['ja','ko']) for (const page of ['/', '/download/', 
'/community-report/']) {
+        const $ = metadata(locale, `/${locale}${page}`);
+        assert.equal($('html').attr('lang'), 'en-US');
+        assert.equal($('link[rel="canonical"]').attr('href'), 
`https://doris.apache.org${page}`);
+        assert.equal($('meta[property="og:url"]').attr('content'), 
`https://doris.apache.org${page}`);
+        assert.equal($('meta[property="og:locale"]').attr('content'), 'en_US');
+        assert.match($('meta[name="robots"]').attr('content'), /noindex/);
+        assert.equal($('link[hreflang="ja"],link[hreflang="ko"]').length, 0);
+    }
+    for (const [locale, pathname, lang] of [['en','/','en-US'], 
['zh-CN','/zh-CN/','zh-Hans-CN']]) {
+        const $ = metadata(locale, pathname);
+        assert.equal($('html').attr('lang'), lang);
+        assert.equal($('meta[name="robots"]').length, 0);
+        assert.equal($('link[hreflang="ja"],link[hreflang="ko"]').length, 0);
+    }
+    
assert.equal(modules('ko','/ko')('src/utils/localization.ts').defaultLocalePath('/ko'),
 '/');
+});
+
+test('translated pages keep their SEO while an untranslated Korean counterpart 
points to English', () => {
+    const pathname = '/why-doris/compare/';
+    const ja = metadata('ja', `/ja${pathname}`);
+    assert.equal(ja('html').attr('lang'), 'ja');
+    assert.equal(ja('meta[name="robots"]').length, 0);
+    assert.equal(ja('link[rel="canonical"]').attr('href'), 
`https://doris.apache.org/ja${pathname}`);
+    const ko = metadata('ko', `/ko${pathname}`);
+    assert.equal(ko('html').attr('lang'), 'en-US');
+    assert.equal(ko('link[rel="canonical"]').attr('href'), 
`https://doris.apache.org${pathname}`);
+    assert.match(ko('meta[name="robots"]').attr('content'), /noindex/);
+    assert.equal(ko('link[hreflang="ko"]').length, 0);
+    assert.equal(ko('link[hreflang="ja"]').attr('href'), 
`https://doris.apache.org/ja${pathname}`);
+    const translated = metadata('ko', `/ko${pathname}`, true, {ja: pagePaths, 
ko: ['/why-doris/compare']});
+    assert.equal(translated('html').attr('lang'), 'ko');
+    assert.equal(translated('meta[name="robots"]').length, 0);
+    assert.equal(translated('link[hreflang="ko"]').length, 1);
+});
+
+test('native page identification excludes documentation, search and generated 
error routes', () => {
+    for (const pathname of ['/ko/docs/dev/guide/', 
'/ko/community/join-community/', '/ko/search/', '/ko/404.html']) {
+        const routes = modules('ko', 
pathname)('src/utils/translation.ts').useTranslationRoutes();
+        assert.equal(routes.pageFallback, false, pathname);
+        assert.equal(metadata('ko', pathname)('html').attr('lang'), 'ko');
+    }
+    const disabled = metadata('en', '/', false);
+    assert.equal(disabled('link[hreflang="ja"],link[hreflang="ko"]').length, 
0);
+});
+
 test('Japanese links retain translated pages and suffixes while other ordinary 
pages use English', () => {
     const {localizedHomeLink, hasTranslatedPage} = modules('ja', 
'/')('src/utils/localization.ts');
     assert.equal(pagePaths.length, 7);
diff --git a/scripts/i18n/status-plugin.cjs b/scripts/i18n/status-plugin.cjs
index ca92fb057cb..3303086b2f2 100644
--- a/scripts/i18n/status-plugin.cjs
+++ b/scripts/i18n/status-plugin.cjs
@@ -30,7 +30,11 @@ module.exports = function translationStatusPlugin(context) {
             const pages = allPages.filter(page => page.locale === locale);
             const prefixes = 
Object.values(allContent['docusaurus-plugin-content-docs'] ?? {})
                 .flatMap(content => content.loadedVersions.map(version => 
version.path));
-            actions.setGlobalData({alternates, prefixes, locales});
+            // Use native page routes so ordinary-page fallback never captures
+            // 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});
             // 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)
@@ -38,7 +42,7 @@ module.exports = function translationStatusPlugin(context) {
             const output = path.join(context.siteDir, '.i18n-build/manifests', 
`${locale}.json`);
             await fs.mkdir(path.dirname(output), {recursive: true});
             await fs.writeFile(output, JSON.stringify({schemaVersion: 1, 
locale,
-                pages, reports}, null, 2) + '\n');
+                pages, pageRoutes, reports}, null, 2) + '\n');
         },
     };
 };
diff --git a/scripts/i18n/verify-static.cjs b/scripts/i18n/verify-static.cjs
index 56d74e217ec..6d91baafdab 100644
--- a/scripts/i18n/verify-static.cjs
+++ b/scripts/i18n/verify-static.cjs
@@ -92,7 +92,35 @@ function verify({site, manifest, partial = false}) {
         }
         verified++;
     }
-    return {verified, skipped, partial, indexedPaths: indexedPaths.size,
+    const ordinaryPages = {verified: 0, fallback: 0};
+    const record = localeRegistry.find(item => item.code === locale);
+    for (const permalink of data.pageRoutes ?? []) {
+        const filename = path.join(site, decodeURIComponent(permalink), 
'index.html');
+        assert(fs.existsSync(filename), `Missing ordinary page: ${permalink}`);
+        const $ = cheerio.load(fs.readFileSync(filename, 'utf8'));
+        const source = permalink.startsWith(`/${locale}/`) ? 
permalink.slice(locale.length + 1) : permalink;
+        const fallback = record?.ordinaryPages === false && 
!record.translatedPages?.includes(source.replace(/\/+$/, ''));
+        if (fallback) {
+            assert.equal($('html').attr('lang'), htmlLangFor('en'), `Fallback 
page language: ${permalink}`);
+            assert.equal($('link[rel="canonical"]').length, 1, `Duplicate 
ordinary canonical: ${permalink}`);
+            assert.equal($('link[rel="canonical"]').attr('href'), 
`https://doris.apache.org${source}`);
+            assert.equal($('meta[property="og:url"]').attr('content'), 
`https://doris.apache.org${source}`);
+            assert.equal($('meta[property="og:locale"]').attr('content'), 
'en_US');
+            assert(($('meta[name="robots"]').attr('content') ?? 
'').includes('noindex'), `Indexed English fallback: ${permalink}`);
+            assert(!sitemapPaths.has(permalink), `Ordinary fallback in 
sitemap: ${permalink}`);
+            
assert.equal($(`link[rel="alternate"][hreflang="${htmlLang}"]`).length, 0, 
`Ordinary fallback alternate: ${permalink}`);
+            for (const sourceRoute of [source, `/zh-CN${source}`]) {
+                const source$ = counterpart(sourceRoute, 'ordinary fallback 
alternates');
+                if (source$) 
assert.equal(source$(`link[rel="alternate"][hreflang="${htmlLang}"]`).length, 0,
+                    `Source advertises ordinary fallback: ${sourceRoute}`);
+            }
+            ordinaryPages.fallback++;
+        } else {
+            assert.equal($('html').attr('lang'), htmlLang, `Translated 
ordinary page language: ${permalink}`);
+        }
+        ordinaryPages.verified++;
+    }
+    return {verified, skipped, partial, indexedPaths: indexedPaths.size, 
ordinaryPages,
         states: Object.fromEntries([...new Set(data.pages.map(page => 
page.status))]
             .map(status => [status, data.pages.filter(page => page.status === 
status).length])), reports: data.reports};
 }
@@ -104,6 +132,6 @@ if (require.main === module) {
         partial: args.includes('--partial')});
     if (args.includes('--output')) fs.writeFileSync(option('--output'), 
JSON.stringify(result, null, 2) + '\n');
     console.log(JSON.stringify({verified: result.verified, skipped: 
result.skipped, partial: result.partial,
-        states: result.states, reports: result.reports.length}));
+        states: result.states, ordinaryPages: result.ordinaryPages, reports: 
result.reports.length}));
 }
 module.exports = {verify};
diff --git a/src/theme/SiteMetadata/index.tsx b/src/theme/SiteMetadata/index.tsx
index e34214e5a03..b9568ed1988 100644
--- a/src/theme/SiteMetadata/index.tsx
+++ b/src/theme/SiteMetadata/index.tsx
@@ -8,7 +8,7 @@
 import React, {type JSX} from 'react';
 import Head from '@docusaurus/Head';
 import {useTranslationRoutes} from '@site/src/utils/translation';
-import {isDefaultLocaleBlogRoute} from '@site/src/utils/localization';
+import {defaultLocalePath, isDefaultLocaleBlogRoute} from 
'@site/src/utils/localization';
 import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
 import useBaseUrl from '@docusaurus/useBaseUrl';
 import {PageMetadata, useThemeConfig} from '@docusaurus/theme-common';
@@ -31,11 +31,11 @@ function AlternateLangHeaders(): JSX.Element {
     i18n: {currentLocale, defaultLocale, localeConfigs},
   } = useDocusaurusContext();
   const alternatePageUtils = useAlternatePageUtils();
-  const {group, managed, translatedLocales} = useTranslationRoutes();
+  const {group, managed, translatedLocales, pageFallback} = 
useTranslationRoutes();
   const availableLocales = Object.entries(localeConfigs).filter(([locale]) =>
     group ? !!group[locale] : !managed || !translatedLocales.includes(locale));
 
-  const currentHtmlLang = localeConfigs[currentLocale]!.htmlLang;
+  const currentHtmlLang = localeConfigs[pageFallback ? defaultLocale : 
currentLocale]!.htmlLang;
 
   // HTML lang is a BCP 47 tag, but the Open Graph protocol requires
   // using underscores instead of dashes.
@@ -123,9 +123,10 @@ function CanonicalUrlHeaders({permalink}: {permalink?: 
string}) {
 
 export default function SiteMetadata(): JSX.Element {
   const {
-    i18n: {currentLocale},
+    i18n: {currentLocale, defaultLocale, localeConfigs},
   } = useDocusaurusContext();
   const {pathname} = useLocation();
+  const {pageFallback} = useTranslationRoutes();
 
   // TODO maybe move these 2 themeConfig to siteConfig?
   // These seems useful for other themes as well
@@ -134,6 +135,7 @@ export default function SiteMetadata(): JSX.Element {
   return (
     <>
       <Head>
+        <html lang={localeConfigs[pageFallback ? defaultLocale : 
currentLocale]!.htmlLang} />
         <meta name="twitter:card" content="summary_large_image" />
         {/* The keyboard focus class name need to be applied when SSR so links
         are outlined when JS is disabled */}
@@ -142,7 +144,13 @@ export default function SiteMetadata(): JSX.Element {
 
       {defaultImage && <PageMetadata image={defaultImage} />}
 
-      <CanonicalUrlHeaders />
+      <CanonicalUrlHeaders permalink={pageFallback ? 
defaultLocalePath(pathname) : undefined} />
+
+      {pageFallback && (
+        <Head>
+          <meta name="robots" content="noindex, follow" />
+        </Head>
+      )}
 
       {/* A locale without a translated blog serves the default locale's
           articles under its own /blog routes. The section stays browsable, but
diff --git a/src/utils/localization.ts b/src/utils/localization.ts
index 7e3b339d7e8..1184fcaef6e 100644
--- a/src/utils/localization.ts
+++ b/src/utils/localization.ts
@@ -61,8 +61,7 @@ export function isDefaultLocaleBlogRoute(pathname: string): 
boolean {
  * so the page can point search engines at the copy worth ranking.
  */
 export function defaultLocalePath(permalink: string): string {
-    const prefix = `/${i18n.currentLocale}/`;
-    const path = permalink.startsWith(prefix) ? 
`/${permalink.slice(prefix.length)}` : permalink;
+    const path = normalizePathname(permalink, [i18n.currentLocale]);
     // Permalinks carry no trailing slash; the served URLs do.
     return applyTrailingSlash(path, { trailingSlash: siteConfig.trailingSlash, 
baseUrl: siteConfig.baseUrl });
 }
diff --git a/src/utils/translation.ts b/src/utils/translation.ts
index faa3dd2f32b..2b51ef1a7b1 100644
--- a/src/utils/translation.ts
+++ b/src/utils/translation.ts
@@ -1,7 +1,7 @@
 import useGlobalData from '@docusaurus/useGlobalData';
 import {useLocation} from '@docusaurus/router';
 import i18n from '@generated/i18n';
-import {hasTranslatedPage, translatedPagePaths} from './localization';
+import {hasTranslatedPage, translatedPagePaths, usesDefaultLocalePages} from 
'./localization';
 import {normalizePathname} from './locale';
 
 export type TranslationState = {
@@ -22,19 +22,22 @@ export function docTranslation(metadata: unknown): 
TranslationState | undefined
     return (metadata as {translation?: TranslationState}).translation;
 }
 
-type StatusData = {alternates: Record<string, Record<string, string>>; 
prefixes: string[]; locales?: string[]};
+type StatusData = {alternates: Record<string, Record<string, string>>; 
prefixes: string[]; locales?: string[]; pageRoutes?: string[]};
 export function useTranslationRoutes() {
     const data = useGlobalData()['doris-translation-status']?.default as 
StatusData | undefined;
     const {pathname} = useLocation();
     const route = pathname.replace(/\/$/, '') + '/';
     const pagePath = normalizePathname(pathname, i18n.locales).replace(/\/+$/, 
'');
     const pageLocales = Object.keys(translatedPagePaths).filter(locale => 
hasTranslatedPage(pagePath, locale));
+    const ordinaryPage = data?.pageRoutes?.includes(route) ?? false;
+    const pageFallback = ordinaryPage && usesDefaultLocalePages() && 
!hasTranslatedPage(pathname);
     if (pageLocales.length) {
         return {
             group: Object.fromEntries([i18n.defaultLocale, 
...pageLocales].map(locale => [locale,
                 `${locale === i18n.defaultLocale ? '' : 
`/${locale}`}${pagePath}/`])),
             translatedLocales: data?.locales ?? [],
             managed: true,
+            pageFallback,
         };
     }
     return {group: data?.alternates[route],
@@ -42,5 +45,6 @@ export function useTranslationRoutes() {
         // group must not offer them as an alternate, because the target page
         // would only repeat the default locale's content.
         translatedLocales: data?.locales ?? [],
-        managed: !!data?.prefixes.some(prefix => route === prefix || 
route.startsWith(prefix.replace(/\/$/, '') + '/'))};
+        managed: ordinaryPage || !!data?.prefixes.some(prefix => route === 
prefix || route.startsWith(prefix.replace(/\/$/, '') + '/')),
+        pageFallback};
 }


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

Reply via email to