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

* Translate versions dropdown label

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Make blog options tranlatable

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Fix names

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Rename functions

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Rename translations path

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Update docs

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Update Chinese translations

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Remove space

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Update docs

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Add jest test

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Fix copyright typing

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Update test

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Update options

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Fix test

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>
This commit is contained in:
Joshua Chen 2021-08-20 23:11:59 +08:00 committed by GitHub
parent 08597045ed
commit 8e1cde135c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 293 additions and 17 deletions

View file

@ -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",
}
`;

View file

@ -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);
});
@ -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: ''},
});
});
@ -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: ''},
});
});

View file

@ -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();
});
});

View file

@ -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 {
@ -102,6 +103,10 @@ export default function pluginContentBlog(
);
},
async getTranslationFiles() {
return getTranslationFiles(options);
},
// Fetches blog contents and returns metadata for the necessary routes.
async loadContent() {
const {
@ -109,6 +114,7 @@ export default function pluginContentBlog(
routeBasePath,
blogDescription,
blogTitle,
blogSidebarTitle,
} = options;
const blogPosts: BlogPost[] = await generateBlogPosts(
@ -119,6 +125,7 @@ export default function pluginContentBlog(
if (!blogPosts.length) {
return {
blogSidebarTitle,
blogPosts: [],
blogListPaginated: [],
blogTags: {},
@ -192,6 +199,7 @@ export default function pluginContentBlog(
Object.keys(blogTags).length > 0 ? tagsPath : null;
return {
blogSidebarTitle,
blogPosts,
blogListPaginated,
blogTags,
@ -213,6 +221,7 @@ export default function pluginContentBlog(
const {addRoute, createData} = actions;
const {
blogSidebarTitle,
blogPosts,
blogListPaginated,
blogTags,
@ -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,
@ -371,6 +380,10 @@ export default function pluginContentBlog(
}
},
translateContent({content, translationFiles}) {
return translateContent(content, translationFiles);
},
configureWebpack(
_config: Configuration,
isServer: boolean,
@ -461,7 +474,7 @@ export default function pluginContentBlog(
},
async postBuild({outDir}: Props) {
if (!options.feedOptions?.type) {
if (!options.feedOptions.type) {
return;
}

View file

@ -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: {},
@ -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
@ -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),
});

View file

@ -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,
},
};
});
}
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,
),
};
}

View file

@ -15,6 +15,7 @@ import type {
export type BlogContentPaths = ContentPaths;
export interface BlogContent {
blogSidebarTitle: string;
blogPosts: BlogPost[];
blogListPaginated: BlogPaginated[];
blogTags: BlogTags;
@ -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;