import { type ReactNode, useEffect, useEffectEvent } from 'react';
import type { AppBskyFeedDefs } from '@atcute/bluesky';
import { getPostRecord } from '#/lib/api/record-casts';
import { usePostTranslation } from '#/state/queries/post-translation';
import { useSession } from '#/state/session';
import { codeToLanguageName, isPostInLanguage } from '#/locale/helpers';
import { LOCALE } from '#/locale/intl/locale';
import { Spinner } from '#/components/Spinner';
import { Text } from '#/components/Text';
import { Button, ButtonIcon } from '#/components/web/Button';
import { InlineButton } from '#/components/web/Link';
import ArrowRightIcon from '#/icons/central/ArrowRight_round_outlined_radius1_stroke2.svg';
import XIcon from '#/icons/central/CrossLarge_round_outlined_radius1_stroke2.svg';
import WarningIcon from '#/icons/central/ExclamationTriangle_round_outlined_radius1_stroke2.svg';
import { m } from '#/paraglide/messages';
import * as css from './Translated.css';
type Props = {
post: AppBskyFeedDefs.PostView;
onHide: () => void;
onShow: () => void;
shown: boolean;
textSize: 'lg' | 'md';
};
/**
* renders translation controls for a post.
*
* @param props post and display options
* @returns translation controls, or `null` when translation is unavailable
*/
export const TranslatedPost = ({ post, onHide, onShow, shown, textSize }: Props): ReactNode => {
const { hasSession } = useSession();
if (!hasSession || getPostRecord(post).text === '') {
return null;
}
return (
<TranslatedPostInner post={post} onHide={onHide} onShow={onShow} shown={shown} textSize={textSize} />
);
};
const TranslatedPostInner = ({ post, onHide, onShow, shown, textSize }: Props): ReactNode => {
const { state, targetLanguage, translate } = usePostTranslation(post);
const startTranslation = useEffectEvent(translate);
useEffect(() => {
if (shown && state.status === 'idle') {
startTranslation();
}
}, [shown, state.status]);
if (!shown) {
if (isPostInLanguage(post, [targetLanguage])) {
return null;
}
return (
<InlineButton className={css.link} onClick={onShow} size="sm">
{m['components.post.translate.action.translate']()}
</InlineButton>
);
}
switch (state.status) {
case 'error': {
return (
<div className={css.box}>
<div className={css.header}>
<WarningIcon className={css.warningIcon} />
<div className={css.headerText}>
<Text color="textContrastHigh" leading="snug" size="xs">
{state.message}
</Text>
</div>
<DismissButton onClick={onHide} />
</div>
<InlineButton className={css.boxLink} onClick={translate} size="sm">
{m['common.action.tryAgain']()}
</InlineButton>
</div>
);
}
case 'success': {
return (
<div className={css.box}>
<div className={css.header}>
<div className={css.headerText}>
<LanguagePair source={state.sourceLanguage} target={targetLanguage} />
</div>
<DismissButton onClick={onHide} />
</div>
<Text leading="snug" selectable size={textSize}>
{state.translation}
</Text>
</div>
);
}
default: {
return (
<div className={css.status}>
<Text color="textContrastMedium" size="sm">
{m['components.post.translate.status.translating']()}
</Text>
<Spinner color="default" label={null} size="xs" />
</div>
);
}
}
};
const LanguagePair = ({ source, target }: { source: string | undefined; target: string }): ReactNode => {
if (source === undefined) {
return (
<Text color="textContrastMedium" leading="snug" size="xs">
{m['components.post.translate.translated']()}
</Text>
);
}
return (
<>
<Text color="textContrastMedium" leading="snug" size="xs">
{codeToLanguageName(source, LOCALE)}
</Text>
<ArrowRightIcon className={css.arrowIcon} />
<Text color="textContrastMedium" leading="snug" size="xs">
{codeToLanguageName(target, LOCALE)}
</Text>
</>
);
};
const DismissButton = ({ onClick }: { onClick: () => void }): ReactNode => {
return (
<Button
className={css.dismiss}
color="secondary"
label={m['components.post.translate.action.hide']()}
onClick={onClick}
shape="round"
size="tiny"
variant="ghost"
>
<ButtonIcon icon={XIcon} />
</Button>
);
};