# All files / src/components/features LanguageSelector.tsx

0% Statements 0/13
0% Branches 0/10
0% Functions 0/10
0% Lines 0/13

Press n or j to go to the next uncovered block, b, p or k for the previous block.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136

     import { ArrowLeftRight, Zap, Brain } from 'lucide-react';
    import { LANGUAGE_CODES, TRANSLATION_ENGINES } from '../../api/types';
    import { useTranslation } from 'react-i18next';

    interface LanguageSelectorProps {
      sourceLang: string;
      targetLang: string;
      onSourceChange: (lang: string) => void;
      onTargetChange: (lang: string) => void;
      onSwap: () => void;
      engine?: string;
      onEngineChange?: (engine: string) => void;
      mode?: string;
      onModeChange?: (mode: string) => void;
    }

    function LanguageSelector({
      sourceLang, targetLang, onSourceChange, onTargetChange, onSwap,
      engine, onEngineChange, mode, onModeChange,
    }: LanguageSelectorProps) {
      const { t } = useTranslation();
      const modeOptions = [
        { value: 'fast', label: t('languageSelector.fast'), icon: Zap },
        { value: 'expert', label: t('languageSelector.expert'), icon: Brain },
      ];

      return (
        <div className="w-full bg-white dark:bg-gray-50 border-b border-gradient-b">
          <div className="max-w-7xl mx-auto px-6 flex items-center justify-between h-14">
            {/* Left: Language selectors + swap */}
            <div className="flex items-center gap-3 flex-1 min-w-0">
              {/* Source language */}
              <select
                value={sourceLang}
                onChange={e => onSourceChange(e.target.value)}
                className="
                  appearance-none bg-transparent text-text-primary text-[14px] font-medium
                  border border-border rounded-input px-3 py-1.5
                  focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent/20
                  cursor-pointer hover:bg-surface-secondary transition-colors
                  min-w-28
                "
              >
                {LANGUAGE_CODES.map(code => (
                  <option key={code} value={code}>{t(`common.languages.${code}`)}</option>
                ))}
              </select>

              {/* Swap button */}
              <button
                onClick={onSwap}
                className={`
                  flex-shrink-0 w-9 h-9 rounded-full flex items-center justify-center
                  transition-all duration-200
                  ${sourceLang === 'auto'
                    ? 'text-gray-300 dark:text-gray-600 cursor-not-allowed'
                    : 'text-text-tertiary hover:text-accent hover:bg-accent-bg hover:scale-110'
                  }
                `}
                title={t('languageSelector.switch')}
                disabled={sourceLang === 'auto'}
              >
                <ArrowLeftRight className="w-4 h-4" />
              </button>

              {/* Target language */}
              <select
                value={targetLang}
                onChange={e => onTargetChange(e.target.value)}
                className="
                  appearance-none bg-transparent text-text-primary text-[14px] font-medium
                  border border-border rounded-input px-3 py-1.5
                  focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent/20
                  cursor-pointer hover:bg-surface-secondary transition-colors
                  min-w-28
                "
              >
                {LANGUAGE_CODES.filter(c => c !== 'auto').map(code => (
                  <option key={code} value={code}>{t(`common.languages.${code}`)}</option>
                ))}
              </select>
            </div>

            {/* Right: Engine + Mode */}
            <div className="flex items-center gap-3 flex-shrink-0">
              {/* Engine selector */}
              {onEngineChange && engine !== undefined && (
                <select
                  value={engine}
                  onChange={e => onEngineChange(e.target.value)}
                  className="
                    appearance-none bg-surface-secondary text-text-secondary text-[13px]
                    border border-border rounded-input px-3 py-1.5
                    focus:outline-none focus:border-accent
                    cursor-pointer hover:text-text-primary transition-colors
                  "
                >
                  {TRANSLATION_ENGINES.map(eng => (
                    <option key={eng.value} value={eng.value}>{t(`engines.${eng.label}`, { defaultValue: eng.label })}</option>
                  ))}
                </select>
              )}

              {/* Mode toggle */}
              {onModeChange && mode !== undefined && (
                <div className="inline-flex items-center gap-0.5 bg-surface-secondary rounded-button p-0.5">
                  {modeOptions.map(m => {
                    const Icon = m.icon;
                    return (
                      <button
                        key={m.value}
                        onClick={() => onModeChange(m.value)}
                        className={`
                          inline-flex items-center gap-1.5 px-3 py-1.5 text-[12px] font-semibold
                          rounded-button transition-all duration-200
                          ${mode === m.value
                            ? 'bg-white dark:bg-gray-50 text-text-primary shadow-subtle'
                            : 'text-text-secondary hover:text-text-primary'
                          }
                        `}
                      >
                        <Icon className="w-3.5 h-3.5" />
                        {m.label}
                      </button>
                    );
                  })}
                </div>
              )}
            </div>
          </div>
        </div>
      );
    }

    export { LanguageSelector };