/** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ import _ from 'lodash'; import {mergeTranslations} from '@docusaurus/utils'; import type {TranslationFile, TranslationFileContent} from '@docusaurus/types'; import type { ThemeConfig, Navbar, NavbarItem, Footer, MultiColumnFooter, SimpleFooter, } from '@docusaurus/theme-common'; 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(); return flattenNavbarItems(allSubItems); }); return [...items, ...subItems]; } const allNavbarItems = flattenNavbarItems(navbar.items); const navbarItemsTranslations: TranslationFileContent = Object.fromEntries( allNavbarItems .filter((navbarItem) => navbarItem.label) .map((navbarItem) => [ `item.label.${navbarItem.label}`, { message: navbarItem.label!, description: `Navbar item with label ${navbarItem.label}`, }, ]), ); const titleTranslations: TranslationFileContent = navbar.title ? {title: {message: navbar.title, description: 'The title in the navbar'}} : {}; const logoAlt: TranslationFileContent = navbar.logo?.alt ? { 'logo.alt': { message: navbar.logo.alt, description: 'The alt text of navbar logo', }, } : {}; return mergeTranslations([ titleTranslations, logoAlt, navbarItemsTranslations, ]); } function translateNavbar( navbar: Navbar, navbarTranslations: TranslationFileContent | undefined, ): Navbar { if (!navbarTranslations) { return navbar; } const logo = navbar.logo ? { ...navbar.logo, alt: navbarTranslations[`logo.alt`]?.message ?? navbar.logo?.alt, } : undefined; return { ...navbar, title: navbarTranslations.title?.message ?? navbar.title, logo, // TODO handle properly all the navbar item types here! items: navbar.items.map((item) => { const subItems = item.items?.map((subItem) => ({ ...subItem, label: navbarTranslations[`item.label.${subItem.label}`]?.message ?? subItem.label, })); return { ...item, label: navbarTranslations[`item.label.${item.label}`]?.message ?? item.label, ...(subItems ? {items: subItems} : undefined), }; }), }; } function isMultiColumnFooterLinks( links: MultiColumnFooter['links'] | SimpleFooter['links'], ): links is MultiColumnFooter['links'] { return links.length > 0 && 'title' in links[0]!; } function getFooterTranslationFile(footer: Footer): TranslationFileContent { const footerLinkTitles: TranslationFileContent = Object.fromEntries( (isMultiColumnFooterLinks(footer.links) ? footer.links.filter((link) => link.title) : [] ).map((link) => [ `link.title.${link.title}`, { message: link.title!, description: `The title of the footer links column with title=${link.title} in the footer`, }, ]), ); const footerLinkLabels: TranslationFileContent = Object.fromEntries( (isMultiColumnFooterLinks(footer.links) ? footer.links.flatMap((link) => link.items).filter((link) => link.label) : footer.links.filter((link) => link.label) ).map((link) => [ `link.item.label.${link.label}`, { message: link.label!, description: `The label of footer link with label=${ link.label } linking to ${link.to ?? link.href}`, }, ]), ); const copyright: TranslationFileContent = footer.copyright ? { copyright: { message: footer.copyright, description: 'The footer copyright', }, } : {}; const logoAlt: TranslationFileContent = footer.logo?.alt ? { 'logo.alt': { message: footer.logo.alt, description: 'The alt text of footer logo', }, } : {}; return mergeTranslations([ footerLinkTitles, footerLinkLabels, copyright, logoAlt, ]); } function translateFooter( footer: Footer, footerTranslations: TranslationFileContent | undefined, ): Footer { if (!footerTranslations) { return footer; } const links = isMultiColumnFooterLinks(footer.links) ? footer.links.map((link) => ({ ...link, title: footerTranslations[`link.title.${link.title}`]?.message ?? link.title, items: link.items.map((linkItem) => ({ ...linkItem, label: footerTranslations[`link.item.label.${linkItem.label}`]?.message ?? linkItem.label, })), })) : footer.links.map((link) => ({ ...link, label: footerTranslations[`link.item.label.${link.label}`]?.message ?? link.label, })); const copyright = footerTranslations.copyright?.message ?? footer.copyright; const logo = footer.logo ? { ...footer.logo, alt: footerTranslations[`logo.alt`]?.message ?? footer.logo?.alt, } : undefined; return { ...footer, links, copyright, logo, }; } export function getTranslationFiles({ themeConfig, }: { themeConfig: ThemeConfig; }): TranslationFile[] { const translationFiles: (TranslationFile | undefined)[] = [ {path: 'navbar', content: getNavbarTranslationFile(themeConfig.navbar)}, themeConfig.footer ? { path: 'footer', content: getFooterTranslationFile(themeConfig.footer), } : undefined, ]; return translationFiles.filter(Boolean) as TranslationFile[]; } export function translateThemeConfig({ themeConfig, translationFiles, }: { // To make TS correctly figure out the contravariance in parameter. // In practice it's always normalized themeConfig: ThemeConfig; translationFiles: TranslationFile[]; }): ThemeConfig { const translationFilesMap: {[fileName: string]: TranslationFile} = _.keyBy( translationFiles, (f) => f.path, ); return { ...themeConfig, navbar: translateNavbar( themeConfig.navbar, translationFilesMap.navbar?.content, ), footer: themeConfig.footer ? translateFooter(themeConfig.footer, translationFilesMap.footer?.content) : undefined, }; }