From 8962f1157816848b1eff8ee992410c120d4696e8 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 20 Jan 2025 10:17:13 +0100 Subject: [PATCH] Add warning when selected and detected language is different in web UI (#33042) --- .../compose/components/language_dropdown.jsx | 16 ++-- .../containers/language_dropdown_container.js | 73 +++++++++++++++++++ .../styles/mastodon/components.scss | 10 +++ config/webpack/rules/babel.js | 2 +- package.json | 1 + yarn.lock | 17 +++++ 6 files changed, 113 insertions(+), 6 deletions(-) diff --git a/app/javascript/mastodon/features/compose/components/language_dropdown.jsx b/app/javascript/mastodon/features/compose/components/language_dropdown.jsx index b164a07cbd..20fba29ecb 100644 --- a/app/javascript/mastodon/features/compose/components/language_dropdown.jsx +++ b/app/javascript/mastodon/features/compose/components/language_dropdown.jsx @@ -27,6 +27,7 @@ class LanguageDropdownMenu extends PureComponent { static propTypes = { value: PropTypes.string.isRequired, + guess: PropTypes.string, frequentlyUsedLanguages: PropTypes.arrayOf(PropTypes.string).isRequired, onClose: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired, @@ -81,14 +82,17 @@ class LanguageDropdownMenu extends PureComponent { }; search () { - const { languages, value, frequentlyUsedLanguages } = this.props; + const { languages, value, frequentlyUsedLanguages, guess } = this.props; const { searchValue } = this.state; if (searchValue === '') { return [...languages].sort((a, b) => { - // Push current selection to the top of the list - if (a[0] === value) { + if (guess && a[0] === guess) { // Push guessed language higher than current selection + return -1; + } else if (guess && b[0] === guess) { + return 1; + } else if (a[0] === value) { // Push current selection to the top of the list return -1; } else if (b[0] === value) { return 1; @@ -238,6 +242,7 @@ class LanguageDropdown extends PureComponent { static propTypes = { value: PropTypes.string, frequentlyUsedLanguages: PropTypes.arrayOf(PropTypes.string), + guess: PropTypes.string, intl: PropTypes.object.isRequired, onChange: PropTypes.func, }; @@ -281,7 +286,7 @@ class LanguageDropdown extends PureComponent { }; render () { - const { value, intl, frequentlyUsedLanguages } = this.props; + const { value, guess, intl, frequentlyUsedLanguages } = this.props; const { open, placement } = this.state; const current = preloadedLanguages.find(lang => lang[0] === value) ?? []; @@ -294,7 +299,7 @@ class LanguageDropdown extends PureComponent { onClick={this.handleToggle} onMouseDown={this.handleMouseDown} onKeyDown={this.handleButtonKeyDown} - className={classNames('dropdown-button', { active: open })} + className={classNames('dropdown-button', { active: open, warning: guess !== '' && guess !== value })} > {current[2] ?? value} @@ -306,6 +311,7 @@ class LanguageDropdown extends PureComponent {
lande(text), 500, { trailing: true }); + +const detectedLanguage = createSelector([ + state => state.getIn(['compose', 'text']), +], text => { + if (text.length > 20) { + const guesses = debouncedLande(text); + const [lang, confidence] = guesses[0]; + + if (confidence > 0.8) { + return ISO_639_MAP[lang]; + } + } + + return ''; +}); + const mapStateToProps = state => ({ frequentlyUsedLanguages: getFrequentlyUsedLanguages(state), value: state.getIn(['compose', 'language']), + guess: detectedLanguage(state), }); const mapDispatchToProps = dispatch => ({ diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 072e7cb4a2..7d23428595 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -939,6 +939,16 @@ body > [data-popper-placement] { border-color: $ui-highlight-color; color: $primary-text-color; } + + &.warning { + border-color: var(--goldenrod-2); + color: var(--goldenrod-2); + + &.active { + background-color: var(--goldenrod-2); + color: var(--indigo-1); + } + } } .character-counter { diff --git a/config/webpack/rules/babel.js b/config/webpack/rules/babel.js index 902b823e1f..f1b53c3606 100644 --- a/config/webpack/rules/babel.js +++ b/config/webpack/rules/babel.js @@ -4,7 +4,7 @@ const { env, settings } = require('../configuration'); // Those modules contain modern ES code that need to be transpiled for Webpack to process it const nodeModulesToProcess = [ - '@reduxjs', 'fuzzysort' + '@reduxjs', 'fuzzysort', 'toygrad' ]; module.exports = { diff --git a/package.json b/package.json index 6cb314db34..c0f48d5dc4 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "imports-loader": "^1.2.0", "intl-messageformat": "^10.3.5", "js-yaml": "^4.1.0", + "lande": "^1.0.10", "lodash": "^4.17.21", "mark-loader": "^0.1.6", "marky": "^1.2.5", diff --git a/yarn.lock b/yarn.lock index 9293af2f24..08a2558986 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2912,6 +2912,7 @@ __metadata: jest: "npm:^29.5.0" jest-environment-jsdom: "npm:^29.5.0" js-yaml: "npm:^4.1.0" + lande: "npm:^1.0.10" lint-staged: "npm:^15.0.0" lodash: "npm:^4.17.21" mark-loader: "npm:^0.1.6" @@ -11456,6 +11457,15 @@ __metadata: languageName: node linkType: hard +"lande@npm:^1.0.10": + version: 1.0.10 + resolution: "lande@npm:1.0.10" + dependencies: + toygrad: "npm:^2.6.0" + checksum: 10c0/27300be5937b6b9e245a7ea7a8216a0dcf5286a3b7ae38886c10c5c75b83fbfa1a69cd6754ab26bb38c6bd18aa8a2dcb62dea873506accb245cf82084acfee71 + languageName: node + linkType: hard + "language-subtag-registry@npm:^0.3.20": version: 0.3.22 resolution: "language-subtag-registry@npm:0.3.22" @@ -17289,6 +17299,13 @@ __metadata: languageName: node linkType: hard +"toygrad@npm:^2.6.0": + version: 2.6.0 + resolution: "toygrad@npm:2.6.0" + checksum: 10c0/96e42ced87431e99cec7d9b446c7827fe7782c2fd82bb5fc8c4a0855679011d809f9967096a60b4c8ceca867a29f1aadd62af447bdb652cb6f7fee279ae743ed + languageName: node + linkType: hard + "tr46@npm:^1.0.1": version: 1.0.1 resolution: "tr46@npm:1.0.1"