From 5e5084098649d7ebf05ed4f192754ea346f66732 Mon Sep 17 00:00:00 2001 From: Thomas Steiner Date: Sun, 22 Jun 2025 12:54:42 +0200 Subject: [PATCH 1/4] Translation works, undo still doesn't --- .../mastodon/components/status_content.jsx | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/app/javascript/mastodon/components/status_content.jsx b/app/javascript/mastodon/components/status_content.jsx index 0628e0791b5..9c886008056 100644 --- a/app/javascript/mastodon/components/status_content.jsx +++ b/app/javascript/mastodon/components/status_content.jsx @@ -10,6 +10,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'; +import { translateStatusSuccess } from 'mastodon/actions/statuses'; import { Icon } from 'mastodon/components/icon'; import { Poll } from 'mastodon/components/poll'; import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; @@ -17,6 +18,9 @@ import { autoPlayGif, languages as preloadedLanguages } from 'mastodon/initial_s const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top) +const supportsTranslator = 'Translator' in globalThis; +const supportedTranslationLanguages = new Map(Object.entries({"ar":["en","pt","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"bg":["en","pt","ar","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"cs":["en","pt","ar","bg","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"da":["en","pt","ar","bg","cs","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"de":["en","pt","ar","bg","cs","da","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"el":["en","pt","ar","bg","cs","da","de","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"en":["pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"es":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"et":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"fi":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"fr":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"hu":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"id":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"it":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"ja":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"ko":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"lt":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"lv":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"nb":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"nl":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"pl":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"pt":["en","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"ro":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"ru":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"sk":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"sl":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sv","tr","uk","zh","zh-HANS","zh-HANT"],"sv":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","tr","uk","zh","zh-HANS","zh-HANT"],"tr":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","uk","zh","zh-HANS","zh-HANT"],"uk":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","zh","zh-HANS","zh-HANT"],"zh":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh-HANS","zh-HANT"],"und":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"]})); + /** * * @param {any} status @@ -64,7 +68,7 @@ class TranslateButton extends PureComponent { } const mapStateToProps = state => ({ - languages: state.getIn(['server', 'translationLanguages', 'items']), + languages: supportsTranslator ? supportedTranslationLanguages : state.getIn(['server', 'translationLanguages', 'items']), }); class StatusContent extends PureComponent { @@ -212,8 +216,31 @@ class StatusContent extends PureComponent { this.startXY = null; }; - handleTranslate = () => { - this.props.onTranslate(); + handleTranslate = async () => { + if (!supportsTranslator) { + this.props.onTranslate(); + return; + } + + const { intl, status, statusContent } = this.props; + const sourceLanguage = status.get('language'); + const targetLanguage = intl.locale.replace(/[_-].*/, ''); + try { + const translator = await Translator.create({ + sourceLanguage, + targetLanguage, + }); + const translatedText = await translator.translate(statusContent); + const translation = { + content: translatedText, + provider: 'Translator API', + detected_source_language: sourceLanguage, + language: targetLanguage, + }; + this.props.dispatch(translateStatusSuccess(status.get('id'), translation)); + } catch (error) { + console.error(error); + } }; setRef = (c) => { From 6c38ab5b6b1455f2284e34618cd7d8ef7130d27f Mon Sep 17 00:00:00 2001 From: Thomas Steiner Date: Sun, 22 Jun 2025 13:14:45 +0200 Subject: [PATCH 2/4] Make translate undo work --- app/javascript/mastodon/components/status_content.jsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/javascript/mastodon/components/status_content.jsx b/app/javascript/mastodon/components/status_content.jsx index 9c886008056..b58613d9698 100644 --- a/app/javascript/mastodon/components/status_content.jsx +++ b/app/javascript/mastodon/components/status_content.jsx @@ -11,6 +11,7 @@ import { connect } from 'react-redux'; import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'; import { translateStatusSuccess } from 'mastodon/actions/statuses'; +import { undoStatusTranslation } from 'mastodon/actions/statuses'; import { Icon } from 'mastodon/components/icon'; import { Poll } from 'mastodon/components/poll'; import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; @@ -223,6 +224,12 @@ class StatusContent extends PureComponent { } const { intl, status, statusContent } = this.props; + + if (status.get('translation')) { + this.props.dispatch(undoStatusTranslation(status.get('id'), status.get('poll'))); + return; + } + const sourceLanguage = status.get('language'); const targetLanguage = intl.locale.replace(/[_-].*/, ''); try { From 46e3c180714143c85c69fa64f4fc6b5c12cccd5d Mon Sep 17 00:00:00 2001 From: Thomas Steiner Date: Mon, 30 Jun 2025 11:10:27 +0200 Subject: [PATCH 3/4] Make supported translate language detection dynamic --- .../mastodon/components/status_content.jsx | 49 +++++++++++++++---- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/app/javascript/mastodon/components/status_content.jsx b/app/javascript/mastodon/components/status_content.jsx index b58613d9698..46f8c1a8655 100644 --- a/app/javascript/mastodon/components/status_content.jsx +++ b/app/javascript/mastodon/components/status_content.jsx @@ -20,7 +20,6 @@ import { autoPlayGif, languages as preloadedLanguages } from 'mastodon/initial_s const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top) const supportsTranslator = 'Translator' in globalThis; -const supportedTranslationLanguages = new Map(Object.entries({"ar":["en","pt","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"bg":["en","pt","ar","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"cs":["en","pt","ar","bg","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"da":["en","pt","ar","bg","cs","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"de":["en","pt","ar","bg","cs","da","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"el":["en","pt","ar","bg","cs","da","de","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"en":["pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"es":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"et":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"fi":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"fr":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"hu":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"id":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"it":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"ja":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"ko":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"lt":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"lv":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"nb":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"nl":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"pl":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"pt":["en","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"ro":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"ru":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"sk":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"],"sl":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sv","tr","uk","zh","zh-HANS","zh-HANT"],"sv":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","tr","uk","zh","zh-HANS","zh-HANT"],"tr":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","uk","zh","zh-HANS","zh-HANT"],"uk":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","zh","zh-HANS","zh-HANT"],"zh":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh-HANS","zh-HANT"],"und":["en","pt","ar","bg","cs","da","de","el","en-GB","en-US","es","et","fi","fr","hu","id","it","ja","ko","lt","lv","nb","nl","pl","pt-BR","pt-PT","ro","ru","sk","sl","sv","tr","uk","zh","zh-HANS","zh-HANT"]})); /** * @@ -69,10 +68,18 @@ class TranslateButton extends PureComponent { } const mapStateToProps = state => ({ - languages: supportsTranslator ? supportedTranslationLanguages : state.getIn(['server', 'translationLanguages', 'items']), + languages: supportsTranslator ? new Map() : state.getIn(['server', 'translationLanguages', 'items']), }); class StatusContent extends PureComponent { + constructor(props) { + super(props); + + this.state = { + showTranslateButton: false, + }; + } + static propTypes = { identity: identityContextPropShape, status: ImmutablePropTypes.map.isRequired, @@ -166,8 +173,34 @@ class StatusContent extends PureComponent { } }; - componentDidMount () { + async componentDidMount () { this._updateStatusLinks(); + + const { status, intl, languages } = this.props; + const contentLocale = intl.locale.replace(/[_-].*/, ''); + const targetLanguages = languages?.get(status.get('language') || 'und'); + + const shouldAttemptTranslate = + this.props.onTranslate && + this.props.identity.signedIn && + ['public', 'unlisted'].includes(status.get('visibility')) && + status.get('search_index').trim().length > 0; + + if (!shouldAttemptTranslate) return; + + let available = false; + if (supportsTranslator) { + available = (await Translator.availability({ + sourceLanguage: status.get('language'), + targetLanguage: contentLocale, + })) !== 'unavailable'; + } else { + available = targetLanguages?.includes(contentLocale); + } + + if (available) { + this.setState({ showTranslateButton: true }); + } } componentDidUpdate () { @@ -255,12 +288,9 @@ class StatusContent extends PureComponent { }; render () { - const { status, intl, statusContent } = this.props; + const { status, statusContent } = this.props; 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.props.identity.signedIn && ['public', 'unlisted'].includes(status.get('visibility')) && status.get('search_index').trim().length > 0 && targetLanguages?.includes(contentLocale); const content = { __html: statusContent ?? getStatusContent(status) }; const language = status.getIn(['translation', 'language']) || status.get('language'); @@ -275,7 +305,7 @@ class StatusContent extends PureComponent { ); - const translateButton = renderTranslate && ( + const translateButton = this.state.showTranslateButton && ( ); @@ -306,8 +336,7 @@ class StatusContent extends PureComponent { ); } - } - + }; } export default withRouter(withIdentity(connect(mapStateToProps)(injectIntl(StatusContent)))); From 1f3e5b9324b380a88e43308b2c5143df0f6e022a Mon Sep 17 00:00:00 2001 From: Thomas Steiner Date: Fri, 18 Jul 2025 15:06:04 +0200 Subject: [PATCH 4/4] Also allow private and direct statuses to be translated --- app/javascript/mastodon/components/status_content.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/javascript/mastodon/components/status_content.jsx b/app/javascript/mastodon/components/status_content.jsx index 46f8c1a8655..d9ecb79a26c 100644 --- a/app/javascript/mastodon/components/status_content.jsx +++ b/app/javascript/mastodon/components/status_content.jsx @@ -180,10 +180,12 @@ class StatusContent extends PureComponent { const contentLocale = intl.locale.replace(/[_-].*/, ''); const targetLanguages = languages?.get(status.get('language') || 'und'); + // The Translator API translates all locally on the client, so private and direct toots are fine to translate. + const allowedVisibilities = supportsTranslator ? ['public', 'unlisted', 'private', 'direct'] : ['public', 'unlisted']; const shouldAttemptTranslate = this.props.onTranslate && this.props.identity.signedIn && - ['public', 'unlisted'].includes(status.get('visibility')) && + allowedVisibilities.includes(status.get('visibility')) && status.get('search_index').trim().length > 0; if (!shouldAttemptTranslate) return;