mirror of
https://github.com/mastodon/mastodon.git
synced 2026-09-12 00:46:13 -05:00
makes new translation button
This commit is contained in:
@@ -1,20 +1,16 @@
|
||||
import type React from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { FormattedMessage, useIntl } from 'react-intl';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { CaretRightIcon } from '@phosphor-icons/react';
|
||||
|
||||
import { useIdentity } from '@/mastodon/identity_context';
|
||||
import { languages as preloadedLanguages } from '@/mastodon/initial_state';
|
||||
import type {
|
||||
ExpandedStatusShape,
|
||||
StatusShape,
|
||||
StatusTranslation,
|
||||
} from '@/mastodon/models/status';
|
||||
import { useAppSelector } from '@/mastodon/store';
|
||||
|
||||
import { Button } from '../button/redesign';
|
||||
import { EmojiHTML } from '../emoji/html';
|
||||
@@ -42,12 +38,6 @@ export const StatusContent: React.FC<
|
||||
className,
|
||||
...props
|
||||
}) => {
|
||||
const { signedIn } = useIdentity();
|
||||
const targetLanguages = useAppSelector(
|
||||
(state) => state.server.translationLanguages.item?.[status.language],
|
||||
);
|
||||
const intl = useIntl();
|
||||
|
||||
// Determines if a long post should show the read more button.
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
const onRef = useCallback(
|
||||
@@ -70,14 +60,6 @@ export const StatusContent: React.FC<
|
||||
const language = status.translation?.language ?? status.language;
|
||||
|
||||
const isCollapsed = !!onReadMore && collapsible && collapsed;
|
||||
const renderTranslate = !!(
|
||||
onTranslate &&
|
||||
!isCollapsed &&
|
||||
signedIn &&
|
||||
['public', 'unlisted'].includes(status.visibility) &&
|
||||
status.search_index?.trim().length &&
|
||||
targetLanguages?.includes(intl.locale.replace(/[_-].*/, ''))
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -102,13 +84,6 @@ export const StatusContent: React.FC<
|
||||
|
||||
{children}
|
||||
|
||||
{renderTranslate && (
|
||||
<TranslateButton
|
||||
translation={status.translation}
|
||||
onTranslate={onTranslate}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isCollapsed && (
|
||||
<Button
|
||||
size='sm'
|
||||
@@ -123,48 +98,3 @@ export const StatusContent: React.FC<
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const TranslateButton: React.FC<{
|
||||
onTranslate: React.MouseEventHandler<HTMLButtonElement>;
|
||||
translation?: StatusTranslation;
|
||||
}> = ({ translation, onTranslate }) => {
|
||||
if (!translation) {
|
||||
return (
|
||||
<Button
|
||||
size='sm'
|
||||
variant='ghost'
|
||||
onClick={onTranslate}
|
||||
className={classes.buttonAlign}
|
||||
>
|
||||
<FormattedMessage id='status.translate' defaultMessage='Translate' />
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
const language = preloadedLanguages?.find(
|
||||
(lang) => lang[0] === translation.detected_source_language,
|
||||
);
|
||||
const languageName = language
|
||||
? language[1]
|
||||
: translation.detected_source_language;
|
||||
const provider = translation.provider;
|
||||
|
||||
return (
|
||||
<div className='translate-button'>
|
||||
<Button size='sm' variant='ghost' onClick={onTranslate}>
|
||||
<FormattedMessage
|
||||
id='status.show_original'
|
||||
defaultMessage='Show original'
|
||||
/>
|
||||
</Button>
|
||||
|
||||
<div className='translate-button__meta'>
|
||||
<FormattedMessage
|
||||
id='status.translated_from_with'
|
||||
defaultMessage='Translated from {lang} using {provider}'
|
||||
values={{ lang: languageName, provider }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -28,6 +28,7 @@ import { StatusMeta } from './meta';
|
||||
import { StatusPrepend } from './prepend';
|
||||
import { StatusRedesignHeader } from './redesign/header';
|
||||
import classes from './styles.module.scss';
|
||||
import { TranslateButton } from './translate';
|
||||
import type { StatusContainerProps, StatusContextType } from './types';
|
||||
|
||||
type StatusRedesignProps = Merge<
|
||||
@@ -185,6 +186,8 @@ export const StatusRedesign: React.FC<StatusRedesignProps> = ({
|
||||
/>
|
||||
)}
|
||||
|
||||
<TranslateButton status={status} onTranslate={onTranslate} />
|
||||
|
||||
{expanded && (
|
||||
<StatusContent
|
||||
status={status}
|
||||
|
||||
@@ -39,10 +39,23 @@
|
||||
.content,
|
||||
.footer,
|
||||
.root :global(.status__prepend),
|
||||
.root :global(.content-warning) {
|
||||
.root :global(.content-warning),
|
||||
.translate {
|
||||
grid-column: var(--content-col);
|
||||
}
|
||||
|
||||
.translate {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.translateInfo {
|
||||
@include mixins.type-label-md;
|
||||
|
||||
color: var(--color-text-secondary);
|
||||
margin-inline-start: auto;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
86
app/javascript/mastodon/components/status/translate.tsx
Normal file
86
app/javascript/mastodon/components/status/translate.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
import { FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { TranslateIcon } from '@phosphor-icons/react';
|
||||
|
||||
import { useIdentity } from '@/mastodon/identity_context';
|
||||
import { languages as preloadedLanguages } from '@/mastodon/initial_state';
|
||||
import type { AnyStatusShape } from '@/mastodon/models/status';
|
||||
import { useAppSelector } from '@/mastodon/store';
|
||||
|
||||
import { Button } from '../button/redesign';
|
||||
|
||||
import classes from './styles.module.scss';
|
||||
|
||||
export const TranslateButton: React.FC<{
|
||||
status: AnyStatusShape;
|
||||
onTranslate: React.MouseEventHandler<HTMLButtonElement>;
|
||||
}> = ({ status, onTranslate }) => {
|
||||
const { signedIn } = useIdentity();
|
||||
const targetLanguages = useAppSelector(
|
||||
(state) => state.server.translationLanguages.item?.[status.language],
|
||||
);
|
||||
const intl = useIntl();
|
||||
|
||||
const { translation } = status;
|
||||
|
||||
if (
|
||||
!signedIn ||
|
||||
!['public', 'unlisted'].includes(status.visibility) ||
|
||||
status.search_index?.trim().length === 0 ||
|
||||
!targetLanguages?.includes(intl.locale.replace(/[_-].*/, ''))
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!translation || translation.isLoading) {
|
||||
return (
|
||||
<div className={classes.translate}>
|
||||
<Button
|
||||
size='sm'
|
||||
color='accent'
|
||||
variant='ghost'
|
||||
onClick={onTranslate}
|
||||
leadingIcon={TranslateIcon}
|
||||
className={classes.buttonAlign}
|
||||
loading={translation?.isLoading}
|
||||
>
|
||||
<FormattedMessage id='status.translate' defaultMessage='Translate' />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const language = preloadedLanguages?.find(
|
||||
(lang) => lang[0] === translation.detected_source_language,
|
||||
);
|
||||
const languageName = language
|
||||
? language[1]
|
||||
: translation.detected_source_language;
|
||||
const provider = translation.provider;
|
||||
|
||||
return (
|
||||
<div className={classes.translate}>
|
||||
<Button
|
||||
size='sm'
|
||||
color='accent'
|
||||
variant='ghost'
|
||||
onClick={onTranslate}
|
||||
leadingIcon={TranslateIcon}
|
||||
className={classes.buttonAlign}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='status.show_original'
|
||||
defaultMessage='Show original'
|
||||
/>
|
||||
</Button>
|
||||
|
||||
<div className={classes.translateInfo}>
|
||||
<FormattedMessage
|
||||
id='status.translated_from_with'
|
||||
defaultMessage='Translated from {lang} using {provider}'
|
||||
values={{ lang: languageName, provider }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user