diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts index ebbca9b18d..cfa1b0c9b5 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts @@ -6,12 +6,13 @@ */ import {jest} from '@jest/globals'; -import path from 'path'; +import * as path from 'path'; import {normalizePluginOptions} from '@docusaurus/utils-validation'; import { posixPath, getFileCommitDate, LAST_UPDATE_FALLBACK, + getLocaleConfig, } from '@docusaurus/utils'; import {DEFAULT_FUTURE_CONFIG} from '@docusaurus/core/src/server/configValidation'; import pluginContentBlog from '../index'; @@ -22,6 +23,7 @@ import type { I18n, Validate, MarkdownConfig, + I18nLocaleConfig, } from '@docusaurus/types'; import type { BlogPost, @@ -67,7 +69,10 @@ Available blog post titles are:\n- ${blogPosts return post; } -function getI18n(locale: string): I18n { +function getI18n( + locale: string, + localeConfigOptions?: Partial, +): I18n { return { currentLocale: locale, locales: [locale], @@ -80,6 +85,8 @@ function getI18n(locale: string): I18n { htmlLang: locale, direction: 'ltr', path: locale, + translate: true, + ...localeConfigOptions, }, }, }; @@ -94,13 +101,14 @@ const BaseEditUrl = 'https://baseEditUrl.com/edit'; const getPlugin = async ( siteDir: string, pluginOptions: Partial = {}, - i18n: I18n = DefaultI18N, + i18nOptions: Partial = {}, ) => { + const i18n = {...DefaultI18N, ...i18nOptions}; const generatedFilesDir: string = path.resolve(siteDir, '.docusaurus'); const localizationDir = path.join( siteDir, i18n.path, - i18n.localeConfigs[i18n.currentLocale]!.path, + getLocaleConfig(i18n).path, ); const siteConfig = { title: 'Hello', @@ -153,20 +161,34 @@ const getBlogTags = async ( }; describe('blog plugin', () => { - it('getPathsToWatch returns right files', async () => { - const siteDir = path.join(__dirname, '__fixtures__', 'website'); - const plugin = await getPlugin(siteDir); - const pathsToWatch = plugin.getPathsToWatch!(); - const relativePathsToWatch = pathsToWatch.map((p) => - posixPath(path.relative(siteDir, p)), - ); - expect(relativePathsToWatch).toEqual([ - 'i18n/en/docusaurus-plugin-content-blog/authors.yml', - 'i18n/en/docusaurus-plugin-content-blog/tags.yml', - 'blog/tags.yml', - 'i18n/en/docusaurus-plugin-content-blog/**/*.{md,mdx}', - 'blog/**/*.{md,mdx}', - ]); + describe('getPathsToWatch', () => { + async function runTest({translate}: {translate: boolean}) { + const siteDir = path.join(__dirname, '__fixtures__', 'website'); + const plugin = await getPlugin(siteDir, {}, getI18n('en', {translate})); + const pathsToWatch = plugin.getPathsToWatch!(); + return pathsToWatch.map((p) => posixPath(path.relative(siteDir, p))); + } + + it('getPathsToWatch returns right files', async () => { + const relativePathsToWatch = await runTest({translate: true}); + expect(relativePathsToWatch).toEqual([ + 'i18n/en/docusaurus-plugin-content-blog/authors.yml', + 'i18n/en/docusaurus-plugin-content-blog/tags.yml', + // 'blog/authors.yml', // TODO weird that it's not here but tags is? + 'blog/tags.yml', + 'i18n/en/docusaurus-plugin-content-blog/**/*.{md,mdx}', + 'blog/**/*.{md,mdx}', + ]); + }); + + it('getPathsToWatch returns right files (translate: false)', async () => { + const relativePathsToWatch = await runTest({translate: false}); + expect(relativePathsToWatch).toEqual([ + 'blog/authors.yml', + 'blog/tags.yml', + 'blog/**/*.{md,mdx}', + ]); + }); }); it('builds a simple website', async () => { @@ -377,6 +399,54 @@ describe('blog plugin', () => { }); }); + describe('i18n config translate is wired properly', () => { + async function runTest({translate}: {translate: boolean}) { + const siteDir = path.join(__dirname, '__fixtures__', 'website'); + const blogPosts = await getBlogPosts( + siteDir, + {}, + getI18n('en', {translate}), + ); + + // Simpler to snapshot + return blogPosts.map((post) => post.metadata.title); + } + + it('works with translate: false', async () => { + await expect(runTest({translate: false})).resolves.toMatchInlineSnapshot(` + [ + "test links", + "MDX Blog Sample with require calls", + "Full Blog Sample", + "Complex Slug", + "Simple Slug", + "draft", + "unlisted", + "some heading", + "date-matter", + "Happy 1st Birthday Slash!", + ] + `); + }); + + it('works with translate: true', async () => { + await expect(runTest({translate: true})).resolves.toMatchInlineSnapshot(` + [ + "test links", + "MDX Blog Sample with require calls", + "Full Blog Sample", + "Complex Slug", + "Simple Slug", + "draft", + "unlisted", + "some heading", + "date-matter", + "Happy 1st Birthday Slash! (translated)", + ] + `); + }); + }); + it('handles edit URL with editLocalizedBlogs: true', async () => { const siteDir = path.join(__dirname, '__fixtures__', 'website'); const blogPosts = await getBlogPosts(siteDir, {editLocalizedFiles: true}); @@ -390,6 +460,23 @@ describe('blog plugin', () => { ); }); + it('handles edit URL with editLocalizedBlogs: true and translate: false', async () => { + const siteDir = path.join(__dirname, '__fixtures__', 'website'); + const blogPosts = await getBlogPosts( + siteDir, + {editLocalizedFiles: true}, + getI18n('en', {translate: false}), + ); + + const localizedBlogPost = blogPosts.find( + (v) => v.metadata.title === 'Happy 1st Birthday Slash!', + )!; + + expect(localizedBlogPost.metadata.editUrl).toBe( + `${BaseEditUrl}/blog/2018-12-14-Happy-First-Birthday-Slash.md`, + ); + }); + it('handles edit URL with editUrl function', async () => { const siteDir = path.join(__dirname, '__fixtures__', 'website'); diff --git a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts index cd3c1b8837..3647a34550 100644 --- a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts +++ b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts @@ -323,7 +323,9 @@ async function processBlogSourceFile( } else if (typeof editUrl === 'string') { const isLocalized = blogDirPath === contentPaths.contentPathLocalized; const fileContentPath = - isLocalized && options.editLocalizedFiles + isLocalized && + options.editLocalizedFiles && + contentPaths.contentPathLocalized ? contentPaths.contentPathLocalized : contentPaths.contentPath; diff --git a/packages/docusaurus-plugin-content-blog/src/index.ts b/packages/docusaurus-plugin-content-blog/src/index.ts index fe38ec471c..e19969ab22 100644 --- a/packages/docusaurus-plugin-content-blog/src/index.ts +++ b/packages/docusaurus-plugin-content-blog/src/index.ts @@ -19,6 +19,7 @@ import { getDataFilePath, DEFAULT_PLUGIN_ID, resolveMarkdownLinkPathname, + getLocaleConfig, } from '@docusaurus/utils'; import {getTagsFilePathsToWatch} from '@docusaurus/utils-validation'; import {createMDXLoaderItem} from '@docusaurus/mdx-loader'; @@ -73,13 +74,16 @@ export default async function pluginContentBlog( const {baseUrl} = siteConfig; + const shouldTranslate = getLocaleConfig(context.i18n).translate; const contentPaths: BlogContentPaths = { contentPath: path.resolve(siteDir, options.path), - contentPathLocalized: getPluginI18nPath({ - localizationDir, - pluginName: PluginName, - pluginId: options.id, - }), + contentPathLocalized: shouldTranslate + ? getPluginI18nPath({ + localizationDir, + pluginName: PluginName, + pluginId: options.id, + }) + : undefined, }; const pluginId = options.id ?? DEFAULT_PLUGIN_ID; diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/index.test.ts.snap index 820c30fec3..c8692a5eb8 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/index.test.ts.snap +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/index.test.ts.snap @@ -2005,17 +2005,6 @@ exports[`simple website content: route config 1`] = ` ] `; -exports[`simple website getPathToWatch 1`] = ` -[ - "sidebars.json", - "i18n/en/docusaurus-plugin-content-docs/current/**/*.{md,mdx}", - "docs/**/*.{md,mdx}", - "i18n/en/docusaurus-plugin-content-docs/current/tags.yml", - "docs/tags.yml", - "docs/**/_category_.{json,yml,yaml}", -] -`; - exports[`site with custom sidebar items generator sidebar is autogenerated according to a custom sidebarItemsGenerator 1`] = ` { "defaultSidebar": [ @@ -3327,23 +3316,6 @@ exports[`versioned website (community) content: route config 1`] = ` ] `; -exports[`versioned website (community) getPathToWatch 1`] = ` -[ - "community_sidebars.json", - "i18n/en/docusaurus-plugin-content-docs-community/current/**/*.{md,mdx}", - "community/**/*.{md,mdx}", - "i18n/en/docusaurus-plugin-content-docs-community/current/tags.yml", - "community/tags.yml", - "community/**/_category_.{json,yml,yaml}", - "community_versioned_sidebars/version-1.0.0-sidebars.json", - "i18n/en/docusaurus-plugin-content-docs-community/version-1.0.0/**/*.{md,mdx}", - "community_versioned_docs/version-1.0.0/**/*.{md,mdx}", - "i18n/en/docusaurus-plugin-content-docs-community/version-1.0.0/tags.yml", - "community_versioned_docs/version-1.0.0/tags.yml", - "community_versioned_docs/version-1.0.0/**/_category_.{json,yml,yaml}", -] -`; - exports[`versioned website content 1`] = ` { "description": "This is next version of bar.", @@ -5209,32 +5181,3 @@ exports[`versioned website content: withSlugs version sidebars 1`] = ` ], } `; - -exports[`versioned website getPathToWatch 1`] = ` -[ - "sidebars.json", - "i18n/en/docusaurus-plugin-content-docs/current/**/*.{md,mdx}", - "docs/**/*.{md,mdx}", - "i18n/en/docusaurus-plugin-content-docs/current/tags.yml", - "docs/tags.yml", - "docs/**/_category_.{json,yml,yaml}", - "versioned_sidebars/version-1.0.1-sidebars.json", - "i18n/en/docusaurus-plugin-content-docs/version-1.0.1/**/*.{md,mdx}", - "versioned_docs/version-1.0.1/**/*.{md,mdx}", - "i18n/en/docusaurus-plugin-content-docs/version-1.0.1/tags.yml", - "versioned_docs/version-1.0.1/tags.yml", - "versioned_docs/version-1.0.1/**/_category_.{json,yml,yaml}", - "versioned_sidebars/version-1.0.0-sidebars.json", - "i18n/en/docusaurus-plugin-content-docs/version-1.0.0/**/*.{md,mdx}", - "versioned_docs/version-1.0.0/**/*.{md,mdx}", - "i18n/en/docusaurus-plugin-content-docs/version-1.0.0/tags.yml", - "versioned_docs/version-1.0.0/tags.yml", - "versioned_docs/version-1.0.0/**/_category_.{json,yml,yaml}", - "versioned_sidebars/version-withSlugs-sidebars.json", - "i18n/en/docusaurus-plugin-content-docs/version-withSlugs/**/*.{md,mdx}", - "versioned_docs/version-withSlugs/**/*.{md,mdx}", - "i18n/en/docusaurus-plugin-content-docs/version-withSlugs/tags.yml", - "versioned_docs/version-withSlugs/tags.yml", - "versioned_docs/version-withSlugs/**/_category_.{json,yml,yaml}", -] -`; diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/cli.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/cli.test.ts index c8c28dfca3..98d73c52ea 100644 Binary files a/packages/docusaurus-plugin-content-docs/src/__tests__/cli.test.ts and b/packages/docusaurus-plugin-content-docs/src/__tests__/cli.test.ts differ diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/docs.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/docs.test.ts index 6b1815ee25..56422102c8 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/docs.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/docs.test.ts @@ -13,6 +13,7 @@ import { posixPath, DEFAULT_PLUGIN_ID, LAST_UPDATE_FALLBACK, + getLocaleConfig, } from '@docusaurus/utils'; import {getTagsFile} from '@docusaurus/utils-validation'; import {createSidebarsUtils} from '../sidebars/utils'; @@ -842,7 +843,11 @@ describe('simple site', () => { describe('versioned site', () => { async function loadSite( - loadSiteOptions: {options: Partial; locale?: string} = { + loadSiteOptions: { + options?: Partial; + locale?: string; + translate?: boolean; + } = { options: {}, }, ) { @@ -851,6 +856,10 @@ describe('versioned site', () => { siteDir, locale: loadSiteOptions.locale, }); + + // hacky but gets the job done + getLocaleConfig(context.i18n).translate = loadSiteOptions.translate ?? true; + const options = { id: DEFAULT_PLUGIN_ID, ...DEFAULT_OPTIONS, @@ -1055,6 +1064,43 @@ describe('versioned site', () => { }); }); + it('versioned docs - translate: false', async () => { + const {version100TestUtils} = await loadSite({ + translate: false, + }); + + // This doc is translated, but we still read the original + await version100TestUtils.testMeta(path.join('hello.md'), { + id: 'hello', + sourceDirName: '.', + permalink: '/docs/1.0.0/', + slug: '/', + title: 'hello', + description: 'Hello 1.0.0 !', + frontMatter: { + slug: '/', + tags: ['inlineTag-v1.0.0', 'globalTag-v1.0.0'], + }, + version: '1.0.0', + source: '@site/versioned_docs/version-1.0.0/hello.md', + tags: [ + { + description: undefined, + inline: true, + label: 'inlineTag-v1.0.0', + permalink: '/docs/1.0.0/tags/inline-tag-v-1-0-0', + }, + { + description: 'globalTag-v1.0.0 description', + inline: false, + label: 'globalTag-v1.0.0 label', + permalink: '/docs/1.0.0/tags/globalTag-v1.0.0 permalink', + }, + ], + unlisted: false, + }); + }); + it('next doc slugs', async () => { const {currentVersionTestUtils} = await loadSite(); diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts index fb1180159f..26594bc95d 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts @@ -18,7 +18,7 @@ import { createConfigureWebpackUtils, } from '@docusaurus/core/src/webpack/configure'; import {sortRoutes} from '@docusaurus/core/src/server/plugins/routeConfig'; -import {posixPath} from '@docusaurus/utils'; +import {getLocaleConfig, posixPath} from '@docusaurus/utils'; import {normalizePluginOptions} from '@docusaurus/utils-validation'; import {fromPartial} from '@total-typescript/shoehorn'; @@ -219,9 +219,13 @@ describe('empty/no docs website', () => { }); describe('simple website', () => { - async function loadSite() { + async function loadSite({translate}: {translate?: boolean} = {}) { const siteDir = path.join(__dirname, '__fixtures__', 'simple-site'); const context = await loadContext({siteDir}); + + // hacky but gets the job done + getLocaleConfig(context.i18n).translate = translate ?? true; + const sidebarPath = path.join(siteDir, 'sidebars.json'); const options = validateOptions({ validate: normalizePluginOptions as Validate, @@ -233,7 +237,20 @@ describe('simple website', () => { const plugin = await pluginContentDocs(context, options); const pluginContentDir = path.join(context.generatedFilesDir, plugin.name); - return {siteDir, context, sidebarPath, plugin, options, pluginContentDir}; + return { + siteDir, + context, + sidebarPath, + plugin, + options, + pluginContentDir, + getPathsToWatch: () => { + const pathToWatch = plugin.getPathsToWatch!(); + return pathToWatch.map((filepath) => + posixPath(path.relative(siteDir, filepath)), + ); + }, + }; } it('extendCli - docsVersion', async () => { @@ -242,8 +259,6 @@ describe('simple website', () => { .spyOn(cliDocs, 'cliDocsVersionCommand') .mockImplementation(async () => {}); const cli = new commander.Command(); - // @ts-expect-error: in actual usage, we pass the static commander instead - // of the new command plugin.extendCli!(cli); cli.parse(['node', 'test', 'docs:version', '1.0.0']); expect(mock).toHaveBeenCalledTimes(1); @@ -251,25 +266,48 @@ describe('simple website', () => { mock.mockRestore(); }); - it('getPathToWatch', async () => { - const {siteDir, plugin} = await loadSite(); + describe('getPathToWatch', () => { + it('translate: false', async () => { + const {getPathsToWatch} = await loadSite({translate: false}); + expect(getPathsToWatch()).toMatchInlineSnapshot(` + [ + "sidebars.json", + "docs/**/*.{md,mdx}", + "docs/tags.yml", + "docs/**/_category_.{json,yml,yaml}", + ] + `); + }); - const pathToWatch = plugin.getPathsToWatch!(); - const matchPattern = pathToWatch.map((filepath) => - posixPath(path.relative(siteDir, filepath)), - ); - expect(matchPattern).toMatchSnapshot(); - expect(isMatch('docs/hello.md', matchPattern)).toBe(true); - expect(isMatch('docs/hello.mdx', matchPattern)).toBe(true); - expect(isMatch('docs/foo/bar.md', matchPattern)).toBe(true); - expect(isMatch('docs/hello.js', matchPattern)).toBe(false); - expect(isMatch('docs/super.mdl', matchPattern)).toBe(false); - expect(isMatch('docs/mdx', matchPattern)).toBe(false); - expect(isMatch('docs/headingAsTitle.md', matchPattern)).toBe(true); - expect(isMatch('sidebars.json', matchPattern)).toBe(true); - expect(isMatch('versioned_docs/hello.md', matchPattern)).toBe(false); - expect(isMatch('hello.md', matchPattern)).toBe(false); - expect(isMatch('super/docs/hello.md', matchPattern)).toBe(false); + it('translate: true', async () => { + const {getPathsToWatch} = await loadSite({translate: true}); + expect(getPathsToWatch()).toMatchInlineSnapshot(` + [ + "sidebars.json", + "i18n/en/docusaurus-plugin-content-docs/current/**/*.{md,mdx}", + "docs/**/*.{md,mdx}", + "i18n/en/docusaurus-plugin-content-docs/current/tags.yml", + "docs/tags.yml", + "docs/**/_category_.{json,yml,yaml}", + ] + `); + }); + + it('returns patterns matching docs', async () => { + const {getPathsToWatch} = await loadSite(); + const matchPattern = getPathsToWatch(); + expect(isMatch('docs/hello.md', matchPattern)).toBe(true); + expect(isMatch('docs/hello.mdx', matchPattern)).toBe(true); + expect(isMatch('docs/foo/bar.md', matchPattern)).toBe(true); + expect(isMatch('docs/hello.js', matchPattern)).toBe(false); + expect(isMatch('docs/super.mdl', matchPattern)).toBe(false); + expect(isMatch('docs/mdx', matchPattern)).toBe(false); + expect(isMatch('docs/headingAsTitle.md', matchPattern)).toBe(true); + expect(isMatch('sidebars.json', matchPattern)).toBe(true); + expect(isMatch('versioned_docs/hello.md', matchPattern)).toBe(false); + expect(isMatch('hello.md', matchPattern)).toBe(false); + expect(isMatch('super/docs/hello.md', matchPattern)).toBe(false); + }); }); it('configureWebpack', async () => { @@ -329,9 +367,13 @@ describe('simple website', () => { }); describe('versioned website', () => { - async function loadSite() { + async function loadSite({translate}: {translate?: boolean} = {}) { const siteDir = path.join(__dirname, '__fixtures__', 'versioned-site'); const context = await loadContext({siteDir}); + + // hacky but gets the job done + getLocaleConfig(context.i18n).translate = translate ?? true; + const sidebarPath = path.join(siteDir, 'sidebars.json'); const routeBasePath = 'docs'; const options = validateOptions({ @@ -356,6 +398,13 @@ describe('versioned website', () => { options, plugin, pluginContentDir, + + getPathsToWatch: () => { + const pathToWatch = plugin.getPathsToWatch!(); + return pathToWatch.map((filepath) => + posixPath(path.relative(siteDir, filepath)), + ); + }, }; } @@ -365,8 +414,6 @@ describe('versioned website', () => { .spyOn(cliDocs, 'cliDocsVersionCommand') .mockImplementation(async () => {}); const cli = new commander.Command(); - // @ts-expect-error: in actual usage, we pass the static commander instead - // of the new command plugin.extendCli!(cli); cli.parse(['node', 'test', 'docs:version', '2.0.0']); expect(mock).toHaveBeenCalledTimes(1); @@ -374,48 +421,101 @@ describe('versioned website', () => { mock.mockRestore(); }); - it('getPathToWatch', async () => { - const {siteDir, plugin} = await loadSite(); - const pathToWatch = plugin.getPathsToWatch!(); - const matchPattern = pathToWatch.map((filepath) => - posixPath(path.relative(siteDir, filepath)), - ); - expect(matchPattern).not.toEqual([]); - expect(matchPattern).toMatchSnapshot(); - expect(isMatch('docs/hello.md', matchPattern)).toBe(true); - expect(isMatch('docs/hello.mdx', matchPattern)).toBe(true); - expect(isMatch('docs/foo/bar.md', matchPattern)).toBe(true); - expect(isMatch('sidebars.json', matchPattern)).toBe(true); - expect(isMatch('versioned_docs/version-1.0.0/hello.md', matchPattern)).toBe( - true, - ); - expect( - isMatch('versioned_docs/version-1.0.0/foo/bar.md', matchPattern), - ).toBe(true); - expect( - isMatch('versioned_sidebars/version-1.0.0-sidebars.json', matchPattern), - ).toBe(true); + describe('getPathToWatch', () => { + it('translate: false', async () => { + const {getPathsToWatch} = await loadSite({translate: false}); + expect(getPathsToWatch()).toMatchInlineSnapshot(` + [ + "sidebars.json", + "docs/**/*.{md,mdx}", + "docs/tags.yml", + "docs/**/_category_.{json,yml,yaml}", + "versioned_sidebars/version-1.0.1-sidebars.json", + "versioned_docs/version-1.0.1/**/*.{md,mdx}", + "versioned_docs/version-1.0.1/tags.yml", + "versioned_docs/version-1.0.1/**/_category_.{json,yml,yaml}", + "versioned_sidebars/version-1.0.0-sidebars.json", + "versioned_docs/version-1.0.0/**/*.{md,mdx}", + "versioned_docs/version-1.0.0/tags.yml", + "versioned_docs/version-1.0.0/**/_category_.{json,yml,yaml}", + "versioned_sidebars/version-withSlugs-sidebars.json", + "versioned_docs/version-withSlugs/**/*.{md,mdx}", + "versioned_docs/version-withSlugs/tags.yml", + "versioned_docs/version-withSlugs/**/_category_.{json,yml,yaml}", + ] + `); + }); - // Non existing version - expect( - isMatch('versioned_docs/version-2.0.0/foo/bar.md', matchPattern), - ).toBe(false); - expect(isMatch('versioned_docs/version-2.0.0/hello.md', matchPattern)).toBe( - false, - ); - expect( - isMatch('versioned_sidebars/version-2.0.0-sidebars.json', matchPattern), - ).toBe(false); + it('translate: true', async () => { + const {getPathsToWatch} = await loadSite({translate: true}); + expect(getPathsToWatch()).toMatchInlineSnapshot(` + [ + "sidebars.json", + "i18n/en/docusaurus-plugin-content-docs/current/**/*.{md,mdx}", + "docs/**/*.{md,mdx}", + "i18n/en/docusaurus-plugin-content-docs/current/tags.yml", + "docs/tags.yml", + "docs/**/_category_.{json,yml,yaml}", + "versioned_sidebars/version-1.0.1-sidebars.json", + "i18n/en/docusaurus-plugin-content-docs/version-1.0.1/**/*.{md,mdx}", + "versioned_docs/version-1.0.1/**/*.{md,mdx}", + "i18n/en/docusaurus-plugin-content-docs/version-1.0.1/tags.yml", + "versioned_docs/version-1.0.1/tags.yml", + "versioned_docs/version-1.0.1/**/_category_.{json,yml,yaml}", + "versioned_sidebars/version-1.0.0-sidebars.json", + "i18n/en/docusaurus-plugin-content-docs/version-1.0.0/**/*.{md,mdx}", + "versioned_docs/version-1.0.0/**/*.{md,mdx}", + "i18n/en/docusaurus-plugin-content-docs/version-1.0.0/tags.yml", + "versioned_docs/version-1.0.0/tags.yml", + "versioned_docs/version-1.0.0/**/_category_.{json,yml,yaml}", + "versioned_sidebars/version-withSlugs-sidebars.json", + "i18n/en/docusaurus-plugin-content-docs/version-withSlugs/**/*.{md,mdx}", + "versioned_docs/version-withSlugs/**/*.{md,mdx}", + "i18n/en/docusaurus-plugin-content-docs/version-withSlugs/tags.yml", + "versioned_docs/version-withSlugs/tags.yml", + "versioned_docs/version-withSlugs/**/_category_.{json,yml,yaml}", + ] + `); + }); - expect(isMatch('docs/hello.js', matchPattern)).toBe(false); - expect(isMatch('docs/super.mdl', matchPattern)).toBe(false); - expect(isMatch('docs/mdx', matchPattern)).toBe(false); - expect(isMatch('hello.md', matchPattern)).toBe(false); - expect(isMatch('super/docs/hello.md', matchPattern)).toBe(false); + it('returns patterns matching docs', async () => { + const {getPathsToWatch} = await loadSite(); + const matchPattern = getPathsToWatch(); + expect(isMatch('docs/hello.md', matchPattern)).toBe(true); + expect(isMatch('docs/hello.mdx', matchPattern)).toBe(true); + expect(isMatch('docs/foo/bar.md', matchPattern)).toBe(true); + expect(isMatch('sidebars.json', matchPattern)).toBe(true); + expect( + isMatch('versioned_docs/version-1.0.0/hello.md', matchPattern), + ).toBe(true); + expect( + isMatch('versioned_docs/version-1.0.0/foo/bar.md', matchPattern), + ).toBe(true); + expect( + isMatch('versioned_sidebars/version-1.0.0-sidebars.json', matchPattern), + ).toBe(true); + + // Non existing version + expect( + isMatch('versioned_docs/version-2.0.0/foo/bar.md', matchPattern), + ).toBe(false); + expect( + isMatch('versioned_docs/version-2.0.0/hello.md', matchPattern), + ).toBe(false); + expect( + isMatch('versioned_sidebars/version-2.0.0-sidebars.json', matchPattern), + ).toBe(false); + + expect(isMatch('docs/hello.js', matchPattern)).toBe(false); + expect(isMatch('docs/super.mdl', matchPattern)).toBe(false); + expect(isMatch('docs/mdx', matchPattern)).toBe(false); + expect(isMatch('hello.md', matchPattern)).toBe(false); + expect(isMatch('super/docs/hello.md', matchPattern)).toBe(false); + }); }); it('content', async () => { - const {plugin, pluginContentDir} = await loadSite(); + const {plugin, pluginContentDir} = await loadSite({translate: true}); const content = await plugin.loadContent!(); expect(content.loadedVersions).toHaveLength(4); const [currentVersion, version101, version100, versionWithSlugs] = @@ -453,9 +553,13 @@ describe('versioned website', () => { }); describe('versioned website (community)', () => { - async function loadSite() { + async function loadSite({translate}: {translate?: boolean} = {}) { const siteDir = path.join(__dirname, '__fixtures__', 'versioned-site'); const context = await loadContext({siteDir}); + + // hacky but gets the job done + getLocaleConfig(context.i18n).translate = translate ?? true; + const sidebarPath = path.join(siteDir, 'community_sidebars.json'); const routeBasePath = 'community'; const pluginId = 'community'; @@ -479,6 +583,13 @@ describe('versioned website (community)', () => { options, plugin, pluginContentDir, + + getPathsToWatch: () => { + const pathToWatch = plugin.getPathsToWatch!(); + return pathToWatch.map((filepath) => + posixPath(path.relative(siteDir, filepath)), + ); + }, }; } @@ -488,8 +599,6 @@ describe('versioned website (community)', () => { .spyOn(cliDocs, 'cliDocsVersionCommand') .mockImplementation(async () => {}); const cli = new commander.Command(); - // @ts-expect-error: in actual usage, we pass the static commander instead - // of the new command plugin.extendCli!(cli); cli.parse(['node', 'test', `docs:version:${pluginId}`, '2.0.0']); expect(mock).toHaveBeenCalledTimes(1); @@ -497,34 +606,67 @@ describe('versioned website (community)', () => { mock.mockRestore(); }); - it('getPathToWatch', async () => { - const {siteDir, plugin} = await loadSite(); - const pathToWatch = plugin.getPathsToWatch!(); - const matchPattern = pathToWatch.map((filepath) => - posixPath(path.relative(siteDir, filepath)), - ); - expect(matchPattern).not.toEqual([]); - expect(matchPattern).toMatchSnapshot(); - expect(isMatch('community/team.md', matchPattern)).toBe(true); - expect( - isMatch('community_versioned_docs/version-1.0.0/team.md', matchPattern), - ).toBe(true); + describe('getPathToWatch', () => { + it('translate: false', async () => { + const {getPathsToWatch} = await loadSite({translate: false}); + expect(getPathsToWatch()).toMatchInlineSnapshot(` + [ + "community_sidebars.json", + "community/**/*.{md,mdx}", + "community/tags.yml", + "community/**/_category_.{json,yml,yaml}", + "community_versioned_sidebars/version-1.0.0-sidebars.json", + "community_versioned_docs/version-1.0.0/**/*.{md,mdx}", + "community_versioned_docs/version-1.0.0/tags.yml", + "community_versioned_docs/version-1.0.0/**/_category_.{json,yml,yaml}", + ] + `); + }); - // Non existing version - expect( - isMatch('community_versioned_docs/version-2.0.0/team.md', matchPattern), - ).toBe(false); - expect( - isMatch( - 'community_versioned_sidebars/version-2.0.0-sidebars.json', - matchPattern, - ), - ).toBe(false); + it('translate: true', async () => { + const {getPathsToWatch} = await loadSite({translate: true}); + expect(getPathsToWatch()).toMatchInlineSnapshot(` + [ + "community_sidebars.json", + "i18n/en/docusaurus-plugin-content-docs-community/current/**/*.{md,mdx}", + "community/**/*.{md,mdx}", + "i18n/en/docusaurus-plugin-content-docs-community/current/tags.yml", + "community/tags.yml", + "community/**/_category_.{json,yml,yaml}", + "community_versioned_sidebars/version-1.0.0-sidebars.json", + "i18n/en/docusaurus-plugin-content-docs-community/version-1.0.0/**/*.{md,mdx}", + "community_versioned_docs/version-1.0.0/**/*.{md,mdx}", + "i18n/en/docusaurus-plugin-content-docs-community/version-1.0.0/tags.yml", + "community_versioned_docs/version-1.0.0/tags.yml", + "community_versioned_docs/version-1.0.0/**/_category_.{json,yml,yaml}", + ] + `); + }); - expect(isMatch('community/team.js', matchPattern)).toBe(false); - expect( - isMatch('community_versioned_docs/version-1.0.0/team.js', matchPattern), - ).toBe(false); + it('returns patterns matching docs', async () => { + const {getPathsToWatch} = await loadSite(); + const matchPattern = getPathsToWatch(); + expect(isMatch('community/team.md', matchPattern)).toBe(true); + expect( + isMatch('community_versioned_docs/version-1.0.0/team.md', matchPattern), + ).toBe(true); + + // Non existing version + expect( + isMatch('community_versioned_docs/version-2.0.0/team.md', matchPattern), + ).toBe(false); + expect( + isMatch( + 'community_versioned_sidebars/version-2.0.0-sidebars.json', + matchPattern, + ), + ).toBe(false); + + expect(isMatch('community/team.js', matchPattern)).toBe(false); + expect( + isMatch('community_versioned_docs/version-1.0.0/team.js', matchPattern), + ).toBe(false); + }); }); it('content', async () => { diff --git a/packages/docusaurus-plugin-content-docs/src/cli.ts b/packages/docusaurus-plugin-content-docs/src/cli.ts index e66e910ebc..d19d3f5fbd 100644 --- a/packages/docusaurus-plugin-content-docs/src/cli.ts +++ b/packages/docusaurus-plugin-content-docs/src/cli.ts @@ -8,7 +8,7 @@ import fs from 'fs-extra'; import path from 'path'; import logger from '@docusaurus/logger'; -import {DEFAULT_PLUGIN_ID} from '@docusaurus/utils'; +import {DEFAULT_PLUGIN_ID, getLocaleConfig} from '@docusaurus/utils'; import { getVersionsFilePath, getVersionDocsDirPath, @@ -89,7 +89,7 @@ async function cliDocsVersionCommand( const localizationDir = path.resolve( siteDir, i18n.path, - i18n.localeConfigs[locale]!.path, + getLocaleConfig(i18n, locale).path, ); // Copy docs files. const docsDir = diff --git a/packages/docusaurus-plugin-content-docs/src/docs.ts b/packages/docusaurus-plugin-content-docs/src/docs.ts index e3eb7cc47f..352ccb3adb 100644 --- a/packages/docusaurus-plugin-content-docs/src/docs.ts +++ b/packages/docusaurus-plugin-content-docs/src/docs.ts @@ -196,7 +196,9 @@ async function doProcessDocMetadata({ locale: context.i18n.currentLocale, }); } else if (typeof options.editUrl === 'string') { - const isLocalized = contentPath === versionMetadata.contentPathLocalized; + const isLocalized = + typeof versionMetadata.contentPathLocalized !== 'undefined' && + contentPath === versionMetadata.contentPathLocalized; const baseVersionEditUrl = isLocalized && options.editLocalizedFiles ? versionMetadata.editUrlLocalized diff --git a/packages/docusaurus-plugin-content-docs/src/versions/__tests__/loadVersion.test.ts b/packages/docusaurus-plugin-content-docs/src/versions/__tests__/loadVersion.test.ts index a4329f7a5c..442e4a43a5 100644 --- a/packages/docusaurus-plugin-content-docs/src/versions/__tests__/loadVersion.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/versions/__tests__/loadVersion.test.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import path from 'path'; +import * as path from 'path'; import {fromPartial} from '@total-typescript/shoehorn'; import {DEFAULT_PARSE_FRONT_MATTER} from '@docusaurus/utils/src'; import {readVersionsMetadata} from '../version'; @@ -19,7 +19,7 @@ const DefaultI18N: I18n = { currentLocale: 'en', locales: ['en'], defaultLocale: 'en', - localeConfigs: {}, + localeConfigs: {en: fromPartial({translate: true})}, }; async function siteFixture(fixture: string) { diff --git a/packages/docusaurus-plugin-content-docs/src/versions/__tests__/version.test.ts b/packages/docusaurus-plugin-content-docs/src/versions/__tests__/version.test.ts index e4e53b58be..4109f7e72f 100644 --- a/packages/docusaurus-plugin-content-docs/src/versions/__tests__/version.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/versions/__tests__/version.test.ts @@ -6,27 +6,44 @@ */ import {jest} from '@jest/globals'; -import path from 'path'; +import * as path from 'path'; import {DEFAULT_PLUGIN_ID} from '@docusaurus/utils'; import {readVersionsMetadata} from '../version'; import {DEFAULT_OPTIONS} from '../../options'; -import type {I18n, LoadContext} from '@docusaurus/types'; +import type {I18n, I18nLocaleConfig, LoadContext} from '@docusaurus/types'; import type { PluginOptions, VersionMetadata, } from '@docusaurus/plugin-content-docs'; -const DefaultI18N: I18n = { - path: 'i18n', - currentLocale: 'en', - locales: ['en'], - defaultLocale: 'en', - localeConfigs: {}, -}; +function getI18n( + locale: string, + localeConfigOptions?: Partial, +): I18n { + return { + path: 'i18n', + currentLocale: locale, + locales: ['en'], + defaultLocale: locale, + localeConfigs: { + [locale]: { + path: locale, + label: locale, + translate: true, + calendar: 'calendar', + htmlLang: locale, + direction: 'rtl', + ...localeConfigOptions, + }, + }, + }; +} + +const DefaultI18N: I18n = getI18n('en'); describe('readVersionsMetadata', () => { describe('simple site', () => { - async function loadSite() { + async function loadSite({context}: {context?: Partial} = {}) { const simpleSiteDir = path.resolve( path.join(__dirname, '../../__tests__/__fixtures__', 'simple-site'), ); @@ -39,6 +56,7 @@ describe('readVersionsMetadata', () => { baseUrl: '/', i18n: DefaultI18N, localizationDir: path.join(simpleSiteDir, 'i18n/en'), + ...context, } as LoadContext; const vCurrent: VersionMetadata = { @@ -73,6 +91,26 @@ describe('readVersionsMetadata', () => { expect(versionsMetadata).toEqual([vCurrent]); }); + it('works with translate: false', async () => { + const {defaultOptions, defaultContext, vCurrent} = await loadSite({ + context: { + i18n: getI18n('en', {translate: false}), + }, + }); + + const versionsMetadata = await readVersionsMetadata({ + options: defaultOptions, + context: defaultContext, + }); + + expect(versionsMetadata).toEqual([ + { + ...vCurrent, + contentPathLocalized: undefined, + }, + ]); + }); + it('works with base url', async () => { const {defaultOptions, defaultContext, vCurrent} = await loadSite(); @@ -188,7 +226,7 @@ describe('readVersionsMetadata', () => { }); describe('versioned site, pluginId=default', () => { - async function loadSite() { + async function loadSite({context}: {context?: Partial} = {}) { const versionedSiteDir = path.resolve( path.join(__dirname, '../../__tests__/__fixtures__', 'versioned-site'), ); @@ -202,6 +240,7 @@ describe('readVersionsMetadata', () => { baseUrl: '/', i18n: DefaultI18N, localizationDir: path.join(versionedSiteDir, 'i18n/en'), + ...context, } as LoadContext; const vCurrent: VersionMetadata = { @@ -436,6 +475,54 @@ describe('readVersionsMetadata', () => { ]); }); + it('works with editUrl and translate=false', async () => { + const {defaultOptions, defaultContext, vCurrent, v101, v100, vWithSlugs} = + await loadSite({ + context: { + i18n: getI18n('en', {translate: false}), + }, + }); + + const versionsMetadata = await readVersionsMetadata({ + options: { + ...defaultOptions, + editUrl: 'https://github.com/facebook/docusaurus/edit/main/website/', + }, + context: defaultContext, + }); + + expect(versionsMetadata).toEqual([ + { + ...vCurrent, + contentPathLocalized: undefined, + editUrl: + 'https://github.com/facebook/docusaurus/edit/main/website/docs', + editUrlLocalized: undefined, + }, + { + ...v101, + contentPathLocalized: undefined, + editUrl: + 'https://github.com/facebook/docusaurus/edit/main/website/versioned_docs/version-1.0.1', + editUrlLocalized: undefined, + }, + { + ...v100, + contentPathLocalized: undefined, + editUrl: + 'https://github.com/facebook/docusaurus/edit/main/website/versioned_docs/version-1.0.0', + editUrlLocalized: undefined, + }, + { + ...vWithSlugs, + contentPathLocalized: undefined, + editUrl: + 'https://github.com/facebook/docusaurus/edit/main/website/versioned_docs/version-withSlugs', + editUrlLocalized: undefined, + }, + ]); + }); + it('works with editUrl and editCurrentVersion=true', async () => { const {defaultOptions, defaultContext, vCurrent, v101, v100, vWithSlugs} = await loadSite(); diff --git a/packages/docusaurus-plugin-content-docs/src/versions/files.ts b/packages/docusaurus-plugin-content-docs/src/versions/files.ts index 33839b9027..d7b8115432 100644 --- a/packages/docusaurus-plugin-content-docs/src/versions/files.ts +++ b/packages/docusaurus-plugin-content-docs/src/versions/files.ts @@ -7,7 +7,11 @@ import path from 'path'; import fs from 'fs-extra'; -import {getPluginI18nPath, DEFAULT_PLUGIN_ID} from '@docusaurus/utils'; +import { + getPluginI18nPath, + getLocaleConfig, + DEFAULT_PLUGIN_ID, +} from '@docusaurus/utils'; import { VERSIONS_JSON_FILE, VERSIONED_DOCS_DIR, @@ -186,11 +190,16 @@ export async function getVersionMetadataPaths({ > > { const isCurrent = versionName === CURRENT_VERSION_NAME; - const contentPathLocalized = getDocsDirPathLocalized({ - localizationDir: context.localizationDir, - pluginId: options.id, - versionName, - }); + + const shouldTranslate = getLocaleConfig(context.i18n).translate; + const contentPathLocalized = shouldTranslate + ? getDocsDirPathLocalized({ + localizationDir: context.localizationDir, + pluginId: options.id, + versionName, + }) + : undefined; + const contentPath = isCurrent ? path.resolve(context.siteDir, options.path) : getVersionDocsDirPath(context.siteDir, options.id, versionName); diff --git a/packages/docusaurus-plugin-content-docs/src/versions/version.ts b/packages/docusaurus-plugin-content-docs/src/versions/version.ts index 5b4ef5c043..a7f85ecbb6 100644 --- a/packages/docusaurus-plugin-content-docs/src/versions/version.ts +++ b/packages/docusaurus-plugin-content-docs/src/versions/version.ts @@ -50,33 +50,47 @@ function getVersionEditUrls({ return {editUrl: undefined, editUrlLocalized: undefined}; } - const editDirPath = options.editCurrentVersion ? options.path : contentPath; - const editDirPathLocalized = options.editCurrentVersion - ? getDocsDirPathLocalized({ - localizationDir: context.localizationDir, - versionName: CURRENT_VERSION_NAME, - pluginId: options.id, - }) - : contentPathLocalized; + // Intermediate var just to please TS not narrowing to "string" + const editUrlOption = options.editUrl; - const versionPathSegment = posixPath( - path.relative(context.siteDir, path.resolve(context.siteDir, editDirPath)), - ); - const versionPathSegmentLocalized = posixPath( - path.relative( - context.siteDir, - path.resolve(context.siteDir, editDirPathLocalized), - ), - ); + const getEditUrl = () => { + const editDirPath = options.editCurrentVersion ? options.path : contentPath; - const editUrl = normalizeUrl([options.editUrl, versionPathSegment]); + return normalizeUrl([ + editUrlOption, + posixPath( + path.relative( + context.siteDir, + path.resolve(context.siteDir, editDirPath), + ), + ), + ]); + }; - const editUrlLocalized = normalizeUrl([ - options.editUrl, - versionPathSegmentLocalized, - ]); + const getEditUrlLocalized = () => { + if (!contentPathLocalized) { + return undefined; + } + const editDirPathLocalized = options.editCurrentVersion + ? getDocsDirPathLocalized({ + localizationDir: context.localizationDir, + versionName: CURRENT_VERSION_NAME, + pluginId: options.id, + }) + : contentPathLocalized; - return {editUrl, editUrlLocalized}; + return normalizeUrl([ + editUrlOption, + posixPath( + path.relative( + context.siteDir, + path.resolve(context.siteDir, editDirPathLocalized), + ), + ), + ]); + }; + + return {editUrl: getEditUrl(), editUrlLocalized: getEditUrlLocalized()}; } /** 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 ecb51dba22..4fb6568e8e 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 @@ -70,6 +70,76 @@ exports[`docusaurus-plugin-content-pages loads simple pages 1`] = ` ] `; +exports[`docusaurus-plugin-content-pages loads simple pages with french translations (translate: false) 1`] = ` +[ + { + "permalink": "/fr/", + "source": "@site/src/pages/index.js", + "type": "jsx", + }, + { + "permalink": "/fr/typescript", + "source": "@site/src/pages/typescript.tsx", + "type": "jsx", + }, + { + "description": "Markdown index page", + "editUrl": undefined, + "frontMatter": { + "custom_frontMatter": "added by parseFrontMatter", + }, + "lastUpdatedAt": undefined, + "lastUpdatedBy": undefined, + "permalink": "/fr/hello/", + "source": "@site/src/pages/hello/index.md", + "title": "Index", + "type": "mdx", + "unlisted": false, + }, + { + "description": "my MDX page", + "editUrl": undefined, + "frontMatter": { + "custom_frontMatter": "added by parseFrontMatter", + "description": "my MDX page", + "slug": "/custom-mdx/slug", + "title": "MDX page", + }, + "lastUpdatedAt": undefined, + "lastUpdatedBy": undefined, + "permalink": "/fr/custom-mdx/slug", + "source": "@site/src/pages/hello/mdxPage.mdx", + "title": "MDX page", + "type": "mdx", + "unlisted": false, + }, + { + "permalink": "/fr/hello/translatedJs", + "source": "@site/src/pages/hello/translatedJs.js", + "type": "jsx", + }, + { + "description": "translated Markdown page", + "editUrl": undefined, + "frontMatter": { + "custom_frontMatter": "added by parseFrontMatter", + }, + "lastUpdatedAt": undefined, + "lastUpdatedBy": undefined, + "permalink": "/fr/hello/translatedMd", + "source": "@site/src/pages/hello/translatedMd.md", + "title": undefined, + "type": "mdx", + "unlisted": false, + }, + { + "permalink": "/fr/hello/world", + "source": "@site/src/pages/hello/world.js", + "type": "jsx", + }, +] +`; + exports[`docusaurus-plugin-content-pages loads simple pages with french translations 1`] = ` [ { diff --git a/packages/docusaurus-plugin-content-pages/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-pages/src/__tests__/index.test.ts index b1f14ebcbb..91efb8c576 100644 --- a/packages/docusaurus-plugin-content-pages/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-pages/src/__tests__/index.test.ts @@ -47,6 +47,25 @@ describe('docusaurus-plugin-content-pages', () => { expect(pagesMetadata).toMatchSnapshot(); }); + it('loads simple pages with french translations (translate: false)', async () => { + const siteDir = path.join(__dirname, '__fixtures__', 'website'); + const context = await loadContext({siteDir, locale: 'fr'}); + context.i18n.localeConfigs.fr.translate = false; + + const plugin = await pluginContentPages( + context, + validateOptions({ + validate: normalizePluginOptions, + options: { + path: 'src/pages', + }, + }), + ); + const pagesMetadata = await plugin.loadContent!(); + + expect(pagesMetadata).toMatchSnapshot(); + }); + it('loads simple pages with last update', async () => { const siteDir = path.join(__dirname, '__fixtures__', 'website'); const context = await loadContext({siteDir}); diff --git a/packages/docusaurus-plugin-content-pages/src/content.ts b/packages/docusaurus-plugin-content-pages/src/content.ts index 97806bfb67..65f1d9b420 100644 --- a/packages/docusaurus-plugin-content-pages/src/content.ts +++ b/packages/docusaurus-plugin-content-pages/src/content.ts @@ -21,10 +21,12 @@ import { getEditUrl, posixPath, getPluginI18nPath, + getContentPathList, + getLocaleConfig, + type ContentPaths, } from '@docusaurus/utils'; import {validatePageFrontMatter} from './frontMatter'; import type {LoadContext} from '@docusaurus/types'; -import type {PagesContentPaths} from './types'; import type { PluginOptions, Metadata, @@ -37,29 +39,29 @@ export function createPagesContentPaths({ }: { context: LoadContext; options: PluginOptions; -}): PagesContentPaths { +}): ContentPaths { const {siteDir, localizationDir} = context; + + const shouldTranslate = getLocaleConfig(context.i18n).translate; return { contentPath: path.resolve(siteDir, options.path), - contentPathLocalized: getPluginI18nPath({ - localizationDir, - pluginName: 'docusaurus-plugin-content-pages', - pluginId: options.id, - }), + contentPathLocalized: shouldTranslate + ? getPluginI18nPath({ + localizationDir, + pluginName: 'docusaurus-plugin-content-pages', + pluginId: options.id, + }) + : undefined, }; } -export function getContentPathList(contentPaths: PagesContentPaths): string[] { - return [contentPaths.contentPathLocalized, contentPaths.contentPath]; -} - const isMarkdownSource = (source: string) => source.endsWith('.md') || source.endsWith('.mdx'); type LoadContentParams = { context: LoadContext; options: PluginOptions; - contentPaths: PagesContentPaths; + contentPaths: ContentPaths; }; export async function loadPagesContent( @@ -158,7 +160,9 @@ async function processPageSourceFile( } else if (typeof editUrl === 'string') { const isLocalized = pagesDirPath === contentPaths.contentPathLocalized; const fileContentPath = - isLocalized && options.editLocalizedFiles + isLocalized && + options.editLocalizedFiles && + contentPaths.contentPathLocalized ? contentPaths.contentPathLocalized : contentPaths.contentPath; diff --git a/packages/docusaurus-plugin-content-pages/src/index.ts b/packages/docusaurus-plugin-content-pages/src/index.ts index bb06e3adcd..aea10501f2 100644 --- a/packages/docusaurus-plugin-content-pages/src/index.ts +++ b/packages/docusaurus-plugin-content-pages/src/index.ts @@ -12,15 +12,12 @@ import { docuHash, addTrailingPathSeparator, createAbsoluteFilePathMatcher, + getContentPathList, DEFAULT_PLUGIN_ID, } from '@docusaurus/utils'; import {createMDXLoaderRule} from '@docusaurus/mdx-loader'; import {createAllRoutes} from './routes'; -import { - createPagesContentPaths, - getContentPathList, - loadPagesContent, -} from './content'; +import {createPagesContentPaths, loadPagesContent} from './content'; import type {LoadContext, Plugin} from '@docusaurus/types'; import type { PluginOptions, diff --git a/packages/docusaurus-plugin-content-pages/src/types.ts b/packages/docusaurus-plugin-content-pages/src/types.ts deleted file mode 100644 index 2d11492cb0..0000000000 --- a/packages/docusaurus-plugin-content-pages/src/types.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * 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. - */ - -export type PagesContentPaths = { - contentPath: string; - contentPathLocalized: string; -}; diff --git a/packages/docusaurus-types/src/i18n.d.ts b/packages/docusaurus-types/src/i18n.d.ts index f8ddf15b83..ea834788e5 100644 --- a/packages/docusaurus-types/src/i18n.d.ts +++ b/packages/docusaurus-types/src/i18n.d.ts @@ -32,6 +32,11 @@ export type I18nLocaleConfig = { * name. */ path: string; + /** + * Should we attempt to translate this locale? + * By default, it will only be run if the `./i18n/` exists. + */ + translate: boolean; }; export type I18nConfig = { diff --git a/packages/docusaurus-utils/src/__tests__/i18nUtils.test.ts b/packages/docusaurus-utils/src/__tests__/i18nUtils.test.ts index 55c0b52e7c..527aa21379 100644 --- a/packages/docusaurus-utils/src/__tests__/i18nUtils.test.ts +++ b/packages/docusaurus-utils/src/__tests__/i18nUtils.test.ts @@ -5,13 +5,15 @@ * LICENSE file in the root directory of this source tree. */ -import path from 'path'; +import * as path from 'path'; import { mergeTranslations, updateTranslationFileMessages, getPluginI18nPath, localizePath, + getLocaleConfig, } from '../i18nUtils'; +import type {I18n, I18nLocaleConfig} from '@docusaurus/types'; describe('mergeTranslations', () => { it('works', () => { @@ -179,3 +181,77 @@ describe('localizePath', () => { ).toBe('/baseUrl/'); }); }); + +describe('getLocaleConfig', () => { + const localeConfigEn: I18nLocaleConfig = { + path: 'path', + direction: 'rtl', + htmlLang: 'en', + calendar: 'calendar', + label: 'EN', + translate: true, + }; + const localeConfigFr: I18nLocaleConfig = { + path: 'path', + direction: 'rtl', + htmlLang: 'fr', + calendar: 'calendar', + label: 'FR', + translate: true, + }; + + function i18n(params: Partial): I18n { + return { + defaultLocale: 'en', + localeConfigs: {}, + locales: ['en'], + path: 'path', + currentLocale: 'en', + ...params, + }; + } + + it('returns single locale config', () => { + expect( + getLocaleConfig( + i18n({currentLocale: 'en', localeConfigs: {en: localeConfigEn}}), + ), + ).toEqual(localeConfigEn); + }); + + it('returns correct locale config among 2', () => { + expect( + getLocaleConfig( + i18n({ + currentLocale: 'fr', + localeConfigs: {en: localeConfigEn, fr: localeConfigFr}, + }), + ), + ).toEqual(localeConfigFr); + }); + + it('accepts locale to look for as param', () => { + expect( + getLocaleConfig( + i18n({ + currentLocale: 'fr', + localeConfigs: {en: localeConfigEn, fr: localeConfigFr}, + }), + 'en', + ), + ).toEqual(localeConfigEn); + }); + + it('throws for locale config that does not exist', () => { + expect(() => + getLocaleConfig( + i18n({ + currentLocale: 'fr', + localeConfigs: {en: localeConfigEn}, + }), + ), + ).toThrowErrorMatchingInlineSnapshot( + `"Can't find locale config for locale \`fr\`"`, + ); + }); +}); diff --git a/packages/docusaurus-utils/src/dataFileUtils.ts b/packages/docusaurus-utils/src/dataFileUtils.ts index decd57da94..8c07f2dd49 100644 --- a/packages/docusaurus-utils/src/dataFileUtils.ts +++ b/packages/docusaurus-utils/src/dataFileUtils.ts @@ -74,7 +74,9 @@ export async function readDataFile(params: DataFileParams): Promise { * in priority. */ export function getContentPathList(contentPaths: ContentPaths): string[] { - return [contentPaths.contentPathLocalized, contentPaths.contentPath]; + return [contentPaths.contentPathLocalized, contentPaths.contentPath].filter( + (p) => p !== undefined, + ); } /** diff --git a/packages/docusaurus-utils/src/i18nUtils.ts b/packages/docusaurus-utils/src/i18nUtils.ts index 15723648d7..8e7080b962 100644 --- a/packages/docusaurus-utils/src/i18nUtils.ts +++ b/packages/docusaurus-utils/src/i18nUtils.ts @@ -7,12 +7,14 @@ import path from 'path'; import _ from 'lodash'; +import logger from '@docusaurus/logger'; import {DEFAULT_PLUGIN_ID} from './constants'; import {normalizeUrl} from './urlUtils'; import type { TranslationFileContent, TranslationFile, I18n, + I18nLocaleConfig, } from '@docusaurus/types'; /** @@ -112,3 +114,17 @@ export function localizePath({ // Url paths; add a trailing slash so it's a valid base URL return normalizeUrl([originalPath, i18n.currentLocale, '/']); } + +// TODO we may extract this to a separate package +// we want to use it on the frontend too +// but "docusaurus-utils-common" (agnostic utils) is not an ideal place since +export function getLocaleConfig(i18n: I18n, locale?: string): I18nLocaleConfig { + const localeToLookFor = locale ?? i18n.currentLocale; + const localeConfig = i18n.localeConfigs[localeToLookFor]; + if (!localeConfig) { + throw new Error( + `Can't find locale config for locale ${logger.code(localeToLookFor)}`, + ); + } + return localeConfig; +} diff --git a/packages/docusaurus-utils/src/index.ts b/packages/docusaurus-utils/src/index.ts index a38a6def09..74b66ca3a9 100644 --- a/packages/docusaurus-utils/src/index.ts +++ b/packages/docusaurus-utils/src/index.ts @@ -34,6 +34,7 @@ export { updateTranslationFileMessages, getPluginI18nPath, localizePath, + getLocaleConfig, } from './i18nUtils'; export {mapAsyncSequential, findAsyncSequential} from './jsUtils'; export { diff --git a/packages/docusaurus-utils/src/markdownLinks.ts b/packages/docusaurus-utils/src/markdownLinks.ts index 1183656aa1..a045ab4cd5 100644 --- a/packages/docusaurus-utils/src/markdownLinks.ts +++ b/packages/docusaurus-utils/src/markdownLinks.ts @@ -20,9 +20,11 @@ export type ContentPaths = { contentPath: string; /** * The absolute path to the localized content directory, like - * `"/i18n/zh-Hans/plugin-content-docs"`. + * `"/i18n/zh-Hans/plugin-content-blog"`. + * + * Undefined when the locale has `translate: false` config */ - contentPathLocalized: string; + contentPathLocalized: string | undefined; }; /** Data structure representing each broken Markdown link to be reported. */ diff --git a/packages/docusaurus/src/commands/build/build.ts b/packages/docusaurus/src/commands/build/build.ts index 7efaa0ca1d..4bfea69201 100644 --- a/packages/docusaurus/src/commands/build/build.ts +++ b/packages/docusaurus/src/commands/build/build.ts @@ -91,7 +91,11 @@ async function getLocalesToBuild({ localizePath, }); - const i18n = await loadI18n(context.siteConfig); + const i18n = await loadI18n({ + siteDir, + config: context.siteConfig, + currentLocale: context.siteConfig.i18n.defaultLocale // Awkward but ok + }); const locales = cliOptions.locale ?? i18n.locales; diff --git a/packages/docusaurus/src/server/__tests__/__fixtures__/custom-i18n-site/i18n/zh-Hans-custom/README.md b/packages/docusaurus/src/server/__tests__/__fixtures__/custom-i18n-site/i18n/zh-Hans-custom/README.md new file mode 100644 index 0000000000..0d736fa79c --- /dev/null +++ b/packages/docusaurus/src/server/__tests__/__fixtures__/custom-i18n-site/i18n/zh-Hans-custom/README.md @@ -0,0 +1 @@ +Since i18n/zh-Hans-custom folder exists, zh-Hans locale should infer to translate = true diff --git a/packages/docusaurus/src/server/__tests__/__fixtures__/load-i18n-site/i18n/de/README.md b/packages/docusaurus/src/server/__tests__/__fixtures__/load-i18n-site/i18n/de/README.md new file mode 100644 index 0000000000..0d58ac5abf --- /dev/null +++ b/packages/docusaurus/src/server/__tests__/__fixtures__/load-i18n-site/i18n/de/README.md @@ -0,0 +1 @@ +Since i18n/de folder exists, de locale should infer to translate = true diff --git a/packages/docusaurus/src/server/__tests__/__fixtures__/load-i18n-site/i18n/fr/README.md b/packages/docusaurus/src/server/__tests__/__fixtures__/load-i18n-site/i18n/fr/README.md new file mode 100644 index 0000000000..8b313dec94 --- /dev/null +++ b/packages/docusaurus/src/server/__tests__/__fixtures__/load-i18n-site/i18n/fr/README.md @@ -0,0 +1 @@ +Since i18n/fr folder exists, fr locale should infer to translate = true diff --git a/packages/docusaurus/src/server/__tests__/__snapshots__/site.test.ts.snap b/packages/docusaurus/src/server/__tests__/__snapshots__/site.test.ts.snap index 40c87e4b93..b15943ef82 100644 --- a/packages/docusaurus/src/server/__tests__/__snapshots__/site.test.ts.snap +++ b/packages/docusaurus/src/server/__tests__/__snapshots__/site.test.ts.snap @@ -20,6 +20,7 @@ exports[`load loads props for site with custom i18n path 1`] = ` "htmlLang": "en", "label": "English", "path": "en-custom", + "translate": false, }, "zh-Hans": { "calendar": "gregory", @@ -27,6 +28,7 @@ exports[`load loads props for site with custom i18n path 1`] = ` "htmlLang": "zh-Hans", "label": "简体中文", "path": "zh-Hans-custom", + "translate": true, }, }, "locales": [ diff --git a/packages/docusaurus/src/server/__tests__/i18n.test.ts b/packages/docusaurus/src/server/__tests__/i18n.test.ts index 205dc7b05c..fb075d8b2b 100644 --- a/packages/docusaurus/src/server/__tests__/i18n.test.ts +++ b/packages/docusaurus/src/server/__tests__/i18n.test.ts @@ -6,23 +6,33 @@ */ import {jest} from '@jest/globals'; +import path from 'path'; import {loadI18n, getDefaultLocaleConfig} from '../i18n'; import {DEFAULT_I18N_CONFIG} from '../configValidation'; import type {DocusaurusConfig, I18nConfig} from '@docusaurus/types'; -function testLocaleConfigsFor(locales: string[]) { - return Object.fromEntries( - locales.map((locale) => [locale, getDefaultLocaleConfig(locale)]), - ); -} +const loadI18nSiteDir = path.resolve( + __dirname, + '__fixtures__', + 'load-i18n-site', +); -function loadI18nTest(i18nConfig: I18nConfig, locale?: string) { - return loadI18n( - { +function loadI18nTest({ + siteDir = loadI18nSiteDir, + i18nConfig, + currentLocale, +}: { + siteDir?: string; + i18nConfig: I18nConfig; + currentLocale: string; +}) { + return loadI18n({ + siteDir, + config: { i18n: i18nConfig, } as DocusaurusConfig, - {locale}, - ); + currentLocale, + }); } describe('defaultLocaleConfig', () => { @@ -109,66 +119,106 @@ describe('loadI18n', () => { }); it('loads I18n for default config', async () => { - await expect(loadI18nTest(DEFAULT_I18N_CONFIG)).resolves.toEqual({ + await expect( + loadI18nTest({ + i18nConfig: DEFAULT_I18N_CONFIG, + currentLocale: 'en', + }), + ).resolves.toEqual({ path: 'i18n', defaultLocale: 'en', locales: ['en'], currentLocale: 'en', - localeConfigs: testLocaleConfigsFor(['en']), + localeConfigs: { + en: { + ...getDefaultLocaleConfig('en'), + translate: false, + }, + }, }); }); it('loads I18n for multi-lang config', async () => { await expect( loadI18nTest({ - path: 'i18n', - defaultLocale: 'fr', - locales: ['en', 'fr', 'de'], - localeConfigs: {}, + i18nConfig: { + path: 'i18n', + defaultLocale: 'fr', + locales: ['en', 'fr', 'de'], + localeConfigs: {}, + }, + currentLocale: 'fr', }), ).resolves.toEqual({ defaultLocale: 'fr', path: 'i18n', locales: ['en', 'fr', 'de'], currentLocale: 'fr', - localeConfigs: testLocaleConfigsFor(['en', 'fr', 'de']), + localeConfigs: { + en: { + ...getDefaultLocaleConfig('en'), + translate: false, + }, + fr: { + ...getDefaultLocaleConfig('fr'), + translate: true, + }, + de: { + ...getDefaultLocaleConfig('de'), + translate: true, + }, + }, }); }); it('loads I18n for multi-locale config with specified locale', async () => { await expect( - loadI18nTest( - { + loadI18nTest({ + i18nConfig: { path: 'i18n', defaultLocale: 'fr', locales: ['en', 'fr', 'de'], localeConfigs: {}, }, - 'de', - ), + currentLocale: 'de', + }), ).resolves.toEqual({ defaultLocale: 'fr', path: 'i18n', locales: ['en', 'fr', 'de'], currentLocale: 'de', - localeConfigs: testLocaleConfigsFor(['en', 'fr', 'de']), + localeConfigs: { + en: { + ...getDefaultLocaleConfig('en'), + translate: false, + }, + fr: { + ...getDefaultLocaleConfig('fr'), + translate: true, + }, + de: { + ...getDefaultLocaleConfig('de'), + translate: true, + }, + }, }); }); it('loads I18n for multi-locale config with some custom locale configs', async () => { await expect( - loadI18nTest( - { + loadI18nTest({ + i18nConfig: { path: 'i18n', defaultLocale: 'fr', locales: ['en', 'fr', 'de'], localeConfigs: { - fr: {label: 'Français'}, - en: {}, + fr: {label: 'Français', translate: false}, + en: {translate: true}, + de: {translate: false}, }, }, - 'de', - ), + currentLocale: 'de', + }), ).resolves.toEqual({ defaultLocale: 'fr', path: 'i18n', @@ -181,23 +231,30 @@ describe('loadI18n', () => { htmlLang: 'fr', calendar: 'gregory', path: 'fr', + translate: false, + }, + en: { + ...getDefaultLocaleConfig('en'), + translate: true, + }, + de: { + ...getDefaultLocaleConfig('de'), + translate: false, }, - en: getDefaultLocaleConfig('en'), - de: getDefaultLocaleConfig('de'), }, }); }); it('warns when trying to load undeclared locale', async () => { - await loadI18nTest( - { + await loadI18nTest({ + i18nConfig: { path: 'i18n', defaultLocale: 'fr', locales: ['en', 'fr', 'de'], localeConfigs: {}, }, - 'it', - ); + currentLocale: 'it', + }); expect(consoleSpy.mock.calls[0]![0]).toMatch( /The locale .*it.* was not found in your site configuration/, ); diff --git a/packages/docusaurus/src/server/i18n.ts b/packages/docusaurus/src/server/i18n.ts index a7b41de4c7..9d97140b3a 100644 --- a/packages/docusaurus/src/server/i18n.ts +++ b/packages/docusaurus/src/server/i18n.ts @@ -5,9 +5,11 @@ * LICENSE file in the root directory of this source tree. */ +import path from 'path'; +import fs from 'fs-extra'; import logger from '@docusaurus/logger'; +import combinePromises from 'combine-promises'; import type {I18n, DocusaurusConfig, I18nLocaleConfig} from '@docusaurus/types'; -import type {LoadContextParams} from './site'; function inferLanguageDisplayName(locale: string) { const tryLocale = (l: string) => { @@ -78,7 +80,9 @@ function getDefaultDirection(localeStr: string) { return textInto.direction; } -export function getDefaultLocaleConfig(locale: string): I18nLocaleConfig { +export function getDefaultLocaleConfig( + locale: string, +): Omit { try { return { label: getDefaultLocaleLabel(locale), @@ -95,14 +99,17 @@ export function getDefaultLocaleConfig(locale: string): I18nLocaleConfig { } } -export async function loadI18n( - config: DocusaurusConfig, - options?: Pick, -): Promise { +export async function loadI18n({ + siteDir, + config, + currentLocale, +}: { + siteDir: string; + config: DocusaurusConfig; + currentLocale: string; +}): Promise { const {i18n: i18nConfig} = config; - const currentLocale = options?.locale ?? i18nConfig.defaultLocale; - if (!i18nConfig.locales.includes(currentLocale)) { logger.warn`The locale name=${currentLocale} was not found in your site configuration: Available locales are: ${i18nConfig.locales} Note: Docusaurus only support running one locale at a time.`; @@ -112,15 +119,36 @@ Note: Docusaurus only support running one locale at a time.`; ? i18nConfig.locales : (i18nConfig.locales.concat(currentLocale) as [string, ...string[]]); - function getLocaleConfig(locale: string): I18nLocaleConfig { - return { + async function getFullLocaleConfig( + locale: string, + ): Promise { + const localeConfigInput = i18nConfig.localeConfigs[locale] ?? {}; + const localeConfig: Omit = { ...getDefaultLocaleConfig(locale), - ...i18nConfig.localeConfigs[locale], + ...localeConfigInput, + }; + + // By default, translations will be enabled if i18n/ dir exists + async function inferTranslate() { + const localizationDir = path.resolve( + siteDir, + i18nConfig.path, + localeConfig.path, + ); + return fs.pathExists(localizationDir); + } + + const translate = localeConfigInput.translate ?? (await inferTranslate()); + return { + ...localeConfig, + translate, }; } - const localeConfigs = Object.fromEntries( - locales.map((locale) => [locale, getLocaleConfig(locale)]), + const localeConfigs = await combinePromises( + Object.fromEntries( + locales.map((locale) => [locale, getFullLocaleConfig(locale)]), + ), ); return { diff --git a/packages/docusaurus/src/server/plugins/__tests__/plugins.test.ts b/packages/docusaurus/src/server/plugins/__tests__/plugins.test.ts index 39c12daa0b..b62276638b 100644 --- a/packages/docusaurus/src/server/plugins/__tests__/plugins.test.ts +++ b/packages/docusaurus/src/server/plugins/__tests__/plugins.test.ts @@ -11,12 +11,16 @@ import {loadPlugins, reloadPlugin} from '../plugins'; import {DEFAULT_FUTURE_CONFIG} from '../../configValidation'; import type {LoadContext, Plugin, PluginConfig} from '@docusaurus/types'; +type TestOptions = {translate?: boolean}; + async function testLoad({ plugins, themes, + options = {}, }: { plugins: PluginConfig[]; themes: PluginConfig[]; + options?: TestOptions; }) { const siteDir = path.join(__dirname, '__fixtures__/site-with-plugin'); @@ -25,6 +29,13 @@ async function testLoad({ siteConfigPath: path.join(siteDir, 'docusaurus.config.js'), generatedFilesDir: path.join(siteDir, '.docusaurus'), outDir: path.join(siteDir, 'build'), + i18n: { + path: 'i18n', + locales: ['en'], + currentLocale: 'en', + defaultLocale: 'en', + localeConfigs: {en: {translate: options.translate ?? true}}, + }, siteConfig: { baseUrl: '/', trailingSlash: true, @@ -49,10 +60,12 @@ const SyntheticPluginNames = [ async function testPlugin( pluginConfig: PluginConfig, + options?: TestOptions, ) { const {context, plugins, routes, globalData} = await testLoad({ plugins: [pluginConfig], themes: [], + options, }); const nonSyntheticPlugins = plugins.filter( @@ -86,65 +99,120 @@ describe('loadPlugins', () => { expect(globalData).toEqual({}); }); - it('typical plugin', async () => { - const {plugin, routes, globalData} = await testPlugin(() => ({ - name: 'plugin-name', - loadContent: () => ({name: 'Toto', age: 42}), - translateContent: ({content}) => ({ - ...content, - name: `${content.name} (translated)`, - }), - contentLoaded({content, actions}) { - actions.addRoute({ - path: '/foo', - component: 'Comp', - modules: {someModule: 'someModulePath'}, - context: {someContext: 'someContextPath'}, - }); - actions.setGlobalData({ - globalName: content.name, - globalAge: content.age, - }); - }, - })); + describe('typical plugin', () => { + function typicalPlugin(options: TestOptions) { + return testPlugin( + () => ({ + name: 'plugin-name', + loadContent: () => ({name: 'Toto', age: 42}), + translateContent: ({content}) => ({ + ...content, + name: `${content.name} (translated)`, + }), + contentLoaded({content, actions}) { + actions.addRoute({ + path: '/foo', + component: 'Comp', + modules: {someModule: 'someModulePath'}, + context: {someContext: 'someContextPath'}, + }); + actions.setGlobalData({ + globalName: content.name, + globalAge: content.age, + }); + }, + }), + options, + ); + } - expect(plugin.content).toMatchInlineSnapshot(` - { - "age": 42, - "name": "Toto (translated)", - } - `); - expect(routes).toMatchInlineSnapshot(` - [ + it('translated: true', async () => { + const {plugin, routes, globalData} = await typicalPlugin({ + translate: true, + }); + + expect(plugin.content).toMatchInlineSnapshot(` + { + "age": 42, + "name": "Toto (translated)", + } + `); + expect(routes).toMatchInlineSnapshot(` + [ + { + "component": "Comp", + "context": { + "data": { + "someContext": "someContextPath", + }, + "plugin": "@generated/plugin-name/default/__plugin.json", + }, + "modules": { + "someModule": "someModulePath", + }, + "path": "/foo/", + "plugin": { + "id": "default", + "name": "plugin-name", + }, + }, + ] + `); + expect(globalData).toMatchInlineSnapshot(` + { + "plugin-name": { + "default": { + "globalAge": 42, + "globalName": "Toto (translated)", + }, + }, + } + `); + }); + + it('translated: false', async () => { + const {plugin, routes, globalData} = await typicalPlugin({ + translate: false, + }); + + expect(plugin.content).toMatchInlineSnapshot(` { - "component": "Comp", - "context": { - "data": { - "someContext": "someContextPath", + "age": 42, + "name": "Toto", + } + `); + expect(routes).toMatchInlineSnapshot(` + [ + { + "component": "Comp", + "context": { + "data": { + "someContext": "someContextPath", + }, + "plugin": "@generated/plugin-name/default/__plugin.json", + }, + "modules": { + "someModule": "someModulePath", + }, + "path": "/foo/", + "plugin": { + "id": "default", + "name": "plugin-name", + }, + }, + ] + `); + expect(globalData).toMatchInlineSnapshot(` + { + "plugin-name": { + "default": { + "globalAge": 42, + "globalName": "Toto", }, - "plugin": "@generated/plugin-name/default/__plugin.json", }, - "modules": { - "someModule": "someModulePath", - }, - "path": "/foo/", - "plugin": { - "id": "default", - "name": "plugin-name", - }, - }, - ] - `); - expect(globalData).toMatchInlineSnapshot(` - { - "plugin-name": { - "default": { - "globalAge": 42, - "globalName": "Toto (translated)", - }, - }, - } - `); + } + `); + }); }); it('plugin with options', async () => { diff --git a/packages/docusaurus/src/server/plugins/plugins.ts b/packages/docusaurus/src/server/plugins/plugins.ts index f16007a5ec..b697878624 100644 --- a/packages/docusaurus/src/server/plugins/plugins.ts +++ b/packages/docusaurus/src/server/plugins/plugins.ts @@ -6,6 +6,7 @@ */ import {PerfLogger} from '@docusaurus/logger'; +import {getLocaleConfig} from '@docusaurus/utils'; import {initPlugins} from './init'; import {createBootstrapPlugin, createMDXFallbackPlugin} from './synthetic'; import {localizePluginTranslationFile} from '../translations/translations'; @@ -81,14 +82,20 @@ async function executePluginContentLoading({ plugin.loadContent?.(), ); - content = await PerfLogger.async('translatePluginContent()', () => - translatePluginContent({ - plugin, - content, - context, - }), - ); + const shouldTranslate = getLocaleConfig(context.i18n).translate; + if (shouldTranslate) { + content = await PerfLogger.async('translatePluginContent()', () => + translatePluginContent({ + plugin, + content, + context, + }), + ); + } + + // If shouldTranslate === false, we still need the code translations + // Otherwise an unlocalized French site would show code strings in English const defaultCodeTranslations = (await PerfLogger.async('getDefaultCodeTranslationMessages()', () => plugin.getDefaultCodeTranslationMessages?.(), diff --git a/packages/docusaurus/src/server/site.ts b/packages/docusaurus/src/server/site.ts index d8fd3f242c..a48e68dd8a 100644 --- a/packages/docusaurus/src/server/site.ts +++ b/packages/docusaurus/src/server/site.ts @@ -10,6 +10,7 @@ import { localizePath, DEFAULT_BUILD_DIR_NAME, GENERATED_FILES_DIR_NAME, + getLocaleConfig, } from '@docusaurus/utils'; import {PerfLogger} from '@docusaurus/logger'; import combinePromises from 'combine-promises'; @@ -96,7 +97,11 @@ export async function loadContext( siteConfig: initialSiteConfig, }); - const i18n = await loadI18n(initialSiteConfig, {locale}); + const i18n = await loadI18n({ + siteDir, + config: initialSiteConfig, + currentLocale: locale ?? initialSiteConfig.i18n.defaultLocale, + }); const baseUrl = localizePath({ path: initialSiteConfig.baseUrl, @@ -113,7 +118,7 @@ export async function loadContext( const localizationDir = path.resolve( siteDir, i18n.path, - i18n.localeConfigs[i18n.currentLocale]!.path, + getLocaleConfig(i18n).path, ); const siteConfig: DocusaurusConfig = {...initialSiteConfig, baseUrl}; diff --git a/packages/docusaurus/src/webpack/client.ts b/packages/docusaurus/src/webpack/client.ts index 66e32fae36..660e7f8119 100644 --- a/packages/docusaurus/src/webpack/client.ts +++ b/packages/docusaurus/src/webpack/client.ts @@ -11,6 +11,7 @@ import {BundleAnalyzerPlugin} from 'webpack-bundle-analyzer'; import ReactLoadableSSRAddon from 'react-loadable-ssr-addon-v5-slorber'; import HtmlWebpackPlugin from 'html-webpack-plugin'; import {getProgressBarPlugin} from '@docusaurus/bundler'; +import {getLocaleConfig} from '@docusaurus/utils'; import {createBaseConfig} from './base'; import ChunkAssetPlugin from './plugins/ChunkAssetPlugin'; import ForceTerminatePlugin from './plugins/ForceTerminatePlugin'; @@ -117,7 +118,7 @@ export async function createStartClientConfig({ headTags, preBodyTags, postBodyTags, - lang: props.i18n.localeConfigs[props.i18n.currentLocale]!.htmlLang, + lang: getLocaleConfig(props.i18n).htmlLang, }), ], }, diff --git a/website/docs/api/docusaurus.config.js.mdx b/website/docs/api/docusaurus.config.js.mdx index 1ac4d1e07b..351da0421a 100644 --- a/website/docs/api/docusaurus.config.js.mdx +++ b/website/docs/api/docusaurus.config.js.mdx @@ -151,6 +151,7 @@ export default { htmlLang: 'en-US', calendar: 'gregory', path: 'en', + translate: false, }, fa: { label: 'فارسی', @@ -158,6 +159,7 @@ export default { htmlLang: 'fa-IR', calendar: 'persian', path: 'fa', + translate: true, }, }, }, @@ -172,7 +174,8 @@ export default { - `direction`: `ltr` (default) or `rtl` (for [right-to-left languages](https://developer.mozilla.org/en-US/docs/Glossary/rtl) like Farsi, Arabic, Hebrew, etc.). Used to select the locale's CSS and HTML meta attribute. - `htmlLang`: BCP 47 language tag to use in `` (or any other DOM tag name) and in `` - `calendar`: the [calendar](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/calendar) used to calculate the date era. Note that it doesn't control the actual string displayed: `MM/DD/YYYY` and `DD/MM/YYYY` are both `gregory`. To choose the format (`DD/MM/YYYY` or `MM/DD/YYYY`), set your locale name to `en-GB` or `en-US` (`en` means `en-US`). - - `path`: Root folder that all plugin localization folders of this locale are relative to. Will be resolved against `i18n.path`. Defaults to the locale's name. Note: this has no effect on the locale's `baseUrl`—customization of base URL is a work-in-progress. + - `path`: Root folder that all plugin localization folders of this locale are relative to. Will be resolved against `i18n.path`. Defaults to the locale's name (`i18n/`). Note: this has no effect on the locale's `baseUrl`—customization of base URL is a work-in-progress. + - `translate`: Should we run the translation process for this locale? By default, it is enabled if the `i18n/` folder exists ### `future` {#future}