feat(v2): core v2 i18n support + Docusaurus site Crowdin integration (#3325)

* docs i18n initial poc

* docs i18n initial poc

* docs i18n initial poc

* docs i18n initial poc

* crowdin-v2 attempt

* fix source

* use crowdin env variable

* try to install crowdin on netlify

* try to install crowdin on netlify

* try to use crowdin jar directly

* try to curl the crowdin jar

* add java version cmd

* try to run crowdin jar in netlify

* fix translatedDocsDirPath

* fix loadContext issue due to site baseUrl not being modified in generted config file

* real validateLocalesFile

* add locale option to deploy command

* better LocalizationFile type

* create util getPluginI18nPath

* better core localization context loading code

* More explicit VersionMetadata type for localized docs folders

* Ability to translate blog posts with Crowdin!

* blog: refactor markdown loader + report broken links + try to get linkify working better

* upgrade crowdin config to upload all docs folder files except source code related files

* try to support translated pages

* make markdown pages translation work

* add write-translations cli command template

* fix site not  reloaded with correct options

* refactor a bit the read/write of @generated/i18n.json file

* Add <Translate> + translate() API + use it on the docusaurus homepage

* watch locale translation dir

* early POC of adding babel parsing for translation extraction

* fs.stat => pathExists

* add install:fast script

* TSC: noUnusedLocals false as it's already checked  by eslint

* POC of extracting translations from source code

* minor typo

* fix extracted key to code

* initial docs extracted translations

* stable plugin translations POC

* add crowdin commands

* quickfix for i18n deployment

* POC  of themeConfig translation

* add ability to have localized site without path prefix

* sidebar typo

* refactor translation system to output multiple translation files

* translate properly  the docs plugin

* improve theme classic translation

* rework translation extractor to handle new Chrome I18n JSON format (include id/description)

* writeTranslations: allow to pass locales cli arg

* fix ThemeConfig TS issues

* fix localizePath errors

* temporary add write-translations to netlify deploy preview

* complete example of french translated folder

* update fr folder

* remove all translations from repo

* minor translation  refactors

* fix all docs-related tests

* fix blog feed tests

* fix last blog tests

* refactor i18n context a bit, extract codeTranslations in an extra generated file

* improve @generated/i18n type

* fix some i18n todos

* minor refactor

* fix logo typing issue after merge

* move i18n.json to siteConfig instead

* try to fix windows CI build

* fix config test

* attempt to fix windows non-posix path

* increase v1 minify css jest timeout due to flaky test

* proper support for localizePath on windows

* remove non-functional install:fast

* docs, fix docsDirPathLocalized

* fix Docs i18n / md linkify issues

* ensure theme-classic swizzling will use "nextjs" sources (transpiled less aggressively, to make them human readable)

* fix some snapshots

* improve themeConfig translation code

* refactor a bit getPluginI18nPath

* readTranslationFileContent => ensure files are valid, fail fast

* fix versions tests

* add extractSourceCodeAstTranslations comments/resource links

* ignore eslint: packages/docusaurus-theme-classic/lib-next/

* fix windows CI with cross-env

* crowdin ignore .DS_Store

* improve writeTranslations + add exhaustive tests for translations.ts

* remove typo

* Wire currentLocale to algolia search

* improve i18n locale error

* Add tests for translationsExtractor.ts

* better code translation extraction regarding statically evaluable code

* fix typo

* fix typo

* improve theme-classic transpilation

* refactor  +  add i18n tests

* typo

* test new utils

* add missing snapshots

* fix snapshot

* blog onBrokenMarkdownLink

* add sidebars tests

* theme-classic index should now use ES modules

* tests for theme-classic translations

* useless comment

* add more translation tests

* simplify/cleanup writeTranslations

* try to fix Netlify fr deployment

* blog: test translated md is used during feed generation

* blog: better i18n tests regarding editUrl + md translation application

* more i18n tests for docs plugin

* more i18n tests for docs plugin

* Add tests for pages i18n

* polish docusaurus build i18n logs
This commit is contained in:
Sébastien Lorber 2020-11-26 12:16:46 +01:00 committed by GitHub
parent 85fe96d112
commit 3166fab307
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
107 changed files with 5447 additions and 649 deletions

View file

@ -0,0 +1,5 @@
---
title: Team title translated
---
Team current version (translated)

View file

@ -616,18 +616,6 @@ Object {
exports[`versioned website (community) content: data 1`] = `
Object {
"site-community-team-md-9d8.json": "{
\\"unversionedId\\": \\"team\\",
\\"id\\": \\"team\\",
\\"isDocsHomePage\\": false,
\\"title\\": \\"team\\",
\\"description\\": \\"Team current version\\",
\\"source\\": \\"@site/community/team.md\\",
\\"slug\\": \\"/team\\",
\\"permalink\\": \\"/community/next/team\\",
\\"version\\": \\"current\\",
\\"sidebar\\": \\"community\\"
}",
"site-community-versioned-docs-version-1-0-0-team-md-359.json": "{
\\"unversionedId\\": \\"team\\",
\\"id\\": \\"version-1.0.0/team\\",
@ -639,6 +627,18 @@ Object {
\\"permalink\\": \\"/community/team\\",
\\"version\\": \\"1.0.0\\",
\\"sidebar\\": \\"version-1.0.0/community\\"
}",
"site-i-18-n-en-docusaurus-plugin-content-docs-community-current-team-md-7e5.json": "{
\\"unversionedId\\": \\"team\\",
\\"id\\": \\"team\\",
\\"isDocsHomePage\\": false,
\\"title\\": \\"Team title translated\\",
\\"description\\": \\"Team current version (translated)\\",
\\"source\\": \\"@site/i18n/en/docusaurus-plugin-content-docs-community/current/team.md\\",
\\"slug\\": \\"/team\\",
\\"permalink\\": \\"/community/next/team\\",
\\"version\\": \\"current\\",
\\"sidebar\\": \\"community\\"
}",
"version-1-0-0-metadata-prop-608.json": "{
\\"pluginId\\": \\"community\\",
@ -667,7 +667,7 @@ Object {
\\"community\\": [
{
\\"type\\": \\"link\\",
\\"label\\": \\"team\\",
\\"label\\": \\"Team title translated\\",
\\"href\\": \\"/community/next/team\\"
}
]
@ -734,7 +734,7 @@ Array [
"component": "@theme/DocItem",
"exact": true,
"modules": Object {
"content": "@site/community/team.md",
"content": "@site/i18n/en/docusaurus-plugin-content-docs-community/current/team.md",
},
"path": "/community/next/team",
},
@ -930,6 +930,22 @@ Object {
\\"slug\\": \\"/tryToEscapeSlug\\",
\\"permalink\\": \\"/docs/next/tryToEscapeSlug\\",
\\"version\\": \\"current\\"
}",
"site-i-18-n-en-docusaurus-plugin-content-docs-version-1-0-0-hello-md-fe5.json": "{
\\"unversionedId\\": \\"hello\\",
\\"id\\": \\"version-1.0.0/hello\\",
\\"isDocsHomePage\\": true,
\\"title\\": \\"hello\\",
\\"description\\": \\"Hello 1.0.0 ! (translated)\\",
\\"source\\": \\"@site/i18n/en/docusaurus-plugin-content-docs/version-1.0.0/hello.md\\",
\\"slug\\": \\"/\\",
\\"permalink\\": \\"/docs/1.0.0/\\",
\\"version\\": \\"1.0.0\\",
\\"sidebar\\": \\"version-1.0.0/docs\\",
\\"previous\\": {
\\"title\\": \\"baz\\",
\\"permalink\\": \\"/docs/1.0.0/foo/baz\\"
}
}",
"site-versioned-docs-version-1-0-0-foo-bar-md-7a6.json": "{
\\"unversionedId\\": \\"foo/bar\\",
@ -966,22 +982,6 @@ Object {
\\"title\\": \\"hello\\",
\\"permalink\\": \\"/docs/1.0.0/\\"
}
}",
"site-versioned-docs-version-1-0-0-hello-md-3ef.json": "{
\\"unversionedId\\": \\"hello\\",
\\"id\\": \\"version-1.0.0/hello\\",
\\"isDocsHomePage\\": true,
\\"title\\": \\"hello\\",
\\"description\\": \\"Hello 1.0.0 !\\",
\\"source\\": \\"@site/versioned_docs/version-1.0.0/hello.md\\",
\\"slug\\": \\"/\\",
\\"permalink\\": \\"/docs/1.0.0/\\",
\\"version\\": \\"1.0.0\\",
\\"sidebar\\": \\"version-1.0.0/docs\\",
\\"previous\\": {
\\"title\\": \\"baz\\",
\\"permalink\\": \\"/docs/1.0.0/foo/baz\\"
}
}",
"site-versioned-docs-version-1-0-1-foo-bar-md-7a3.json": "{
\\"unversionedId\\": \\"foo/bar\\",
@ -1410,7 +1410,7 @@ Array [
"component": "@theme/DocItem",
"exact": true,
"modules": Object {
"content": "@site/versioned_docs/version-1.0.0/hello.md",
"content": "@site/i18n/en/docusaurus-plugin-content-docs/version-1.0.0/hello.md",
},
"path": "/docs/1.0.0/",
},

View file

@ -0,0 +1,487 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`getLoadedContentTranslationFiles should return translation files matching snapshot 1`] = `
Array [
Object {
"content": Object {
"sidebar.docs.category.Getting started": Object {
"description": "The label for category Getting started in sidebar docs",
"message": "Getting started",
},
"sidebar.docs.link.Link label": Object {
"description": "The label for link Link label in sidebar docs, linking to https://facebook.com",
"message": "Link label",
},
"version.label": Object {
"description": "The label for version current",
"message": "current label",
},
},
"path": "current",
},
Object {
"content": Object {
"sidebar.docs.category.Getting started": Object {
"description": "The label for category Getting started in sidebar docs",
"message": "Getting started",
},
"sidebar.docs.link.Link label": Object {
"description": "The label for link Link label in sidebar docs, linking to https://facebook.com",
"message": "Link label",
},
"version.label": Object {
"description": "The label for version 2.0.0",
"message": "2.0.0 label",
},
},
"path": "version-2.0.0",
},
Object {
"content": Object {
"sidebar.docs.category.Getting started": Object {
"description": "The label for category Getting started in sidebar docs",
"message": "Getting started",
},
"sidebar.docs.link.Link label": Object {
"description": "The label for link Link label in sidebar docs, linking to https://facebook.com",
"message": "Link label",
},
"version.label": Object {
"description": "The label for version 1.0.0",
"message": "1.0.0 label",
},
},
"path": "version-1.0.0",
},
]
`;
exports[`translateLoadedContent should return translated loaded content matching snapshot 1`] = `
Object {
"loadedVersions": Array [
Object {
"docs": Array [
Object {
"description": "doc1 description",
"editUrl": "any",
"id": "doc1",
"isDocsHomePage": false,
"lastUpdatedAt": 0,
"lastUpdatedBy": "any",
"next": undefined,
"permalink": "any",
"previous": undefined,
"sidebar_label": "doc1 title",
"slug": "any",
"source": "any",
"title": "doc1 title",
"unversionedId": "any",
"version": "any",
},
Object {
"description": "doc2 description",
"editUrl": "any",
"id": "doc2",
"isDocsHomePage": false,
"lastUpdatedAt": 0,
"lastUpdatedBy": "any",
"next": undefined,
"permalink": "any",
"previous": undefined,
"sidebar_label": "doc2 title",
"slug": "any",
"source": "any",
"title": "doc2 title",
"unversionedId": "any",
"version": "any",
},
Object {
"description": "doc3 description",
"editUrl": "any",
"id": "doc3",
"isDocsHomePage": false,
"lastUpdatedAt": 0,
"lastUpdatedBy": "any",
"next": undefined,
"permalink": "any",
"previous": undefined,
"sidebar_label": "doc3 title",
"slug": "any",
"source": "any",
"title": "doc3 title",
"unversionedId": "any",
"version": "any",
},
Object {
"description": "doc4 description",
"editUrl": "any",
"id": "doc4",
"isDocsHomePage": false,
"lastUpdatedAt": 0,
"lastUpdatedBy": "any",
"next": undefined,
"permalink": "any",
"previous": undefined,
"sidebar_label": "doc4 title",
"slug": "any",
"source": "any",
"title": "doc4 title",
"unversionedId": "any",
"version": "any",
},
Object {
"description": "doc5 description",
"editUrl": "any",
"id": "doc5",
"isDocsHomePage": false,
"lastUpdatedAt": 0,
"lastUpdatedBy": "any",
"next": undefined,
"permalink": "any",
"previous": undefined,
"sidebar_label": "doc5 title",
"slug": "any",
"source": "any",
"title": "doc5 title",
"unversionedId": "any",
"version": "any",
},
],
"docsDirPath": "any",
"docsDirPathLocalized": "any",
"isLast": true,
"mainDocId": "",
"permalinkToSidebar": Object {},
"routePriority": undefined,
"sidebarFilePath": "any",
"sidebars": Object {
"docs": Array [
Object {
"collapsed": false,
"items": Array [
Object {
"id": "doc1",
"type": "doc",
},
Object {
"id": "doc2",
"type": "doc",
},
Object {
"href": "https://facebook.com",
"label": "Link label (translated)",
"type": "link",
},
Object {
"id": "doc1",
"type": "ref",
},
],
"label": "Getting started (translated)",
"type": "category",
},
Object {
"id": "doc3",
"type": "doc",
},
],
"otherSidebar": Array [
Object {
"id": "doc4",
"type": "doc",
},
Object {
"id": "doc5",
"type": "doc",
},
],
},
"versionLabel": "current label (translated)",
"versionName": "current",
"versionPath": "/docs/",
},
Object {
"docs": Array [
Object {
"description": "doc1 description",
"editUrl": "any",
"id": "doc1",
"isDocsHomePage": false,
"lastUpdatedAt": 0,
"lastUpdatedBy": "any",
"next": undefined,
"permalink": "any",
"previous": undefined,
"sidebar_label": "doc1 title",
"slug": "any",
"source": "any",
"title": "doc1 title",
"unversionedId": "any",
"version": "any",
},
Object {
"description": "doc2 description",
"editUrl": "any",
"id": "doc2",
"isDocsHomePage": false,
"lastUpdatedAt": 0,
"lastUpdatedBy": "any",
"next": undefined,
"permalink": "any",
"previous": undefined,
"sidebar_label": "doc2 title",
"slug": "any",
"source": "any",
"title": "doc2 title",
"unversionedId": "any",
"version": "any",
},
Object {
"description": "doc3 description",
"editUrl": "any",
"id": "doc3",
"isDocsHomePage": false,
"lastUpdatedAt": 0,
"lastUpdatedBy": "any",
"next": undefined,
"permalink": "any",
"previous": undefined,
"sidebar_label": "doc3 title",
"slug": "any",
"source": "any",
"title": "doc3 title",
"unversionedId": "any",
"version": "any",
},
Object {
"description": "doc4 description",
"editUrl": "any",
"id": "doc4",
"isDocsHomePage": false,
"lastUpdatedAt": 0,
"lastUpdatedBy": "any",
"next": undefined,
"permalink": "any",
"previous": undefined,
"sidebar_label": "doc4 title",
"slug": "any",
"source": "any",
"title": "doc4 title",
"unversionedId": "any",
"version": "any",
},
Object {
"description": "doc5 description",
"editUrl": "any",
"id": "doc5",
"isDocsHomePage": false,
"lastUpdatedAt": 0,
"lastUpdatedBy": "any",
"next": undefined,
"permalink": "any",
"previous": undefined,
"sidebar_label": "doc5 title",
"slug": "any",
"source": "any",
"title": "doc5 title",
"unversionedId": "any",
"version": "any",
},
],
"docsDirPath": "any",
"docsDirPathLocalized": "any",
"isLast": true,
"mainDocId": "",
"permalinkToSidebar": Object {},
"routePriority": undefined,
"sidebarFilePath": "any",
"sidebars": Object {
"docs": Array [
Object {
"collapsed": false,
"items": Array [
Object {
"id": "doc1",
"type": "doc",
},
Object {
"id": "doc2",
"type": "doc",
},
Object {
"href": "https://facebook.com",
"label": "Link label (translated)",
"type": "link",
},
Object {
"id": "doc1",
"type": "ref",
},
],
"label": "Getting started (translated)",
"type": "category",
},
Object {
"id": "doc3",
"type": "doc",
},
],
"otherSidebar": Array [
Object {
"id": "doc4",
"type": "doc",
},
Object {
"id": "doc5",
"type": "doc",
},
],
},
"versionLabel": "2.0.0 label (translated)",
"versionName": "2.0.0",
"versionPath": "/docs/",
},
Object {
"docs": Array [
Object {
"description": "doc1 description",
"editUrl": "any",
"id": "doc1",
"isDocsHomePage": false,
"lastUpdatedAt": 0,
"lastUpdatedBy": "any",
"next": undefined,
"permalink": "any",
"previous": undefined,
"sidebar_label": "doc1 title",
"slug": "any",
"source": "any",
"title": "doc1 title",
"unversionedId": "any",
"version": "any",
},
Object {
"description": "doc2 description",
"editUrl": "any",
"id": "doc2",
"isDocsHomePage": false,
"lastUpdatedAt": 0,
"lastUpdatedBy": "any",
"next": undefined,
"permalink": "any",
"previous": undefined,
"sidebar_label": "doc2 title",
"slug": "any",
"source": "any",
"title": "doc2 title",
"unversionedId": "any",
"version": "any",
},
Object {
"description": "doc3 description",
"editUrl": "any",
"id": "doc3",
"isDocsHomePage": false,
"lastUpdatedAt": 0,
"lastUpdatedBy": "any",
"next": undefined,
"permalink": "any",
"previous": undefined,
"sidebar_label": "doc3 title",
"slug": "any",
"source": "any",
"title": "doc3 title",
"unversionedId": "any",
"version": "any",
},
Object {
"description": "doc4 description",
"editUrl": "any",
"id": "doc4",
"isDocsHomePage": false,
"lastUpdatedAt": 0,
"lastUpdatedBy": "any",
"next": undefined,
"permalink": "any",
"previous": undefined,
"sidebar_label": "doc4 title",
"slug": "any",
"source": "any",
"title": "doc4 title",
"unversionedId": "any",
"version": "any",
},
Object {
"description": "doc5 description",
"editUrl": "any",
"id": "doc5",
"isDocsHomePage": false,
"lastUpdatedAt": 0,
"lastUpdatedBy": "any",
"next": undefined,
"permalink": "any",
"previous": undefined,
"sidebar_label": "doc5 title",
"slug": "any",
"source": "any",
"title": "doc5 title",
"unversionedId": "any",
"version": "any",
},
],
"docsDirPath": "any",
"docsDirPathLocalized": "any",
"isLast": true,
"mainDocId": "",
"permalinkToSidebar": Object {},
"routePriority": undefined,
"sidebarFilePath": "any",
"sidebars": Object {
"docs": Array [
Object {
"collapsed": false,
"items": Array [
Object {
"id": "doc1",
"type": "doc",
},
Object {
"id": "doc2",
"type": "doc",
},
Object {
"href": "https://facebook.com",
"label": "Link label (translated)",
"type": "link",
},
Object {
"id": "doc1",
"type": "ref",
},
],
"label": "Getting started (translated)",
"type": "category",
},
Object {
"id": "doc3",
"type": "doc",
},
],
"otherSidebar": Array [
Object {
"id": "doc4",
"type": "doc",
},
Object {
"id": "doc5",
"type": "doc",
},
],
},
"versionLabel": "1.0.0 label (translated)",
"versionName": "1.0.0",
"versionPath": "/docs/",
},
],
}
`;

View file

@ -42,6 +42,7 @@ ${markdown}
source,
content,
lastUpdate: {},
filePath: source,
};
};
@ -57,7 +58,7 @@ function createTestUtils({
options: MetadataOptions;
}) {
async function readDoc(docFileSource: string) {
return readDocFile(versionMetadata.docsDirPath, docFileSource, options);
return readDocFile(versionMetadata, docFileSource, options);
}
function processDocFile(docFile: DocFile) {
return processDocMetadata({
@ -110,30 +111,41 @@ function createTestUtils({
}
describe('simple site', () => {
const siteDir = path.join(fixtureDir, 'simple-site');
const context = loadContext(siteDir);
const options = {
id: DEFAULT_PLUGIN_ID,
...DEFAULT_OPTIONS,
};
const versionsMetadata = readVersionsMetadata({
context,
options: {
async function loadSite() {
const siteDir = path.join(fixtureDir, 'simple-site');
const context = await loadContext(siteDir);
const options = {
id: DEFAULT_PLUGIN_ID,
...DEFAULT_OPTIONS,
},
});
expect(versionsMetadata.length).toEqual(1);
const [currentVersion] = versionsMetadata;
};
const versionsMetadata = readVersionsMetadata({
context,
options: {
id: DEFAULT_PLUGIN_ID,
...DEFAULT_OPTIONS,
},
});
expect(versionsMetadata.length).toEqual(1);
const [currentVersion] = versionsMetadata;
const defaultTestUtils = createTestUtils({
siteDir,
context,
options,
versionMetadata: currentVersion,
});
const defaultTestUtils = createTestUtils({
siteDir,
context,
options,
versionMetadata: currentVersion,
});
return {
siteDir,
context,
options,
versionsMetadata,
defaultTestUtils,
currentVersion,
};
}
test('readVersionDocs', async () => {
const {options, currentVersion} = await loadSite();
const docs = await readVersionDocs(currentVersion, options);
expect(docs.map((doc) => doc.source).sort()).toEqual(
[
@ -155,6 +167,7 @@ describe('simple site', () => {
});
test('normal docs', async () => {
const {defaultTestUtils} = await loadSite();
await defaultTestUtils.testMeta(path.join('foo', 'bar.md'), {
version: 'current',
id: 'foo/bar',
@ -178,6 +191,8 @@ describe('simple site', () => {
});
test('homePageId doc', async () => {
const {siteDir, context, options, currentVersion} = await loadSite();
const testUtilsLocal = createTestUtils({
siteDir,
context,
@ -198,6 +213,8 @@ describe('simple site', () => {
});
test('homePageId doc nested', async () => {
const {siteDir, context, options, currentVersion} = await loadSite();
const testUtilsLocal = createTestUtils({
siteDir,
context,
@ -218,6 +235,8 @@ describe('simple site', () => {
});
test('docs with editUrl', async () => {
const {siteDir, context, options, currentVersion} = await loadSite();
const testUtilsLocal = createTestUtils({
siteDir,
context,
@ -243,6 +262,8 @@ describe('simple site', () => {
});
test('docs with custom editUrl & unrelated frontmatter', async () => {
const {defaultTestUtils} = await loadSite();
await defaultTestUtils.testMeta('lorem.md', {
version: 'current',
id: 'lorem',
@ -257,6 +278,8 @@ describe('simple site', () => {
});
test('docs with last update time and author', async () => {
const {siteDir, context, options, currentVersion} = await loadSite();
const testUtilsLocal = createTestUtils({
siteDir,
context,
@ -284,6 +307,8 @@ describe('simple site', () => {
});
test('docs with slugs', async () => {
const {defaultTestUtils} = await loadSite();
await defaultTestUtils.testSlug(
path.join('rootRelativeSlug.md'),
'/docs/rootRelativeSlug',
@ -319,7 +344,8 @@ describe('simple site', () => {
);
});
test('docs with invalid id', () => {
test('docs with invalid id', async () => {
const {defaultTestUtils} = await loadSite();
expect(() => {
defaultTestUtils.processDocFile(
createFakeDocFile({
@ -335,6 +361,8 @@ describe('simple site', () => {
});
test('docs with slug on doc home', async () => {
const {siteDir, context, options, currentVersion} = await loadSite();
const testUtilsLocal = createTestUtils({
siteDir,
context,
@ -360,55 +388,71 @@ describe('simple site', () => {
});
describe('versioned site', () => {
const siteDir = path.join(fixtureDir, 'versioned-site');
const context = loadContext(siteDir);
const options = {
id: DEFAULT_PLUGIN_ID,
...DEFAULT_OPTIONS,
};
const versionsMetadata = readVersionsMetadata({
context,
options: {
async function loadSite() {
const siteDir = path.join(fixtureDir, 'versioned-site');
const context = await loadContext(siteDir);
const options = {
id: DEFAULT_PLUGIN_ID,
...DEFAULT_OPTIONS,
},
});
expect(versionsMetadata.length).toEqual(4);
const [
currentVersion,
version101,
version100,
versionWithSlugs,
] = versionsMetadata;
};
const versionsMetadata = readVersionsMetadata({
context,
options: {
id: DEFAULT_PLUGIN_ID,
...DEFAULT_OPTIONS,
},
});
expect(versionsMetadata.length).toEqual(4);
const [
currentVersion,
version101,
version100,
versionWithSlugs,
] = versionsMetadata;
const currentVersionTestUtils = createTestUtils({
siteDir,
context,
options,
versionMetadata: currentVersion,
});
const version101TestUtils = createTestUtils({
siteDir,
context,
options,
versionMetadata: version101,
});
const currentVersionTestUtils = createTestUtils({
siteDir,
context,
options,
versionMetadata: currentVersion,
});
const version101TestUtils = createTestUtils({
siteDir,
context,
options,
versionMetadata: version101,
});
const version100TestUtils = createTestUtils({
siteDir,
context,
options,
versionMetadata: version100,
});
const version100TestUtils = createTestUtils({
siteDir,
context,
options,
versionMetadata: version100,
});
const versionWithSlugsTestUtils = createTestUtils({
siteDir,
context,
options,
versionMetadata: versionWithSlugs,
});
const versionWithSlugsTestUtils = createTestUtils({
siteDir,
context,
options,
versionMetadata: versionWithSlugs,
});
return {
siteDir,
context,
options,
versionsMetadata,
currentVersionTestUtils,
version101TestUtils,
version100,
version100TestUtils,
versionWithSlugsTestUtils,
};
}
test('next docs', async () => {
const {currentVersionTestUtils} = await loadSite();
await currentVersionTestUtils.testMeta(path.join('foo', 'bar.md'), {
id: 'foo/bar',
unversionedId: 'foo/bar',
@ -432,6 +476,8 @@ describe('versioned site', () => {
});
test('versioned docs', async () => {
const {version101TestUtils, version100TestUtils} = await loadSite();
await version100TestUtils.testMeta(path.join('foo', 'bar.md'), {
id: 'version-1.0.0/foo/bar',
unversionedId: 'foo/bar',
@ -449,8 +495,10 @@ describe('versioned site', () => {
permalink: '/docs/1.0.0/hello',
slug: '/hello',
title: 'hello',
description: 'Hello 1.0.0 !',
description: 'Hello 1.0.0 ! (translated)',
version: '1.0.0',
source:
'@site/i18n/en/docusaurus-plugin-content-docs/version-1.0.0/hello.md',
});
await version101TestUtils.testMeta(path.join('foo', 'bar.md'), {
id: 'version-1.0.1/foo/bar',
@ -475,6 +523,8 @@ describe('versioned site', () => {
});
test('next doc slugs', async () => {
const {currentVersionTestUtils} = await loadSite();
await currentVersionTestUtils.testSlug(
path.join('slugs', 'absoluteSlug.md'),
'/docs/next/absoluteSlug',
@ -494,6 +544,8 @@ describe('versioned site', () => {
});
test('versioned doc slugs', async () => {
const {versionWithSlugsTestUtils} = await loadSite();
await versionWithSlugsTestUtils.testSlug(
path.join('rootAbsoluteSlug.md'),
'/docs/withSlugs/rootAbsoluteSlug',
@ -528,4 +580,33 @@ describe('versioned site', () => {
'/docs/withSlugs/tryToEscapeSlug',
);
});
test('translated doc with editUrl', async () => {
const {siteDir, context, options, version100} = await loadSite();
const testUtilsLocal = createTestUtils({
siteDir,
context,
options: {
...options,
editUrl: 'https://github.com/facebook/docusaurus/edit/master/website',
},
versionMetadata: version100,
});
await testUtilsLocal.testMeta(path.join('hello.md'), {
id: 'version-1.0.0/hello',
unversionedId: 'hello',
isDocsHomePage: false,
permalink: '/docs/1.0.0/hello',
slug: '/hello',
title: 'hello',
description: 'Hello 1.0.0 ! (translated)',
version: '1.0.0',
source:
'@site/i18n/en/docusaurus-plugin-content-docs/version-1.0.0/hello.md',
editUrl:
'https://github.com/facebook/docusaurus/edit/master/website/i18n/en/docusaurus-plugin-content-docs/version-1.0.0/hello.md',
});
});
});

View file

@ -106,7 +106,7 @@ Entries created:
test('site with wrong sidebar file', async () => {
const siteDir = path.join(__dirname, '__fixtures__', 'simple-site');
const context = loadContext(siteDir);
const context = await loadContext(siteDir);
const sidebarPath = path.join(siteDir, 'wrong-sidebars.json');
const plugin = pluginContentDocs(
context,
@ -119,9 +119,9 @@ test('site with wrong sidebar file', async () => {
describe('empty/no docs website', () => {
const siteDir = path.join(__dirname, '__fixtures__', 'empty-site');
const context = loadContext(siteDir);
test('no files in docs folder', async () => {
const context = await loadContext(siteDir);
await fs.ensureDir(path.join(siteDir, 'docs'));
const plugin = pluginContentDocs(
context,
@ -135,6 +135,7 @@ describe('empty/no docs website', () => {
});
test('docs folder does not exist', async () => {
const context = await loadContext(siteDir);
expect(() =>
pluginContentDocs(
context,
@ -149,20 +150,25 @@ describe('empty/no docs website', () => {
});
describe('simple website', () => {
const siteDir = path.join(__dirname, '__fixtures__', 'simple-site');
const context = loadContext(siteDir);
const sidebarPath = path.join(siteDir, 'sidebars.json');
const plugin = pluginContentDocs(
context,
normalizePluginOptions(OptionsSchema, {
path: 'docs',
sidebarPath,
homePageId: 'hello',
}),
);
const pluginContentDir = path.join(context.generatedFilesDir, plugin.name);
async function loadSite() {
const siteDir = path.join(__dirname, '__fixtures__', 'simple-site');
const context = await loadContext(siteDir);
const sidebarPath = path.join(siteDir, 'sidebars.json');
const plugin = pluginContentDocs(
context,
normalizePluginOptions(OptionsSchema, {
path: 'docs',
sidebarPath,
homePageId: 'hello',
}),
);
const pluginContentDir = path.join(context.generatedFilesDir, plugin.name);
test('extendCli - docsVersion', () => {
return {siteDir, context, sidebarPath, plugin, pluginContentDir};
}
test('extendCli - docsVersion', async () => {
const {siteDir, sidebarPath, plugin} = await loadSite();
const mock = jest
.spyOn(cliDocs, 'cliDocsVersionCommand')
.mockImplementation();
@ -178,7 +184,9 @@ describe('simple website', () => {
mock.mockRestore();
});
test('getPathToWatch', () => {
test('getPathToWatch', async () => {
const {siteDir, plugin} = await loadSite();
const pathToWatch = plugin.getPathsToWatch!();
const matchPattern = pathToWatch.map((filepath) =>
posixPath(path.relative(siteDir, filepath)),
@ -187,6 +195,7 @@ describe('simple website', () => {
expect(matchPattern).toMatchInlineSnapshot(`
Array [
"sidebars.json",
"i18n/en/docusaurus-plugin-content-docs/current/**/*.{md,mdx}",
"docs/**/*.{md,mdx}",
]
`);
@ -203,6 +212,8 @@ describe('simple website', () => {
});
test('configureWebpack', async () => {
const {plugin} = await loadSite();
const config = applyConfigureWebpack(
plugin.configureWebpack,
{
@ -219,6 +230,7 @@ describe('simple website', () => {
});
test('content', async () => {
const {siteDir, plugin, pluginContentDir} = await loadSite();
const content = await plugin.loadContent!();
expect(content.loadedVersions.length).toEqual(1);
const [currentVersion] = content.loadedVersions;
@ -287,22 +299,32 @@ describe('simple website', () => {
});
describe('versioned website', () => {
const siteDir = path.join(__dirname, '__fixtures__', 'versioned-site');
const context = loadContext(siteDir);
const sidebarPath = path.join(siteDir, 'sidebars.json');
const routeBasePath = 'docs';
const plugin = pluginContentDocs(
context,
normalizePluginOptions(OptionsSchema, {
async function loadSite() {
const siteDir = path.join(__dirname, '__fixtures__', 'versioned-site');
const context = await loadContext(siteDir);
const sidebarPath = path.join(siteDir, 'sidebars.json');
const routeBasePath = 'docs';
const plugin = pluginContentDocs(
context,
normalizePluginOptions(OptionsSchema, {
routeBasePath,
sidebarPath,
homePageId: 'hello',
}),
);
const pluginContentDir = path.join(context.generatedFilesDir, plugin.name);
return {
siteDir,
context,
routeBasePath,
sidebarPath,
homePageId: 'hello',
}),
);
plugin,
pluginContentDir,
};
}
const pluginContentDir = path.join(context.generatedFilesDir, plugin.name);
test('extendCli - docsVersion', () => {
test('extendCli - docsVersion', async () => {
const {siteDir, routeBasePath, sidebarPath, plugin} = await loadSite();
const mock = jest
.spyOn(cliDocs, 'cliDocsVersionCommand')
.mockImplementation();
@ -318,7 +340,8 @@ describe('versioned website', () => {
mock.mockRestore();
});
test('getPathToWatch', () => {
test('getPathToWatch', async () => {
const {siteDir, plugin} = await loadSite();
const pathToWatch = plugin.getPathsToWatch!();
const matchPattern = pathToWatch.map((filepath) =>
posixPath(path.relative(siteDir, filepath)),
@ -327,12 +350,16 @@ describe('versioned website', () => {
expect(matchPattern).toMatchInlineSnapshot(`
Array [
"sidebars.json",
"i18n/en/docusaurus-plugin-content-docs/current/**/*.{md,mdx}",
"docs/**/*.{md,mdx}",
"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}",
"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}",
"versioned_sidebars/version-withSlugs-sidebars.json",
"i18n/en/docusaurus-plugin-content-docs/version-withSlugs/**/*.{md,mdx}",
"versioned_docs/version-withSlugs/**/*.{md,mdx}",
]
`);
@ -369,6 +396,7 @@ describe('versioned website', () => {
});
test('content', async () => {
const {siteDir, plugin, pluginContentDir} = await loadSite();
const content = await plugin.loadContent!();
expect(content.loadedVersions.length).toEqual(4);
const [
@ -499,23 +527,41 @@ describe('versioned website', () => {
});
describe('versioned website (community)', () => {
const siteDir = path.join(__dirname, '__fixtures__', 'versioned-site');
const context = loadContext(siteDir);
const sidebarPath = path.join(siteDir, 'community_sidebars.json');
const routeBasePath = 'community';
const pluginId = 'community';
const plugin = pluginContentDocs(
context,
normalizePluginOptions(OptionsSchema, {
id: 'community',
path: 'community',
async function loadSite() {
const siteDir = path.join(__dirname, '__fixtures__', 'versioned-site');
const context = await loadContext(siteDir);
const sidebarPath = path.join(siteDir, 'community_sidebars.json');
const routeBasePath = 'community';
const pluginId = 'community';
const plugin = pluginContentDocs(
context,
normalizePluginOptions(OptionsSchema, {
id: 'community',
path: 'community',
routeBasePath,
sidebarPath,
}),
);
const pluginContentDir = path.join(context.generatedFilesDir, plugin.name);
return {
siteDir,
context,
routeBasePath,
sidebarPath,
}),
);
const pluginContentDir = path.join(context.generatedFilesDir, plugin.name);
pluginId,
plugin,
pluginContentDir,
};
}
test('extendCli - docsVersion', () => {
test('extendCli - docsVersion', async () => {
const {
siteDir,
routeBasePath,
sidebarPath,
pluginId,
plugin,
} = await loadSite();
const mock = jest
.spyOn(cliDocs, 'cliDocsVersionCommand')
.mockImplementation();
@ -531,7 +577,8 @@ describe('versioned website (community)', () => {
mock.mockRestore();
});
test('getPathToWatch', () => {
test('getPathToWatch', async () => {
const {siteDir, plugin} = await loadSite();
const pathToWatch = plugin.getPathsToWatch!();
const matchPattern = pathToWatch.map((filepath) =>
posixPath(path.relative(siteDir, filepath)),
@ -540,8 +587,10 @@ describe('versioned website (community)', () => {
expect(matchPattern).toMatchInlineSnapshot(`
Array [
"community_sidebars.json",
"i18n/en/docusaurus-plugin-content-docs-community/current/**/*.{md,mdx}",
"community/**/*.{md,mdx}",
"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}",
]
`);
@ -568,6 +617,7 @@ describe('versioned website (community)', () => {
});
test('content', async () => {
const {siteDir, plugin, pluginContentDir} = await loadSite();
const content = await plugin.loadContent!();
expect(content.loadedVersions.length).toEqual(2);
const [currentVersion, version100] = content.loadedVersions;
@ -579,13 +629,17 @@ describe('versioned website (community)', () => {
isDocsHomePage: false,
permalink: '/community/next/team',
slug: '/team',
/*
source: path.join(
'@site',
path.relative(siteDir, currentVersion.docsDirPath),
'team.md',
),
title: 'team',
description: 'Team current version',
*/
source:
'@site/i18n/en/docusaurus-plugin-content-docs-community/current/team.md',
title: 'Team title translated',
description: 'Team current version (translated)',
version: 'current',
sidebar: 'community',
});

View file

@ -11,6 +11,9 @@ import {
collectSidebarDocItems,
collectSidebarsDocIds,
createSidebarsUtils,
collectSidebarCategories,
collectSidebarLinks,
transformSidebarItems,
} from '../sidebars';
import {Sidebar, Sidebars} from '../types';
@ -163,7 +166,7 @@ describe('loadSidebars', () => {
});
describe('collectSidebarDocItems', () => {
test('can collect recursively', async () => {
test('can collect docs', async () => {
const sidebar: Sidebar = [
{
type: 'category',
@ -213,7 +216,96 @@ describe('collectSidebarDocItems', () => {
});
});
describe('collectSidebarsDocItems', () => {
describe('collectSidebarCategories', () => {
test('can collect categories', async () => {
const sidebar: Sidebar = [
{
type: 'category',
collapsed: false,
label: 'Category1',
items: [
{
type: 'category',
collapsed: false,
label: 'Subcategory 1',
items: [{type: 'doc', id: 'doc1'}],
},
{
type: 'category',
collapsed: false,
label: 'Subcategory 2',
items: [
{type: 'doc', id: 'doc2'},
{
type: 'category',
collapsed: false,
label: 'Sub sub category 1',
items: [{type: 'doc', id: 'doc3'}],
},
],
},
],
},
{
type: 'category',
collapsed: false,
label: 'Category2',
items: [
{type: 'doc', id: 'doc4'},
{type: 'doc', id: 'doc5'},
],
},
];
expect(
collectSidebarCategories(sidebar).map((category) => category.label),
).toEqual([
'Category1',
'Subcategory 1',
'Subcategory 2',
'Sub sub category 1',
'Category2',
]);
});
});
describe('collectSidebarLinks', () => {
test('can collect links', async () => {
const sidebar: Sidebar = [
{
type: 'category',
collapsed: false,
label: 'Category1',
items: [
{
type: 'link',
href: 'https://google.com',
label: 'Google',
},
{
type: 'category',
collapsed: false,
label: 'Subcategory 2',
items: [
{
type: 'link',
href: 'https://facebook.com',
label: 'Facebook',
},
],
},
],
},
];
expect(collectSidebarLinks(sidebar).map((link) => link.href)).toEqual([
'https://google.com',
'https://facebook.com',
]);
});
});
describe('collectSidebarsDocIds', () => {
test('can collect sidebars doc items', async () => {
const sidebar1: Sidebar = [
{
@ -256,6 +348,95 @@ describe('collectSidebarsDocItems', () => {
});
});
describe('transformSidebarItems', () => {
test('can transform sidebar items', async () => {
const sidebar: Sidebar = [
{
type: 'category',
collapsed: false,
label: 'Category1',
items: [
{
type: 'category',
collapsed: false,
label: 'Subcategory 1',
items: [{type: 'doc', id: 'doc1'}],
},
{
type: 'category',
collapsed: false,
label: 'Subcategory 2',
items: [
{type: 'doc', id: 'doc2'},
{
type: 'category',
collapsed: false,
label: 'Sub sub category 1',
items: [{type: 'doc', id: 'doc3'}],
},
],
},
],
},
{
type: 'category',
collapsed: false,
label: 'Category2',
items: [
{type: 'doc', id: 'doc4'},
{type: 'doc', id: 'doc5'},
],
},
];
expect(
transformSidebarItems(sidebar, (item) => {
if (item.type === 'category') {
return {...item, label: `MODIFIED LABEL: ${item.label}`};
}
return item;
}),
).toEqual([
{
type: 'category',
collapsed: false,
label: 'MODIFIED LABEL: Category1',
items: [
{
type: 'category',
collapsed: false,
label: 'MODIFIED LABEL: Subcategory 1',
items: [{type: 'doc', id: 'doc1'}],
},
{
type: 'category',
collapsed: false,
label: 'MODIFIED LABEL: Subcategory 2',
items: [
{type: 'doc', id: 'doc2'},
{
type: 'category',
collapsed: false,
label: 'MODIFIED LABEL: Sub sub category 1',
items: [{type: 'doc', id: 'doc3'}],
},
],
},
],
},
{
type: 'category',
collapsed: false,
label: 'MODIFIED LABEL: Category2',
items: [
{type: 'doc', id: 'doc4'},
{type: 'doc', id: 'doc5'},
],
},
]);
});
});
describe('createSidebarsUtils', () => {
const sidebar1: Sidebar = [
{

View file

@ -0,0 +1,159 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {LoadedContent, DocMetadata, LoadedVersion} from '../types';
import {CURRENT_VERSION_NAME} from '../constants';
import {
getLoadedContentTranslationFiles,
translateLoadedContent,
} from '../translations';
import {updateTranslationFileMessages} from '@docusaurus/utils';
function createSampleDoc(doc: Pick<DocMetadata, 'id'>): DocMetadata {
return {
editUrl: 'any',
isDocsHomePage: false,
lastUpdatedAt: 0,
lastUpdatedBy: 'any',
next: undefined,
previous: undefined,
permalink: 'any',
slug: 'any',
source: 'any',
unversionedId: 'any',
version: 'any',
title: `${doc.id} title`,
sidebar_label: `${doc.id} title`,
description: `${doc.id} description`,
...doc,
};
}
function createSampleVersion(
version: Pick<LoadedVersion, 'versionName'>,
): LoadedVersion {
return {
versionLabel: `${version.versionName} label`,
versionPath: '/docs/',
mainDocId: '',
permalinkToSidebar: {},
routePriority: undefined,
sidebarFilePath: 'any',
isLast: true,
docsDirPath: 'any',
docsDirPathLocalized: 'any',
docs: [
createSampleDoc({
id: 'doc1',
}),
createSampleDoc({
id: 'doc2',
}),
createSampleDoc({
id: 'doc3',
}),
createSampleDoc({
id: 'doc4',
}),
createSampleDoc({
id: 'doc5',
}),
],
sidebars: {
docs: [
{
type: 'category',
label: 'Getting started',
collapsed: false,
items: [
{
type: 'doc',
id: 'doc1',
},
{
type: 'doc',
id: 'doc2',
},
{
type: 'link',
label: 'Link label',
href: 'https://facebook.com',
},
{
type: 'ref',
id: 'doc1',
},
],
},
{
type: 'doc',
id: 'doc3',
},
],
otherSidebar: [
{
type: 'doc',
id: 'doc4',
},
{
type: 'doc',
id: 'doc5',
},
],
},
...version,
};
}
const SampleLoadedContent: LoadedContent = {
loadedVersions: [
createSampleVersion({
versionName: CURRENT_VERSION_NAME,
}),
createSampleVersion({
versionName: '2.0.0',
}),
createSampleVersion({
versionName: '1.0.0',
}),
],
};
function getSampleTranslationFiles() {
return getLoadedContentTranslationFiles(SampleLoadedContent);
}
function getSampleTranslationFilesTranslated() {
const translationFiles = getSampleTranslationFiles();
return translationFiles.map((translationFile) =>
updateTranslationFileMessages(
translationFile,
(message) => `${message} (translated)`,
),
);
}
describe('getLoadedContentTranslationFiles', () => {
test('should return translation files matching snapshot', async () => {
expect(getSampleTranslationFiles()).toMatchSnapshot();
});
});
describe('translateLoadedContent', () => {
test('should not translate anything if translation files are untranslated', () => {
const translationFiles = getSampleTranslationFiles();
expect(
translateLoadedContent(SampleLoadedContent, translationFiles),
).toEqual(SampleLoadedContent);
});
test('should return translated loaded content matching snapshot', () => {
const translationFiles = getSampleTranslationFilesTranslated();
expect(
translateLoadedContent(SampleLoadedContent, translationFiles),
).toMatchSnapshot();
});
});

View file

@ -14,7 +14,14 @@ import {
} from '../versions';
import {DEFAULT_OPTIONS} from '../options';
import {DEFAULT_PLUGIN_ID} from '@docusaurus/core/lib/constants';
import {VersionMetadata} from '../types';
import {PluginOptions, VersionMetadata} from '../types';
import {I18n} from '@docusaurus/types';
const DefaultI18N: I18n = {
currentLocale: 'en',
locales: ['en'],
defaultLocale: 'en',
};
describe('version paths', () => {
test('getVersionedDocsDirPath', () => {
@ -46,29 +53,39 @@ describe('version paths', () => {
});
describe('simple site', () => {
const simpleSiteDir = path.resolve(
path.join(__dirname, '__fixtures__', 'simple-site'),
);
const defaultOptions = {
id: DEFAULT_PLUGIN_ID,
...DEFAULT_OPTIONS,
};
const defaultContext = {
siteDir: simpleSiteDir,
baseUrl: '/',
};
async function loadSite() {
const simpleSiteDir = path.resolve(
path.join(__dirname, '__fixtures__', 'simple-site'),
);
const defaultOptions: PluginOptions = {
id: DEFAULT_PLUGIN_ID,
...DEFAULT_OPTIONS,
};
const defaultContext = {
siteDir: simpleSiteDir,
baseUrl: '/',
i18n: DefaultI18N,
};
const vCurrent: VersionMetadata = {
docsDirPath: path.join(simpleSiteDir, 'docs'),
isLast: true,
routePriority: -1,
sidebarFilePath: path.join(simpleSiteDir, 'sidebars.json'),
versionLabel: 'Next',
versionName: 'current',
versionPath: '/docs',
};
const vCurrent: VersionMetadata = {
docsDirPath: path.join(simpleSiteDir, 'docs'),
docsDirPathLocalized: path.join(
simpleSiteDir,
'i18n/en/docusaurus-plugin-content-docs/current',
),
isLast: true,
routePriority: -1,
sidebarFilePath: path.join(simpleSiteDir, 'sidebars.json'),
versionLabel: 'Next',
versionName: 'current',
versionPath: '/docs',
};
return {simpleSiteDir, defaultOptions, defaultContext, vCurrent};
}
test('readVersionsMetadata simple site', async () => {
const {defaultOptions, defaultContext, vCurrent} = await loadSite();
test('readVersionsMetadata simple site', () => {
const versionsMetadata = readVersionsMetadata({
options: defaultOptions,
context: defaultContext,
@ -77,7 +94,9 @@ describe('simple site', () => {
expect(versionsMetadata).toEqual([vCurrent]);
});
test('readVersionsMetadata simple site with base url', () => {
test('readVersionsMetadata simple site with base url', async () => {
const {defaultOptions, defaultContext, vCurrent} = await loadSite();
const versionsMetadata = readVersionsMetadata({
options: defaultOptions,
context: {
@ -94,7 +113,9 @@ describe('simple site', () => {
]);
});
test('readVersionsMetadata simple site with current version config', () => {
test('readVersionsMetadata simple site with current version config', async () => {
const {defaultOptions, defaultContext, vCurrent} = await loadSite();
const versionsMetadata = readVersionsMetadata({
options: {
...defaultOptions,
@ -121,7 +142,9 @@ describe('simple site', () => {
]);
});
test('readVersionsMetadata simple site with unknown lastVersion should throw', () => {
test('readVersionsMetadata simple site with unknown lastVersion should throw', async () => {
const {defaultOptions, defaultContext} = await loadSite();
expect(() =>
readVersionsMetadata({
options: {...defaultOptions, lastVersion: 'unknownVersionName'},
@ -132,7 +155,9 @@ describe('simple site', () => {
);
});
test('readVersionsMetadata simple site with unknown version configurations should throw', () => {
test('readVersionsMetadata simple site with unknown version configurations should throw', async () => {
const {defaultOptions, defaultContext} = await loadSite();
expect(() =>
readVersionsMetadata({
options: {
@ -150,7 +175,9 @@ describe('simple site', () => {
);
});
test('readVersionsMetadata simple site with disableVersioning while single version should throw', () => {
test('readVersionsMetadata simple site with disableVersioning while single version should throw', async () => {
const {defaultOptions, defaultContext} = await loadSite();
expect(() =>
readVersionsMetadata({
options: {...defaultOptions, disableVersioning: true},
@ -161,7 +188,9 @@ describe('simple site', () => {
);
});
test('readVersionsMetadata simple site without including current version should throw', () => {
test('readVersionsMetadata simple site without including current version should throw', async () => {
const {defaultOptions, defaultContext} = await loadSite();
expect(() =>
readVersionsMetadata({
options: {...defaultOptions, includeCurrentVersion: false},
@ -174,71 +203,109 @@ describe('simple site', () => {
});
describe('versioned site, pluginId=default', () => {
const versionedSiteDir = path.resolve(
path.join(__dirname, '__fixtures__', 'versioned-site'),
);
const defaultOptions = {
id: DEFAULT_PLUGIN_ID,
...DEFAULT_OPTIONS,
};
const defaultContext = {
siteDir: versionedSiteDir,
baseUrl: '/',
};
async function loadSite() {
const versionedSiteDir = path.resolve(
path.join(__dirname, '__fixtures__', 'versioned-site'),
);
const defaultOptions: PluginOptions = {
id: DEFAULT_PLUGIN_ID,
...DEFAULT_OPTIONS,
};
const defaultContext = {
siteDir: versionedSiteDir,
baseUrl: '/',
i18n: DefaultI18N,
};
const vCurrent: VersionMetadata = {
docsDirPath: path.join(versionedSiteDir, 'docs'),
isLast: false,
routePriority: undefined,
sidebarFilePath: path.join(versionedSiteDir, 'sidebars.json'),
versionLabel: 'Next',
versionName: 'current',
versionPath: '/docs/next',
};
const vCurrent: VersionMetadata = {
docsDirPath: path.join(versionedSiteDir, 'docs'),
docsDirPathLocalized: path.join(
versionedSiteDir,
'i18n/en/docusaurus-plugin-content-docs/current',
),
isLast: false,
routePriority: undefined,
sidebarFilePath: path.join(versionedSiteDir, 'sidebars.json'),
versionLabel: 'Next',
versionName: 'current',
versionPath: '/docs/next',
};
const v101: VersionMetadata = {
docsDirPath: path.join(versionedSiteDir, 'versioned_docs/version-1.0.1'),
isLast: true,
routePriority: -1,
sidebarFilePath: path.join(
const v101: VersionMetadata = {
docsDirPath: path.join(versionedSiteDir, 'versioned_docs/version-1.0.1'),
docsDirPathLocalized: path.join(
versionedSiteDir,
'i18n/en/docusaurus-plugin-content-docs/version-1.0.1',
),
isLast: true,
routePriority: -1,
sidebarFilePath: path.join(
versionedSiteDir,
'versioned_sidebars/version-1.0.1-sidebars.json',
),
versionLabel: '1.0.1',
versionName: '1.0.1',
versionPath: '/docs',
};
const v100: VersionMetadata = {
docsDirPath: path.join(versionedSiteDir, 'versioned_docs/version-1.0.0'),
docsDirPathLocalized: path.join(
versionedSiteDir,
'i18n/en/docusaurus-plugin-content-docs/version-1.0.0',
),
isLast: false,
routePriority: undefined,
sidebarFilePath: path.join(
versionedSiteDir,
'versioned_sidebars/version-1.0.0-sidebars.json',
),
versionLabel: '1.0.0',
versionName: '1.0.0',
versionPath: '/docs/1.0.0',
};
const vwithSlugs: VersionMetadata = {
docsDirPath: path.join(
versionedSiteDir,
'versioned_docs/version-withSlugs',
),
docsDirPathLocalized: path.join(
versionedSiteDir,
'i18n/en/docusaurus-plugin-content-docs/version-withSlugs',
),
isLast: false,
routePriority: undefined,
sidebarFilePath: path.join(
versionedSiteDir,
'versioned_sidebars/version-withSlugs-sidebars.json',
),
versionLabel: 'withSlugs',
versionName: 'withSlugs',
versionPath: '/docs/withSlugs',
};
return {
versionedSiteDir,
'versioned_sidebars/version-1.0.1-sidebars.json',
),
versionLabel: '1.0.1',
versionName: '1.0.1',
versionPath: '/docs',
};
defaultOptions,
defaultContext,
vCurrent,
v101,
v100,
vwithSlugs,
};
}
const v100: VersionMetadata = {
docsDirPath: path.join(versionedSiteDir, 'versioned_docs/version-1.0.0'),
isLast: false,
routePriority: undefined,
sidebarFilePath: path.join(
versionedSiteDir,
'versioned_sidebars/version-1.0.0-sidebars.json',
),
versionLabel: '1.0.0',
versionName: '1.0.0',
versionPath: '/docs/1.0.0',
};
test('readVersionsMetadata versioned site', async () => {
const {
defaultOptions,
defaultContext,
vCurrent,
v101,
v100,
vwithSlugs,
} = await loadSite();
const vwithSlugs: VersionMetadata = {
docsDirPath: path.join(
versionedSiteDir,
'versioned_docs/version-withSlugs',
),
isLast: false,
routePriority: undefined,
sidebarFilePath: path.join(
versionedSiteDir,
'versioned_sidebars/version-withSlugs-sidebars.json',
),
versionLabel: 'withSlugs',
versionName: 'withSlugs',
versionPath: '/docs/withSlugs',
};
test('readVersionsMetadata versioned site', () => {
const versionsMetadata = readVersionsMetadata({
options: defaultOptions,
context: defaultContext,
@ -247,7 +314,15 @@ describe('versioned site, pluginId=default', () => {
expect(versionsMetadata).toEqual([vCurrent, v101, v100, vwithSlugs]);
});
test('readVersionsMetadata versioned site with includeCurrentVersion=false', () => {
test('readVersionsMetadata versioned site with includeCurrentVersion=false', async () => {
const {
defaultOptions,
defaultContext,
v101,
v100,
vwithSlugs,
} = await loadSite();
const versionsMetadata = readVersionsMetadata({
options: {...defaultOptions, includeCurrentVersion: false},
context: defaultContext,
@ -261,7 +336,16 @@ describe('versioned site, pluginId=default', () => {
]);
});
test('readVersionsMetadata versioned site with version options', () => {
test('readVersionsMetadata versioned site with version options', async () => {
const {
defaultOptions,
defaultContext,
vCurrent,
v101,
v100,
vwithSlugs,
} = await loadSite();
const versionsMetadata = readVersionsMetadata({
options: {
...defaultOptions,
@ -297,7 +381,9 @@ describe('versioned site, pluginId=default', () => {
]);
});
test('readVersionsMetadata versioned site with onlyIncludeVersions option', () => {
test('readVersionsMetadata versioned site with onlyIncludeVersions option', async () => {
const {defaultOptions, defaultContext, v101, vwithSlugs} = await loadSite();
const versionsMetadata = readVersionsMetadata({
options: {
...defaultOptions,
@ -310,7 +396,9 @@ describe('versioned site, pluginId=default', () => {
expect(versionsMetadata).toEqual([v101, vwithSlugs]);
});
test('readVersionsMetadata versioned site with disableVersioning', () => {
test('readVersionsMetadata versioned site with disableVersioning', async () => {
const {defaultOptions, defaultContext, vCurrent} = await loadSite();
const versionsMetadata = readVersionsMetadata({
options: {...defaultOptions, disableVersioning: true},
context: defaultContext,
@ -321,7 +409,9 @@ describe('versioned site, pluginId=default', () => {
]);
});
test('readVersionsMetadata versioned site with all versions disabled', () => {
test('readVersionsMetadata versioned site with all versions disabled', async () => {
const {defaultOptions, defaultContext} = await loadSite();
expect(() =>
readVersionsMetadata({
options: {
@ -336,7 +426,9 @@ describe('versioned site, pluginId=default', () => {
);
});
test('readVersionsMetadata versioned site with empty onlyIncludeVersions', () => {
test('readVersionsMetadata versioned site with empty onlyIncludeVersions', async () => {
const {defaultOptions, defaultContext} = await loadSite();
expect(() =>
readVersionsMetadata({
options: {
@ -350,7 +442,9 @@ describe('versioned site, pluginId=default', () => {
);
});
test('readVersionsMetadata versioned site with unknown versions in onlyIncludeVersions', () => {
test('readVersionsMetadata versioned site with unknown versions in onlyIncludeVersions', async () => {
const {defaultOptions, defaultContext} = await loadSite();
expect(() =>
readVersionsMetadata({
options: {
@ -364,7 +458,9 @@ describe('versioned site, pluginId=default', () => {
);
});
test('readVersionsMetadata versioned site with lastVersion not in onlyIncludeVersions', () => {
test('readVersionsMetadata versioned site with lastVersion not in onlyIncludeVersions', async () => {
const {defaultOptions, defaultContext} = await loadSite();
expect(() =>
readVersionsMetadata({
options: {
@ -379,7 +475,9 @@ describe('versioned site, pluginId=default', () => {
);
});
test('readVersionsMetadata versioned site with invalid versions.json file', () => {
test('readVersionsMetadata versioned site with invalid versions.json file', async () => {
const {defaultOptions, defaultContext} = await loadSite();
const mock = jest.spyOn(JSON, 'parse').mockImplementationOnce(() => {
return {
invalid: 'json',
@ -399,47 +497,62 @@ describe('versioned site, pluginId=default', () => {
});
describe('versioned site, pluginId=community', () => {
const versionedSiteDir = path.resolve(
path.join(__dirname, '__fixtures__', 'versioned-site'),
);
const defaultOptions = {
...DEFAULT_OPTIONS,
id: 'community',
path: 'community',
routeBasePath: 'communityBasePath',
};
const defaultContext = {
siteDir: versionedSiteDir,
baseUrl: '/',
};
async function loadSite() {
const versionedSiteDir = path.resolve(
path.join(__dirname, '__fixtures__', 'versioned-site'),
);
const defaultOptions: PluginOptions = {
...DEFAULT_OPTIONS,
id: 'community',
path: 'community',
routeBasePath: 'communityBasePath',
};
const defaultContext = {
siteDir: versionedSiteDir,
baseUrl: '/',
i18n: DefaultI18N,
};
const vCurrent: VersionMetadata = {
docsDirPath: path.join(versionedSiteDir, 'community'),
isLast: false,
routePriority: undefined,
sidebarFilePath: path.join(versionedSiteDir, 'sidebars.json'),
versionLabel: 'Next',
versionName: 'current',
versionPath: '/communityBasePath/next',
};
const vCurrent: VersionMetadata = {
docsDirPath: path.join(versionedSiteDir, 'community'),
docsDirPathLocalized: path.join(
versionedSiteDir,
'i18n/en/docusaurus-plugin-content-docs-community/current',
),
isLast: false,
routePriority: undefined,
sidebarFilePath: path.join(versionedSiteDir, 'sidebars.json'),
versionLabel: 'Next',
versionName: 'current',
versionPath: '/communityBasePath/next',
};
const v100: VersionMetadata = {
docsDirPath: path.join(
versionedSiteDir,
'community_versioned_docs/version-1.0.0',
),
isLast: true,
routePriority: -1,
sidebarFilePath: path.join(
versionedSiteDir,
'community_versioned_sidebars/version-1.0.0-sidebars.json',
),
versionLabel: '1.0.0',
versionName: '1.0.0',
versionPath: '/communityBasePath',
};
const v100: VersionMetadata = {
docsDirPath: path.join(
versionedSiteDir,
'community_versioned_docs/version-1.0.0',
),
docsDirPathLocalized: path.join(
versionedSiteDir,
'i18n/en/docusaurus-plugin-content-docs-community/version-1.0.0',
),
isLast: true,
routePriority: -1,
sidebarFilePath: path.join(
versionedSiteDir,
'community_versioned_sidebars/version-1.0.0-sidebars.json',
),
versionLabel: '1.0.0',
versionName: '1.0.0',
versionPath: '/communityBasePath',
};
return {versionedSiteDir, defaultOptions, defaultContext, vCurrent, v100};
}
test('readVersionsMetadata versioned site (community)', async () => {
const {defaultOptions, defaultContext, vCurrent, v100} = await loadSite();
test('readVersionsMetadata versioned site (community)', () => {
const versionsMetadata = readVersionsMetadata({
options: defaultOptions,
context: defaultContext,
@ -448,7 +561,9 @@ describe('versioned site, pluginId=community', () => {
expect(versionsMetadata).toEqual([vCurrent, v100]);
});
test('readVersionsMetadata versioned site (community) with includeCurrentVersion=false', () => {
test('readVersionsMetadata versioned site (community) with includeCurrentVersion=false', async () => {
const {defaultOptions, defaultContext, v100} = await loadSite();
const versionsMetadata = readVersionsMetadata({
options: {...defaultOptions, includeCurrentVersion: false},
context: defaultContext,
@ -460,7 +575,9 @@ describe('versioned site, pluginId=community', () => {
]);
});
test('readVersionsMetadata versioned site (community) with disableVersioning', () => {
test('readVersionsMetadata versioned site (community) with disableVersioning', async () => {
const {defaultOptions, defaultContext, vCurrent} = await loadSite();
const versionsMetadata = readVersionsMetadata({
options: {...defaultOptions, disableVersioning: true},
context: defaultContext,
@ -476,7 +593,9 @@ describe('versioned site, pluginId=community', () => {
]);
});
test('readVersionsMetadata versioned site (community) with all versions disabled', () => {
test('readVersionsMetadata versioned site (community) with all versions disabled', async () => {
const {defaultOptions, defaultContext} = await loadSite();
expect(() =>
readVersionsMetadata({
options: {

View file

@ -12,6 +12,7 @@ import {
normalizeUrl,
getEditUrl,
parseMarkdownString,
getFolderContainingFile,
} from '@docusaurus/utils';
import {LoadContext} from '@docusaurus/types';
@ -27,6 +28,7 @@ import {
import getSlug from './slug';
import {CURRENT_VERSION_NAME} from './constants';
import globby from 'globby';
import {getDocsDirPaths} from './versions';
type LastUpdateOptions = Pick<
PluginOptions,
@ -61,16 +63,25 @@ async function readLastUpdateData(
}
export async function readDocFile(
docsDirPath: string,
versionMetadata: Pick<
VersionMetadata,
'docsDirPath' | 'docsDirPathLocalized'
>,
source: string,
options: LastUpdateOptions,
): Promise<DocFile> {
const filePath = path.join(docsDirPath, source);
const folderPath = await getFolderContainingFile(
getDocsDirPaths(versionMetadata),
source,
);
const filePath = path.join(folderPath, source);
const [content, lastUpdate] = await Promise.all([
fs.readFile(filePath, 'utf-8'),
readLastUpdateData(filePath, options),
]);
return {source, content, lastUpdate};
return {source, content, lastUpdate, filePath};
}
export async function readVersionDocs(
@ -84,9 +95,7 @@ export async function readVersionDocs(
cwd: versionMetadata.docsDirPath,
});
return Promise.all(
sources.map((source) =>
readDocFile(versionMetadata.docsDirPath, source, options),
),
sources.map((source) => readDocFile(versionMetadata, source, options)),
);
}
@ -101,10 +110,9 @@ export function processDocMetadata({
context: LoadContext;
options: MetadataOptions;
}): DocMetadataBase {
const {source, content, lastUpdate} = docFile;
const {source, content, lastUpdate, filePath} = docFile;
const {editUrl, homePageId} = options;
const {siteDir} = context;
const filePath = path.join(versionMetadata.docsDirPath, source);
// ex: api/myDoc -> api
// ex: myDoc -> .

View file

@ -21,7 +21,7 @@ import {LoadContext, Plugin, RouteConfig} from '@docusaurus/types';
import {loadSidebars, createSidebarsUtils} from './sidebars';
import {readVersionDocs, processDocMetadata} from './docs';
import {readVersionsMetadata} from './versions';
import {getDocsDirPaths, readVersionsMetadata} from './versions';
import {
PluginOptions,
@ -44,6 +44,10 @@ import {OptionsSchema} from './options';
import {flatten, keyBy, compact} from 'lodash';
import {toGlobalDataVersion} from './globalData';
import {toVersionMetadataProp} from './props';
import {
translateLoadedContent,
getLoadedContentTranslationFiles,
} from './translations';
export default function pluginContentDocs(
context: LoadContext,
@ -99,6 +103,10 @@ export default function pluginContentDocs(
});
},
async getTranslationFiles() {
return getLoadedContentTranslationFiles(await this.loadContent!());
},
getClientModules() {
const modules = [];
if (options.admonitions) {
@ -111,8 +119,12 @@ export default function pluginContentDocs(
function getVersionPathsToWatch(version: VersionMetadata): string[] {
return [
version.sidebarFilePath,
...options.include.map(
(pattern) => `${version.docsDirPath}/${pattern}`,
...flatten(
options.include.map((pattern) =>
getDocsDirPaths(version).map(
(docsDirPath) => `${docsDirPath}/${pattern}`,
),
),
),
];
}
@ -235,6 +247,10 @@ export default function pluginContentDocs(
};
},
translateContent({content, translationFiles}) {
return translateLoadedContent(content, translationFiles);
},
async contentLoaded({content, actions}) {
const {loadedVersions} = content;
const {docLayoutComponent, docItemComponent} = options;
@ -318,7 +334,6 @@ export default function pluginContentDocs(
if (siteConfig.onBrokenMarkdownLinks === 'ignore') {
return;
}
reportMessage(
`Docs markdown link couldn't be resolved: (${brokenMarkdownLink.link}) in ${brokenMarkdownLink.filePath} for version ${brokenMarkdownLink.version.versionName}`,
siteConfig.onBrokenMarkdownLinks,
@ -329,7 +344,7 @@ export default function pluginContentDocs(
function createMDXLoaderRule(): RuleSetRule {
return {
test: /(\.mdx?)$/,
include: versionsMetadata.map((vmd) => vmd.docsDirPath),
include: flatten(versionsMetadata.map(getDocsDirPaths)),
use: compact([
getCacheLoader(isServer),
getBabelLoader(isServer),

View file

@ -8,3 +8,5 @@
- [doc1](doc1.md)
- [doc2](./doc2.md)
- [doc-localized](./doc-localized.md)

View file

@ -35,6 +35,8 @@ exports[`transform to correct links 1`] = `
- [doc1](/docs/doc1)
- [doc2](/docs/doc2)
- [doc-localized](/fr/doc-localized)
"
`;

View file

@ -16,15 +16,21 @@ import {
} from '../../types';
import {VERSIONED_DOCS_DIR, CURRENT_VERSION_NAME} from '../../constants';
function createFakeVersion(
versionName: string,
docsDirPath: string,
): VersionMetadata {
function createFakeVersion({
versionName,
docsDirPath,
docsDirPathLocalized,
}: {
versionName: string;
docsDirPath: string;
docsDirPathLocalized: string;
}): VersionMetadata {
return {
versionName,
versionLabel: 'Any',
versionPath: 'any',
docsDirPath,
docsDirPathLocalized,
sidebarFilePath: 'any',
routePriority: undefined,
isLast: false,
@ -33,14 +39,29 @@ function createFakeVersion(
const siteDir = path.join(__dirname, '__fixtures__');
const versionCurrent = createFakeVersion(
CURRENT_VERSION_NAME,
path.join(siteDir, 'docs'),
);
const version100 = createFakeVersion(
CURRENT_VERSION_NAME,
path.join(siteDir, VERSIONED_DOCS_DIR, 'version-1.0.0'),
);
const versionCurrent = createFakeVersion({
versionName: CURRENT_VERSION_NAME,
docsDirPath: path.join(siteDir, 'docs'),
docsDirPathLocalized: path.join(
siteDir,
'i18n',
'fr',
'docusaurus-plugin-content-docs',
CURRENT_VERSION_NAME,
),
});
const version100 = createFakeVersion({
versionName: '1.0.0',
docsDirPath: path.join(siteDir, VERSIONED_DOCS_DIR, 'version-1.0.0'),
docsDirPathLocalized: path.join(
siteDir,
'i18n',
'fr',
'docusaurus-plugin-content-docs',
'version-1.0.0',
),
});
const sourceToPermalink: SourceToPermalink = {
'@site/docs/doc1.md': '/docs/doc1',
@ -50,6 +71,10 @@ const sourceToPermalink: SourceToPermalink = {
'@site/versioned_docs/version-1.0.0/doc2.md': '/docs/1.0.0/doc2',
'@site/versioned_docs/version-1.0.0/subdir/doc1.md':
'/docs/1.0.0/subdir/doc1',
'@site/i18n/fr/docusaurus-plugin-content-docs/current/doc-localized.md':
'/fr/doc-localized',
'@site/docs/doc-localized': '/doc-localized',
};
function createMarkdownOptions(
@ -85,9 +110,11 @@ test('transform to correct links', () => {
expect(transformedContent).toContain('](/docs/doc1');
expect(transformedContent).toContain('](/docs/doc2');
expect(transformedContent).toContain('](/docs/subdir/doc3');
expect(transformedContent).toContain('](/fr/doc-localized');
expect(transformedContent).not.toContain('](doc1.md)');
expect(transformedContent).not.toContain('](./doc2.md)');
expect(transformedContent).not.toContain('](subdir/doc3.md)');
expect(transformedContent).not.toContain('](/doc-localized');
expect(content).not.toEqual(transformedContent);
});

View file

@ -12,10 +12,13 @@ import {
VersionMetadata,
BrokenMarkdownLink,
} from '../types';
import {getDocsDirPaths} from '../versions';
function getVersion(filePath: string, options: DocsMarkdownOption) {
const versionFound = options.versionsMetadata.find((version) =>
filePath.startsWith(version.docsDirPath),
getDocsDirPaths(version).some((docsDirPath) =>
filePath.startsWith(docsDirPath),
),
);
if (!versionFound) {
throw new Error(
@ -32,7 +35,7 @@ function replaceMarkdownLinks(
options: DocsMarkdownOption,
) {
const {siteDir, sourceToPermalink, onBrokenMarkdownLink} = options;
const {docsDirPath} = version;
const {docsDirPath, docsDirPathLocalized} = version;
// Replace internal markdown linking (except in fenced blocks).
let fencedBlock = false;
@ -53,12 +56,15 @@ function replaceMarkdownLinks(
while (mdMatch !== null) {
// Replace it to correct html link.
const mdLink = mdMatch[1];
const targetSource = `${docsDirPath}/${mdLink}`;
const aliasedSource = (source: string) =>
`@site/${path.relative(siteDir, source)}`;
const permalink =
sourceToPermalink[aliasedSource(resolve(filePath, mdLink))] ||
sourceToPermalink[aliasedSource(targetSource)];
sourceToPermalink[aliasedSource(`${docsDirPathLocalized}/${mdLink}`)] ||
sourceToPermalink[aliasedSource(`${docsDirPath}/${mdLink}`)];
if (permalink) {
modifiedLine = modifiedLine.replace(mdLink, permalink);
} else {

View file

@ -14,6 +14,8 @@ import {
SidebarItemLink,
SidebarItemDoc,
Sidebar,
SidebarItemCategory,
SidebarItemType,
} from './types';
import {mapValues, flatten, difference} from 'lodash';
import {getElementsAround} from '@docusaurus/utils';
@ -213,25 +215,51 @@ export function loadSidebars(sidebarFilePath: string): Sidebars {
return normalizeSidebars(sidebarJson);
}
// traverse the sidebar tree in depth to find all doc items, in correct order
export function collectSidebarDocItems(sidebar: Sidebar): SidebarItemDoc[] {
function collectRecursive(item: SidebarItem): SidebarItemDoc[] {
if (item.type === 'doc') {
return [item];
}
if (item.type === 'category') {
return flatten(item.items.map(collectRecursive));
}
// Refs and links should not be shown in navigation.
if (item.type === 'ref' || item.type === 'link') {
return [];
}
throw new Error(`unknown sidebar item type = ${item.type}`);
function collectSidebarItemsOfType<
Type extends SidebarItemType,
Item extends SidebarItem & {type: SidebarItemType}
>(type: Type, sidebar: Sidebar): Item[] {
function collectRecursive(item: SidebarItem): Item[] {
const currentItemsCollected: Item[] =
item.type === type ? [item as Item] : [];
const childItemsCollected: Item[] =
item.type === 'category' ? flatten(item.items.map(collectRecursive)) : [];
return [...currentItemsCollected, ...childItemsCollected];
}
return flatten(sidebar.map(collectRecursive));
}
export function collectSidebarDocItems(sidebar: Sidebar): SidebarItemDoc[] {
return collectSidebarItemsOfType('doc', sidebar);
}
export function collectSidebarCategories(
sidebar: Sidebar,
): SidebarItemCategory[] {
return collectSidebarItemsOfType('category', sidebar);
}
export function collectSidebarLinks(sidebar: Sidebar): SidebarItemLink[] {
return collectSidebarItemsOfType('link', sidebar);
}
export function transformSidebarItems(
sidebar: Sidebar,
updateFn: (item: SidebarItem) => SidebarItem,
): Sidebar {
function transformRecursive(item: SidebarItem): SidebarItem {
if (item.type === 'category') {
return updateFn({
...item,
items: item.items.map(transformRecursive),
});
}
return updateFn(item);
}
return sidebar.map(transformRecursive);
}
export function collectSidebarsDocIds(
sidebars: Sidebars,
): Record<string, string[]> {

View file

@ -0,0 +1,259 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {
LoadedVersion,
Sidebar,
LoadedContent,
Sidebars,
SidebarItem,
} from './types';
import {chain, mapValues, flatten, keyBy} from 'lodash';
import {
collectSidebarCategories,
transformSidebarItems,
collectSidebarLinks,
} from './sidebars';
import {
TranslationFileContent,
TranslationFile,
TranslationFiles,
} from '@docusaurus/types';
import {mergeTranslations} from '@docusaurus/utils';
import {CURRENT_VERSION_NAME} from './constants';
function getVersionFileName(versionName: string): string {
if (versionName === CURRENT_VERSION_NAME) {
return versionName;
} else {
// I don't like this "version-" prefix,
// but it's for consistency with site/versioned_docs
return `version-${versionName}`;
}
}
// TODO legacy, the sidebar name is like "version-2.0.0-alpha.66/docs"
// input: "version-2.0.0-alpha.66/docs"
// output: "docs"
function getNormalizedSidebarName({
versionName,
sidebarName,
}: {
versionName: string;
sidebarName: string;
}): string {
if (versionName === CURRENT_VERSION_NAME || !sidebarName.includes('/')) {
return sidebarName;
}
const [, ...rest] = sidebarName.split('/');
return rest.join('/');
}
/*
// Do we need to translate doc metadatas?
// It seems translating frontmatter labels is good enough
function getDocTranslations(doc: DocMetadata): TranslationFileContent {
return {
[`${doc.unversionedId}.title`]: {
message: doc.title,
description: `The title for doc with id=${doc.unversionedId}`,
},
...(doc.sidebar_label
? {
[`${doc.unversionedId}.sidebar_label`]: {
message: doc.sidebar_label,
description: `The sidebar label for doc with id=${doc.unversionedId}`,
},
}
: undefined),
};
}
function translateDoc(
doc: DocMetadata,
docsTranslations: TranslationFileContent,
): DocMetadata {
return {
...doc,
title: docsTranslations[`${doc.unversionedId}.title`]?.message ?? doc.title,
sidebar_label:
docsTranslations[`${doc.unversionedId}.sidebar_label`]?.message ??
doc.sidebar_label,
};
}
function getDocsTranslations(version: LoadedVersion): TranslationFileContent {
return mergeTranslations(version.docs.map(getDocTranslations));
}
function translateDocs(
docs: DocMetadata[],
docsTranslations: TranslationFileContent,
): DocMetadata[] {
return docs.map((doc) => translateDoc(doc, docsTranslations));
}
*/
function getSidebarTranslationFileContent(
sidebar: Sidebar,
sidebarName: string,
): TranslationFileContent {
const categories = collectSidebarCategories(sidebar);
const categoryContent: TranslationFileContent = chain(categories)
.keyBy((category) => `sidebar.${sidebarName}.category.${category.label}`)
.mapValues((category) => ({
message: category.label,
description: `The label for category ${category.label} in sidebar ${sidebarName}`,
}))
.value();
const links = collectSidebarLinks(sidebar);
const linksContent: TranslationFileContent = chain(links)
.keyBy((link) => `sidebar.${sidebarName}.link.${link.label}`)
.mapValues((link) => ({
message: link.label,
description: `The label for link ${link.label} in sidebar ${sidebarName}, linking to ${link.href}`,
}))
.value();
return mergeTranslations([categoryContent, linksContent]);
}
function translateSidebar({
sidebar,
sidebarName,
sidebarsTranslations,
}: {
sidebar: Sidebar;
sidebarName: string;
sidebarsTranslations: TranslationFileContent;
}): Sidebar {
return transformSidebarItems(
sidebar,
(item: SidebarItem): SidebarItem => {
if (item.type === 'category') {
return {
...item,
label:
sidebarsTranslations[
`sidebar.${sidebarName}.category.${item.label}`
]?.message ?? item.label,
};
}
if (item.type === 'link') {
return {
...item,
label:
sidebarsTranslations[`sidebar.${sidebarName}.link.${item.label}`]
?.message ?? item.label,
};
}
return item;
},
);
}
function getSidebarsTranslations(
version: LoadedVersion,
): TranslationFileContent {
return mergeTranslations(
Object.entries(version.sidebars).map(([sidebarName, sidebar]) => {
const normalizedSidebarName = getNormalizedSidebarName({
sidebarName,
versionName: version.versionName,
});
return getSidebarTranslationFileContent(sidebar, normalizedSidebarName);
}),
);
}
function translateSidebars(
version: LoadedVersion,
sidebarsTranslations: TranslationFileContent,
): Sidebars {
return mapValues(version.sidebars, (sidebar, sidebarName) => {
return translateSidebar({
sidebar,
sidebarName: getNormalizedSidebarName({
sidebarName,
versionName: version.versionName,
}),
sidebarsTranslations,
});
});
}
function getVersionTranslationFiles(version: LoadedVersion): TranslationFiles {
const versionTranslations: TranslationFileContent = {
'version.label': {
message: version.versionLabel,
description: `The label for version ${version.versionName}`,
},
};
const sidebarsTranslations: TranslationFileContent = getSidebarsTranslations(
version,
);
// const docsTranslations: TranslationFileContent = getDocsTranslations(version);
return [
{
path: getVersionFileName(version.versionName),
content: mergeTranslations([
versionTranslations,
sidebarsTranslations,
// docsTranslations,
]),
},
];
}
function translateVersion(
version: LoadedVersion,
translationFiles: Record<string, TranslationFile>,
): LoadedVersion {
const versionTranslations =
translationFiles[getVersionFileName(version.versionName)].content;
return {
...version,
versionLabel: versionTranslations['version.label']?.message,
sidebars: translateSidebars(version, versionTranslations),
// docs: translateDocs(version.docs, versionTranslations),
};
}
function getVersionsTranslationFiles(
versions: LoadedVersion[],
): TranslationFiles {
return flatten(versions.map(getVersionTranslationFiles));
}
function translateVersions(
versions: LoadedVersion[],
translationFiles: Record<string, TranslationFile>,
): LoadedVersion[] {
return versions.map((version) => translateVersion(version, translationFiles));
}
export function getLoadedContentTranslationFiles(
loadedContent: LoadedContent,
): TranslationFiles {
return getVersionsTranslationFiles(loadedContent.loadedVersions);
}
export function translateLoadedContent(
loadedContent: LoadedContent,
translationFiles: TranslationFile[],
): LoadedContent {
const translationFilesMap: Record<string, TranslationFile> = keyBy(
translationFiles,
(f) => f.path,
);
return {
loadedVersions: translateVersions(
loadedContent.loadedVersions,
translationFilesMap,
),
};
}

View file

@ -9,6 +9,7 @@
/// <reference types="@docusaurus/module-type-aliases" />
export type DocFile = {
filePath: string;
source: string;
content: string;
lastUpdate: LastUpdateData;
@ -21,7 +22,8 @@ export type VersionMetadata = {
versionLabel: string; // Version 1.0.0
versionPath: string; // /baseUrl/docs/1.0.0
isLast: boolean;
docsDirPath: string; // versioned_docs/1.0.0
docsDirPath: string; // "versioned_docs/version-1.0.0"
docsDirPathLocalized: string; // "i18n/fr/version-1.0.0/default"
sidebarFilePath: string; // versioned_sidebars/1.0.0.json
routePriority: number | undefined; // -1 for the latest docs
};
@ -91,6 +93,7 @@ export type SidebarItem =
| SidebarItemCategory;
export type Sidebar = SidebarItem[];
export type SidebarItemType = SidebarItem['type'];
export type Sidebars = Record<string, Sidebar>;

View file

@ -22,7 +22,7 @@ import {
import {DEFAULT_PLUGIN_ID} from '@docusaurus/core/lib/constants';
import {LoadContext} from '@docusaurus/types';
import {normalizeUrl} from '@docusaurus/utils';
import {getPluginI18nPath, normalizeUrl} from '@docusaurus/utils';
import {difference} from 'lodash';
import chalk from 'chalk';
@ -137,9 +137,12 @@ function getVersionMetadataPaths({
options,
}: {
versionName: string;
context: Pick<LoadContext, 'siteDir'>;
context: Pick<LoadContext, 'siteDir' | 'i18n'>;
options: Pick<PluginOptions, 'id' | 'path' | 'sidebarPath'>;
}): Pick<VersionMetadata, 'docsDirPath' | 'sidebarFilePath'> {
}): Pick<
VersionMetadata,
'docsDirPath' | 'docsDirPathLocalized' | 'sidebarFilePath'
> {
const isCurrentVersion = versionName === CURRENT_VERSION_NAME;
const docsDirPath = isCurrentVersion
@ -149,6 +152,18 @@ function getVersionMetadataPaths({
`version-${versionName}`,
);
const docsDirPathLocalized = getPluginI18nPath({
siteDir: context.siteDir,
locale: context.i18n.currentLocale,
pluginName: 'docusaurus-plugin-content-docs',
pluginId: options.id,
subPaths: [
versionName === CURRENT_VERSION_NAME
? CURRENT_VERSION_NAME
: `version-${versionName}`,
],
});
const sidebarFilePath = isCurrentVersion
? path.resolve(context.siteDir, options.sidebarPath)
: path.join(
@ -156,7 +171,7 @@ function getVersionMetadataPaths({
`version-${versionName}-sidebars.json`,
);
return {docsDirPath, sidebarFilePath};
return {docsDirPath, docsDirPathLocalized, sidebarFilePath};
}
function createVersionMetadata({
@ -167,13 +182,17 @@ function createVersionMetadata({
}: {
versionName: string;
isLast: boolean;
context: Pick<LoadContext, 'siteDir' | 'baseUrl'>;
context: Pick<LoadContext, 'siteDir' | 'baseUrl' | 'i18n'>;
options: Pick<
PluginOptions,
'id' | 'path' | 'sidebarPath' | 'routeBasePath' | 'versions'
>;
}): VersionMetadata {
const {sidebarFilePath, docsDirPath} = getVersionMetadataPaths({
const {
sidebarFilePath,
docsDirPath,
docsDirPathLocalized,
} = getVersionMetadataPaths({
versionName,
context,
options,
@ -210,6 +229,7 @@ function createVersionMetadata({
routePriority,
sidebarFilePath,
docsDirPath,
docsDirPathLocalized,
};
}
@ -322,7 +342,7 @@ export function readVersionsMetadata({
context,
options,
}: {
context: Pick<LoadContext, 'siteDir' | 'baseUrl'>;
context: Pick<LoadContext, 'siteDir' | 'baseUrl' | 'i18n'>;
options: Pick<
PluginOptions,
| 'id'
@ -356,3 +376,15 @@ export function readVersionsMetadata({
versionsMetadata.forEach(checkVersionMetadataPaths);
return versionsMetadata;
}
// order matter!
// Read in priority the localized path, then the unlocalized one
// We want the localized doc to "override" the unlocalized one
export function getDocsDirPaths(
versionMetadata: Pick<
VersionMetadata,
'docsDirPath' | 'docsDirPathLocalized'
>,
): [string, string] {
return [versionMetadata.docsDirPathLocalized, versionMetadata.docsDirPath];
}