feat: I18n (#1653)
* feat: i18n * Changing language single source of truth from TEAM to USER * Changes according to @tommoor comments on PR * Changed package.json for build:i18n and translation label * Finished 1st MVP of i18n for outline * new translation labels & Portuguese from Portugal translation * Fixes from PR request * Described language dropdown as an experimental feature * Set keySeparator to false in order to cowork with html keys * Added useTranslation to Breadcrumb * Repositioned <strong> element * Removed extra space from TemplatesMenu * Fortified the test suite for i18n * Fixed trans component problematic * Check if selected language is available * Update yarn.lock * Removed unused Trans * Removing debug variable from i18n init * Removed debug variable * test: update snapshots * flow: Remove decorator usage to get proper flow typing It's a shame, but hopefully we'll move to Typescript in the next 6 months and we can forget this whole Flow mistake ever happened * translate: Drafts * More translatable strings * Mo translation strings * translation: Search * async translations loading * cache translations in client * Revert "cache translations in client" This reverts commit 08fb61ce36384ff90a704faffe4761eccfb76da1. * Revert localStorage cache for cache headers * Update Crowdin configuration file * Moved translation files to locales folder and fixed english text * Added CONTRIBUTING File for CrowdIn * chore: Move translations again to please CrowdIn * fix: loading paths chore: Add strings for editor * fix: Improve validation on documents.import endpoint * test: mock bull * fix: Unknown mimetype should fallback to Markdown parsing if markdown extension (#1678) * closes #1675 * Update CONTRIBUTING * chore: Add link to translation portal from app UI * refactor: Centralize language config * fix: Ensure creation of i18n directory in build * feat: Add language prompt * chore: Improve contributing guidelines, add link from README * chore: Normalize tab header casing * chore: More string externalization * fix: Language prompt in dark mode Co-authored-by: André Glatzl <andreglatzl@gmail.com>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
import { observable } from "mobx";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { withTranslation, type TFunction } from "react-i18next";
|
||||
import { Redirect } from "react-router-dom";
|
||||
import AuthStore from "stores/AuthStore";
|
||||
import CollectionStore from "stores/CollectionsStore";
|
||||
@@ -38,6 +39,7 @@ type Props = {
|
||||
label?: React.Node,
|
||||
onOpen?: () => void,
|
||||
onClose?: () => void,
|
||||
t: TFunction,
|
||||
};
|
||||
|
||||
@observer
|
||||
@@ -83,7 +85,8 @@ class DocumentMenu extends React.Component<Props> {
|
||||
|
||||
// when duplicating, go straight to the duplicated document content
|
||||
this.redirectTo = duped.url;
|
||||
this.props.ui.showToast("Document duplicated");
|
||||
const { t } = this.props;
|
||||
this.props.ui.showToast(t("Document duplicated"));
|
||||
};
|
||||
|
||||
handleOpenTemplateModal = () => {
|
||||
@@ -100,7 +103,8 @@ class DocumentMenu extends React.Component<Props> {
|
||||
|
||||
handleArchive = async (ev: SyntheticEvent<>) => {
|
||||
await this.props.document.archive();
|
||||
this.props.ui.showToast("Document archived");
|
||||
const { t } = this.props;
|
||||
this.props.ui.showToast(t("Document archived"));
|
||||
};
|
||||
|
||||
handleRestore = async (
|
||||
@@ -108,12 +112,14 @@ class DocumentMenu extends React.Component<Props> {
|
||||
options?: { collectionId: string }
|
||||
) => {
|
||||
await this.props.document.restore(options);
|
||||
this.props.ui.showToast("Document restored");
|
||||
const { t } = this.props;
|
||||
this.props.ui.showToast(t("Document restored"));
|
||||
};
|
||||
|
||||
handleUnpublish = async (ev: SyntheticEvent<>) => {
|
||||
await this.props.document.unpublish();
|
||||
this.props.ui.showToast("Document unpublished");
|
||||
const { t } = this.props;
|
||||
this.props.ui.showToast(t("Document unpublished"));
|
||||
};
|
||||
|
||||
handlePin = (ev: SyntheticEvent<>) => {
|
||||
@@ -164,6 +170,7 @@ class DocumentMenu extends React.Component<Props> {
|
||||
label,
|
||||
onOpen,
|
||||
onClose,
|
||||
t,
|
||||
} = this.props;
|
||||
|
||||
const can = policies.abilities(document.id);
|
||||
@@ -183,17 +190,17 @@ class DocumentMenu extends React.Component<Props> {
|
||||
<DropdownMenuItems
|
||||
items={[
|
||||
{
|
||||
title: "Restore",
|
||||
title: t("Restore"),
|
||||
visible: !!can.unarchive,
|
||||
onClick: this.handleRestore,
|
||||
},
|
||||
{
|
||||
title: "Restore",
|
||||
title: t("Restore"),
|
||||
visible: !!(collection && can.restore),
|
||||
onClick: this.handleRestore,
|
||||
},
|
||||
{
|
||||
title: "Restore…",
|
||||
title: t("Restore…"),
|
||||
visible: !collection && !!can.restore,
|
||||
style: {
|
||||
left: -170,
|
||||
@@ -204,7 +211,7 @@ class DocumentMenu extends React.Component<Props> {
|
||||
items: [
|
||||
{
|
||||
type: "heading",
|
||||
title: "Choose a collection",
|
||||
title: t("Choose a collection"),
|
||||
},
|
||||
...collections.orderedData.map((collection) => {
|
||||
const can = policies.abilities(collection.id);
|
||||
@@ -224,37 +231,37 @@ class DocumentMenu extends React.Component<Props> {
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Unpin",
|
||||
title: t("Unpin"),
|
||||
onClick: this.handleUnpin,
|
||||
visible: !!(showPin && document.pinned && can.unpin),
|
||||
},
|
||||
{
|
||||
title: "Pin to collection",
|
||||
title: t("Pin to collection"),
|
||||
onClick: this.handlePin,
|
||||
visible: !!(showPin && !document.pinned && can.pin),
|
||||
},
|
||||
{
|
||||
title: "Unstar",
|
||||
title: t("Unstar"),
|
||||
onClick: this.handleUnstar,
|
||||
visible: document.isStarred && !!can.unstar,
|
||||
},
|
||||
{
|
||||
title: "Star",
|
||||
title: t("Star"),
|
||||
onClick: this.handleStar,
|
||||
visible: !document.isStarred && !!can.star,
|
||||
},
|
||||
{
|
||||
title: "Share link…",
|
||||
title: t("Share link…"),
|
||||
onClick: this.handleShareLink,
|
||||
visible: canShareDocuments,
|
||||
},
|
||||
{
|
||||
title: "Enable embeds",
|
||||
title: t("Enable embeds"),
|
||||
onClick: document.enableEmbeds,
|
||||
visible: !!showToggleEmbeds && document.embedsDisabled,
|
||||
},
|
||||
{
|
||||
title: "Disable embeds",
|
||||
title: t("Disable embeds"),
|
||||
onClick: document.disableEmbeds,
|
||||
visible: !!showToggleEmbeds && !document.embedsDisabled,
|
||||
},
|
||||
@@ -262,42 +269,42 @@ class DocumentMenu extends React.Component<Props> {
|
||||
type: "separator",
|
||||
},
|
||||
{
|
||||
title: "New nested document",
|
||||
title: t("New nested document"),
|
||||
onClick: this.handleNewChild,
|
||||
visible: !!can.createChildDocument,
|
||||
},
|
||||
{
|
||||
title: "Create template…",
|
||||
title: t("Create template…"),
|
||||
onClick: this.handleOpenTemplateModal,
|
||||
visible: !!can.update && !document.isTemplate,
|
||||
},
|
||||
{
|
||||
title: "Edit",
|
||||
title: t("Edit"),
|
||||
onClick: this.handleEdit,
|
||||
visible: !!can.update,
|
||||
},
|
||||
{
|
||||
title: "Duplicate",
|
||||
title: t("Duplicate"),
|
||||
onClick: this.handleDuplicate,
|
||||
visible: !!can.update,
|
||||
},
|
||||
{
|
||||
title: "Unpublish",
|
||||
title: t("Unpublish"),
|
||||
onClick: this.handleUnpublish,
|
||||
visible: !!can.unpublish,
|
||||
},
|
||||
{
|
||||
title: "Archive",
|
||||
title: t("Archive"),
|
||||
onClick: this.handleArchive,
|
||||
visible: !!can.archive,
|
||||
},
|
||||
{
|
||||
title: "Delete…",
|
||||
title: t("Delete…"),
|
||||
onClick: this.handleDelete,
|
||||
visible: !!can.delete,
|
||||
},
|
||||
{
|
||||
title: "Move…",
|
||||
title: t("Move…"),
|
||||
onClick: this.handleMove,
|
||||
visible: !!can.move,
|
||||
},
|
||||
@@ -305,17 +312,17 @@ class DocumentMenu extends React.Component<Props> {
|
||||
type: "separator",
|
||||
},
|
||||
{
|
||||
title: "History",
|
||||
title: t("History"),
|
||||
onClick: this.handleDocumentHistory,
|
||||
visible: canViewHistory,
|
||||
},
|
||||
{
|
||||
title: "Download",
|
||||
title: t("Download"),
|
||||
onClick: this.handleExport,
|
||||
visible: !!can.download,
|
||||
},
|
||||
{
|
||||
title: "Print",
|
||||
title: t("Print"),
|
||||
onClick: window.print,
|
||||
visible: !!showPrint,
|
||||
},
|
||||
@@ -323,7 +330,9 @@ class DocumentMenu extends React.Component<Props> {
|
||||
/>
|
||||
</DropdownMenu>
|
||||
<Modal
|
||||
title={`Delete ${this.props.document.noun}`}
|
||||
title={t("Delete {{ documentName }}", {
|
||||
documentName: this.props.document.noun,
|
||||
})}
|
||||
onRequestClose={this.handleCloseDeleteModal}
|
||||
isOpen={this.showDeleteModal}
|
||||
>
|
||||
@@ -333,7 +342,7 @@ class DocumentMenu extends React.Component<Props> {
|
||||
/>
|
||||
</Modal>
|
||||
<Modal
|
||||
title="Create template"
|
||||
title={t("Create template")}
|
||||
onRequestClose={this.handleCloseTemplateModal}
|
||||
isOpen={this.showTemplateModal}
|
||||
>
|
||||
@@ -343,7 +352,7 @@ class DocumentMenu extends React.Component<Props> {
|
||||
/>
|
||||
</Modal>
|
||||
<Modal
|
||||
title="Share document"
|
||||
title={t("Share document")}
|
||||
onRequestClose={this.handleCloseShareModal}
|
||||
isOpen={this.showShareModal}
|
||||
>
|
||||
@@ -357,4 +366,6 @@ class DocumentMenu extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
export default inject("ui", "auth", "collections", "policies")(DocumentMenu);
|
||||
export default withTranslation()<DocumentMenu>(
|
||||
inject("ui", "auth", "collections", "policies")(DocumentMenu)
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user