From 968b2943fb842ea1e8f562469a5d914c1dfa5f0d Mon Sep 17 00:00:00 2001 From: Yue Yang Date: Sun, 8 Nov 2026 11:50:12 +0800 Subject: [PATCH] fix(theme): translate version dropdown items before/after Navbar translation only extracts and applies labels for `items`, so labels declared in `dropdownItemsBefore` and `dropdownItemsAfter` (used by the docs version dropdown) stay untranslated even when they are listed in `navbar.json`. Include both arrays in `flattenNavbarItems` and `translateNavbar`, and add regression tests for extraction, translation and isomorphism. --- .../src/__tests__/translations.test.ts | 36 +++++++++++++++++++ .../src/translations.ts | 25 +++++++++---- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/packages/docusaurus-theme-classic/src/__tests__/translations.test.ts b/packages/docusaurus-theme-classic/src/__tests__/translations.test.ts index 4322aae5e3c3..3268d5b37991 100644 --- a/packages/docusaurus-theme-classic/src/__tests__/translations.test.ts +++ b/packages/docusaurus-theme-classic/src/__tests__/translations.test.ts @@ -74,6 +74,20 @@ const ThemeConfigSampleSimpleFooter: ThemeConfig = { }, }; +const ThemeConfigSampleWithVersionDropdown = { + ...ThemeConfigSample, + navbar: { + ...ThemeConfigSample.navbar, + items: [ + { + type: 'docsVersionDropdown', + dropdownItemsBefore: [{label: 'Dropdown item before', to: '/before'}], + dropdownItemsAfter: [{label: 'Dropdown item after', to: '/after'}], + }, + ], + }, +} as unknown as ThemeConfig; + function getSampleTranslationFiles(themeConfig: ThemeConfig) { return getTranslationFiles({ themeConfig, @@ -118,6 +132,24 @@ describe('translateThemeConfig', () => { }), ).toMatchSnapshot(); }); + + it('translates the items of dropdownItemsBefore and dropdownItemsAfter', () => { + const translatedThemeConfig = translateThemeConfig({ + themeConfig: ThemeConfigSampleWithVersionDropdown, + translationFiles: getSampleTranslationFilesTranslated( + ThemeConfigSampleWithVersionDropdown, + ), + }); + + expect(translatedThemeConfig.navbar.items[0]).toMatchObject({ + dropdownItemsBefore: [ + {label: 'Dropdown item before (translated)', to: '/before'}, + ], + dropdownItemsAfter: [ + {label: 'Dropdown item after (translated)', to: '/after'}, + ], + }); + }); }); describe('getTranslationFiles and translateThemeConfig isomorphism', () => { @@ -137,6 +169,10 @@ describe('getTranslationFiles and translateThemeConfig isomorphism', () => { verifyIsomorphism(ThemeConfigSampleSimpleFooter); }); + it('is verified for sample with version dropdown items', () => { + verifyIsomorphism(ThemeConfigSampleWithVersionDropdown); + }); + // Undefined footer should not make the translation code crash // See https://github.com/facebook/docusaurus/issues/3936 it('is verified for sample without footer', () => { diff --git a/packages/docusaurus-theme-classic/src/translations.ts b/packages/docusaurus-theme-classic/src/translations.ts index 56258c86eece..b8b4346fd05c 100644 --- a/packages/docusaurus-theme-classic/src/translations.ts +++ b/packages/docusaurus-theme-classic/src/translations.ts @@ -21,7 +21,11 @@ function getNavbarTranslationFile(navbar: Navbar): TranslationFileContent { // TODO handle properly all the navbar item types here! function flattenNavbarItems(items: NavbarItem[]): NavbarItem[] { const subItems = items.flatMap((item) => { - const allSubItems = [item.items ?? []].flat(); + const allSubItems = [ + item.items ?? [], + (item.dropdownItemsBefore as NavbarItem[] | undefined) ?? [], + (item.dropdownItemsAfter as NavbarItem[] | undefined) ?? [], + ].flat(); return flattenNavbarItems(allSubItems); }); return [...items, ...subItems]; @@ -81,17 +85,26 @@ function translateNavbar( logo, // TODO handle properly all the navbar item types here! items: navbar.items.map((item) => { - const subItems = item.items?.map((subItem) => ({ - ...subItem, + const translateItem = (navbarItem: NavbarItem) => ({ + ...navbarItem, label: - navbarTranslations[`item.label.${subItem.label}`]?.message ?? - subItem.label, - })); + navbarTranslations[`item.label.${navbarItem.label}`]?.message ?? + navbarItem.label, + }); + const subItems = item.items?.map(translateItem); + const dropdownItemsBefore = ( + item.dropdownItemsBefore as NavbarItem[] | undefined + )?.map(translateItem); + const dropdownItemsAfter = ( + item.dropdownItemsAfter as NavbarItem[] | undefined + )?.map(translateItem); return { ...item, label: navbarTranslations[`item.label.${item.label}`]?.message ?? item.label, ...(subItems ? {items: subItems} : undefined), + ...(dropdownItemsBefore ? {dropdownItemsBefore} : undefined), + ...(dropdownItemsAfter ? {dropdownItemsAfter} : undefined), }; }), };