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:
Elias Papavasileiou 2022-04-29 19:04:25 +03:00 committed by GitHub
parent ae788c536f
commit 3b1170eb44
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 885 additions and 52 deletions

View file

@ -32,6 +32,7 @@ module.exports = {
'plugin:@typescript-eslint/recommended',
'plugin:regexp/recommended',
'prettier',
'plugin:@docusaurus/all',
],
settings: {
'import/resolver': {
@ -41,7 +42,14 @@ module.exports = {
},
},
reportUnusedDisableDirectives: true,
plugins: ['react-hooks', 'header', 'jest', '@typescript-eslint', 'regexp'],
plugins: [
'react-hooks',
'header',
'jest',
'@typescript-eslint',
'regexp',
'@docusaurus',
],
rules: {
'array-callback-return': WARNING,
camelcase: WARNING,
@ -305,6 +313,24 @@ module.exports = {
// locals must be justified with a disable comment.
'@typescript-eslint/no-unused-vars': [ERROR, {ignoreRestSiblings: true}],
'@typescript-eslint/prefer-optional-chain': ERROR,
'@docusaurus/no-untranslated-text': [
WARNING,
{
ignoreStrings: [
'·',
'-',
'—',
'×',
'', // zwj: &#8203;
'@',
'WebContainers',
'Twitter',
'GitHub',
'Dev.to',
'1.x',
],
},
],
},
overrides: [
{
@ -327,6 +353,7 @@ module.exports = {
'header/header': OFF,
'global-require': 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,
},
},
{
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
// they run

View file

@ -82,6 +82,7 @@ export default function LastUpdated({
</Translate>
{process.env.NODE_ENV === 'development' && (
<div>
{/* eslint-disable-next-line @docusaurus/no-untranslated-text */}
<small> (Simulated during dev for better perf)</small>
</div>
)}

View file

@ -23,6 +23,7 @@ function Header({children}: {children: React.ReactNode}) {
function LivePreviewLoader() {
// Is it worth improving/translating?
// eslint-disable-next-line @docusaurus/no-untranslated-text
return <div>Loading...</div>;
}

View file

@ -67,6 +67,7 @@ describe('<Translate>', () => {
it('rejects when children is not a string', () => {
expect(() =>
renderer.create(
// eslint-disable-next-line @docusaurus/string-literal-i18n-messages
<Translate id="foo">
{/* @ts-expect-error: for test */}
<span>aaa</span>

View file

@ -5,6 +5,9 @@
* 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 Layout from '@theme/Layout';
import ErrorBoundary from '@docusaurus/ErrorBoundary';

View file

@ -5,6 +5,9 @@
* 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 type {LoadingComponentProps} from 'react-loadable';

View file

@ -5,6 +5,9 @@
* 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 Layout from '@theme/Layout';
import Head from '@docusaurus/Head';

View 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'],
};

View file

@ -0,0 +1,6 @@
copyUntypedFiles.mjs
.tsbuildinfo
tsconfig*
__tests__
.eslintrc.js

View 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).

View 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',
},
},
},
};

View file

@ -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>&#8203;</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']}],
},
],
});

View file

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

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

View file

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

View 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,
};

View 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"
}
}

View file

@ -245,12 +245,14 @@ refactorings
regexes
rehype
reponame
requireindex
retrocompatibility
retrocompatible
roadmap
rocketvalidator
rtcts
rtlcss
saurus
scaleway
searchbar
sebastien

View file

@ -0,0 +1,2 @@
label: Miscellaneous
position: 4

View 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: ['·', '—', '×']},
],
},
};
```

View 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

View file

@ -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

View file

@ -621,7 +621,7 @@ import {interpolate} from '@docusaurus/Interpolate';
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).

View file

@ -80,6 +80,7 @@
},
"devDependencies": {
"@tsconfig/docusaurus": "^1.0.5",
"@docusaurus/eslint-plugin": "2.0.0-beta.18",
"@types/jest": "^27.4.1",
"cross-env": "^7.0.3",
"rimraf": "^3.0.2"

View file

@ -279,6 +279,7 @@ export default function ColorGenerator(): JSX.Element {
<p>
<Translate
id="colorGenerator.text"
// eslint-disable-next-line @docusaurus/no-untranslated-text
values={{cssPath: <code>src/css/custom.css</code>}}>
{'Replace the variables in {cssPath} with these new variables.'}
</Translate>

View file

@ -82,6 +82,7 @@ function VersionNotice() {
<Link href="/community/canary">
<Translate
id="upgradeGuide.unreleasedVersion.notice.canaryDocLink.label"
// eslint-disable-next-line @docusaurus/no-untranslated-text
values={{canaryTag: <code>@canary</code>}}>
{'{canaryTag} release'}
</Translate>

View file

@ -5,6 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
/* eslint-disable @docusaurus/no-untranslated-text */
import React from 'react';
import type {Props as Tweet} from '../components/Tweet';

View file

@ -90,9 +90,8 @@ function MigrationAnnouncement() {
</Link>
),
}}>
{`Coming from {docusaurusV1Link}? Check out our {migrationGuideLink}`}
{`Coming from {docusaurusV1Link}? Check out our {migrationGuideLink}.`}
</Translate>
.
</div>
</div>
);
@ -241,11 +240,19 @@ export default function Home(): JSX.Element {
<main>
<div>
<div className={styles.banner}>
Support Ukraine 🇺🇦{' '}
<Link to="https://opensource.facebook.com/support-ukraine">
Help Provide Humanitarian Aid to Ukraine
</Link>
.
<Translate
id="homepage.banner"
values={{
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>
<HeroBanner />

View file

@ -59,10 +59,11 @@ export default function ShowcaseFilterToggle(): JSX.Element {
}}
checked={operator}
/>
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
<label htmlFor={id} className={clsx(styles.checkboxLabel, 'shadow--md')}>
{/* eslint-disable @docusaurus/no-untranslated-text */}
<span className={styles.checkboxLabelOr}>OR</span>
<span className={styles.checkboxLabelAnd}>AND</span>
{/* eslint-enable @docusaurus/no-untranslated-text */}
</label>
</div>
);

View file

@ -170,9 +170,15 @@ export default function Version(): JSX.Element {
</div>
)}
<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>
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>
<table>
<tbody>

View file

@ -8,6 +8,7 @@
import React from 'react';
import BlogLayout from '@theme/BlogLayout';
import BlogListPaginator from '@theme/BlogListPaginator';
import Translate from '@docusaurus/Translate';
import type {Props} from '@theme/BlogListPage';
import {
PageMetadata,
@ -41,45 +42,60 @@ function ChangelogListContent(props: Props): JSX.Element {
<header className="margin-bottom--lg">
<h1 style={{fontSize: '3rem'}}>{blogTitle}</h1>
<p>
Subscribe through{' '}
<Link href="pathname:///changelog/rss.xml" className={styles.rss}>
<b>RSS feeds</b>
<svg
style={{
fill: '#f26522',
position: 'relative',
left: 4,
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>{' '}
or follow us on{' '}
<Link
href="https://twitter.com/docusaurus"
className={styles.twitter}>
<b>Twitter</b>
<svg
style={{
fill: '#1da1f2',
position: 'relative',
left: 4,
top: 1,
marginRight: 8,
}}
width="16"
height="16"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512">
<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" />
</svg>
</Link>{' '}
to stay up-to-date with new releases!
<Translate
id="changelog.description"
values={{
twitterLink: (
<Link
href="https://twitter.com/docusaurus"
className={styles.twitter}>
<b>Twitter</b>
<svg
style={{
fill: '#1da1f2',
position: 'relative',
left: 4,
top: 1,
marginRight: 8,
}}
width="16"
height="16"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512">
<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" />
</svg>
</Link>
),
rssLink: (
<Link
href="pathname:///changelog/rss.xml"
className={styles.rss}>
<b>
<Translate id="changelog.description.rssLink">
RSS feeds
</Translate>
</b>
<svg
style={{
fill: '#f26522',
position: 'relative',
left: 4,
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>
</header>
{items.map(({content: BlogPostContent}) => (

View file

@ -6,6 +6,7 @@
*/
import React from 'react';
import Translate from '@docusaurus/Translate';
import BlogLayout from '@theme/BlogLayout';
import ChangelogItem from '@theme/ChangelogItem';
import ChangelogPaginator from '@theme/ChangelogPaginator';
@ -86,7 +87,9 @@ function ChangelogPageContent(props: Props): JSX.Element {
/>
) : undefined
}>
<Link to={listPageLink}> Back to index page</Link>
<Link to={listPageLink}>
<Translate id="changelog.backLink"> Back to index page</Translate>
</Link>
<ChangelogItem
frontMatter={frontMatter}

View file

@ -11,6 +11,7 @@ import {useLocation} from '@docusaurus/router';
function SidebarAd() {
return (
// eslint-disable-next-line @docusaurus/no-untranslated-text
<div style={{border: 'solid thin red', padding: 10, textAlign: 'center'}}>
Sidebar Ad
</div>

View file

@ -6861,6 +6861,14 @@ eslint-module-utils@^2.7.3:
debug "^3.2.7"
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:
version "3.1.1"
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"
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:
version "1.0.0"
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"