Skip to content

Commit

Permalink
Add a dialog to the translation feature (#2027)
Browse files Browse the repository at this point in the history
  • Loading branch information
VP-DS authored Jun 11, 2024
1 parent 5bbb2ee commit 5e25348
Show file tree
Hide file tree
Showing 11 changed files with 221 additions and 29 deletions.
18 changes: 18 additions & 0 deletions .changeset/harmonious-morus-alba.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
"@comet/admin": minor
"@comet/admin-rte": minor
---

Add a dialog option to the translation feature

If enabled a dialog will open when pressing the translation button showing the original text and an editable translation

Control if the dialog should be shown for the current scope via the `showApplyTranslationDialog` prop (default: true)

```diff
<ContentTranslationServiceProvider
enabled={true}
+ showApplyTranslationDialog={true}
translate={...}
>
```
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { IControlProps } from "../types";
function TranslationControls(props: IControlProps) {
const translationContext = useContentTranslationService();

if (translationContext.enabled) {
if (translationContext.enabled && !props.options.disableContentTranslation) {
return (
<ButtonGroup>
<TranslationToolbarButton {...props} />
Expand Down
1 change: 1 addition & 0 deletions packages/admin/admin-rte/src/core/Rte.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export interface IRteOptions {
*/
customBlockMap?: ICustomBlockTypeMap_Deprecated;
customInlineStyles?: CustomInlineStyles;
disableContentTranslation?: boolean;
}

export type IOptions = Partial<IRteOptions>;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { BaseTranslationDialog } from "@comet/admin";
import { EditorState } from "draft-js";
import * as React from "react";

import Rte from "../Rte";
import RteReadOnly from "../RteReadOnly";

interface EditorStateTranslationDialogProps {
open: boolean;
onClose: () => void;
originalText: EditorState;
translatedText: EditorState;
onApplyTranslation: (newValue: EditorState) => void;
}

export const EditorStateTranslationDialog: React.FC<EditorStateTranslationDialogProps> = (props) => {
const { open, onClose, originalText, translatedText, onApplyTranslation } = props;

return (
<BaseTranslationDialog
open={open}
onClose={onClose}
originalText={originalText}
translatedText={translatedText}
onApplyTranslation={onApplyTranslation}
renderOriginalText={(text) => <RteReadOnly value={text} />}
renderTranslatedText={(text, onChange) => <Rte value={text} onChange={onChange} options={{ disableContentTranslation: true }} />}
/>
);
};
33 changes: 27 additions & 6 deletions packages/admin/admin-rte/src/core/translation/ToolbarButton.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import { Tooltip, useContentTranslationService } from "@comet/admin";
import { Translate } from "@comet/admin-icons";
import { EditorState } from "draft-js";
import * as React from "react";
import { FormattedMessage } from "react-intl";

import ControlButton from "../Controls/ControlButton";
import { IControlProps } from "../types";
import { EditorStateTranslationDialog } from "./EditorStateTranslationDialog";
import { htmlToState } from "./htmlToState";
import { stateToHtml } from "./stateToHtml";

function ToolbarButton({ editorState, setEditorState, options }: IControlProps): React.ReactElement {
const translationContext = useContentTranslationService();

const [open, setOpen] = React.useState<boolean>(false);
const [pendingTranslation, setPendingTranslation] = React.useState<EditorState | undefined>(undefined);

async function handleClick(event: React.MouseEvent) {
if (!translationContext) return;

Expand All @@ -22,15 +27,31 @@ function ToolbarButton({ editorState, setEditorState, options }: IControlProps):

const translatedEditorState = htmlToState({ html: translation, entities });

setEditorState(translatedEditorState);
if (translationContext.showApplyTranslationDialog) {
setPendingTranslation(translatedEditorState);
setOpen(true);
} else {
setEditorState(translatedEditorState);
}
}

return (
<Tooltip title={<FormattedMessage id="comet.rte.translation.buttonTooltip" defaultMessage="Translate" />} placement="top">
<span>
<ControlButton icon={Translate} onButtonClick={handleClick} />
</span>
</Tooltip>
<>
<Tooltip title={<FormattedMessage id="comet.rte.translation.buttonTooltip" defaultMessage="Translate" />} placement="top">
<span>
<ControlButton icon={Translate} onButtonClick={handleClick} />
</span>
</Tooltip>
{open && pendingTranslation && (
<EditorStateTranslationDialog
open={open}
onClose={() => setOpen(false)}
originalText={editorState}
translatedText={pendingTranslation}
onApplyTranslation={setEditorState}
/>
)}
</>
);
}

Expand Down
64 changes: 44 additions & 20 deletions packages/admin/admin/src/form/FinalFormInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { FieldRenderProps } from "react-final-form";
import { FormattedMessage } from "react-intl";

import { ClearInputAdornment } from "../common/ClearInputAdornment";
import { PlainTextTranslationDialog } from "../translator/PlainTextTranslationDialog";
import { useContentTranslationService } from "../translator/useContentTranslationService";

export type FinalFormInputProps = InputBaseProps &
Expand All @@ -23,28 +24,51 @@ export function FinalFormInput({
...props
}: FinalFormInputProps): React.ReactElement {
const type = props.type ?? input.type ?? "text";
const { enabled: translationEnabled, translate } = useContentTranslationService();
const { enabled: translationEnabled, showApplyTranslationDialog, translate } = useContentTranslationService();
const isTranslatable = translationEnabled && !disableContentTranslation && type === "text" && !props.disabled;

const [open, setOpen] = React.useState<boolean>(false);
const [pendingTranslation, setPendingTranslation] = React.useState<string | undefined>(undefined);

return (
<InputBase
{...input}
{...props}
endAdornment={
<>
{clearable && (
<ClearInputAdornment position="end" hasClearableContent={Boolean(input.value)} onClick={() => input.onChange("")} />
)}
{isTranslatable && (
<Tooltip title={<FormattedMessage id="comet.translate" defaultMessage="Translate" />}>
<IconButton onClick={async () => input.onChange(await translate(input.value))}>
<Translate />
</IconButton>
</Tooltip>
)}
{endAdornment}
</>
}
/>
<>
<InputBase
{...input}
{...props}
endAdornment={
<>
{clearable && (
<ClearInputAdornment position="end" hasClearableContent={Boolean(input.value)} onClick={() => input.onChange("")} />
)}
{isTranslatable && (
<Tooltip title={<FormattedMessage id="comet.translate" defaultMessage="Translate" />}>
<IconButton
onClick={async () => {
if (showApplyTranslationDialog) {
setPendingTranslation(await translate(input.value));
setOpen(true);
} else {
input.onChange(await translate(input.value));
}
}}
>
<Translate />
</IconButton>
</Tooltip>
)}
{endAdornment}
</>
}
/>
{open && pendingTranslation && (
<PlainTextTranslationDialog
open={open}
onClose={() => setOpen(false)}
originalText={input.value}
translatedText={pendingTranslation}
onApplyTranslation={input.onChange}
/>
)}
</>
);
}
1 change: 1 addition & 0 deletions packages/admin/admin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,5 +217,6 @@ export { RouterTabsClassKey } from "./tabs/RouterTabs.styles";
export { Tab, Tabs, TabsProps } from "./tabs/Tabs";
export { TabsClassKey } from "./tabs/Tabs.styles";
export { TabScrollButton, TabScrollButtonClassKey, TabScrollButtonProps } from "./tabs/TabScrollButton";
export { BaseTranslationDialog } from "./translator/BaseTranslationDialog";
export { ContentTranslationServiceProvider } from "./translator/ContentTranslationServiceProvider";
export { useContentTranslationService } from "./translator/useContentTranslationService";
59 changes: 59 additions & 0 deletions packages/admin/admin/src/translator/BaseTranslationDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Grid, Typography } from "@mui/material";
import * as React from "react";
import { FormattedMessage } from "react-intl";

import { messages } from "../messages";

interface TranslationDialogBaseProps<T> {
open: boolean;
onClose: () => void;
originalText: T;
translatedText: T;
onApplyTranslation: (value: T) => void;
renderOriginalText: (text: T) => JSX.Element;
renderTranslatedText: (text: T, onChange: (value: T) => void) => JSX.Element;
}

export const BaseTranslationDialog = <T,>(props: TranslationDialogBaseProps<T>) => {
const { open, onClose, originalText, translatedText, onApplyTranslation, renderOriginalText, renderTranslatedText } = props;
const [translation, setTranslation] = React.useState(translatedText);

return (
<Dialog open={open} onClose={onClose} fullWidth maxWidth="lg">
<DialogTitle>
<FormattedMessage id="comet.translator.translation" defaultMessage="Translation" />
</DialogTitle>
<DialogContent>
<Grid container spacing={4} columns={2} alignItems="center">
<Grid item xs={1}>
<Typography variant="h6">
<FormattedMessage id="comet.translator.original" defaultMessage="Original" />
</Typography>
{renderOriginalText(originalText)}
</Grid>
<Grid item xs={1}>
<Typography variant="h6">
<FormattedMessage id="comet.translator.translation" defaultMessage="Translation" />
</Typography>
{renderTranslatedText(translation, setTranslation)}
</Grid>
</Grid>
</DialogContent>
<DialogActions>
<Button onClick={onClose} color="primary">
<FormattedMessage {...messages.cancel} />
</Button>
<Button
onClick={() => {
onApplyTranslation(translation);
onClose();
}}
color="primary"
variant="contained"
>
<FormattedMessage {...messages.apply} />
</Button>
</DialogActions>
</Dialog>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as React from "react";

export interface ContentTranslationServiceContext {
enabled: boolean;
showApplyTranslationDialog?: boolean;
translate: (text: string) => Promise<string>;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ import * as React from "react";

import { ContentTranslationServiceContext } from "./ContentTranslationServiceContext";

export const ContentTranslationServiceProvider: React.FunctionComponent<ContentTranslationServiceContext> = ({ children, enabled, translate }) => {
return <ContentTranslationServiceContext.Provider value={{ enabled, translate }}>{children}</ContentTranslationServiceContext.Provider>;
export const ContentTranslationServiceProvider: React.FunctionComponent<ContentTranslationServiceContext> = ({
children,
enabled,
showApplyTranslationDialog,
translate,
}) => {
return (
<ContentTranslationServiceContext.Provider value={{ enabled, showApplyTranslationDialog, translate }}>
{children}
</ContentTranslationServiceContext.Provider>
);
};
28 changes: 28 additions & 0 deletions packages/admin/admin/src/translator/PlainTextTranslationDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { TextField } from "@mui/material";
import * as React from "react";

import { BaseTranslationDialog } from "./BaseTranslationDialog";

interface PlainTextTranslationDialogProps {
open: boolean;
onClose: () => void;
originalText: string;
translatedText: string;
onApplyTranslation: (value: string) => void;
}

export const PlainTextTranslationDialog: React.FC<PlainTextTranslationDialogProps> = (props) => {
const { open, onClose, originalText, translatedText, onApplyTranslation } = props;

return (
<BaseTranslationDialog
open={open}
onClose={onClose}
originalText={originalText}
translatedText={translatedText}
onApplyTranslation={onApplyTranslation}
renderOriginalText={(text) => <TextField value={text} disabled fullWidth />}
renderTranslatedText={(text, onChange) => <TextField value={text} onChange={(event) => onChange(event.target.value)} fullWidth />}
/>
);
};

0 comments on commit 5e25348

Please sign in to comment.