From 429ab8d21055aa25d8c7d92c140c7f1fd5db27ce Mon Sep 17 00:00:00 2001 From: Thomas Steiner Date: Sat, 21 Jun 2025 13:19:04 +0200 Subject: [PATCH 1/4] Add language detection to compose widget --- .../compose/components/compose_form.jsx | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/app/javascript/mastodon/features/compose/components/compose_form.jsx b/app/javascript/mastodon/features/compose/components/compose_form.jsx index 3611a74b4f..1ee9bbb692 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.jsx +++ b/app/javascript/mastodon/features/compose/components/compose_form.jsx @@ -11,6 +11,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import { length } from 'stringz'; import { missingAltTextModal } from 'mastodon/initial_state'; +import { changeComposeLanguage } from 'mastodon/actions/compose'; import AutosuggestInput from '../../../components/autosuggest_input'; import AutosuggestTextarea from '../../../components/autosuggest_textarea'; @@ -31,6 +32,8 @@ import { ReplyIndicator } from './reply_indicator'; import { UploadForm } from './upload_form'; import { Warning } from './warning'; +import { connect } from 'react-redux'; + const allowedAroundShortCode = '><\u0085\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\u0009\u000a\u000b\u000c\u000d'; const messages = defineMessages({ @@ -41,6 +44,28 @@ const messages = defineMessages({ reply: { id: 'compose_form.reply', defaultMessage: 'Reply' }, }); +const mapStateToProps = (state) => ({ + currentLanguage: state.compose.get('language'), +}); + +const languageDetectorInGlobalThis = 'LanguageDetector' in globalThis; +let supportsLanguageDetector = languageDetectorInGlobalThis && await globalThis.LanguageDetector.availability() === 'available'; +let languageDetector; +// If the API is supported, but the model not loaded yet… +if (languageDetectorInGlobalThis && !supportsLanguageDetector) { + // …trigger the model download + LanguageDetector.create().then((_languageDetector) => { + supportsLanguageDetector = true + languageDetector = _languageDetector + }) +} + +function countLetters(text) { + const segmenter = new Intl.Segmenter('und', { granularity: 'grapheme' }) + const letters = [...segmenter.segment(text)] + return letters.length +} + class ComposeForm extends ImmutablePureComponent { static propTypes = { intl: PropTypes.object.isRequired, @@ -97,6 +122,30 @@ class ComposeForm extends ImmutablePureComponent { } }; + handleKeyUp = async (e) => { + if (!supportsLanguageDetector) { + return; + } + if (!languageDetector) { + languageDetector = await globalThis.LanguageDetector.create(); + } + const text = this.getFulltextForCharacterCounting(); + const currentLanguage = this.props.currentLanguage; + if (!text || countLetters(text) <= 5) { + this.props.dispatch(changeComposeLanguage(currentLanguage)); + return; + } + + try { + let detectedLanguage = (await languageDetector.detect(text))[0].detectedLanguage + detectedLanguage = detectedLanguage === 'und' ? currentLanguage : detectedLanguage.substring(0, 2); + this.props.dispatch(changeComposeLanguage(detectedLanguage)); + } + catch { + this.props.dispatch(changeComposeLanguage(currentLanguage)); + } + } + getFulltextForCharacterCounting = () => { return [this.props.spoiler? this.props.spoilerText: '', countableText(this.props.text)].join(''); }; @@ -274,6 +323,7 @@ class ComposeForm extends ImmutablePureComponent { suggestions={this.props.suggestions} onFocus={this.handleFocus} onKeyDown={this.handleKeyDown} + onKeyUp={this.handleKeyUp} onSuggestionsFetchRequested={this.onSuggestionsFetchRequested} onSuggestionsClearRequested={this.onSuggestionsClearRequested} onSuggestionSelected={this.onSuggestionSelected} @@ -318,4 +368,4 @@ class ComposeForm extends ImmutablePureComponent { } -export default injectIntl(ComposeForm); +export default injectIntl(connect(mapStateToProps)(ComposeForm)); From c50cf3b5444d62b176eb7e708fe34562c38dd402 Mon Sep 17 00:00:00 2001 From: Thomas Steiner Date: Sat, 21 Jun 2025 13:27:03 +0200 Subject: [PATCH 2/4] Use the UI language as default compose language --- .../mastodon/features/compose/components/compose_form.jsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/javascript/mastodon/features/compose/components/compose_form.jsx b/app/javascript/mastodon/features/compose/components/compose_form.jsx index 1ee9bbb692..b65b544607 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.jsx +++ b/app/javascript/mastodon/features/compose/components/compose_form.jsx @@ -45,7 +45,7 @@ const messages = defineMessages({ }); const mapStateToProps = (state) => ({ - currentLanguage: state.compose.get('language'), + currentLanguage: state.meta.get('locale'), }); const languageDetectorInGlobalThis = 'LanguageDetector' in globalThis; @@ -129,9 +129,10 @@ class ComposeForm extends ImmutablePureComponent { if (!languageDetector) { languageDetector = await globalThis.LanguageDetector.create(); } - const text = this.getFulltextForCharacterCounting(); + const text = this.getFulltextForCharacterCounting().trim(); const currentLanguage = this.props.currentLanguage; if (!text || countLetters(text) <= 5) { + console.log('hier', currentLanguage) this.props.dispatch(changeComposeLanguage(currentLanguage)); return; } From 0733590c3b29e0aaef6aae96f67579e6ff4b26ab Mon Sep 17 00:00:00 2001 From: Thomas Steiner Date: Sat, 21 Jun 2025 13:28:29 +0200 Subject: [PATCH 3/4] Remove log --- .../mastodon/features/compose/components/compose_form.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/app/javascript/mastodon/features/compose/components/compose_form.jsx b/app/javascript/mastodon/features/compose/components/compose_form.jsx index b65b544607..bb45cecb3a 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.jsx +++ b/app/javascript/mastodon/features/compose/components/compose_form.jsx @@ -132,7 +132,6 @@ class ComposeForm extends ImmutablePureComponent { const text = this.getFulltextForCharacterCounting().trim(); const currentLanguage = this.props.currentLanguage; if (!text || countLetters(text) <= 5) { - console.log('hier', currentLanguage) this.props.dispatch(changeComposeLanguage(currentLanguage)); return; } From 3e1e6762aa7c35e0bb193d65f108a1f1733bcc16 Mon Sep 17 00:00:00 2001 From: Thomas Steiner Date: Fri, 11 Jul 2025 08:37:44 +0200 Subject: [PATCH 4/4] Debounce language detection --- .../features/compose/components/compose_form.jsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/javascript/mastodon/features/compose/components/compose_form.jsx b/app/javascript/mastodon/features/compose/components/compose_form.jsx index bb45cecb3a..61c7521156 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.jsx +++ b/app/javascript/mastodon/features/compose/components/compose_form.jsx @@ -10,6 +10,8 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import { length } from 'stringz'; +import debounce from 'lodash.debounce'; + import { missingAltTextModal } from 'mastodon/initial_state'; import { changeComposeLanguage } from 'mastodon/actions/compose'; @@ -110,6 +112,7 @@ class ComposeForm extends ImmutablePureComponent { constructor(props) { super(props); this.textareaRef = createRef(null); + this.debouncedHandleKeyUp = debounce(this._handleKeyUp.bind(this), 500); } handleChange = (e) => { @@ -122,7 +125,11 @@ class ComposeForm extends ImmutablePureComponent { } }; - handleKeyUp = async (e) => { + handleKeyUp = (e) => { + this.debouncedHandleKeyUp(e); + } + + _handleKeyUp = async (e) => { if (!supportsLanguageDetector) { return; } @@ -211,6 +218,7 @@ class ComposeForm extends ImmutablePureComponent { componentWillUnmount () { if (this.timeout) clearTimeout(this.timeout); + this.debouncedHandleKeyUp.cancel(); } componentDidUpdate (prevProps) {