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 };
|