Add warning when selected and detected language is different in web UI (#33042)
This commit is contained in:
		@@ -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 })}
 | 
			
		||||
        >
 | 
			
		||||
          <Icon icon={TranslateIcon} />
 | 
			
		||||
          <span className='dropdown-button__label'>{current[2] ?? value}</span>
 | 
			
		||||
@@ -306,6 +311,7 @@ class LanguageDropdown extends PureComponent {
 | 
			
		||||
              <div className={`dropdown-animation language-dropdown__dropdown ${placement}`} >
 | 
			
		||||
                <LanguageDropdownMenu
 | 
			
		||||
                  value={value}
 | 
			
		||||
                  guess={guess}
 | 
			
		||||
                  frequentlyUsedLanguages={frequentlyUsedLanguages}
 | 
			
		||||
                  onClose={this.handleClose}
 | 
			
		||||
                  onChange={this.handleChange}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,8 @@ import { createSelector } from '@reduxjs/toolkit';
 | 
			
		||||
import { Map as ImmutableMap } from 'immutable';
 | 
			
		||||
import { connect } from 'react-redux';
 | 
			
		||||
 | 
			
		||||
import lande from 'lande';
 | 
			
		||||
import { debounce } from 'lodash';
 | 
			
		||||
 | 
			
		||||
import { changeComposeLanguage } from 'mastodon/actions/compose';
 | 
			
		||||
 | 
			
		||||
@@ -16,9 +18,80 @@ const getFrequentlyUsedLanguages = createSelector([
 | 
			
		||||
    .toArray()
 | 
			
		||||
));
 | 
			
		||||
 | 
			
		||||
const ISO_639_MAP = {
 | 
			
		||||
  afr: 'af', // Afrikaans
 | 
			
		||||
  ara: 'ar', // Arabic
 | 
			
		||||
  aze: 'az', // Azerbaijani
 | 
			
		||||
  bel: 'be', // Belarusian
 | 
			
		||||
  ben: 'bn', // Bengali
 | 
			
		||||
  bul: 'bg', // Bulgarian
 | 
			
		||||
  cat: 'ca', // Catalan
 | 
			
		||||
  ces: 'cs', // Czech
 | 
			
		||||
  ckb: 'ku', // Kurdish
 | 
			
		||||
  cmn: 'zh', // Mandarin
 | 
			
		||||
  dan: 'da', // Danish
 | 
			
		||||
  deu: 'de', // German
 | 
			
		||||
  ell: 'el', // Greek
 | 
			
		||||
  eng: 'en', // English
 | 
			
		||||
  est: 'et', // Estonian
 | 
			
		||||
  eus: 'eu', // Basque
 | 
			
		||||
  fin: 'fi', // Finnish
 | 
			
		||||
  fra: 'fr', // French
 | 
			
		||||
  hau: 'ha', // Hausa
 | 
			
		||||
  heb: 'he', // Hebrew
 | 
			
		||||
  hin: 'hi', // Hindi
 | 
			
		||||
  hrv: 'hr', // Croatian
 | 
			
		||||
  hun: 'hu', // Hungarian
 | 
			
		||||
  hye: 'hy', // Armenian
 | 
			
		||||
  ind: 'id', // Indonesian
 | 
			
		||||
  isl: 'is', // Icelandic
 | 
			
		||||
  ita: 'it', // Italian
 | 
			
		||||
  jpn: 'ja', // Japanese
 | 
			
		||||
  kat: 'ka', // Georgian
 | 
			
		||||
  kaz: 'kk', // Kazakh
 | 
			
		||||
  kor: 'ko', // Korean
 | 
			
		||||
  lit: 'lt', // Lithuanian
 | 
			
		||||
  mar: 'mr', // Marathi
 | 
			
		||||
  mkd: 'mk', // Macedonian
 | 
			
		||||
  nld: 'nl', // Dutch
 | 
			
		||||
  nob: 'no', // Norwegian
 | 
			
		||||
  pes: 'fa', // Persian
 | 
			
		||||
  pol: 'pl', // Polish
 | 
			
		||||
  por: 'pt', // Portuguese
 | 
			
		||||
  ron: 'ro', // Romanian
 | 
			
		||||
  run: 'rn', // Rundi
 | 
			
		||||
  rus: 'ru', // Russian
 | 
			
		||||
  slk: 'sk', // Slovak
 | 
			
		||||
  spa: 'es', // Spanish
 | 
			
		||||
  srp: 'sr', // Serbian
 | 
			
		||||
  swe: 'sv', // Swedish
 | 
			
		||||
  tgl: 'tl', // Tagalog
 | 
			
		||||
  tur: 'tr', // Turkish
 | 
			
		||||
  ukr: 'uk', // Ukrainian
 | 
			
		||||
  vie: 'vi', // Vietnamese
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const debouncedLande = debounce((text) => 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 => ({
 | 
			
		||||
 
 | 
			
		||||
@@ -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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -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 = {
 | 
			
		||||
 
 | 
			
		||||
@@ -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",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										17
									
								
								yarn.lock
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								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"
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user