From aba5eb259ff3414724b988b4a0ea8bad46c38c30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denny=20Mor=C3=A1n?= Date: Wed, 11 Mar 2026 01:54:08 -0500 Subject: [PATCH 1/5] fix(content-docs): translate generated-index category titles in pagination links When a category with a generated-index link appears as a previous/next pagination item, its title was not being translated. This happened because doc navigation data was computed before sidebar translations were applied, and translateVersion only updated sidebar labels without propagating changes to docs' previous/next navigation links. Added translateDocNavigation that builds a permalink-to-translated-label map from the already-translated sidebars and updates pagination titles for generated-index categories. Closes #8118 --- .../src/__tests__/translations.test.ts | 72 +++++++++++++++++++ .../src/translations.ts | 47 +++++++++++- 2 files changed, 118 insertions(+), 1 deletion(-) diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/translations.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/translations.test.ts index 87485078fad5..3678f2f7f41b 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/translations.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/translations.test.ts @@ -331,4 +331,76 @@ describe('translateLoadedContent', () => { translateLoadedContent(SampleLoadedContent, translationFiles), ).toMatchSnapshot(); }); + + it('translates pagination navigation titles for generated-index categories', () => { + const content: LoadedContent = { + loadedVersions: [ + createSampleVersion({ + versionName: CURRENT_VERSION_NAME, + docs: [ + createSampleDoc({ + id: 'doc1', + next: { + title: 'Getting started', + permalink: '/docs/category/getting-started-index-slug', + }, + }), + createSampleDoc({ + id: 'doc2', + previous: { + title: 'Getting started', + permalink: '/docs/category/getting-started-index-slug', + }, + next: { + title: 'doc3 title', + permalink: '/docs/doc3', + }, + }), + createSampleDoc({ + id: 'doc3', + previous: { + title: 'doc2 title', + permalink: '/docs/doc2', + }, + }), + ], + }), + ], + }; + + const translationFiles = getLoadedContentTranslationFiles(content); + const translatedFiles = translationFiles.map((translationFile) => + updateTranslationFileMessages( + translationFile, + (message) => `${message} (translated)`, + ), + ); + + const translated = translateLoadedContent(content, translatedFiles); + const [doc1, doc2, doc3] = translated.loadedVersions[0]!.docs; + + // doc1.next points to a generated-index category => title should be translated + expect(doc1!.next).toEqual({ + title: 'Getting started (translated)', + permalink: '/docs/category/getting-started-index-slug', + }); + + // doc2.previous points to a generated-index category => title should be translated + expect(doc2!.previous).toEqual({ + title: 'Getting started (translated)', + permalink: '/docs/category/getting-started-index-slug', + }); + + // doc2.next points to a regular doc => title should NOT be changed + expect(doc2!.next).toEqual({ + title: 'doc3 title', + permalink: '/docs/doc3', + }); + + // doc3.previous points to a regular doc => title should NOT be changed + expect(doc3!.previous).toEqual({ + title: 'doc2 title', + permalink: '/docs/doc2', + }); + }); }); diff --git a/packages/docusaurus-plugin-content-docs/src/translations.ts b/packages/docusaurus-plugin-content-docs/src/translations.ts index ecea3252d438..277f10b0437f 100644 --- a/packages/docusaurus-plugin-content-docs/src/translations.ts +++ b/packages/docusaurus-plugin-content-docs/src/translations.ts @@ -269,16 +269,61 @@ function getVersionTranslationFiles(version: LoadedVersion): TranslationFile[] { }, ]; } +function translateDocNavigation( + docs: LoadedVersion['docs'], + translatedSidebars: Sidebars, +): LoadedVersion['docs'] { + // Build a map of permalink -> translated label for generated-index categories + const translatedLabelByPermalink = new Map(); + for (const sidebar of Object.values(translatedSidebars)) { + for (const category of collectSidebarCategories(sidebar)) { + if (category.link?.type === 'generated-index') { + translatedLabelByPermalink.set( + category.link.permalink, + category.label, + ); + } + } + } + + if (translatedLabelByPermalink.size === 0) { + return docs; + } + + return docs.map((doc) => { + const previous = + doc.previous && translatedLabelByPermalink.has(doc.previous.permalink) + ? { + ...doc.previous, + title: translatedLabelByPermalink.get(doc.previous.permalink)!, + } + : doc.previous; + const next = + doc.next && translatedLabelByPermalink.has(doc.next.permalink) + ? { + ...doc.next, + title: translatedLabelByPermalink.get(doc.next.permalink)!, + } + : doc.next; + if (previous === doc.previous && next === doc.next) { + return doc; + } + return {...doc, previous, next}; + }); +} + function translateVersion( version: LoadedVersion, translationFiles: {[fileName: string]: TranslationFile}, ): LoadedVersion { const versionTranslations = translationFiles[getVersionFileName(version.versionName)]!.content; + const translatedSidebars = translateSidebars(version, versionTranslations); return { ...version, label: versionTranslations['version.label']?.message ?? version.label, - sidebars: translateSidebars(version, versionTranslations), + sidebars: translatedSidebars, + docs: translateDocNavigation(version.docs, translatedSidebars), }; } From ac6d4b827f2009466af03af0e03107194d71ba3d Mon Sep 17 00:00:00 2001 From: sebastien Date: Wed, 11 Mar 2026 18:35:19 +0100 Subject: [PATCH 2/5] fix test types --- .../src/__tests__/translations.test.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/translations.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/translations.test.ts index 3678f2f7f41b..9d7d10afff3a 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/translations.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/translations.test.ts @@ -18,10 +18,13 @@ import type { } from '@docusaurus/plugin-content-docs'; import type {Sidebar} from '../sidebars/types'; -function createSampleDoc(doc: Pick): DocMetadata { +function createSampleDoc( + doc: Pick & Partial, +): DocMetadata { return { sourceDirName: '', draft: false, + unlisted: false, tags: [], editUrl: 'any', lastUpdatedAt: 0, @@ -50,6 +53,7 @@ function createSampleVersion( routePriority: undefined, sidebarFilePath: 'any', isLast: true, + noIndex: false, contentPath: 'any', contentPathLocalized: 'any', tagsPath: '/tags/', From 03f644412e834bb5e7f6940377b5e1e65dea6927 Mon Sep 17 00:00:00 2001 From: sebastien Date: Wed, 11 Mar 2026 18:38:21 +0100 Subject: [PATCH 3/5] add line breaks --- .../src/__tests__/translations.test.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/translations.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/translations.test.ts index 9d7d10afff3a..0f3b149af6b7 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/translations.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/translations.test.ts @@ -383,25 +383,29 @@ describe('translateLoadedContent', () => { const translated = translateLoadedContent(content, translatedFiles); const [doc1, doc2, doc3] = translated.loadedVersions[0]!.docs; - // doc1.next points to a generated-index category => title should be translated + // doc1.next points to a generated-index category + // => title should be translated expect(doc1!.next).toEqual({ title: 'Getting started (translated)', permalink: '/docs/category/getting-started-index-slug', }); - // doc2.previous points to a generated-index category => title should be translated + // doc2.previous points to a generated-index category + // => title should be translated expect(doc2!.previous).toEqual({ title: 'Getting started (translated)', permalink: '/docs/category/getting-started-index-slug', }); - // doc2.next points to a regular doc => title should NOT be changed + // doc2.next points to a regular doc + // => title should NOT be changed expect(doc2!.next).toEqual({ title: 'doc3 title', permalink: '/docs/doc3', }); - // doc3.previous points to a regular doc => title should NOT be changed + // doc3.previous points to a regular doc + // => title should NOT be changed expect(doc3!.previous).toEqual({ title: 'doc2 title', permalink: '/docs/doc2', From b0872516f05d0eb0af2b8e3e549b7cebac62d40f Mon Sep 17 00:00:00 2001 From: sebastien Date: Wed, 11 Mar 2026 18:46:56 +0100 Subject: [PATCH 4/5] add comments --- .../src/__tests__/translations.test.ts | 4 ++-- .../src/translations.ts | 11 +++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/translations.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/translations.test.ts index 0f3b149af6b7..7b5eddd68f06 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/translations.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/translations.test.ts @@ -398,14 +398,14 @@ describe('translateLoadedContent', () => { }); // doc2.next points to a regular doc - // => title should NOT be changed + // => title should NOT be changed, it's already translated from the i18n MDX expect(doc2!.next).toEqual({ title: 'doc3 title', permalink: '/docs/doc3', }); // doc3.previous points to a regular doc - // => title should NOT be changed + // => title should NOT be changed, it's already translated from the i18n MDX expect(doc3!.previous).toEqual({ title: 'doc2 title', permalink: '/docs/doc2', diff --git a/packages/docusaurus-plugin-content-docs/src/translations.ts b/packages/docusaurus-plugin-content-docs/src/translations.ts index 277f10b0437f..3230453a4a21 100644 --- a/packages/docusaurus-plugin-content-docs/src/translations.ts +++ b/packages/docusaurus-plugin-content-docs/src/translations.ts @@ -269,6 +269,12 @@ function getVersionTranslationFiles(version: LoadedVersion): TranslationFile[] { }, ]; } + +// TODO Docusaurus v4 or later +// this temporarily works, but it is not an ideal solution +// The docs navigation can be computed and shouldn't be part of LoadedVersion +// We need to derive the navigation from already translated content +// See https://github.com/facebook/docusaurus/pull/11794 function translateDocNavigation( docs: LoadedVersion['docs'], translatedSidebars: Sidebars, @@ -278,10 +284,7 @@ function translateDocNavigation( for (const sidebar of Object.values(translatedSidebars)) { for (const category of collectSidebarCategories(sidebar)) { if (category.link?.type === 'generated-index') { - translatedLabelByPermalink.set( - category.link.permalink, - category.label, - ); + translatedLabelByPermalink.set(category.link.permalink, category.label); } } } From 7109808474242ee91dccc4390fac016171fc4bd5 Mon Sep 17 00:00:00 2001 From: sebastien Date: Wed, 11 Mar 2026 18:53:32 +0100 Subject: [PATCH 5/5] fix snapshots --- .../__snapshots__/translations.test.ts.snap | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/translations.test.ts.snap b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/translations.test.ts.snap index 8d5f668a1b26..51541aadebad 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/translations.test.ts.snap +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/translations.test.ts.snap @@ -168,6 +168,7 @@ exports[`translateLoadedContent returns translated loaded content 1`] = ` "sourceDirName": "", "tags": [], "title": "doc1 title", + "unlisted": false, "version": "any", }, { @@ -188,6 +189,7 @@ exports[`translateLoadedContent returns translated loaded content 1`] = ` "sourceDirName": "", "tags": [], "title": "doc2 title", + "unlisted": false, "version": "any", }, { @@ -208,6 +210,7 @@ exports[`translateLoadedContent returns translated loaded content 1`] = ` "sourceDirName": "", "tags": [], "title": "doc3 title", + "unlisted": false, "version": "any", }, { @@ -228,6 +231,7 @@ exports[`translateLoadedContent returns translated loaded content 1`] = ` "sourceDirName": "", "tags": [], "title": "doc4 title", + "unlisted": false, "version": "any", }, { @@ -248,12 +252,14 @@ exports[`translateLoadedContent returns translated loaded content 1`] = ` "sourceDirName": "", "tags": [], "title": "doc5 title", + "unlisted": false, "version": "any", }, ], "drafts": [], "isLast": true, "label": "current label (translated)", + "noIndex": false, "path": "/docs/", "routePriority": undefined, "sidebarFilePath": "any", @@ -354,6 +360,7 @@ exports[`translateLoadedContent returns translated loaded content 1`] = ` "sourceDirName": "", "tags": [], "title": "doc1 title", + "unlisted": false, "version": "any", }, { @@ -374,6 +381,7 @@ exports[`translateLoadedContent returns translated loaded content 1`] = ` "sourceDirName": "", "tags": [], "title": "doc2 title", + "unlisted": false, "version": "any", }, { @@ -394,6 +402,7 @@ exports[`translateLoadedContent returns translated loaded content 1`] = ` "sourceDirName": "", "tags": [], "title": "doc3 title", + "unlisted": false, "version": "any", }, { @@ -414,6 +423,7 @@ exports[`translateLoadedContent returns translated loaded content 1`] = ` "sourceDirName": "", "tags": [], "title": "doc4 title", + "unlisted": false, "version": "any", }, { @@ -434,12 +444,14 @@ exports[`translateLoadedContent returns translated loaded content 1`] = ` "sourceDirName": "", "tags": [], "title": "doc5 title", + "unlisted": false, "version": "any", }, ], "drafts": [], "isLast": true, "label": "2.0.0 label (translated)", + "noIndex": false, "path": "/docs/", "routePriority": undefined, "sidebarFilePath": "any", @@ -540,6 +552,7 @@ exports[`translateLoadedContent returns translated loaded content 1`] = ` "sourceDirName": "", "tags": [], "title": "doc1 title", + "unlisted": false, "version": "any", }, { @@ -560,6 +573,7 @@ exports[`translateLoadedContent returns translated loaded content 1`] = ` "sourceDirName": "", "tags": [], "title": "doc2 title", + "unlisted": false, "version": "any", }, { @@ -580,6 +594,7 @@ exports[`translateLoadedContent returns translated loaded content 1`] = ` "sourceDirName": "", "tags": [], "title": "doc3 title", + "unlisted": false, "version": "any", }, { @@ -600,6 +615,7 @@ exports[`translateLoadedContent returns translated loaded content 1`] = ` "sourceDirName": "", "tags": [], "title": "doc4 title", + "unlisted": false, "version": "any", }, { @@ -620,12 +636,14 @@ exports[`translateLoadedContent returns translated loaded content 1`] = ` "sourceDirName": "", "tags": [], "title": "doc5 title", + "unlisted": false, "version": "any", }, ], "drafts": [], "isLast": true, "label": "1.0.0 label (translated)", + "noIndex": false, "path": "/docs/", "routePriority": undefined, "sidebarFilePath": "any",