Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: make blog config options and navbar versions dropdown label translatable #5371

Merged
merged 15 commits into from
Aug 20, 2021
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`getContentTranslationFiles should return translation files matching snapshot 1`] = `
Array [
Object {
"content": Object {
"description": Object {
"description": "The description for the blog used in SEO",
"message": "Someone's random blog",
},
"sidebar.title": Object {
"description": "The label for the left sidebar",
"message": "All my posts",
},
"title": Object {
"description": "The title for the blog used in SEO",
"message": "My blog",
},
},
"path": "options",
},
]
`;

exports[`translateContent should return translated loaded content matching snapshot 1`] = `
Object {
"blogListPaginated": Array [
Object {
"items": Array [
"hello",
],
"metadata": Object {
"blogDescription": "Someone's random blog (translated)",
"blogTitle": "My blog (translated)",
"nextPage": null,
"page": 1,
"permalink": "/",
"postsPerPage": 10,
"previousPage": null,
"totalCount": 1,
"totalPages": 1,
},
},
],
"blogPosts": Array [
Object {
"id": "hello",
"metadata": Object {
"date": 2021-07-19T00:00:00.000Z,
"description": "/blog/2021/06/19/hello",
"formattedDate": "June 19, 2021",
"permalink": "/blog/2021/06/19/hello",
"source": "/blog/2021/06/19/hello",
"tags": Array [],
"title": "Hello",
"truncated": true,
},
},
],
"blogSidebarTitle": "All my posts (translated)",
"blogTags": Object {},
"blogTagsListPath": "/tags",
}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ test('should accept correctly defined user options', () => {
const {value, error} = PluginOptionSchema.validate(userOptions);
expect(value).toEqual({
...userOptions,
feedOptions: {type: ['rss'], title: 'myTitle'},
feedOptions: {type: ['rss'], title: 'myTitle', copyright: ''},
});
expect(error).toBe(undefined);
});
Expand Down Expand Up @@ -78,7 +78,7 @@ test('should convert all feed type to array with other feed type', () => {
});
expect(value).toEqual({
...DEFAULT_OPTIONS,
feedOptions: {type: ['rss', 'atom']},
feedOptions: {type: ['rss', 'atom'], copyright: ''},
});
});

Expand Down Expand Up @@ -106,7 +106,7 @@ test('should have array with rss + atom, title for missing feed type', () => {
});
expect(value).toEqual({
...DEFAULT_OPTIONS,
feedOptions: {type: ['rss', 'atom'], title: 'title'},
feedOptions: {type: ['rss', 'atom'], title: 'title', copyright: ''},
});
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/**
* 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 {BlogPost, BlogContent, PluginOptions} from '../types';
import {getTranslationFiles, translateContent} from '../translations';
import {DEFAULT_OPTIONS} from '../pluginOptionSchema';
import {updateTranslationFileMessages} from '@docusaurus/utils';

const sampleBlogOptions: PluginOptions = {
...DEFAULT_OPTIONS,
blogSidebarTitle: 'All my posts',
blogTitle: 'My blog',
blogDescription: "Someone's random blog",
};

const sampleBlogPosts: BlogPost[] = [
{
id: 'hello',
metadata: {
permalink: '/blog/2021/06/19/hello',
source: '/blog/2021/06/19/hello',
description: '/blog/2021/06/19/hello',
date: new Date(2021, 6, 19),
formattedDate: 'June 19, 2021',
tags: [],
title: 'Hello',
truncated: true,
},
},
];

const sampleBlogContent: BlogContent = {
blogSidebarTitle: sampleBlogOptions.blogSidebarTitle,
blogListPaginated: [
{
items: ['hello'],
metadata: {
permalink: '/',
page: 1,
postsPerPage: 10,
totalPages: 1,
totalCount: 1,
previousPage: null,
nextPage: null,
blogTitle: sampleBlogOptions.blogTitle,
blogDescription: sampleBlogOptions.blogDescription,
},
},
],
blogPosts: sampleBlogPosts,
blogTags: {},
blogTagsListPath: '/tags',
};

function getSampleTranslationFiles() {
return getTranslationFiles(sampleBlogOptions);
}
function getSampleTranslationFilesTranslated() {
const translationFiles = getSampleTranslationFiles();
return translationFiles.map((translationFile) =>
updateTranslationFileMessages(
translationFile,
(message) => `${message} (translated)`,
),
);
}

describe('getContentTranslationFiles', () => {
test('should return translation files matching snapshot', async () => {
expect(getSampleTranslationFiles()).toMatchSnapshot();
});
});

describe('translateContent', () => {
test('should not translate anything if translation files are untranslated', () => {
const translationFiles = getSampleTranslationFiles();
expect(translateContent(sampleBlogContent, translationFiles)).toEqual(
sampleBlogContent,
);
});

test('should return translated loaded content matching snapshot', () => {
const translationFiles = getSampleTranslationFilesTranslated();
expect(
translateContent(sampleBlogContent, translationFiles),
).toMatchSnapshot();
});
});
17 changes: 15 additions & 2 deletions packages/docusaurus-plugin-content-blog/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
STATIC_DIR_NAME,
DEFAULT_PLUGIN_ID,
} from '@docusaurus/core/lib/constants';
import {translateContent, getTranslationFiles} from './translations';
import {flatten, take} from 'lodash';

import {
Expand Down Expand Up @@ -102,13 +103,18 @@ export default function pluginContentBlog(
);
},

async getTranslationFiles() {
return getTranslationFiles(options);
},

// Fetches blog contents and returns metadata for the necessary routes.
async loadContent() {
const {
postsPerPage: postsPerPageOption,
routeBasePath,
blogDescription,
blogTitle,
blogSidebarTitle,
} = options;

const blogPosts: BlogPost[] = await generateBlogPosts(
Expand All @@ -119,6 +125,7 @@ export default function pluginContentBlog(

if (!blogPosts.length) {
return {
blogSidebarTitle,
blogPosts: [],
blogListPaginated: [],
blogTags: {},
Expand Down Expand Up @@ -192,6 +199,7 @@ export default function pluginContentBlog(
Object.keys(blogTags).length > 0 ? tagsPath : null;

return {
blogSidebarTitle,
blogPosts,
blogListPaginated,
blogTags,
Expand All @@ -213,6 +221,7 @@ export default function pluginContentBlog(

const {addRoute, createData} = actions;
const {
blogSidebarTitle,
blogPosts,
blogListPaginated,
blogTags,
Expand All @@ -233,7 +242,7 @@ export default function pluginContentBlog(
`blog-post-list-prop-${pluginId}.json`,
JSON.stringify(
{
title: options.blogSidebarTitle,
title: blogSidebarTitle,
items: sidebarBlogPosts.map((blogPost) => ({
title: blogPost.metadata.title,
permalink: blogPost.metadata.permalink,
Expand Down Expand Up @@ -371,6 +380,10 @@ export default function pluginContentBlog(
}
},

translateContent({content, translationFiles}) {
return translateContent(content, translationFiles);
},

configureWebpack(
_config: Configuration,
isServer: boolean,
Expand Down Expand Up @@ -461,7 +474,7 @@ export default function pluginContentBlog(
},

async postBuild({outDir}: Props) {
if (!options.feedOptions?.type) {
if (!options.feedOptions.type) {
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ import {
URISchema,
} from '@docusaurus/utils-validation';
import {GlobExcludeDefault} from '@docusaurus/utils';
import {PluginOptions} from './types';

export const DEFAULT_OPTIONS = {
feedOptions: {type: ['rss', 'atom']},
export const DEFAULT_OPTIONS: PluginOptions = {
feedOptions: {type: ['rss', 'atom'], copyright: ''},
beforeDefaultRehypePlugins: [],
beforeDefaultRemarkPlugins: [],
admonitions: {},
Expand All @@ -39,7 +40,7 @@ export const DEFAULT_OPTIONS = {
editLocalizedFiles: false,
};

export const PluginOptionSchema = Joi.object({
export const PluginOptionSchema = Joi.object<PluginOptions>({
path: Joi.string().default(DEFAULT_OPTIONS.path),
routeBasePath: Joi.string()
// '' not allowed, see https://github.com/facebook/docusaurus/issues/3374
Expand Down Expand Up @@ -96,7 +97,14 @@ export const PluginOptionSchema = Joi.object({
.default(DEFAULT_OPTIONS.feedOptions.type),
title: Joi.string().allow(''),
description: Joi.string().allow(''),
copyright: Joi.string(),
// only add default value when user actually wants a feed (type is not null)
copyright: Joi.when('type', {
is: Joi.any().valid(null),
then: Joi.string().optional(),
otherwise: Joi.string()
.allow('')
.default(DEFAULT_OPTIONS.feedOptions.copyright),
}),
language: Joi.string(),
}).default(DEFAULT_OPTIONS.feedOptions),
});
63 changes: 63 additions & 0 deletions packages/docusaurus-plugin-content-blog/src/translations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/**
* 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 type {BlogContent, PluginOptions, BlogPaginated} from './types';
import type {TranslationFileContent, TranslationFiles} from '@docusaurus/types';

function translateListPage(
blogListPaginated: BlogPaginated[],
translations: TranslationFileContent,
) {
return blogListPaginated.map((page) => {
const {items, metadata} = page;
return {
items,
metadata: {
...metadata,
blogTitle: translations.title.message,
blogDescription: translations.description.message,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of translating each page title this way, I'm wondering if we shouldn't create a bundle with all those generic blog infos, so that multiple pages could use the same bundle?

Like:

const blogMetadataProp = await createData({title,description,sidebarTitle})

addRoute({
  modules: {
    blogMetadata: blogMetadataProp
  }
});

Not sure it make sense though so I'll let you figure out, current implementation is good enough for me

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No strong feelings, I also considered this, but the current implementation equally makes sense. I was thinking if it is possible to have a translateOptions lifecycle?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's keep it this way, I may refactor this plugin a bit in the near future because it's a bit messy.

About translateOptions, I think adding options to content is fine and we don't need another new lifecycle, but let's see, we may add this later if this becomes a common need for ourselves or other plugin authors

},
};
});
}

export function getTranslationFiles(options: PluginOptions): TranslationFiles {
return [
{
path: 'options',
content: {
title: {
message: options.blogTitle,
description: 'The title for the blog used in SEO',
},
description: {
message: options.blogDescription,
description: 'The description for the blog used in SEO',
},
'sidebar.title': {
message: options.blogSidebarTitle,
description: 'The label for the left sidebar',
},
},
},
];
}

export function translateContent(
content: BlogContent,
translationFiles: TranslationFiles,
): BlogContent {
const [{content: optonsTranslations}] = translationFiles;
return {
...content,
blogSidebarTitle: optonsTranslations['sidebar.title'].message,
blogListPaginated: translateListPage(
content.blogListPaginated,
optonsTranslations,
),
};
}
3 changes: 2 additions & 1 deletion packages/docusaurus-plugin-content-blog/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type {
export type BlogContentPaths = ContentPaths;

export interface BlogContent {
blogSidebarTitle: string;
blogPosts: BlogPost[];
blogListPaginated: BlogPaginated[];
blogTags: BlogTags;
Expand Down Expand Up @@ -48,7 +49,7 @@ export interface PluginOptions extends RemarkAndRehypePluginOptions {
truncateMarker: RegExp;
showReadingTime: boolean;
feedOptions: {
type?: [FeedType] | null;
type?: FeedType[] | null;
title?: string;
description?: string;
copyright: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"theme.lastUpdated.byUser": " بواسطة {user}",
"theme.lastUpdated.lastUpdatedAtBy": "آخر تحديث{atDate}{byUser}",
"theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": "← Back to main menu",
"theme.navbar.mobileVersionsDropdown.label": "Versions",
"theme.tags.tagsListLabel": "الوسوم:",
"theme.tags.tagsPageLink": "عرض كل الوسوم",
"theme.tags.tagsPageTitle": "الوسوم"
Expand Down