Skip to content

Commit

Permalink
Translate CW, poll options and media descriptions (#24175)
Browse files Browse the repository at this point in the history
Co-authored-by: Claire <claire.github-309c@sitedethib.com>
  • Loading branch information
c960657 and ClearlyClaire committed May 31, 2023
1 parent 44cd88a commit 6905746
Show file tree
Hide file tree
Showing 25 changed files with 603 additions and 100 deletions.
38 changes: 32 additions & 6 deletions app/javascript/mastodon/actions/importer/normalizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { unescapeHTML } from '../../utils/html';

const domParser = new DOMParser();

const makeEmojiMap = record => record.emojis.reduce((obj, emoji) => {
const makeEmojiMap = emojis => emojis.reduce((obj, emoji) => {
obj[`:${emoji.shortcode}:`] = emoji;
return obj;
}, {});
Expand All @@ -20,7 +20,7 @@ export function searchTextFromRawStatus (status) {
export function normalizeAccount(account) {
account = { ...account };

const emojiMap = makeEmojiMap(account);
const emojiMap = makeEmojiMap(account.emojis);
const displayName = account.display_name.trim().length === 0 ? account.username : account.display_name;

account.display_name_html = emojify(escapeTextContentForBrowser(displayName), emojiMap);
Expand Down Expand Up @@ -86,7 +86,7 @@ export function normalizeStatus(status, normalOldStatus) {

const spoilerText = normalStatus.spoiler_text || '';
const searchContent = ([spoilerText, status.content].concat((status.poll && status.poll.options) ? status.poll.options.map(option => option.title) : [])).concat(status.media_attachments.map(att => att.description)).join('\n\n').replace(/<br\s*\/?>/g, '\n').replace(/<\/p><p>/g, '\n\n');
const emojiMap = makeEmojiMap(normalStatus);
const emojiMap = makeEmojiMap(normalStatus.emojis);

normalStatus.search_index = domParser.parseFromString(searchContent, 'text/html').documentElement.textContent;
normalStatus.contentHtml = emojify(normalStatus.content, emojiMap);
Expand All @@ -97,22 +97,48 @@ export function normalizeStatus(status, normalOldStatus) {
return normalStatus;
}

export function normalizeStatusTranslation(translation, status) {
const emojiMap = makeEmojiMap(status.get('emojis').toJS());

const normalTranslation = {
detected_source_language: translation.detected_source_language,
language: translation.language,
provider: translation.provider,
contentHtml: emojify(translation.content, emojiMap),
spoilerHtml: emojify(escapeTextContentForBrowser(translation.spoiler_text), emojiMap),
spoiler_text: translation.spoiler_text,
};

return normalTranslation;
}

export function normalizePoll(poll) {
const normalPoll = { ...poll };
const emojiMap = makeEmojiMap(normalPoll);
const emojiMap = makeEmojiMap(poll.emojis);

normalPoll.options = poll.options.map((option, index) => ({
...option,
voted: poll.own_votes && poll.own_votes.includes(index),
title_emojified: emojify(escapeTextContentForBrowser(option.title), emojiMap),
titleHtml: emojify(escapeTextContentForBrowser(option.title), emojiMap),
}));

return normalPoll;
}

export function normalizePollOptionTranslation(translation, poll) {
const emojiMap = makeEmojiMap(poll.get('emojis').toJS());

const normalTranslation = {
...translation,
titleHtml: emojify(escapeTextContentForBrowser(translation.title), emojiMap),
};

return normalTranslation;
}

export function normalizeAnnouncement(announcement) {
const normalAnnouncement = { ...announcement };
const emojiMap = makeEmojiMap(normalAnnouncement);
const emojiMap = makeEmojiMap.emojis(normalAnnouncement);

normalAnnouncement.contentHtml = emojify(normalAnnouncement.content, emojiMap);

Expand Down
3 changes: 2 additions & 1 deletion app/javascript/mastodon/actions/statuses.js
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,8 @@ export const translateStatusFail = (id, error) => ({
error,
});

export const undoStatusTranslation = id => ({
export const undoStatusTranslation = (id, pollId) => ({
type: STATUS_TRANSLATE_UNDO,
id,
pollId,
});
15 changes: 9 additions & 6 deletions app/javascript/mastodon/components/media_attachments.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,23 +51,25 @@ export default class MediaAttachments extends ImmutablePureComponent {
};

render () {
const { status, lang, width, height } = this.props;
const { status, width, height } = this.props;
const mediaAttachments = status.get('media_attachments');
const language = status.getIn(['language', 'translation']) || status.get('language') || this.props.lang;

if (mediaAttachments.size === 0) {
return null;
}

if (mediaAttachments.getIn([0, 'type']) === 'audio') {
const audio = mediaAttachments.get(0);
const description = audio.getIn(['translation', 'description']) || audio.get('description');

return (
<Bundle fetchComponent={Audio} loading={this.renderLoadingAudioPlayer} >
{Component => (
<Component
src={audio.get('url')}
alt={audio.get('description')}
lang={lang || status.get('language')}
alt={description}
lang={language}
width={width}
height={height}
poster={audio.get('preview_url') || status.getIn(['account', 'avatar_static'])}
Expand All @@ -81,6 +83,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
);
} else if (mediaAttachments.getIn([0, 'type']) === 'video') {
const video = mediaAttachments.get(0);
const description = video.getIn(['translation', 'description']) || video.get('description');

return (
<Bundle fetchComponent={Video} loading={this.renderLoadingVideoPlayer} >
Expand All @@ -90,8 +93,8 @@ export default class MediaAttachments extends ImmutablePureComponent {
frameRate={video.getIn(['meta', 'original', 'frame_rate'])}
blurhash={video.get('blurhash')}
src={video.get('url')}
alt={video.get('description')}
lang={lang || status.get('language')}
alt={description}
lang={language}
width={width}
height={height}
inline
Expand All @@ -107,7 +110,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
{Component => (
<Component
media={mediaAttachments}
lang={lang || status.get('language')}
lang={language}
sensitive={status.get('sensitive')}
defaultWidth={width}
height={height}
Expand Down
12 changes: 7 additions & 5 deletions app/javascript/mastodon/components/media_gallery.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,12 @@ class Item extends PureComponent {
badges.push(<span key='alt' className='media-gallery__gifv__label'>ALT</span>);
}

const description = attachment.getIn(['translation', 'description']) || attachment.get('description');

if (attachment.get('type') === 'unknown') {
return (
<div className={classNames('media-gallery__item', { standalone, 'media-gallery__item--tall': height === 100, 'media-gallery__item--wide': width === 100 })} key={attachment.get('id')}>
<a className='media-gallery__item-thumbnail' href={attachment.get('remote_url') || attachment.get('url')} style={{ cursor: 'pointer' }} title={attachment.get('description')} lang={lang} target='_blank' rel='noopener noreferrer'>
<a className='media-gallery__item-thumbnail' href={attachment.get('remote_url') || attachment.get('url')} style={{ cursor: 'pointer' }} title={description} lang={lang} target='_blank' rel='noopener noreferrer'>
<Blurhash
hash={attachment.get('blurhash')}
className='media-gallery__preview'
Expand Down Expand Up @@ -146,8 +148,8 @@ class Item extends PureComponent {
src={previewUrl}
srcSet={srcSet}
sizes={sizes}
alt={attachment.get('description')}
title={attachment.get('description')}
alt={description}
title={description}
lang={lang}
style={{ objectPosition: `${x}% ${y}%` }}
onLoad={this.handleImageLoad}
Expand All @@ -163,8 +165,8 @@ class Item extends PureComponent {
<div className={classNames('media-gallery__gifv', { autoplay: autoPlay })}>
<video
className='media-gallery__item-gifv-thumbnail'
aria-label={attachment.get('description')}
title={attachment.get('description')}
aria-label={description}
title={description}
lang={lang}
role='application'
src={attachment.get('url')}
Expand Down
12 changes: 7 additions & 5 deletions app/javascript/mastodon/components/poll.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,12 @@ class Poll extends ImmutablePureComponent {
const active = !!this.state.selected[`${optionIndex}`];
const voted = option.get('voted') || (poll.get('own_votes') && poll.get('own_votes').includes(optionIndex));

let titleEmojified = option.get('title_emojified');
if (!titleEmojified) {
const title = option.getIn(['translation', 'title']) || option.get('title');
let titleHtml = option.getIn(['translation', 'titleHtml']) || option.get('titleHtml');

if (!titleHtml) {
const emojiMap = makeEmojiMap(poll);
titleEmojified = emojify(escapeTextContentForBrowser(option.get('title')), emojiMap);
titleHtml = emojify(escapeTextContentForBrowser(title), emojiMap);
}

return (
Expand All @@ -163,7 +165,7 @@ class Poll extends ImmutablePureComponent {
role={poll.get('multiple') ? 'checkbox' : 'radio'}
onKeyPress={this.handleOptionKeyPress}
aria-checked={active}
aria-label={option.get('title')}
aria-label={title}
lang={lang}
data-index={optionIndex}
/>
Expand All @@ -182,7 +184,7 @@ class Poll extends ImmutablePureComponent {
<span
className='poll__option__text translate'
lang={lang}
dangerouslySetInnerHTML={{ __html: titleEmojified }}
dangerouslySetInnerHTML={{ __html: titleHtml }}
/>

{!!voted && <span className='poll__voted'>
Expand Down
30 changes: 21 additions & 9 deletions app/javascript/mastodon/components/status.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,18 @@ import { RelativeTimestamp } from './relative_timestamp';
import StatusActionBar from './status_action_bar';
import StatusContent from './status_content';

const domParser = new DOMParser();

export const textForScreenReader = (intl, status, rebloggedByText = false) => {
const displayName = status.getIn(['account', 'display_name']);

const spoilerText = status.getIn(['translation', 'spoiler_text']) || status.get('spoiler_text');
const contentHtml = status.getIn(['translation', 'contentHtml']) || status.get('contentHtml');
const contentText = domParser.parseFromString(contentHtml, 'text/html').documentElement.textContent;

const values = [
displayName.length === 0 ? status.getIn(['account', 'acct']).split('@')[0] : displayName,
status.get('spoiler_text') && status.get('hidden') ? status.get('spoiler_text') : status.get('search_index').slice(status.get('spoiler_text').length),
spoilerText && status.get('hidden') ? spoilerText : contentText,
intl.formatDate(status.get('created_at'), { hour: '2-digit', minute: '2-digit', month: 'short', day: 'numeric' }),
status.getIn(['account', 'acct']),
];
Expand Down Expand Up @@ -199,12 +205,14 @@ class Status extends ImmutablePureComponent {

handleOpenVideo = (options) => {
const status = this._properStatus();
this.props.onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), status.get('language'), options);
const lang = status.getIn(['translation', 'language']) || status.get('language');
this.props.onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), lang, options);
};

handleOpenMedia = (media, index) => {
const status = this._properStatus();
this.props.onOpenMedia(status.get('id'), media, index, status.get('language'));
const lang = status.getIn(['translation', 'language']) || status.get('language');
this.props.onOpenMedia(status.get('id'), media, index, lang);
};

handleHotkeyOpenMedia = e => {
Expand All @@ -214,7 +222,7 @@ class Status extends ImmutablePureComponent {
e.preventDefault();

if (status.get('media_attachments').size > 0) {
const lang = status.get('language');
const lang = status.getIn(['translation', 'language']) || status.get('language');
if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), lang, { startTime: 0 });
} else {
Expand Down Expand Up @@ -420,6 +428,8 @@ class Status extends ImmutablePureComponent {
if (pictureInPicture.get('inUse')) {
media = <PictureInPicturePlaceholder />;
} else if (status.get('media_attachments').size > 0) {
const language = status.getIn(['translation', 'language']) || status.get('language');

if (this.props.muted) {
media = (
<AttachmentList
Expand All @@ -429,14 +439,15 @@ class Status extends ImmutablePureComponent {
);
} else if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
const attachment = status.getIn(['media_attachments', 0]);
const description = attachment.getIn(['translation', 'description']) || attachment.get('description');

media = (
<Bundle fetchComponent={Audio} loading={this.renderLoadingAudioPlayer} >
{Component => (
<Component
src={attachment.get('url')}
alt={attachment.get('description')}
lang={status.get('language')}
alt={description}
lang={language}
poster={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])}
backgroundColor={attachment.getIn(['meta', 'colors', 'background'])}
foregroundColor={attachment.getIn(['meta', 'colors', 'foreground'])}
Expand All @@ -456,6 +467,7 @@ class Status extends ImmutablePureComponent {
);
} else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
const attachment = status.getIn(['media_attachments', 0]);
const description = attachment.getIn(['translation', 'description']) || attachment.get('description');

media = (
<Bundle fetchComponent={Video} loading={this.renderLoadingVideoPlayer} >
Expand All @@ -465,8 +477,8 @@ class Status extends ImmutablePureComponent {
frameRate={attachment.getIn(['meta', 'original', 'frame_rate'])}
blurhash={attachment.get('blurhash')}
src={attachment.get('url')}
alt={attachment.get('description')}
lang={status.get('language')}
alt={description}
lang={language}
inline
sensitive={status.get('sensitive')}
onOpenVideo={this.handleOpenVideo}
Expand All @@ -483,7 +495,7 @@ class Status extends ImmutablePureComponent {
{Component => (
<Component
media={status.get('media_attachments')}
lang={status.get('language')}
lang={language}
sensitive={status.get('sensitive')}
height={110}
onOpenMedia={this.handleOpenMedia}
Expand Down
20 changes: 10 additions & 10 deletions app/javascript/mastodon/components/status_content.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -231,11 +231,11 @@ class StatusContent extends PureComponent {
const renderReadMore = this.props.onClick && status.get('collapsed');
const contentLocale = intl.locale.replace(/[_-].*/, '');
const targetLanguages = this.props.languages?.get(status.get('language') || 'und');
const renderTranslate = this.props.onTranslate && this.context.identity.signedIn && ['public', 'unlisted'].includes(status.get('visibility')) && status.get('contentHtml').length > 0 && targetLanguages?.includes(contentLocale);
const renderTranslate = this.props.onTranslate && this.context.identity.signedIn && ['public', 'unlisted'].includes(status.get('visibility')) && status.get('search_index').trim().length > 0 && targetLanguages?.includes(contentLocale);

const content = { __html: status.get('translation') ? status.getIn(['translation', 'content']) : status.get('contentHtml') };
const spoilerContent = { __html: status.get('spoilerHtml') };
const lang = status.get('translation') ? intl.locale : status.get('language');
const content = { __html: status.getIn(['translation', 'contentHtml']) || status.get('contentHtml') };
const spoilerContent = { __html: status.getIn(['translation', 'spoilerHtml']) || status.get('spoilerHtml') };
const language = status.getIn(['translation', 'language']) || status.get('language');
const classNames = classnames('status__content', {
'status__content--with-action': this.props.onClick && this.context.router,
'status__content--with-spoiler': status.get('spoiler_text').length > 0,
Expand All @@ -253,7 +253,7 @@ class StatusContent extends PureComponent {
);

const poll = !!status.get('poll') && (
<PollContainer pollId={status.get('poll')} lang={status.get('language')} />
<PollContainer pollId={status.get('poll')} lang={language} />
);

if (status.get('spoiler_text').length > 0) {
Expand All @@ -274,24 +274,24 @@ class StatusContent extends PureComponent {
return (
<div className={classNames} ref={this.setRef} tabIndex={0} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
<p style={{ marginBottom: hidden && status.get('mentions').isEmpty() ? '0px' : null }}>
<span dangerouslySetInnerHTML={spoilerContent} className='translate' lang={lang} />
<span dangerouslySetInnerHTML={spoilerContent} className='translate' lang={language} />
{' '}
<button type='button' className={`status__content__spoiler-link ${hidden ? 'status__content__spoiler-link--show-more' : 'status__content__spoiler-link--show-less'}`} onClick={this.handleSpoilerClick} aria-expanded={!hidden}>{toggleText}</button>
</p>

{mentionsPlaceholder}

<div tabIndex={!hidden ? 0 : null} className={`status__content__text ${!hidden ? 'status__content__text--visible' : ''} translate`} lang={lang} dangerouslySetInnerHTML={content} />
<div tabIndex={!hidden ? 0 : null} className={`status__content__text ${!hidden ? 'status__content__text--visible' : ''} translate`} lang={language} dangerouslySetInnerHTML={content} />

{!hidden && poll}
{!hidden && translateButton}
{translateButton}
</div>
);
} else if (this.props.onClick) {
return (
<>
<div className={classNames} ref={this.setRef} tabIndex={0} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp} key='status-content' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
<div className='status__content__text status__content__text--visible translate' lang={lang} dangerouslySetInnerHTML={content} />
<div className='status__content__text status__content__text--visible translate' lang={language} dangerouslySetInnerHTML={content} />

{poll}
{translateButton}
Expand All @@ -303,7 +303,7 @@ class StatusContent extends PureComponent {
} else {
return (
<div className={classNames} ref={this.setRef} tabIndex={0} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
<div className='status__content__text status__content__text--visible translate' lang={lang} dangerouslySetInnerHTML={content} />
<div className='status__content__text status__content__text--visible translate' lang={language} dangerouslySetInnerHTML={content} />

{poll}
{translateButton}
Expand Down
2 changes: 1 addition & 1 deletion app/javascript/mastodon/containers/status_container.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({

onTranslate (status) {
if (status.get('translation')) {
dispatch(undoStatusTranslation(status.get('id')));
dispatch(undoStatusTranslation(status.get('id'), status.get('poll')));
} else {
dispatch(translateStatus(status.get('id')));
}
Expand Down

0 comments on commit 6905746

Please sign in to comment.