mirror of
https://github.com/facebook/docusaurus.git
synced 2025-12-25 17:22:50 +00:00
feat(core): add `i18n.localeConfigs.translate` + skip translation process if `i18n/<locale>` dir doesn't exist (#11304)
This commit is contained in:
parent
e0524a5c84
commit
1808945c1f
|
|
@ -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<I18nLocaleConfig>,
|
||||
): 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<PluginOptions> = {},
|
||||
i18n: I18n = DefaultI18N,
|
||||
i18nOptions: Partial<I18n> = {},
|
||||
) => {
|
||||
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');
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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}",
|
||||
]
|
||||
`;
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -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<PluginOptions>; locale?: string} = {
|
||||
loadSiteOptions: {
|
||||
options?: Partial<PluginOptions>;
|
||||
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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Options, PluginOptions>,
|
||||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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<I18nLocaleConfig>,
|
||||
): 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<LoadContext>} = {}) {
|
||||
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<LoadContext>} = {}) {
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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()};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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`] = `
|
||||
[
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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/<locale>` exists.
|
||||
*/
|
||||
translate: boolean;
|
||||
};
|
||||
|
||||
export type I18nConfig = {
|
||||
|
|
|
|||
|
|
@ -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>): 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\`"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -74,7 +74,9 @@ export async function readDataFile(params: DataFileParams): Promise<unknown> {
|
|||
* in priority.
|
||||
*/
|
||||
export function getContentPathList(contentPaths: ContentPaths): string[] {
|
||||
return [contentPaths.contentPathLocalized, contentPaths.contentPath];
|
||||
return [contentPaths.contentPathLocalized, contentPaths.contentPath].filter(
|
||||
(p) => p !== undefined,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ export {
|
|||
updateTranslationFileMessages,
|
||||
getPluginI18nPath,
|
||||
localizePath,
|
||||
getLocaleConfig,
|
||||
} from './i18nUtils';
|
||||
export {mapAsyncSequential, findAsyncSequential} from './jsUtils';
|
||||
export {
|
||||
|
|
|
|||
|
|
@ -20,9 +20,11 @@ export type ContentPaths = {
|
|||
contentPath: string;
|
||||
/**
|
||||
* The absolute path to the localized content directory, like
|
||||
* `"<siteDir>/i18n/zh-Hans/plugin-content-docs"`.
|
||||
* `"<siteDir>/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. */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
Since i18n/zh-Hans-custom folder exists, zh-Hans locale should infer to translate = true
|
||||
1
packages/docusaurus/src/server/__tests__/__fixtures__/load-i18n-site/i18n/de/README.md
generated
Normal file
1
packages/docusaurus/src/server/__tests__/__fixtures__/load-i18n-site/i18n/de/README.md
generated
Normal file
|
|
@ -0,0 +1 @@
|
|||
Since i18n/de folder exists, de locale should infer to translate = true
|
||||
1
packages/docusaurus/src/server/__tests__/__fixtures__/load-i18n-site/i18n/fr/README.md
generated
Normal file
1
packages/docusaurus/src/server/__tests__/__fixtures__/load-i18n-site/i18n/fr/README.md
generated
Normal file
|
|
@ -0,0 +1 @@
|
|||
Since i18n/fr folder exists, fr locale should infer to translate = true
|
||||
|
|
@ -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": [
|
||||
|
|
|
|||
|
|
@ -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/,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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<I18nLocaleConfig, 'translate'> {
|
||||
try {
|
||||
return {
|
||||
label: getDefaultLocaleLabel(locale),
|
||||
|
|
@ -95,14 +99,17 @@ export function getDefaultLocaleConfig(locale: string): I18nLocaleConfig {
|
|||
}
|
||||
}
|
||||
|
||||
export async function loadI18n(
|
||||
config: DocusaurusConfig,
|
||||
options?: Pick<LoadContextParams, 'locale'>,
|
||||
): Promise<I18n> {
|
||||
export async function loadI18n({
|
||||
siteDir,
|
||||
config,
|
||||
currentLocale,
|
||||
}: {
|
||||
siteDir: string;
|
||||
config: DocusaurusConfig;
|
||||
currentLocale: string;
|
||||
}): Promise<I18n> {
|
||||
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<I18nLocaleConfig> {
|
||||
const localeConfigInput = i18nConfig.localeConfigs[locale] ?? {};
|
||||
const localeConfig: Omit<I18nLocaleConfig, 'translate'> = {
|
||||
...getDefaultLocaleConfig(locale),
|
||||
...i18nConfig.localeConfigs[locale],
|
||||
...localeConfigInput,
|
||||
};
|
||||
|
||||
// By default, translations will be enabled if i18n/<locale> 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 {
|
||||
|
|
|
|||
|
|
@ -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<any>[];
|
||||
themes: PluginConfig<any>[];
|
||||
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<Content = unknown>(
|
||||
pluginConfig: PluginConfig<Content>,
|
||||
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 () => {
|
||||
|
|
|
|||
|
|
@ -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?.(),
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}),
|
||||
],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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 `<html lang="...">` (or any other DOM tag name) and in `<link ... hreflang="...">`
|
||||
- `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/<locale>`). 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/<locale>` folder exists
|
||||
|
||||
### `future` {#future}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue