From fc726695286177f6fd119e1340b303575f650ee6 Mon Sep 17 00:00:00 2001 From: sebastienlorber Date: Thu, 4 Jan 2024 18:19:54 +0100 Subject: [PATCH 1/8] Add support for locale extensions in pages plugin --- .../__fixtures__/website/docusaurus.config.js | 4 ++ .../website/src/pages/typescript.fr.tsx | 22 ++++++ .../__snapshots__/index.test.ts.snap | 2 +- .../src/index.ts | 69 +++++++++++++++---- .../_dogfooding/_pages tests/i18n/index.fr.md | 3 + .../_dogfooding/_pages tests/i18n/index.md | 3 + website/_dogfooding/_pages tests/index.mdx | 1 + 7 files changed, 90 insertions(+), 14 deletions(-) create mode 100644 packages/docusaurus-plugin-content-pages/src/__tests__/__fixtures__/website/src/pages/typescript.fr.tsx create mode 100644 website/_dogfooding/_pages tests/i18n/index.fr.md create mode 100644 website/_dogfooding/_pages tests/i18n/index.md diff --git a/packages/docusaurus-plugin-content-pages/src/__tests__/__fixtures__/website/docusaurus.config.js b/packages/docusaurus-plugin-content-pages/src/__tests__/__fixtures__/website/docusaurus.config.js index d048d2caf5a1..b6b686f517e5 100644 --- a/packages/docusaurus-plugin-content-pages/src/__tests__/__fixtures__/website/docusaurus.config.js +++ b/packages/docusaurus-plugin-content-pages/src/__tests__/__fixtures__/website/docusaurus.config.js @@ -11,6 +11,10 @@ module.exports = { url: 'https://your-docusaurus-site.example.com', baseUrl: '/', favicon: 'img/favicon.ico', + i18n: { + defaultLocale: 'en', + locales: ['en', 'fr'], + }, markdown: { parseFrontMatter: async (params) => { const result = await params.defaultParseFrontMatter(params); diff --git a/packages/docusaurus-plugin-content-pages/src/__tests__/__fixtures__/website/src/pages/typescript.fr.tsx b/packages/docusaurus-plugin-content-pages/src/__tests__/__fixtures__/website/src/pages/typescript.fr.tsx new file mode 100644 index 000000000000..96e86b6afd99 --- /dev/null +++ b/packages/docusaurus-plugin-content-pages/src/__tests__/__fixtures__/website/src/pages/typescript.fr.tsx @@ -0,0 +1,22 @@ +/** + * 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 React from 'react'; +import Head from '@docusaurus/Head'; + +export default class Home extends React.Component { + render() { + return ( +
+ + translated Hello + +
translated TypeScript...
+
+ ); + } +} diff --git a/packages/docusaurus-plugin-content-pages/src/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-plugin-content-pages/src/__tests__/__snapshots__/index.test.ts.snap index fc5fa2196778..f082b1028eec 100644 --- a/packages/docusaurus-plugin-content-pages/src/__tests__/__snapshots__/index.test.ts.snap +++ b/packages/docusaurus-plugin-content-pages/src/__tests__/__snapshots__/index.test.ts.snap @@ -69,7 +69,7 @@ exports[`docusaurus-plugin-content-pages loads simple pages with french translat }, { "permalink": "/fr/typescript", - "source": "@site/src/pages/typescript.tsx", + "source": "@site/src/pages/typescript.fr.tsx", "type": "jsx", }, { diff --git a/packages/docusaurus-plugin-content-pages/src/index.ts b/packages/docusaurus-plugin-content-pages/src/index.ts index a4707110f2b4..01b0d0d5cfdb 100644 --- a/packages/docusaurus-plugin-content-pages/src/index.ts +++ b/packages/docusaurus-plugin-content-pages/src/index.ts @@ -13,7 +13,6 @@ import { aliasedSitePath, docuHash, getPluginI18nPath, - getFolderContainingFile, addTrailingPathSeparator, Globby, createAbsoluteFilePathMatcher, @@ -22,6 +21,7 @@ import { parseMarkdownFile, isUnlisted, isDraft, + findAsyncSequential, } from '@docusaurus/utils'; import {validatePageFrontMatter} from './frontMatter'; @@ -38,6 +38,48 @@ export function getContentPathList(contentPaths: PagesContentPaths): string[] { return [contentPaths.contentPathLocalized, contentPaths.contentPath]; } +async function getLocalizedSource({ + relativeSource, + contentPaths, + locale, +}: { + relativeSource: string; + contentPaths: PagesContentPaths; + locale: string; +}) { + const {name, dir, ext} = path.parse(relativeSource); + + // Lookup in localized folder in priority + const possibleSources = getContentPathList(contentPaths).flatMap( + (contentPath) => [ + path.join(contentPath, dir, `${name}.${locale}${ext}`), + path.join(contentPath, relativeSource), + ], + ); + + const localizedSource = await findAsyncSequential( + possibleSources, + fs.pathExists, + ); + + if (!localizedSource) { + throw new Error('unexpected'); + } + + return localizedSource; +} + +function filterLocaleExtensionFiles( + files: string[], + locales: string[], +): string[] { + const localeExtensions = locales.map((locale) => `.${locale}`); + return files.filter((file) => { + const {name} = path.parse(file); + return !localeExtensions.includes(path.extname(name)); + }); +} + const isMarkdownSource = (source: string) => source.endsWith('.md') || source.endsWith('.mdx'); @@ -62,6 +104,14 @@ export default function pluginContentPages( ); const dataDir = path.join(pluginDataDirRoot, options.id ?? DEFAULT_PLUGIN_ID); + async function getPageFiles() { + const files = await Globby(options.include, { + cwd: contentPaths.contentPath, + ignore: options.exclude, + }); + return filterLocaleExtensionFiles(files, context.i18n.locales); + } + return { name: 'docusaurus-plugin-content-pages', @@ -73,28 +123,21 @@ export default function pluginContentPages( }, async loadContent() { - const {include} = options; - if (!(await fs.pathExists(contentPaths.contentPath))) { return null; } const {baseUrl} = siteConfig; - const pagesFiles = await Globby(include, { - cwd: contentPaths.contentPath, - ignore: options.exclude, - }); + const pagesFiles = await getPageFiles(); async function processPageSourceFile( relativeSource: string, ): Promise { - // Lookup in localized folder in priority - const contentPath = await getFolderContainingFile( - getContentPathList(contentPaths), + const source = await getLocalizedSource({ relativeSource, - ); - - const source = path.join(contentPath, relativeSource); + contentPaths, + locale: context.i18n.currentLocale, + }); const aliasedSourcePath = aliasedSitePath(source, siteDir); const permalink = normalizeUrl([ baseUrl, diff --git a/website/_dogfooding/_pages tests/i18n/index.fr.md b/website/_dogfooding/_pages tests/i18n/index.fr.md new file mode 100644 index 000000000000..4917f6b55fdd --- /dev/null +++ b/website/_dogfooding/_pages tests/i18n/index.fr.md @@ -0,0 +1,3 @@ +# Page i18n test + +French version diff --git a/website/_dogfooding/_pages tests/i18n/index.md b/website/_dogfooding/_pages tests/i18n/index.md new file mode 100644 index 000000000000..3d23cbe7b921 --- /dev/null +++ b/website/_dogfooding/_pages tests/i18n/index.md @@ -0,0 +1,3 @@ +# Page i18n test + +English version diff --git a/website/_dogfooding/_pages tests/index.mdx b/website/_dogfooding/_pages tests/index.mdx index 4f7c2889bff1..851eebb03835 100644 --- a/website/_dogfooding/_pages tests/index.mdx +++ b/website/_dogfooding/_pages tests/index.mdx @@ -26,6 +26,7 @@ import Readme from "../README.mdx" ### Other tests - [React 18](/tests/pages/react-18) +- [i18n](/tests/pages/i18n) - [Crash test](/tests/pages/crashTest) - [Code block tests](/tests/pages/code-block-tests) - [Link tests](/tests/pages/link-tests) From b4f95929cf6c9d02300c3cc4459c8ad2fbf0e8ce Mon Sep 17 00:00:00 2001 From: sebastienlorber Date: Thu, 4 Jan 2024 18:30:16 +0100 Subject: [PATCH 2/8] add docs and blog dogfood files --- website/_dogfooding/_blog tests/i18n/index.fr.md | 3 +++ website/_dogfooding/_blog tests/i18n/index.md | 3 +++ website/_dogfooding/_docs tests/tests/i18n/index.fr.md | 3 +++ website/_dogfooding/_docs tests/tests/i18n/index.md | 3 +++ 4 files changed, 12 insertions(+) create mode 100644 website/_dogfooding/_blog tests/i18n/index.fr.md create mode 100644 website/_dogfooding/_blog tests/i18n/index.md create mode 100644 website/_dogfooding/_docs tests/tests/i18n/index.fr.md create mode 100644 website/_dogfooding/_docs tests/tests/i18n/index.md diff --git a/website/_dogfooding/_blog tests/i18n/index.fr.md b/website/_dogfooding/_blog tests/i18n/index.fr.md new file mode 100644 index 000000000000..aa8904d60af2 --- /dev/null +++ b/website/_dogfooding/_blog tests/i18n/index.fr.md @@ -0,0 +1,3 @@ +# Blog i18n test + +French version diff --git a/website/_dogfooding/_blog tests/i18n/index.md b/website/_dogfooding/_blog tests/i18n/index.md new file mode 100644 index 000000000000..6f36a63e0036 --- /dev/null +++ b/website/_dogfooding/_blog tests/i18n/index.md @@ -0,0 +1,3 @@ +# Blog i18n test + +English version diff --git a/website/_dogfooding/_docs tests/tests/i18n/index.fr.md b/website/_dogfooding/_docs tests/tests/i18n/index.fr.md new file mode 100644 index 000000000000..eed021b827b1 --- /dev/null +++ b/website/_dogfooding/_docs tests/tests/i18n/index.fr.md @@ -0,0 +1,3 @@ +# Docs i18n test + +French version diff --git a/website/_dogfooding/_docs tests/tests/i18n/index.md b/website/_dogfooding/_docs tests/tests/i18n/index.md new file mode 100644 index 000000000000..e108fbddee42 --- /dev/null +++ b/website/_dogfooding/_docs tests/tests/i18n/index.md @@ -0,0 +1,3 @@ +# Docs i18n test + +English version From 87b9df54c880322c07e5a2be628befd8d853d9c5 Mon Sep 17 00:00:00 2001 From: sebastienlorber Date: Thu, 4 Jan 2024 18:33:35 +0100 Subject: [PATCH 3/8] add explicit blog post date --- website/_dogfooding/_blog tests/i18n/index.fr.md | 4 ++++ website/_dogfooding/_blog tests/i18n/index.md | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/website/_dogfooding/_blog tests/i18n/index.fr.md b/website/_dogfooding/_blog tests/i18n/index.fr.md index aa8904d60af2..f948b507458b 100644 --- a/website/_dogfooding/_blog tests/i18n/index.fr.md +++ b/website/_dogfooding/_blog tests/i18n/index.fr.md @@ -1,3 +1,7 @@ +--- +date: 2024-01-03 +--- + # Blog i18n test French version diff --git a/website/_dogfooding/_blog tests/i18n/index.md b/website/_dogfooding/_blog tests/i18n/index.md index 6f36a63e0036..06fcf1dafdfb 100644 --- a/website/_dogfooding/_blog tests/i18n/index.md +++ b/website/_dogfooding/_blog tests/i18n/index.md @@ -1,3 +1,7 @@ +--- +date: 2024-01-03 +--- + # Blog i18n test English version From 90b18951565d1c31be1214642dbb3d0fb5a56bb6 Mon Sep 17 00:00:00 2001 From: sebastienlorber Date: Thu, 4 Jan 2024 18:35:33 +0100 Subject: [PATCH 4/8] refactor PagesContentPaths type --- packages/docusaurus-plugin-content-pages/src/types.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/docusaurus-plugin-content-pages/src/types.ts b/packages/docusaurus-plugin-content-pages/src/types.ts index 2d11492cb04e..cec39777a595 100644 --- a/packages/docusaurus-plugin-content-pages/src/types.ts +++ b/packages/docusaurus-plugin-content-pages/src/types.ts @@ -5,7 +5,6 @@ * LICENSE file in the root directory of this source tree. */ -export type PagesContentPaths = { - contentPath: string; - contentPathLocalized: string; -}; +import type {ContentPaths} from '@docusaurus/utils'; + +export type PagesContentPaths = ContentPaths; From b49b4623890fe96fb9cddb84c1a3621b729a7b1a Mon Sep 17 00:00:00 2001 From: sebastienlorber Date: Thu, 4 Jan 2024 18:59:50 +0100 Subject: [PATCH 5/8] extract code to docusaurus utils --- .../src/index.ts | 52 ++---------- packages/docusaurus-utils/src/i18nUtils.ts | 82 +++++++++++++++++++ packages/docusaurus-utils/src/index.ts | 2 + 3 files changed, 91 insertions(+), 45 deletions(-) diff --git a/packages/docusaurus-plugin-content-pages/src/index.ts b/packages/docusaurus-plugin-content-pages/src/index.ts index 01b0d0d5cfdb..3b12b3c908d8 100644 --- a/packages/docusaurus-plugin-content-pages/src/index.ts +++ b/packages/docusaurus-plugin-content-pages/src/index.ts @@ -21,7 +21,8 @@ import { parseMarkdownFile, isUnlisted, isDraft, - findAsyncSequential, + getLocalizedSourcePath, + filterFilesWithLocaleExtension, } from '@docusaurus/utils'; import {validatePageFrontMatter} from './frontMatter'; @@ -38,48 +39,6 @@ export function getContentPathList(contentPaths: PagesContentPaths): string[] { return [contentPaths.contentPathLocalized, contentPaths.contentPath]; } -async function getLocalizedSource({ - relativeSource, - contentPaths, - locale, -}: { - relativeSource: string; - contentPaths: PagesContentPaths; - locale: string; -}) { - const {name, dir, ext} = path.parse(relativeSource); - - // Lookup in localized folder in priority - const possibleSources = getContentPathList(contentPaths).flatMap( - (contentPath) => [ - path.join(contentPath, dir, `${name}.${locale}${ext}`), - path.join(contentPath, relativeSource), - ], - ); - - const localizedSource = await findAsyncSequential( - possibleSources, - fs.pathExists, - ); - - if (!localizedSource) { - throw new Error('unexpected'); - } - - return localizedSource; -} - -function filterLocaleExtensionFiles( - files: string[], - locales: string[], -): string[] { - const localeExtensions = locales.map((locale) => `.${locale}`); - return files.filter((file) => { - const {name} = path.parse(file); - return !localeExtensions.includes(path.extname(name)); - }); -} - const isMarkdownSource = (source: string) => source.endsWith('.md') || source.endsWith('.mdx'); @@ -109,7 +68,10 @@ export default function pluginContentPages( cwd: contentPaths.contentPath, ignore: options.exclude, }); - return filterLocaleExtensionFiles(files, context.i18n.locales); + return filterFilesWithLocaleExtension({ + files, + locales: context.i18n.locales, + }); } return { @@ -133,7 +95,7 @@ export default function pluginContentPages( async function processPageSourceFile( relativeSource: string, ): Promise { - const source = await getLocalizedSource({ + const source = await getLocalizedSourcePath({ relativeSource, contentPaths, locale: context.i18n.currentLocale, diff --git a/packages/docusaurus-utils/src/i18nUtils.ts b/packages/docusaurus-utils/src/i18nUtils.ts index 15723648d72a..cac9e269ecc4 100644 --- a/packages/docusaurus-utils/src/i18nUtils.ts +++ b/packages/docusaurus-utils/src/i18nUtils.ts @@ -6,14 +6,17 @@ */ import path from 'path'; +import fs from 'fs-extra'; import _ from 'lodash'; import {DEFAULT_PLUGIN_ID} from './constants'; import {normalizeUrl} from './urlUtils'; +import {findAsyncSequential} from './jsUtils'; import type { TranslationFileContent, TranslationFile, I18n, } from '@docusaurus/types'; +import type {ContentPaths} from './markdownLinks'; /** * Takes a list of translation file contents, and shallow-merges them into one. @@ -112,3 +115,82 @@ export function localizePath({ // Url paths; add a trailing slash so it's a valid base URL return normalizeUrl([originalPath, i18n.currentLocale, '/']); } + +/** + * Localize a content file path + * ./dir/myDoc.md => ./dir/myDoc.fr.md + * @param filePath + * @param locale + */ +function addLocaleExtension(filePath: string, locale: string) { + const {name, dir, ext} = path.parse(filePath); + return path.join(dir, `${name}.${locale}${ext}`); +} + +/** + * Returns the first existing localized path of a content file + * @param relativeSource + * @param contentPaths + * @param locale + */ +export async function getLocalizedSourcePath({ + relativeSource, + contentPaths, + locale, +}: { + relativeSource: string; + contentPaths: ContentPaths; + locale: string; +}): Promise { + // docs/myDoc.fr.md + const localeExtensionSource = path.join( + contentPaths.contentPath, + addLocaleExtension(relativeSource, locale), + ); + + // i18n/fr/docs/current/myDoc.md + const i18nFolderSource = path.join( + contentPaths.contentPathLocalized, + relativeSource, + ); + + // docs/myDoc.md + const originalSource = path.join(contentPaths.contentPath, relativeSource); + + // Order matters + const possibleSources = [ + localeExtensionSource, + i18nFolderSource, + originalSource, + ]; + + // TODO can we avoid/optimize this by passing all the files we know as param? + const localizedSource = await findAsyncSequential( + possibleSources, + fs.pathExists, + ); + + if (!localizedSource) { + throw new Error( + `Unexpected error, couldn't find any existing file at ${originalSource}`, + ); + } + + return localizedSource; +} + +export function filterFilesWithLocaleExtension({ + files, + locales, +}: { + files: string[]; + locales: string[]; +}): string[] { + const possibleLocaleExtensions = new Set( + locales.map((locale) => `.${locale}`), + ); + return files.filter((file) => { + const {name} = path.parse(file); + return !possibleLocaleExtensions.has(path.extname(name)); + }); +} diff --git a/packages/docusaurus-utils/src/index.ts b/packages/docusaurus-utils/src/index.ts index 6db01244d006..ce4daefb77bf 100644 --- a/packages/docusaurus-utils/src/index.ts +++ b/packages/docusaurus-utils/src/index.ts @@ -34,6 +34,8 @@ export { updateTranslationFileMessages, getPluginI18nPath, localizePath, + getLocalizedSourcePath, + filterFilesWithLocaleExtension, } from './i18nUtils'; export { removeSuffix, From f27bedb76f0e8abea01450f90d3bedc8326a81e6 Mon Sep 17 00:00:00 2001 From: sebastienlorber Date: Thu, 4 Jan 2024 19:30:18 +0100 Subject: [PATCH 6/8] dogfooding editUrl typo --- website/_dogfooding/dogfooding.config.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/website/_dogfooding/dogfooding.config.ts b/website/_dogfooding/dogfooding.config.ts index 3e57c13c36ad..49879ca4bafa 100644 --- a/website/_dogfooding/dogfooding.config.ts +++ b/website/_dogfooding/dogfooding.config.ts @@ -34,6 +34,8 @@ export const dogfoodingPluginInstances: PluginConfig[] = [ { id: 'docs-tests', routeBasePath: '/tests/docs', + editUrl: + 'https://github.com/facebook/docusaurus/edit/main/website/_dogfooding/_docs tests', sidebarPath: '_dogfooding/docs-tests-sidebars.js', versions: { current: { @@ -70,7 +72,7 @@ export const dogfoodingPluginInstances: PluginConfig[] = [ path: '_dogfooding/_blog tests', routeBasePath: '/tests/blog', editUrl: - 'https://github.com/facebook/docusaurus/edit/main/website/_dogfooding/_blog-tests', + 'https://github.com/facebook/docusaurus/edit/main/website/_dogfooding/_blog tests', postsPerPage: 3, feedOptions: { type: 'all', From ecf688884099d151c8a02ff06b09cf3fb445daac Mon Sep 17 00:00:00 2001 From: sebastienlorber Date: Thu, 4 Jan 2024 19:47:10 +0100 Subject: [PATCH 7/8] dogfooding editUrl typo --- website/_dogfooding/dogfooding.config.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/website/_dogfooding/dogfooding.config.ts b/website/_dogfooding/dogfooding.config.ts index 49879ca4bafa..1cb8c7a27af4 100644 --- a/website/_dogfooding/dogfooding.config.ts +++ b/website/_dogfooding/dogfooding.config.ts @@ -34,8 +34,7 @@ export const dogfoodingPluginInstances: PluginConfig[] = [ { id: 'docs-tests', routeBasePath: '/tests/docs', - editUrl: - 'https://github.com/facebook/docusaurus/edit/main/website/_dogfooding/_docs tests', + editUrl: 'https://github.com/facebook/docusaurus/edit/main/website', sidebarPath: '_dogfooding/docs-tests-sidebars.js', versions: { current: { @@ -71,8 +70,7 @@ export const dogfoodingPluginInstances: PluginConfig[] = [ id: 'blog-tests', path: '_dogfooding/_blog tests', routeBasePath: '/tests/blog', - editUrl: - 'https://github.com/facebook/docusaurus/edit/main/website/_dogfooding/_blog tests', + editUrl: 'https://github.com/facebook/docusaurus/edit/main/website', postsPerPage: 3, feedOptions: { type: 'all', From e9b20ca990ccf00a6c38336781a4f68b2dfa1748 Mon Sep 17 00:00:00 2001 From: sebastienlorber Date: Thu, 4 Jan 2024 20:08:49 +0100 Subject: [PATCH 8/8] refactor, create getLocalizedSource() --- .../src/blogUtils.ts | 69 +++++++++++----- .../src/plugin-content-blog.d.ts | 1 + .../src/index.ts | 4 +- packages/docusaurus-utils/src/i18nUtils.ts | 80 +++++++++++++------ packages/docusaurus-utils/src/index.ts | 2 +- 5 files changed, 106 insertions(+), 50 deletions(-) diff --git a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts index 3bbb5301bf92..cd80af445ba8 100644 --- a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts +++ b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts @@ -26,6 +26,8 @@ import { getContentPathList, isUnlisted, isDraft, + filterFilesWithLocaleExtension, + getLocalizedSource, } from '@docusaurus/utils'; import {validateBlogPostFrontMatter} from './frontMatter'; import {type AuthorsMap, getAuthorsMap, getBlogPostAuthors} from './authors'; @@ -208,13 +210,19 @@ async function parseBlogPostMarkdownFile({ const defaultReadingTime: ReadingTimeFunction = ({content, options}) => readingTime(content, options).minutes; -async function processBlogSourceFile( - blogSourceRelative: string, - contentPaths: BlogContentPaths, - context: LoadContext, - options: PluginOptions, - authorsMap?: AuthorsMap, -): Promise { +async function processBlogSourceFile({ + blogSourceRelative, + contentPaths, + context, + options, + authorsMap, +}: { + blogSourceRelative: string; + contentPaths: BlogContentPaths; + context: LoadContext; + options: PluginOptions; + authorsMap?: AuthorsMap; +}): Promise { const { siteConfig: { baseUrl, @@ -231,21 +239,30 @@ async function processBlogSourceFile( editUrl, } = options; + // TODO remove this in favor of getLocalizedSource // Lookup in localized folder in priority const blogDirPath = await getFolderContainingFile( getContentPathList(contentPaths), blogSourceRelative, ); - const blogSourceAbsolute = path.join(blogDirPath, blogSourceRelative); + const { + source: blogSource, + + // contentPath: blogDirPath + } = await getLocalizedSource({ + relativeSource: blogSourceRelative, + contentPaths, + locale: context.i18n.currentLocale, + }); const {frontMatter, content, contentTitle, excerpt} = await parseBlogPostMarkdownFile({ - filePath: blogSourceAbsolute, + filePath: blogSource, parseFrontMatter, }); - const aliasedSource = aliasedSitePath(blogSourceAbsolute, siteDir); + const aliasedSource = aliasedSitePath(blogSource, siteDir); const draft = isDraft({frontMatter}); const unlisted = isUnlisted({frontMatter}); @@ -274,14 +291,14 @@ async function processBlogSourceFile( } try { - const result = getFileCommitDate(blogSourceAbsolute, { + const result = getFileCommitDate(blogSource, { age: 'oldest', includeAuthor: false, }); return result.date; } catch (err) { logger.warn(err); - return (await fs.stat(blogSourceAbsolute)).birthtime; + return (await fs.stat(blogSource)).birthtime; } } @@ -302,7 +319,7 @@ async function processBlogSourceFile( function getBlogEditUrl() { const blogPathRelative = path.relative( blogDirPath, - path.resolve(blogSourceAbsolute), + path.resolve(blogSource), ); if (typeof editUrl === 'function') { @@ -374,28 +391,36 @@ export async function generateBlogPosts( return []; } - const blogSourceFiles = await Globby(include, { - cwd: contentPaths.contentPath, - ignore: exclude, - }); + async function getBlogSourceFiles() { + const files = await Globby(include, { + cwd: contentPaths.contentPath, + ignore: exclude, + }); + return filterFilesWithLocaleExtension({ + files, + locales: context.i18n.locales, + }); + } + + const blogSourceFiles = await getBlogSourceFiles(); const authorsMap = await getAuthorsMap({ contentPaths, authorsMapPath: options.authorsMapPath, }); - async function doProcessBlogSourceFile(blogSourceFile: string) { + async function doProcessBlogSourceFile(blogSourceRelative: string) { try { - return await processBlogSourceFile( - blogSourceFile, + return await processBlogSourceFile({ + blogSourceRelative, contentPaths, context, options, authorsMap, - ); + }); } catch (err) { throw new Error( - `Processing of blog source file path=${blogSourceFile} failed.`, + `Processing of blog source file path=${blogSourceRelative} failed.`, {cause: err as Error}, ); } diff --git a/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts b/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts index b1915f75196f..04ce1ba1d13b 100644 --- a/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts +++ b/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts @@ -72,6 +72,7 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the * preserved as-is. Default values will be applied when generating metadata */ export type BlogPostFrontMatter = { + // TODO Docusaurus v4: remove /** * @deprecated Use `slug` instead. */ diff --git a/packages/docusaurus-plugin-content-pages/src/index.ts b/packages/docusaurus-plugin-content-pages/src/index.ts index 3b12b3c908d8..cd9913e89108 100644 --- a/packages/docusaurus-plugin-content-pages/src/index.ts +++ b/packages/docusaurus-plugin-content-pages/src/index.ts @@ -21,7 +21,7 @@ import { parseMarkdownFile, isUnlisted, isDraft, - getLocalizedSourcePath, + getLocalizedSource, filterFilesWithLocaleExtension, } from '@docusaurus/utils'; import {validatePageFrontMatter} from './frontMatter'; @@ -95,7 +95,7 @@ export default function pluginContentPages( async function processPageSourceFile( relativeSource: string, ): Promise { - const source = await getLocalizedSourcePath({ + const {source} = await getLocalizedSource({ relativeSource, contentPaths, locale: context.i18n.currentLocale, diff --git a/packages/docusaurus-utils/src/i18nUtils.ts b/packages/docusaurus-utils/src/i18nUtils.ts index cac9e269ecc4..7b8c458b61ac 100644 --- a/packages/docusaurus-utils/src/i18nUtils.ts +++ b/packages/docusaurus-utils/src/i18nUtils.ts @@ -127,13 +127,56 @@ function addLocaleExtension(filePath: string, locale: string) { return path.join(dir, `${name}.${locale}${ext}`); } +type LocalizedSource = { + contentPath: string; + source: string; + type: 'locale-extension' | 'locale-folder' | 'original'; +}; + +function getLocalizedSourceCandidates({ + relativeSource, + contentPaths, + locale, +}: { + relativeSource: string; + contentPaths: ContentPaths; + locale: string; +}): LocalizedSource[] { + // docs/myDoc.fr.md + const localeExtensionSource: LocalizedSource = { + contentPath: contentPaths.contentPath, + source: path.join( + contentPaths.contentPath, + addLocaleExtension(relativeSource, locale), + ), + type: 'locale-extension', + }; + + // i18n/fr/docs/current/myDoc.md + const i18nFolderSource: LocalizedSource = { + contentPath: contentPaths.contentPath, + source: path.join(contentPaths.contentPathLocalized, relativeSource), + type: 'locale-folder', + }; + + // docs/myDoc.md + const originalSource: LocalizedSource = { + contentPath: contentPaths.contentPath, + source: path.join(contentPaths.contentPath, relativeSource), + type: 'original', + }; + + // Order matters + return [localeExtensionSource, i18nFolderSource, originalSource]; +} + /** * Returns the first existing localized path of a content file * @param relativeSource * @param contentPaths * @param locale */ -export async function getLocalizedSourcePath({ +export async function getLocalizedSource({ relativeSource, contentPaths, locale, @@ -141,38 +184,25 @@ export async function getLocalizedSourcePath({ relativeSource: string; contentPaths: ContentPaths; locale: string; -}): Promise { +}): Promise { // docs/myDoc.fr.md - const localeExtensionSource = path.join( - contentPaths.contentPath, - addLocaleExtension(relativeSource, locale), - ); - - // i18n/fr/docs/current/myDoc.md - const i18nFolderSource = path.join( - contentPaths.contentPathLocalized, + const candidates = getLocalizedSourceCandidates({ relativeSource, - ); - - // docs/myDoc.md - const originalSource = path.join(contentPaths.contentPath, relativeSource); - - // Order matters - const possibleSources = [ - localeExtensionSource, - i18nFolderSource, - originalSource, - ]; + contentPaths, + locale, + }); // TODO can we avoid/optimize this by passing all the files we know as param? - const localizedSource = await findAsyncSequential( - possibleSources, - fs.pathExists, + const localizedSource = await findAsyncSequential(candidates, (candidate) => + fs.pathExists(candidate.source), ); if (!localizedSource) { throw new Error( - `Unexpected error, couldn't find any existing file at ${originalSource}`, + `Unexpected error, couldn't find any localized source for file at ${path.join( + contentPaths.contentPath, + relativeSource, + )}`, ); } diff --git a/packages/docusaurus-utils/src/index.ts b/packages/docusaurus-utils/src/index.ts index ce4daefb77bf..9e772c643286 100644 --- a/packages/docusaurus-utils/src/index.ts +++ b/packages/docusaurus-utils/src/index.ts @@ -34,7 +34,7 @@ export { updateTranslationFileMessages, getPluginI18nPath, localizePath, - getLocalizedSourcePath, + getLocalizedSource, filterFilesWithLocaleExtension, } from './i18nUtils'; export {