fix: Add translation hooks on document and collection pages (#2307)

This commit is contained in:
Saumya Pandey
2021-07-16 01:49:09 +05:30
committed by GitHub
parent 31714efb0b
commit 9596979993
5 changed files with 206 additions and 213 deletions

View File

@@ -1,62 +1,60 @@
// @flow // @flow
import { observable } from "mobx"; import { observer } from "mobx-react";
import { inject, observer } from "mobx-react";
import * as React from "react"; import * as React from "react";
import { withRouter, type RouterHistory } from "react-router-dom"; import { useTranslation, Trans } from "react-i18next";
import CollectionsStore from "stores/CollectionsStore"; import { useHistory } from "react-router-dom";
import UiStore from "stores/UiStore";
import Collection from "models/Collection"; import Collection from "models/Collection";
import Button from "components/Button"; import Button from "components/Button";
import Flex from "components/Flex"; import Flex from "components/Flex";
import HelpText from "components/HelpText"; import HelpText from "components/HelpText";
import useStores from "hooks/useStores";
import { homeUrl } from "utils/routeHelpers"; import { homeUrl } from "utils/routeHelpers";
type Props = { type Props = {
history: RouterHistory,
collection: Collection, collection: Collection,
collections: CollectionsStore,
ui: UiStore,
onSubmit: () => void, onSubmit: () => void,
}; };
@observer function CollectionDelete({ collection, onSubmit }: Props) {
class CollectionDelete extends React.Component<Props> { const [isDeleting, setIsDeleting] = React.useState();
@observable isDeleting: boolean; const { ui } = useStores();
const history = useHistory();
const { t } = useTranslation();
handleSubmit = async (ev: SyntheticEvent<>) => { const handleSubmit = React.useCallback(
ev.preventDefault(); async (ev: SyntheticEvent<>) => {
this.isDeleting = true; ev.preventDefault();
setIsDeleting(true);
try { try {
await this.props.collection.delete(); await collection.delete();
this.props.history.push(homeUrl()); history.push(homeUrl());
this.props.onSubmit(); onSubmit();
} catch (err) { } catch (err) {
this.props.ui.showToast(err.message, { type: "error" }); ui.showToast(err.message, { type: "error" });
} finally { } finally {
this.isDeleting = false; setIsDeleting(false);
} }
}; },
[ui, onSubmit, collection, history]
);
render() { return (
const { collection } = this.props; <Flex column>
<form onSubmit={handleSubmit}>
return ( <HelpText>
<Flex column> <Trans
<form onSubmit={this.handleSubmit}> defaults="Are you sure about that? Deleting the <em>{{collectionName}}</em> collection is permanent and cannot be restored, however documents within will be moved to the trash."
<HelpText> values={{ collectionName: collection.name }}
Are you sure about that? Deleting the{" "} components={{ em: <strong /> }}
<strong>{collection.name}</strong> collection is permanent and />
cannot be restored, however documents within will be moved to the </HelpText>
trash. <Button type="submit" disabled={isDeleting} autoFocus danger>
</HelpText> {isDeleting ? `${t("Deleting")}` : t("Im sure  Delete")}
<Button type="submit" disabled={this.isDeleting} autoFocus danger> </Button>
{this.isDeleting ? "Deleting…" : "Im sure  Delete"} </form>
</Button> </Flex>
</form> );
</Flex>
);
}
} }
export default inject("collections", "ui")(withRouter(CollectionDelete)); export default observer(CollectionDelete);

View File

@@ -1,9 +1,7 @@
// @flow // @flow
import { observable } from "mobx"; import { observer } from "mobx-react";
import { inject, observer } from "mobx-react";
import * as React from "react"; import * as React from "react";
import AuthStore from "stores/AuthStore"; import { useTranslation, Trans } from "react-i18next";
import UiStore from "stores/UiStore";
import Collection from "models/Collection"; import Collection from "models/Collection";
import Button from "components/Button"; import Button from "components/Button";
import Flex from "components/Flex"; import Flex from "components/Flex";
@@ -11,43 +9,41 @@ import HelpText from "components/HelpText";
type Props = { type Props = {
collection: Collection, collection: Collection,
auth: AuthStore,
ui: UiStore,
onSubmit: () => void, onSubmit: () => void,
}; };
@observer function CollectionExport({ collection, onSubmit }: Props) {
class CollectionExport extends React.Component<Props> { const [isLoading, setIsLoading] = React.useState();
@observable isLoading: boolean = false; const { t } = useTranslation();
handleSubmit = async (ev: SyntheticEvent<>) => { const handleSubmit = React.useCallback(
ev.preventDefault(); async (ev: SyntheticEvent<>) => {
ev.preventDefault();
this.isLoading = true; setIsLoading(true);
await this.props.collection.export(); await collection.export();
this.isLoading = false; setIsLoading(false);
this.props.onSubmit(); onSubmit();
}; },
[collection, onSubmit]
);
render() { return (
const { collection, auth } = this.props; <Flex column>
if (!auth.user) return null; <form onSubmit={handleSubmit}>
<HelpText>
return ( <Trans
<Flex column> defaults="Exporting the collection <em>{{collectionName}}</em> may take a few seconds. Your documents will be downloaded as a zip of folders with files in Markdown format."
<form onSubmit={this.handleSubmit}> values={{ collectionName: collection.name }}
<HelpText> components={{ em: <strong /> }}
Exporting the collection <strong>{collection.name}</strong> may take />
a few seconds. Your documents will be downloaded as a zip of folders </HelpText>
with files in Markdown format. <Button type="submit" disabled={isLoading} primary>
</HelpText> {isLoading ? `${t("Exporting")}` : t("Export Collection")}
<Button type="submit" disabled={this.isLoading} primary> </Button>
{this.isLoading ? "Exporting…" : "Export Collection"} </form>
</Button> </Flex>
</form> );
</Flex>
);
}
} }
export default inject("ui", "auth")(CollectionExport); export default observer(CollectionExport);

View File

@@ -1,37 +1,32 @@
// @flow // @flow
import { Search } from "js-search"; import { Search } from "js-search";
import { last } from "lodash"; import { last } from "lodash";
import { observable, computed } from "mobx"; import { observer } from "mobx-react";
import { observer, inject } from "mobx-react";
import * as React from "react"; import * as React from "react";
import { useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import AutoSizer from "react-virtualized-auto-sizer"; import AutoSizer from "react-virtualized-auto-sizer";
import { FixedSizeList as List } from "react-window"; import { FixedSizeList as List } from "react-window";
import styled from "styled-components"; import styled from "styled-components";
import CollectionsStore, { type DocumentPath } from "stores/CollectionsStore"; import { type DocumentPath } from "stores/CollectionsStore";
import DocumentsStore from "stores/DocumentsStore";
import UiStore from "stores/UiStore";
import Document from "models/Document"; import Document from "models/Document";
import Flex from "components/Flex"; import Flex from "components/Flex";
import { Outline } from "components/Input"; import { Outline } from "components/Input";
import Labeled from "components/Labeled"; import Labeled from "components/Labeled";
import PathToDocument from "components/PathToDocument"; import PathToDocument from "components/PathToDocument";
import useStores from "hooks/useStores";
type Props = {| type Props = {|
document: Document, document: Document,
documents: DocumentsStore,
collections: CollectionsStore,
ui: UiStore,
onRequestClose: () => void, onRequestClose: () => void,
|}; |};
@observer function DocumentMove({ document, onRequestClose }: Props) {
class DocumentMove extends React.Component<Props> { const [searchTerm, setSearchTerm] = useState();
@observable searchTerm: ?string; const { ui, collections, documents } = useStores();
@observable isSaving: boolean; const { t } = useTranslation();
@computed const searchIndex = useMemo(() => {
get searchIndex() {
const { collections, documents } = this.props;
const paths = collections.pathsToDocuments; const paths = collections.pathsToDocuments;
const index = new Search("id"); const index = new Search("id");
index.addIndex("title"); index.addIndex("title");
@@ -47,19 +42,16 @@ class DocumentMove extends React.Component<Props> {
index.addDocuments(indexeableDocuments); index.addDocuments(indexeableDocuments);
return index; return index;
} }, [documents, collections.pathsToDocuments]);
@computed const results: DocumentPath[] = useMemo(() => {
get results(): DocumentPath[] {
const { document, collections } = this.props;
const onlyShowCollections = document.isTemplate; const onlyShowCollections = document.isTemplate;
let results = []; let results = [];
if (collections.isLoaded) { if (collections.isLoaded) {
if (this.searchTerm) { if (searchTerm) {
results = this.searchIndex.search(this.searchTerm); results = searchIndex.search(searchTerm);
} else { } else {
results = this.searchIndex._documents; results = searchIndex._documents;
} }
} }
@@ -82,19 +74,18 @@ class DocumentMove extends React.Component<Props> {
} }
return results; return results;
} }, [document, collections, searchTerm, searchIndex]);
handleSuccess = () => { const handleSuccess = () => {
this.props.ui.showToast("Document moved", { type: "info" }); ui.showToast(t("Document moved"), { type: "info" });
this.props.onRequestClose(); onRequestClose();
}; };
handleFilter = (ev: SyntheticInputEvent<*>) => { const handleFilter = (ev: SyntheticInputEvent<*>) => {
this.searchTerm = ev.target.value; setSearchTerm(ev.target.value);
}; };
renderPathToCurrentDocument() { const renderPathToCurrentDocument = () => {
const { collections, document } = this.props;
const result = collections.getPathForDocument(document.id); const result = collections.getPathForDocument(document.id);
if (result) { if (result) {
@@ -105,75 +96,71 @@ class DocumentMove extends React.Component<Props> {
/> />
); );
} }
} };
row = ({ index, data, style }) => { const row = ({ index, data, style }) => {
const result = data[index]; const result = data[index];
const { document, collections } = this.props;
return ( return (
<PathToDocument <PathToDocument
result={result} result={result}
document={document} document={document}
collection={collections.get(result.collectionId)} collection={collections.get(result.collectionId)}
onSuccess={this.handleSuccess} onSuccess={handleSuccess}
style={style} style={style}
/> />
); );
}; };
render() { const data = results;
const { document, collections } = this.props;
const data = this.results;
if (!document || !collections.isLoaded) { if (!document || !collections.isLoaded) {
return null; return null;
}
return (
<Flex column>
<Section>
<Labeled label="Current location">
{this.renderPathToCurrentDocument()}
</Labeled>
</Section>
<Section column>
<Labeled label="Choose a new location" />
<NewLocation>
<InputWrapper>
<Input
type="search"
placeholder="Search collections & documents…"
onChange={this.handleFilter}
required
autoFocus
/>
</InputWrapper>
<Results>
<AutoSizer>
{({ width, height }) => (
<Flex role="listbox" column>
<List
key={data.length}
width={width}
height={height}
itemData={data}
itemCount={data.length}
itemSize={40}
itemKey={(index, data) => data[index].id}
>
{this.row}
</List>
</Flex>
)}
</AutoSizer>
</Results>
</NewLocation>
</Section>
</Flex>
);
} }
return (
<Flex column>
<Section>
<Labeled label={t("Current location")}>
{renderPathToCurrentDocument()}
</Labeled>
</Section>
<Section column>
<Labeled label={t("Choose a new location")} />
<NewLocation>
<InputWrapper>
<Input
type="search"
placeholder={`${t("Search collections & documents")}`}
onChange={handleFilter}
required
autoFocus
/>
</InputWrapper>
<Results>
<AutoSizer>
{({ width, height }) => (
<Flex role="listbox" column>
<List
key={data.length}
width={width}
height={height}
itemData={data}
itemCount={data.length}
itemSize={40}
itemKey={(index, data) => data[index].id}
>
{row}
</List>
</Flex>
)}
</AutoSizer>
</Results>
</NewLocation>
</Section>
</Flex>
);
} }
const InputWrapper = styled("div")` const InputWrapper = styled("div")`
@@ -210,4 +197,4 @@ const Section = styled(Flex)`
margin-bottom: 24px; margin-bottom: 24px;
`; `;
export default inject("documents", "collections", "ui")(DocumentMove); export default observer(DocumentMove);

View File

@@ -1,63 +1,64 @@
// @flow // @flow
import { observable } from "mobx"; import { observer } from "mobx-react";
import { inject, observer } from "mobx-react";
import * as React from "react"; import * as React from "react";
import { withRouter, type RouterHistory } from "react-router-dom"; import { useState } from "react";
import UiStore from "stores/UiStore"; import { useTranslation, Trans } from "react-i18next";
import { useHistory } from "react-router-dom";
import Document from "models/Document"; import Document from "models/Document";
import Button from "components/Button"; import Button from "components/Button";
import Flex from "components/Flex"; import Flex from "components/Flex";
import HelpText from "components/HelpText"; import HelpText from "components/HelpText";
import useStores from "hooks/useStores";
import { documentUrl } from "utils/routeHelpers"; import { documentUrl } from "utils/routeHelpers";
type Props = { type Props = {
ui: UiStore,
document: Document, document: Document,
history: RouterHistory,
onSubmit: () => void, onSubmit: () => void,
}; };
@observer function DocumentTemplatize({ document, onSubmit }: Props) {
class DocumentTemplatize extends React.Component<Props> { const [isSaving, setIsSaving] = useState();
@observable isSaving: boolean; const history = useHistory();
const { ui } = useStores();
const { t } = useTranslation();
handleSubmit = async (ev: SyntheticEvent<>) => { const handleSubmit = React.useCallback(
ev.preventDefault(); async (ev: SyntheticEvent<>) => {
this.isSaving = true; ev.preventDefault();
setIsSaving(true);
try { try {
const template = await this.props.document.templatize(); const template = await document.templatize();
this.props.history.push(documentUrl(template)); history.push(documentUrl(template));
this.props.ui.showToast("Template created, go ahead and customize it", { ui.showToast(t("Template created, go ahead and customize it"), {
type: "info", type: "info",
}); });
this.props.onSubmit(); onSubmit();
} catch (err) { } catch (err) {
this.props.ui.showToast(err.message, { type: "error" }); ui.showToast(err.message, { type: "error" });
} finally { } finally {
this.isSaving = false; setIsSaving(false);
} }
}; },
[document, ui, history, onSubmit, t]
);
render() { return (
const { document } = this.props; <Flex column>
<form onSubmit={handleSubmit}>
return ( <HelpText>
<Flex column> <Trans
<form onSubmit={this.handleSubmit}> defaults="Creating a template from <em>{{titleWithDefault}}</em> is a non-destructive action we'll make a copy of the document and turn it into a template that can be used as a starting point for new documents."
<HelpText> values={{ titleWithDefault: document.titleWithDefault }}
Creating a template from{" "} components={{ em: <strong /> }}
<strong>{document.titleWithDefault}</strong> is a non-destructive />
action we'll make a copy of the document and turn it into a </HelpText>
template that can be used as a starting point for new documents. <Button type="submit">
</HelpText> {isSaving ? `${t("Creating")}` : t("Create template")}
<Button type="submit"> </Button>
{this.isSaving ? "Creating…" : "Create template"} </form>
</Button> </Flex>
</form> );
</Flex>
);
}
} }
export default inject("ui")(withRouter(DocumentTemplatize)); export default observer(DocumentTemplatize);

View File

@@ -219,6 +219,7 @@
"Contents": "Contents", "Contents": "Contents",
"Headings you add to the document will appear here": "Headings you add to the document will appear here", "Headings you add to the document will appear here": "Headings you add to the document will appear here",
"Table of contents": "Table of contents", "Table of contents": "Table of contents",
"Contents": "Contents",
"By {{ author }}": "By {{ author }}", "By {{ author }}": "By {{ author }}",
"Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.": "Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.", "Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.": "Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.",
"Are you sure you want to make {{ userName }} a member?": "Are you sure you want to make {{ userName }} a member?", "Are you sure you want to make {{ userName }} a member?": "Are you sure you want to make {{ userName }} a member?",
@@ -248,6 +249,9 @@
"Least recently updated": "Least recently updated", "Least recently updated": "Least recently updated",
"AZ": "AZ", "AZ": "AZ",
"Drop documents to import": "Drop documents to import", "Drop documents to import": "Drop documents to import",
"Are you sure about that? Deleting the <em>{{collectionName}}</em> collection is permanent and cannot be restored, however documents within will be moved to the trash.": "Are you sure about that? Deleting the <em>{{collectionName}}</em> collection is permanent and cannot be restored, however documents within will be moved to the trash.",
"Deleting": "Deleting",
"Im sure  Delete": "Im sure  Delete",
"The collection was updated": "The collection was updated", "The collection was updated": "The collection was updated",
"You can edit the name and other details at any time, however doing so often might confuse your team mates.": "You can edit the name and other details at any time, however doing so often might confuse your team mates.", "You can edit the name and other details at any time, however doing so often might confuse your team mates.": "You can edit the name and other details at any time, however doing so often might confuse your team mates.",
"Name": "Name", "Name": "Name",
@@ -257,6 +261,9 @@
"Public sharing is currently disabled in the team security settings.": "Public sharing is currently disabled in the team security settings.", "Public sharing is currently disabled in the team security settings.": "Public sharing is currently disabled in the team security settings.",
"Saving": "Saving", "Saving": "Saving",
"Save": "Save", "Save": "Save",
"Exporting the collection <em>{{collectionName}}</em> may take a few seconds. Your documents will be downloaded as a zip of folders with files in Markdown format.": "Exporting the collection <em>{{collectionName}}</em> may take a few seconds. Your documents will be downloaded as a zip of folders with files in Markdown format.",
"Exporting": "Exporting",
"Export Collection": "Export Collection",
"Collections are for grouping your documents. They work best when organized around a topic or internal team — Product or Engineering for example.": "Collections are for grouping your documents. They work best when organized around a topic or internal team — Product or Engineering for example.", "Collections are for grouping your documents. They work best when organized around a topic or internal team — Product or Engineering for example.": "Collections are for grouping your documents. They work best when organized around a topic or internal team — Product or Engineering for example.",
"This is the default level of access given to team members, you can give specific users or groups more access once the collection is created.": "This is the default level of access given to team members, you can give specific users or groups more access once the collection is created.", "This is the default level of access given to team members, you can give specific users or groups more access once the collection is created.": "This is the default level of access given to team members, you can give specific users or groups more access once the collection is created.",
"Creating": "Creating", "Creating": "Creating",
@@ -323,12 +330,16 @@
"Are you sure you want to delete the <em>{{ documentTitle }}</em> template?": "Are you sure you want to delete the <em>{{ documentTitle }}</em> template?", "Are you sure you want to delete the <em>{{ documentTitle }}</em> template?": "Are you sure you want to delete the <em>{{ documentTitle }}</em> template?",
"Are you sure about that? Deleting the <em>{{ documentTitle }}</em> document will delete all of its history and any nested documents.": "Are you sure about that? Deleting the <em>{{ documentTitle }}</em> document will delete all of its history and any nested documents.", "Are you sure about that? Deleting the <em>{{ documentTitle }}</em> document will delete all of its history and any nested documents.": "Are you sure about that? Deleting the <em>{{ documentTitle }}</em> document will delete all of its history and any nested documents.",
"If youd like the option of referencing or restoring the {{noun}} in the future, consider archiving it instead.": "If youd like the option of referencing or restoring the {{noun}} in the future, consider archiving it instead.", "If youd like the option of referencing or restoring the {{noun}} in the future, consider archiving it instead.": "If youd like the option of referencing or restoring the {{noun}} in the future, consider archiving it instead.",
"Deleting": "Deleting",
"Im sure  Delete": "Im sure  Delete",
"Archiving": "Archiving", "Archiving": "Archiving",
"Document moved": "Document moved",
"Current location": "Current location",
"Choose a new location": "Choose a new location",
"Search collections & documents": "Search collections & documents",
"Couldnt create the document, try again?": "Couldnt create the document, try again?", "Couldnt create the document, try again?": "Couldnt create the document, try again?",
"Document permanently deleted": "Document permanently deleted", "Document permanently deleted": "Document permanently deleted",
"Are you sure you want to permanently delete the <em>{{ documentTitle }}</em> document? This action is immediate and cannot be undone.": "Are you sure you want to permanently delete the <em>{{ documentTitle }}</em> document? This action is immediate and cannot be undone.", "Are you sure you want to permanently delete the <em>{{ documentTitle }}</em> document? This action is immediate and cannot be undone.": "Are you sure you want to permanently delete the <em>{{ documentTitle }}</em> document? This action is immediate and cannot be undone.",
"Template created, go ahead and customize it": "Template created, go ahead and customize it",
"Creating a template from <em>{{titleWithDefault}}</em> is a non-destructive action we'll make a copy of the document and turn it into a template that can be used as a starting point for new documents.": "Creating a template from <em>{{titleWithDefault}}</em> is a non-destructive action we'll make a copy of the document and turn it into a template that can be used as a starting point for new documents.",
"Search documents": "Search documents", "Search documents": "Search documents",
"No documents found for your filters.": "No documents found for your filters.", "No documents found for your filters.": "No documents found for your filters.",
"Youve not got any drafts at the moment.": "Youve not got any drafts at the moment.", "Youve not got any drafts at the moment.": "Youve not got any drafts at the moment.",