mirror of
https://github.com/facebook/docusaurus.git
synced 2025-06-13 16:23:34 +02:00
feat: Docusaurus ESLint plugin to enforce best Docusaurus practices (#7206)
* feat: add eslint plugin * refactor * add tests * fixups! * fix(no-dynamic-i18n-messages): make translate() recognize template literals * refactor: rename rule no-dynamic-i18n-messages --> string-literal-i18n-messages * feat: add ignoreStrings option and refactor * docs: migrate docs to /docs/api/plugins * docs: fix anchor links in README.md * fix: add some ignored strings * docs: update eslint-plugin docs * fix: update README link * docs: various updates - Reorder sidebar entries - Fix title size - Use Markdown file paths - Simplify relative links * address reviews * wording polish * add npmignore * fix all internal warnings * doc improvements * fix test Co-authored-by: Joshua Chen <sidachen2003@gmail.com>
This commit is contained in:
parent
ae788c536f
commit
3b1170eb44
34 changed files with 885 additions and 52 deletions
39
.eslintrc.js
39
.eslintrc.js
|
@ -32,6 +32,7 @@ module.exports = {
|
||||||
'plugin:@typescript-eslint/recommended',
|
'plugin:@typescript-eslint/recommended',
|
||||||
'plugin:regexp/recommended',
|
'plugin:regexp/recommended',
|
||||||
'prettier',
|
'prettier',
|
||||||
|
'plugin:@docusaurus/all',
|
||||||
],
|
],
|
||||||
settings: {
|
settings: {
|
||||||
'import/resolver': {
|
'import/resolver': {
|
||||||
|
@ -41,7 +42,14 @@ module.exports = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
reportUnusedDisableDirectives: true,
|
reportUnusedDisableDirectives: true,
|
||||||
plugins: ['react-hooks', 'header', 'jest', '@typescript-eslint', 'regexp'],
|
plugins: [
|
||||||
|
'react-hooks',
|
||||||
|
'header',
|
||||||
|
'jest',
|
||||||
|
'@typescript-eslint',
|
||||||
|
'regexp',
|
||||||
|
'@docusaurus',
|
||||||
|
],
|
||||||
rules: {
|
rules: {
|
||||||
'array-callback-return': WARNING,
|
'array-callback-return': WARNING,
|
||||||
camelcase: WARNING,
|
camelcase: WARNING,
|
||||||
|
@ -305,6 +313,24 @@ module.exports = {
|
||||||
// locals must be justified with a disable comment.
|
// locals must be justified with a disable comment.
|
||||||
'@typescript-eslint/no-unused-vars': [ERROR, {ignoreRestSiblings: true}],
|
'@typescript-eslint/no-unused-vars': [ERROR, {ignoreRestSiblings: true}],
|
||||||
'@typescript-eslint/prefer-optional-chain': ERROR,
|
'@typescript-eslint/prefer-optional-chain': ERROR,
|
||||||
|
'@docusaurus/no-untranslated-text': [
|
||||||
|
WARNING,
|
||||||
|
{
|
||||||
|
ignoreStrings: [
|
||||||
|
'·',
|
||||||
|
'-',
|
||||||
|
'—',
|
||||||
|
'×',
|
||||||
|
'', // zwj: ​
|
||||||
|
'@',
|
||||||
|
'WebContainers',
|
||||||
|
'Twitter',
|
||||||
|
'GitHub',
|
||||||
|
'Dev.to',
|
||||||
|
'1.x',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
|
@ -327,6 +353,7 @@ module.exports = {
|
||||||
'header/header': OFF,
|
'header/header': OFF,
|
||||||
'global-require': OFF,
|
'global-require': OFF,
|
||||||
'@typescript-eslint/no-var-requires': OFF,
|
'@typescript-eslint/no-var-requires': OFF,
|
||||||
|
'@docusaurus/no-untranslated-text': OFF,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -350,6 +377,16 @@ module.exports = {
|
||||||
'@typescript-eslint/explicit-module-boundary-types': OFF,
|
'@typescript-eslint/explicit-module-boundary-types': OFF,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
files: [
|
||||||
|
'**/__tests__/**',
|
||||||
|
'packages/docusaurus-plugin-debug/**',
|
||||||
|
'website/_dogfooding/**',
|
||||||
|
],
|
||||||
|
rules: {
|
||||||
|
'@docusaurus/no-untranslated-text': OFF,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// Internal files where extraneous deps don't matter much at long as
|
// Internal files where extraneous deps don't matter much at long as
|
||||||
// they run
|
// they run
|
||||||
|
|
|
@ -82,6 +82,7 @@ export default function LastUpdated({
|
||||||
</Translate>
|
</Translate>
|
||||||
{process.env.NODE_ENV === 'development' && (
|
{process.env.NODE_ENV === 'development' && (
|
||||||
<div>
|
<div>
|
||||||
|
{/* eslint-disable-next-line @docusaurus/no-untranslated-text */}
|
||||||
<small> (Simulated during dev for better perf)</small>
|
<small> (Simulated during dev for better perf)</small>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -23,6 +23,7 @@ function Header({children}: {children: React.ReactNode}) {
|
||||||
|
|
||||||
function LivePreviewLoader() {
|
function LivePreviewLoader() {
|
||||||
// Is it worth improving/translating?
|
// Is it worth improving/translating?
|
||||||
|
// eslint-disable-next-line @docusaurus/no-untranslated-text
|
||||||
return <div>Loading...</div>;
|
return <div>Loading...</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,7 @@ describe('<Translate>', () => {
|
||||||
it('rejects when children is not a string', () => {
|
it('rejects when children is not a string', () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
renderer.create(
|
renderer.create(
|
||||||
|
// eslint-disable-next-line @docusaurus/string-literal-i18n-messages
|
||||||
<Translate id="foo">
|
<Translate id="foo">
|
||||||
{/* @ts-expect-error: for test */}
|
{/* @ts-expect-error: for test */}
|
||||||
<span>aaa</span>
|
<span>aaa</span>
|
||||||
|
|
|
@ -5,6 +5,9 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Should we translate theme-fallback?
|
||||||
|
/* eslint-disable @docusaurus/no-untranslated-text */
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Layout from '@theme/Layout';
|
import Layout from '@theme/Layout';
|
||||||
import ErrorBoundary from '@docusaurus/ErrorBoundary';
|
import ErrorBoundary from '@docusaurus/ErrorBoundary';
|
||||||
|
|
|
@ -5,6 +5,9 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Should we translate theme-fallback?
|
||||||
|
/* eslint-disable @docusaurus/no-untranslated-text */
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type {LoadingComponentProps} from 'react-loadable';
|
import type {LoadingComponentProps} from 'react-loadable';
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,9 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Should we translate theme-fallback?
|
||||||
|
/* eslint-disable @docusaurus/no-untranslated-text */
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Layout from '@theme/Layout';
|
import Layout from '@theme/Layout';
|
||||||
import Head from '@docusaurus/Head';
|
import Head from '@docusaurus/Head';
|
||||||
|
|
10
packages/eslint-plugin/.eslintrc.js
Normal file
10
packages/eslint-plugin/.eslintrc.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
extends: ['../../.eslintrc.js', 'plugin:eslint-plugin/recommended'],
|
||||||
|
};
|
6
packages/eslint-plugin/.npmignore
Normal file
6
packages/eslint-plugin/.npmignore
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
copyUntypedFiles.mjs
|
||||||
|
.tsbuildinfo
|
||||||
|
tsconfig*
|
||||||
|
__tests__
|
||||||
|
|
||||||
|
.eslintrc.js
|
7
packages/eslint-plugin/README.md
Normal file
7
packages/eslint-plugin/README.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# `@docusaurus/eslint-plugin`
|
||||||
|
|
||||||
|
ESLint plugin to enforce best Docusaurus practices.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
See [eslint-plugin documentation](https://docusaurus.io/docs/api/misc/@docusaurus/eslint-plugin).
|
25
packages/eslint-plugin/lib/index.js
Normal file
25
packages/eslint-plugin/lib/index.js
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const requireIndex = require('requireindex');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
rules: requireIndex(`${__dirname}/rules`),
|
||||||
|
configs: {
|
||||||
|
recommended: {
|
||||||
|
rules: {
|
||||||
|
'@docusaurus/string-literal-i18n-messages': 'error',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
all: {
|
||||||
|
rules: {
|
||||||
|
'@docusaurus/string-literal-i18n-messages': 'error',
|
||||||
|
'@docusaurus/no-untranslated-text': 'warn',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,147 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const rule = require('../no-untranslated-text');
|
||||||
|
const {RuleTester} = require('eslint');
|
||||||
|
const {getCommonValidTests} = require('../../util');
|
||||||
|
|
||||||
|
const errorsJSX = [{messageId: 'translateChildren', type: 'JSXElement'}];
|
||||||
|
const errorsJSXFragment = [
|
||||||
|
{messageId: 'translateChildren', type: 'JSXFragment'},
|
||||||
|
];
|
||||||
|
|
||||||
|
const ruleTester = new RuleTester({
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 2022,
|
||||||
|
ecmaFeatures: {jsx: true},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
ruleTester.run('no-untranslated-text', rule, {
|
||||||
|
valid: [
|
||||||
|
...getCommonValidTests(),
|
||||||
|
{
|
||||||
|
code: '<Component>·</Component>',
|
||||||
|
options: [{ignoreStrings: ['·', '—', '×']}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: '<Component>· </Component>',
|
||||||
|
options: [{ignoreStrings: ['·', '—', '×']}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: '<Component> · </Component>',
|
||||||
|
options: [{ignoreStrings: ['·', '—', '×']}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: '<Component>· ·</Component>',
|
||||||
|
options: [{ignoreStrings: ['·', '—', '×']}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: '<Component>· — ×</Component>',
|
||||||
|
options: [{ignoreStrings: ['·', '—', '×']}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: '<Component>{"·"}</Component>',
|
||||||
|
options: [{ignoreStrings: ['·']}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: "<Component>{'·'}</Component>",
|
||||||
|
options: [{ignoreStrings: ['·']}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: '<Component>{`·`}</Component>',
|
||||||
|
options: [{ignoreStrings: ['·', '—', '×']}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: '<Component>Docusaurus</Component>',
|
||||||
|
options: [{ignoreStrings: ['Docusaurus']}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: '<Component>​</Component>',
|
||||||
|
options: [{ignoreStrings: ['']}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: `<>
|
||||||
|
{' · '}
|
||||||
|
</>`,
|
||||||
|
options: [{ignoreStrings: ['·', "'"]}],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
invalid: [
|
||||||
|
{
|
||||||
|
code: '<Component>text</Component>',
|
||||||
|
errors: errorsJSX,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: '<Component> text </Component>',
|
||||||
|
errors: errorsJSX,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: '<Component>"text"</Component>',
|
||||||
|
errors: errorsJSX,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: "<Component>'text'</Component>",
|
||||||
|
errors: errorsJSX,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: '<Component>`text`</Component>',
|
||||||
|
errors: errorsJSX,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: '<Component>{"text"}</Component>',
|
||||||
|
errors: errorsJSX,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: "<Component>{'text'}</Component>",
|
||||||
|
errors: errorsJSX,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: '<Component>{`text`}</Component>',
|
||||||
|
errors: errorsJSX,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: '<>text</>',
|
||||||
|
errors: errorsJSXFragment,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: '<Component>· — ×</Component>',
|
||||||
|
errors: errorsJSX,
|
||||||
|
options: [{ignoreStrings: ['·', '—']}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: '<Component>··</Component>',
|
||||||
|
errors: errorsJSX,
|
||||||
|
options: [{ignoreStrings: ['·', '—', '×']}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: '<Component> ·· </Component>',
|
||||||
|
errors: errorsJSX,
|
||||||
|
options: [{ignoreStrings: ['·', '—', '×']}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: '<Component>"·"</Component>',
|
||||||
|
errors: errorsJSX,
|
||||||
|
options: [{ignoreStrings: ['·', '—', '×']}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: "<Component>'·'</Component>",
|
||||||
|
errors: errorsJSX,
|
||||||
|
options: [{ignoreStrings: ['·', '—', '×']}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: '<Component>`·`</Component>',
|
||||||
|
errors: errorsJSX,
|
||||||
|
options: [{ignoreStrings: ['·', '—', '×']}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: '<Component>Docusaurus</Component>',
|
||||||
|
errors: errorsJSX,
|
||||||
|
options: [{ignoreStrings: ['Docu', 'saurus']}],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
|
@ -0,0 +1,51 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const rule = require('../string-literal-i18n-messages');
|
||||||
|
const {RuleTester} = require('eslint');
|
||||||
|
const {getCommonValidTests} = require('../../util');
|
||||||
|
|
||||||
|
const errorsJSX = [{messageId: 'translateChildren', type: 'JSXElement'}];
|
||||||
|
const errorsFunc = [{messageId: 'translateArg', type: 'Identifier'}];
|
||||||
|
|
||||||
|
const ruleTester = new RuleTester({
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 2022,
|
||||||
|
ecmaFeatures: {jsx: true},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
ruleTester.run('string-literal-i18n-messages', rule, {
|
||||||
|
valid: [...getCommonValidTests()],
|
||||||
|
|
||||||
|
invalid: [
|
||||||
|
{
|
||||||
|
code: '<Translate>{text}</Translate>',
|
||||||
|
errors: errorsJSX,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: '<Translate>Hi {text} my friend</Translate>',
|
||||||
|
errors: errorsJSX,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: '<Translate> {text} </Translate>',
|
||||||
|
errors: errorsJSX,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: '<Translate>`{text}`</Translate>',
|
||||||
|
errors: errorsJSX,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// eslint-disable-next-line no-template-curly-in-string
|
||||||
|
code: '<Translate>{`${text}`}</Translate>',
|
||||||
|
errors: errorsJSX,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'translate({message: metaTitle})',
|
||||||
|
errors: errorsFunc,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
71
packages/eslint-plugin/lib/rules/no-untranslated-text.js
Normal file
71
packages/eslint-plugin/lib/rules/no-untranslated-text.js
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const {isTextLabelChild, report} = require('../util');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {import('eslint').Rule.RuleModule}
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
meta: {
|
||||||
|
type: 'suggestion',
|
||||||
|
docs: {
|
||||||
|
description:
|
||||||
|
'enforce text labels in JSX to be wrapped by translate calls',
|
||||||
|
category: 'Suggestions',
|
||||||
|
url: 'https://docusaurus.io/docs/api/misc/@docusaurus/eslint-plugin/no-untranslated-text',
|
||||||
|
},
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
ignoreStrings: {
|
||||||
|
type: 'array',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
additionalProperties: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
messages: {
|
||||||
|
translateChildren:
|
||||||
|
'All text labels in JSX should be wrapped by translate calls',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
create(context) {
|
||||||
|
const stringsToIgnore = context.options[0]?.ignoreStrings ?? [];
|
||||||
|
|
||||||
|
const isParentTranslate = ({child, isParentFragment}) =>
|
||||||
|
!isParentFragment &&
|
||||||
|
child.parent.openingElement.name.name === 'Translate';
|
||||||
|
|
||||||
|
const isChildValid = ({child, isParentFragment}) => {
|
||||||
|
if (!isTextLabelChild({child, ignoreWhitespace: true, stringsToIgnore})) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return isParentTranslate({child, isParentFragment});
|
||||||
|
};
|
||||||
|
|
||||||
|
const isNodeValid = ({node, isFragment = false} = {}) =>
|
||||||
|
node.children.every((child) =>
|
||||||
|
isChildValid({child, isParentFragment: isFragment}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
'JSXElement[openingElement.selfClosing=false]': (node) => {
|
||||||
|
if (!isNodeValid({node})) {
|
||||||
|
report(context, node, 'translateChildren');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'JSXFragment[openingFragment]': (node) => {
|
||||||
|
if (!isNodeValid({node, isFragment: true})) {
|
||||||
|
report(context, node, 'translateChildren');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,62 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const {
|
||||||
|
isTextLabelChild,
|
||||||
|
report,
|
||||||
|
isStringWithoutExpressions,
|
||||||
|
} = require('../util');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {import('eslint').Rule.RuleModule}
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
meta: {
|
||||||
|
type: 'problem',
|
||||||
|
docs: {
|
||||||
|
description: 'enforce translate APIs to be called on plain text labels',
|
||||||
|
category: 'Possible Problems',
|
||||||
|
url: 'https://docusaurus.io/docs/api/misc/@docusaurus/eslint-plugin/string-literal-i18n-messages',
|
||||||
|
},
|
||||||
|
schema: [],
|
||||||
|
messages: {
|
||||||
|
translateChildren:
|
||||||
|
'<Translate> children must be hardcoded strings. You can have in-string dynamic placeholders using the values prop.',
|
||||||
|
translateArg:
|
||||||
|
'translation message must be a hardcoded string. You can have in-string dynamic placeholders using the values argument.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
create(context) {
|
||||||
|
const isNodeValid = (node) =>
|
||||||
|
node.children.every((child) => isTextLabelChild({child}));
|
||||||
|
|
||||||
|
return {
|
||||||
|
"JSXElement[openingElement.name.name='Translate']": (node) => {
|
||||||
|
if (!isNodeValid(node)) {
|
||||||
|
report(context, node, 'translateChildren');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"CallExpression > Identifier[name='translate']": (node) => {
|
||||||
|
const messageProperty = node.parent.arguments[0].properties.find(
|
||||||
|
(property) => property.key.name === 'message',
|
||||||
|
);
|
||||||
|
if (!messageProperty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!isStringWithoutExpressions({
|
||||||
|
text: messageProperty.value,
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
report(context, node, 'translateArg');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
145
packages/eslint-plugin/lib/util.js
Normal file
145
packages/eslint-plugin/lib/util.js
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const isMadeOfIgnoredStrings = ({text, stringsToIgnore}) =>
|
||||||
|
text
|
||||||
|
.trim()
|
||||||
|
.split(/\s+/)
|
||||||
|
.every((string) => stringsToIgnore.includes(string));
|
||||||
|
|
||||||
|
const isWhitespace = (text) => !text || !text.trim();
|
||||||
|
|
||||||
|
const isTextValid = ({text, ignoreWhitespace, stringsToIgnore}) =>
|
||||||
|
!!text &&
|
||||||
|
!(ignoreWhitespace && isWhitespace(text)) &&
|
||||||
|
!isMadeOfIgnoredStrings({
|
||||||
|
text,
|
||||||
|
stringsToIgnore,
|
||||||
|
});
|
||||||
|
|
||||||
|
const isStringWithoutExpressions = ({
|
||||||
|
text,
|
||||||
|
ignoreWhitespace = false,
|
||||||
|
stringsToIgnore = [],
|
||||||
|
} = {}) => {
|
||||||
|
switch (text.type) {
|
||||||
|
case 'Literal':
|
||||||
|
return isTextValid({text: text.value, ignoreWhitespace, stringsToIgnore});
|
||||||
|
case 'TemplateLiteral':
|
||||||
|
return (
|
||||||
|
!text.expressions.length &&
|
||||||
|
isTextValid({
|
||||||
|
text: text.quasis[0].value.raw,
|
||||||
|
ignoreWhitespace,
|
||||||
|
stringsToIgnore,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isTextLabelChild = ({
|
||||||
|
child,
|
||||||
|
ignoreWhitespace = false,
|
||||||
|
stringsToIgnore = [],
|
||||||
|
} = {}) => {
|
||||||
|
switch (child.type) {
|
||||||
|
case 'JSXText':
|
||||||
|
return isTextValid({
|
||||||
|
text: child.value,
|
||||||
|
ignoreWhitespace,
|
||||||
|
stringsToIgnore,
|
||||||
|
});
|
||||||
|
case 'JSXExpressionContainer':
|
||||||
|
return isStringWithoutExpressions({
|
||||||
|
text: child.expression,
|
||||||
|
ignoreWhitespace,
|
||||||
|
stringsToIgnore,
|
||||||
|
});
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const report = (context, node, messageId) => {
|
||||||
|
context.report({
|
||||||
|
node,
|
||||||
|
messageId,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCommonValidTests = () => [
|
||||||
|
{
|
||||||
|
code: '<Translate>text</Translate>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: '<Translate> text </Translate>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: '<Translate>"text"</Translate>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: "<Translate>'text'</Translate>",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: '<Translate>`text`</Translate>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: '<Translate>{"text"}</Translate>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: "<Translate>{'text'}</Translate>",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: '<Translate>{`text`}</Translate>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: '<Component>{text}</Component>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: '<Component> {text} </Component>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'translate({message: `My page meta title`})',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: `<Translate
|
||||||
|
id="homepage.title"
|
||||||
|
description="The homepage welcome message">
|
||||||
|
Welcome to my website
|
||||||
|
</Translate>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: `<Translate
|
||||||
|
values={{firstName: 'Sébastien'}}>
|
||||||
|
{'Welcome, {firstName}! How are you?'}
|
||||||
|
</Translate>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: `<Translate>{'This'} is {\`valid\`}</Translate>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: "translate({message: 'My page meta title'})",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: "translate({message: 'The logo of site {siteName}'}, {siteName: 'Docusaurus'})",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'translate({otherProp: metaTitle})',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'translate({otherProp: `My page meta title`})',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
isTextLabelChild,
|
||||||
|
report,
|
||||||
|
getCommonValidTests,
|
||||||
|
isStringWithoutExpressions,
|
||||||
|
};
|
32
packages/eslint-plugin/package.json
Normal file
32
packages/eslint-plugin/package.json
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
{
|
||||||
|
"name": "@docusaurus/eslint-plugin",
|
||||||
|
"version": "2.0.0-beta.18",
|
||||||
|
"description": "ESLint plugin to enforce best Docusaurus practices.",
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"keywords": [
|
||||||
|
"eslint",
|
||||||
|
"eslintplugin",
|
||||||
|
"eslint-plugin"
|
||||||
|
],
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/facebook/docusaurus.git",
|
||||||
|
"directory": "packages/eslint-plugin"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"requireindex": "^1.2.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"eslint-plugin-eslint-plugin": "^4.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"eslint": ">=6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
}
|
|
@ -245,12 +245,14 @@ refactorings
|
||||||
regexes
|
regexes
|
||||||
rehype
|
rehype
|
||||||
reponame
|
reponame
|
||||||
|
requireindex
|
||||||
retrocompatibility
|
retrocompatibility
|
||||||
retrocompatible
|
retrocompatible
|
||||||
roadmap
|
roadmap
|
||||||
rocketvalidator
|
rocketvalidator
|
||||||
rtcts
|
rtcts
|
||||||
rtlcss
|
rtlcss
|
||||||
|
saurus
|
||||||
scaleway
|
scaleway
|
||||||
searchbar
|
searchbar
|
||||||
sebastien
|
sebastien
|
||||||
|
|
2
website/docs/api/misc/_category_.yml
Normal file
2
website/docs/api/misc/_category_.yml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
label: Miscellaneous
|
||||||
|
position: 4
|
74
website/docs/api/misc/eslint-plugin/README.md
Normal file
74
website/docs/api/misc/eslint-plugin/README.md
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
---
|
||||||
|
sidebar_position: 0
|
||||||
|
id: eslint-plugin
|
||||||
|
title: '📦 eslint-plugin'
|
||||||
|
slug: '/api/misc/@docusaurus/eslint-plugin'
|
||||||
|
---
|
||||||
|
|
||||||
|
[ESLint](https://eslint.org/) is a tool that statically analyzes your code and reports problems or suggests best practices through editor hints and command line. Docusaurus provides an ESLint plugin to enforce best Docusaurus practices.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash npm2yarn
|
||||||
|
npm install --save-dev @docusaurus/eslint-plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Add `@docusaurus` to the plugins section of your `.eslintrc` configuration file:
|
||||||
|
|
||||||
|
```json title=".eslintrc"
|
||||||
|
{
|
||||||
|
"plugins": ["@docusaurus"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, you can extend one of the configs (e.g. the `recommended` config):
|
||||||
|
|
||||||
|
```json title=".eslintrc"
|
||||||
|
{
|
||||||
|
"extends": ["plugin:@docusaurus/recommended"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Each config contains a set of rules. For more fine-grained control, you can also configure the rules you want to use directly:
|
||||||
|
|
||||||
|
```json title=".eslintrc"
|
||||||
|
{
|
||||||
|
"rules": {
|
||||||
|
"@docusaurus/string-literal-i18n-messages": "error",
|
||||||
|
"@docusaurus/no-untranslated-text": "warn"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Supported Configs
|
||||||
|
|
||||||
|
- Recommended: recommended rule set for most Docusaurus sites that should be extended from.
|
||||||
|
- All: **all** rules enabled. This will change between minor versions, so you should not use this if you want to avoid unexpected breaking changes.
|
||||||
|
|
||||||
|
## Supported Rules
|
||||||
|
|
||||||
|
| Name | Description | |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| [`@docusaurus/no-untranslated-text`](./no-untranslated-text.md) | Enforce text labels in JSX to be wrapped by translate calls | |
|
||||||
|
| [`@docusaurus/string-literal-i18n-messages`](./string-literal-i18n-messages.md) | Enforce translate APIs to be called on plain text labels | ✅ |
|
||||||
|
|
||||||
|
✅ = recommended
|
||||||
|
|
||||||
|
## Example configuration
|
||||||
|
|
||||||
|
Here's an example configuration:
|
||||||
|
|
||||||
|
```js title=".eslintrc.js"
|
||||||
|
module.exports = {
|
||||||
|
extends: ['plugin:@docusaurus/recommended'],
|
||||||
|
plugins: ['@docusaurus'],
|
||||||
|
rules: {
|
||||||
|
'@docusaurus/no-untranslated-text': [
|
||||||
|
'warn',
|
||||||
|
{ignoreStrings: ['·', '—', '×']},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
48
website/docs/api/misc/eslint-plugin/no-untranslated-text.md
Normal file
48
website/docs/api/misc/eslint-plugin/no-untranslated-text.md
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
---
|
||||||
|
slug: '/api/misc/@docusaurus/eslint-plugin/no-untranslated-text'
|
||||||
|
---
|
||||||
|
|
||||||
|
# no-untranslated-text
|
||||||
|
|
||||||
|
Enforce text labels in JSX to be wrapped by translate calls.
|
||||||
|
|
||||||
|
When the [i18n feature](../../../i18n/i18n-introduction.md) is used, this rule ensures that all labels appearing on the website are translatable, so no string accidentally slips through untranslated.
|
||||||
|
|
||||||
|
## Rule Details {#details}
|
||||||
|
|
||||||
|
Examples of **incorrect** code for this rule:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// Hello World is not translated
|
||||||
|
<Component>Hello World</Component>
|
||||||
|
```
|
||||||
|
|
||||||
|
Examples of **correct** code for this rule:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// Hello World is translated
|
||||||
|
<Component>
|
||||||
|
<Translate>Hello World</Translate>
|
||||||
|
</Component>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rule Configuration {#configuration}
|
||||||
|
|
||||||
|
Accepted fields:
|
||||||
|
|
||||||
|
<APITable>
|
||||||
|
|
||||||
|
| Option | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `ignoreStrings` | `string[]` | `[]` | Text labels that only contain strings in this list will not be reported. |
|
||||||
|
|
||||||
|
</APITable>
|
||||||
|
|
||||||
|
## When Not To Use It {#when-not-to-use}
|
||||||
|
|
||||||
|
If you're not using the [i18n feature](../../../i18n/i18n-introduction.md), you can disable this rule. You can also disable this rule where the text is not supposed to be translated.
|
||||||
|
|
||||||
|
## Further Reading {#further-reading}
|
||||||
|
|
||||||
|
- https://docusaurus.io/docs/docusaurus-core#translate
|
||||||
|
- https://docusaurus.io/docs/docusaurus-core#translate-imperative
|
|
@ -0,0 +1,50 @@
|
||||||
|
---
|
||||||
|
slug: '/api/misc/@docusaurus/eslint-plugin/string-literal-i18n-messages'
|
||||||
|
---
|
||||||
|
|
||||||
|
# string-literal-i18n-messages
|
||||||
|
|
||||||
|
Enforce translate APIs to be called on plain text labels.
|
||||||
|
|
||||||
|
Docusaurus offers the [`docusaurus write-translations`](../../../cli.md#docusaurus-write-translations-sitedir) API, which statically extracts the text labels marked as translatable. Dynamic values used in `<Translate>` or `translate()` calls will fail to be extracted. This rule will ensure that all translate calls are statically extractable.
|
||||||
|
|
||||||
|
## Rule Details {#details}
|
||||||
|
|
||||||
|
Examples of **incorrect** code for this rule:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const text = 'Some text to be translated'
|
||||||
|
|
||||||
|
// Invalid <Translate> child
|
||||||
|
<Translate>{text}</Translate>
|
||||||
|
|
||||||
|
// Invalid message attribute
|
||||||
|
translate({message: text})
|
||||||
|
```
|
||||||
|
|
||||||
|
Examples of **correct** code for this rule:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// Valid <Translate> child
|
||||||
|
<Translate>Some text to be translated</Translate>
|
||||||
|
|
||||||
|
// Valid message attribute
|
||||||
|
translate({message: 'Some text to be translated'})
|
||||||
|
|
||||||
|
// Valid <Translate> child using values object as prop
|
||||||
|
<Translate values={{firstName: 'Sébastien'}}>
|
||||||
|
{'Welcome, {firstName}! How are you?'}
|
||||||
|
</Translate>
|
||||||
|
|
||||||
|
// Valid message attribute using values object as second argument
|
||||||
|
translate({message: 'The logo of site {siteName}'}, {siteName: 'Docusaurus'})
|
||||||
|
```
|
||||||
|
|
||||||
|
## When Not To Use It {#when-not-to-use}
|
||||||
|
|
||||||
|
If you're not using the [i18n feature](../../../i18n/i18n-introduction.md), you can disable this rule.
|
||||||
|
|
||||||
|
## Further Reading {#further-reading}
|
||||||
|
|
||||||
|
- https://docusaurus.io/docs/docusaurus-core#translate
|
||||||
|
- https://docusaurus.io/docs/docusaurus-core#translate-imperative
|
|
@ -621,7 +621,7 @@ import {interpolate} from '@docusaurus/Interpolate';
|
||||||
const message = interpolate('Welcome {firstName}', {firstName: 'Sébastien'});
|
const message = interpolate('Welcome {firstName}', {firstName: 'Sébastien'});
|
||||||
```
|
```
|
||||||
|
|
||||||
### `translate` {#translate-1}
|
### `translate` {#translate-imperative}
|
||||||
|
|
||||||
The imperative counterpart of the [`<Translate>`](#translate) component. Also supporting [placeholders interpolation](#interpolate).
|
The imperative counterpart of the [`<Translate>`](#translate) component. Also supporting [placeholders interpolation](#interpolate).
|
||||||
|
|
||||||
|
|
|
@ -80,6 +80,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tsconfig/docusaurus": "^1.0.5",
|
"@tsconfig/docusaurus": "^1.0.5",
|
||||||
|
"@docusaurus/eslint-plugin": "2.0.0-beta.18",
|
||||||
"@types/jest": "^27.4.1",
|
"@types/jest": "^27.4.1",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"rimraf": "^3.0.2"
|
"rimraf": "^3.0.2"
|
||||||
|
|
|
@ -279,6 +279,7 @@ export default function ColorGenerator(): JSX.Element {
|
||||||
<p>
|
<p>
|
||||||
<Translate
|
<Translate
|
||||||
id="colorGenerator.text"
|
id="colorGenerator.text"
|
||||||
|
// eslint-disable-next-line @docusaurus/no-untranslated-text
|
||||||
values={{cssPath: <code>src/css/custom.css</code>}}>
|
values={{cssPath: <code>src/css/custom.css</code>}}>
|
||||||
{'Replace the variables in {cssPath} with these new variables.'}
|
{'Replace the variables in {cssPath} with these new variables.'}
|
||||||
</Translate>
|
</Translate>
|
||||||
|
|
|
@ -82,6 +82,7 @@ function VersionNotice() {
|
||||||
<Link href="/community/canary">
|
<Link href="/community/canary">
|
||||||
<Translate
|
<Translate
|
||||||
id="upgradeGuide.unreleasedVersion.notice.canaryDocLink.label"
|
id="upgradeGuide.unreleasedVersion.notice.canaryDocLink.label"
|
||||||
|
// eslint-disable-next-line @docusaurus/no-untranslated-text
|
||||||
values={{canaryTag: <code>@canary</code>}}>
|
values={{canaryTag: <code>@canary</code>}}>
|
||||||
{'{canaryTag} release'}
|
{'{canaryTag} release'}
|
||||||
</Translate>
|
</Translate>
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable @docusaurus/no-untranslated-text */
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import type {Props as Tweet} from '../components/Tweet';
|
import type {Props as Tweet} from '../components/Tweet';
|
||||||
|
|
|
@ -90,9 +90,8 @@ function MigrationAnnouncement() {
|
||||||
</Link>
|
</Link>
|
||||||
),
|
),
|
||||||
}}>
|
}}>
|
||||||
{`Coming from {docusaurusV1Link}? Check out our {migrationGuideLink}`}
|
{`Coming from {docusaurusV1Link}? Check out our {migrationGuideLink}.`}
|
||||||
</Translate>
|
</Translate>
|
||||||
.
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -241,11 +240,19 @@ export default function Home(): JSX.Element {
|
||||||
<main>
|
<main>
|
||||||
<div>
|
<div>
|
||||||
<div className={styles.banner}>
|
<div className={styles.banner}>
|
||||||
Support Ukraine 🇺🇦{' '}
|
<Translate
|
||||||
<Link to="https://opensource.facebook.com/support-ukraine">
|
id="homepage.banner"
|
||||||
Help Provide Humanitarian Aid to Ukraine
|
values={{
|
||||||
</Link>
|
link: (
|
||||||
.
|
<Link to="https://opensource.facebook.com/support-ukraine">
|
||||||
|
<Translate id="homepage.banner.link">
|
||||||
|
Help Provide Humanitarian Aid to Ukraine
|
||||||
|
</Translate>
|
||||||
|
</Link>
|
||||||
|
),
|
||||||
|
}}>
|
||||||
|
{'Support Ukraine 🇺🇦 {link}.'}
|
||||||
|
</Translate>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<HeroBanner />
|
<HeroBanner />
|
||||||
|
|
|
@ -59,10 +59,11 @@ export default function ShowcaseFilterToggle(): JSX.Element {
|
||||||
}}
|
}}
|
||||||
checked={operator}
|
checked={operator}
|
||||||
/>
|
/>
|
||||||
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
|
|
||||||
<label htmlFor={id} className={clsx(styles.checkboxLabel, 'shadow--md')}>
|
<label htmlFor={id} className={clsx(styles.checkboxLabel, 'shadow--md')}>
|
||||||
|
{/* eslint-disable @docusaurus/no-untranslated-text */}
|
||||||
<span className={styles.checkboxLabelOr}>OR</span>
|
<span className={styles.checkboxLabelOr}>OR</span>
|
||||||
<span className={styles.checkboxLabelAnd}>AND</span>
|
<span className={styles.checkboxLabelAnd}>AND</span>
|
||||||
|
{/* eslint-enable @docusaurus/no-untranslated-text */}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -170,9 +170,15 @@ export default function Version(): JSX.Element {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="margin-bottom--lg">
|
<div className="margin-bottom--lg">
|
||||||
<h3 id="legacy">Docusaurus v1 (Legacy)</h3>
|
<h3 id="legacy">
|
||||||
|
<Translate id="versionsPage.legacy.title">
|
||||||
|
Docusaurus v1 (Legacy)
|
||||||
|
</Translate>
|
||||||
|
</h3>
|
||||||
<p>
|
<p>
|
||||||
Here you can find documentation for legacy version of Docusaurus.
|
<Translate id="versionsPage.legacy.description">
|
||||||
|
Here you can find documentation for legacy version of Docusaurus.
|
||||||
|
</Translate>
|
||||||
</p>
|
</p>
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import BlogLayout from '@theme/BlogLayout';
|
import BlogLayout from '@theme/BlogLayout';
|
||||||
import BlogListPaginator from '@theme/BlogListPaginator';
|
import BlogListPaginator from '@theme/BlogListPaginator';
|
||||||
|
import Translate from '@docusaurus/Translate';
|
||||||
import type {Props} from '@theme/BlogListPage';
|
import type {Props} from '@theme/BlogListPage';
|
||||||
import {
|
import {
|
||||||
PageMetadata,
|
PageMetadata,
|
||||||
|
@ -41,45 +42,60 @@ function ChangelogListContent(props: Props): JSX.Element {
|
||||||
<header className="margin-bottom--lg">
|
<header className="margin-bottom--lg">
|
||||||
<h1 style={{fontSize: '3rem'}}>{blogTitle}</h1>
|
<h1 style={{fontSize: '3rem'}}>{blogTitle}</h1>
|
||||||
<p>
|
<p>
|
||||||
Subscribe through{' '}
|
<Translate
|
||||||
<Link href="pathname:///changelog/rss.xml" className={styles.rss}>
|
id="changelog.description"
|
||||||
<b>RSS feeds</b>
|
values={{
|
||||||
<svg
|
twitterLink: (
|
||||||
style={{
|
<Link
|
||||||
fill: '#f26522',
|
href="https://twitter.com/docusaurus"
|
||||||
position: 'relative',
|
className={styles.twitter}>
|
||||||
left: 4,
|
<b>Twitter</b>
|
||||||
top: 1,
|
<svg
|
||||||
marginRight: 8,
|
style={{
|
||||||
}}
|
fill: '#1da1f2',
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
position: 'relative',
|
||||||
width="16"
|
left: 4,
|
||||||
height="16"
|
top: 1,
|
||||||
viewBox="0 0 24 24">
|
marginRight: 8,
|
||||||
<path d="M6.503 20.752c0 1.794-1.456 3.248-3.251 3.248-1.796 0-3.252-1.454-3.252-3.248 0-1.794 1.456-3.248 3.252-3.248 1.795.001 3.251 1.454 3.251 3.248zm-6.503-12.572v4.811c6.05.062 10.96 4.966 11.022 11.009h4.817c-.062-8.71-7.118-15.758-15.839-15.82zm0-3.368c10.58.046 19.152 8.594 19.183 19.188h4.817c-.03-13.231-10.755-23.954-24-24v4.812z" />
|
}}
|
||||||
</svg>
|
width="16"
|
||||||
</Link>{' '}
|
height="16"
|
||||||
or follow us on{' '}
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
<Link
|
viewBox="0 0 512 512">
|
||||||
href="https://twitter.com/docusaurus"
|
<path d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z" />
|
||||||
className={styles.twitter}>
|
</svg>
|
||||||
<b>Twitter</b>
|
</Link>
|
||||||
<svg
|
),
|
||||||
style={{
|
rssLink: (
|
||||||
fill: '#1da1f2',
|
<Link
|
||||||
position: 'relative',
|
href="pathname:///changelog/rss.xml"
|
||||||
left: 4,
|
className={styles.rss}>
|
||||||
top: 1,
|
<b>
|
||||||
marginRight: 8,
|
<Translate id="changelog.description.rssLink">
|
||||||
}}
|
RSS feeds
|
||||||
width="16"
|
</Translate>
|
||||||
height="16"
|
</b>
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<svg
|
||||||
viewBox="0 0 512 512">
|
style={{
|
||||||
<path d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z" />
|
fill: '#f26522',
|
||||||
</svg>
|
position: 'relative',
|
||||||
</Link>{' '}
|
left: 4,
|
||||||
to stay up-to-date with new releases!
|
top: 1,
|
||||||
|
marginRight: 8,
|
||||||
|
}}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24">
|
||||||
|
<path d="M6.503 20.752c0 1.794-1.456 3.248-3.251 3.248-1.796 0-3.252-1.454-3.252-3.248 0-1.794 1.456-3.248 3.252-3.248 1.795.001 3.251 1.454 3.251 3.248zm-6.503-12.572v4.811c6.05.062 10.96 4.966 11.022 11.009h4.817c-.062-8.71-7.118-15.758-15.839-15.82zm0-3.368c10.58.046 19.152 8.594 19.183 19.188h4.817c-.03-13.231-10.755-23.954-24-24v4.812z" />
|
||||||
|
</svg>
|
||||||
|
</Link>
|
||||||
|
),
|
||||||
|
}}>
|
||||||
|
{
|
||||||
|
'Subscribe through {rssLink} or follow us on {twitterLink} to stay up-to-date with new releases!'
|
||||||
|
}
|
||||||
|
</Translate>
|
||||||
</p>
|
</p>
|
||||||
</header>
|
</header>
|
||||||
{items.map(({content: BlogPostContent}) => (
|
{items.map(({content: BlogPostContent}) => (
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import Translate from '@docusaurus/Translate';
|
||||||
import BlogLayout from '@theme/BlogLayout';
|
import BlogLayout from '@theme/BlogLayout';
|
||||||
import ChangelogItem from '@theme/ChangelogItem';
|
import ChangelogItem from '@theme/ChangelogItem';
|
||||||
import ChangelogPaginator from '@theme/ChangelogPaginator';
|
import ChangelogPaginator from '@theme/ChangelogPaginator';
|
||||||
|
@ -86,7 +87,9 @@ function ChangelogPageContent(props: Props): JSX.Element {
|
||||||
/>
|
/>
|
||||||
) : undefined
|
) : undefined
|
||||||
}>
|
}>
|
||||||
<Link to={listPageLink}>← Back to index page</Link>
|
<Link to={listPageLink}>
|
||||||
|
<Translate id="changelog.backLink">← Back to index page</Translate>
|
||||||
|
</Link>
|
||||||
|
|
||||||
<ChangelogItem
|
<ChangelogItem
|
||||||
frontMatter={frontMatter}
|
frontMatter={frontMatter}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {useLocation} from '@docusaurus/router';
|
||||||
|
|
||||||
function SidebarAd() {
|
function SidebarAd() {
|
||||||
return (
|
return (
|
||||||
|
// eslint-disable-next-line @docusaurus/no-untranslated-text
|
||||||
<div style={{border: 'solid thin red', padding: 10, textAlign: 'center'}}>
|
<div style={{border: 'solid thin red', padding: 10, textAlign: 'center'}}>
|
||||||
Sidebar Ad
|
Sidebar Ad
|
||||||
</div>
|
</div>
|
||||||
|
|
13
yarn.lock
13
yarn.lock
|
@ -6861,6 +6861,14 @@ eslint-module-utils@^2.7.3:
|
||||||
debug "^3.2.7"
|
debug "^3.2.7"
|
||||||
find-up "^2.1.0"
|
find-up "^2.1.0"
|
||||||
|
|
||||||
|
eslint-plugin-eslint-plugin@^4.1.0:
|
||||||
|
version "4.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/eslint-plugin-eslint-plugin/-/eslint-plugin-eslint-plugin-4.1.0.tgz#40ae944d79e845dc9d4a85328eea3c5bf4ae0f7d"
|
||||||
|
integrity sha512-QJVw+WYXJuG2469gx5G929bz7crfxySDlK1i569FkuT6dpeHDeP7MmDrKaswCx17snG25LRFD6wmVX+AO5x7Qg==
|
||||||
|
dependencies:
|
||||||
|
eslint-utils "^3.0.0"
|
||||||
|
estraverse "^5.2.0"
|
||||||
|
|
||||||
eslint-plugin-header@^3.1.1:
|
eslint-plugin-header@^3.1.1:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-plugin-header/-/eslint-plugin-header-3.1.1.tgz#6ce512432d57675265fac47292b50d1eff11acd6"
|
resolved "https://registry.yarnpkg.com/eslint-plugin-header/-/eslint-plugin-header-3.1.1.tgz#6ce512432d57675265fac47292b50d1eff11acd6"
|
||||||
|
@ -13185,6 +13193,11 @@ require-from-string@^2.0.2:
|
||||||
resolved "https://registry.yarnpkg.com/require-like/-/require-like-0.1.2.tgz#ad6f30c13becd797010c468afa775c0c0a6b47fa"
|
resolved "https://registry.yarnpkg.com/require-like/-/require-like-0.1.2.tgz#ad6f30c13becd797010c468afa775c0c0a6b47fa"
|
||||||
integrity sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==
|
integrity sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==
|
||||||
|
|
||||||
|
requireindex@^1.2.0:
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/requireindex/-/requireindex-1.2.0.tgz#3463cdb22ee151902635aa6c9535d4de9c2ef1ef"
|
||||||
|
integrity sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==
|
||||||
|
|
||||||
requires-port@^1.0.0:
|
requires-port@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
|
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue