i18n: translate the CW Keyer settings panel + drop the serial blurb
The whole "Manipulateur CW" settings panel was hardcoded in English, so it stayed English even with the app in French. Added ~35 wk.* keys (EN + FR) and wired every label/checkbox/select/placeholder through t(). Also removed the long serial-engine explanation paragraph as requested.
This commit is contained in:
@@ -3021,47 +3021,45 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
|
||||
<div className="space-y-4 max-w-2xl">
|
||||
<label className="flex items-center gap-2 text-sm cursor-pointer">
|
||||
<Checkbox checked={wk.enabled} onCheckedChange={(c) => setWkField({ enabled: !!c })} />
|
||||
Enable CW keyer (shows the keyer panel)
|
||||
{t('wk.enable')}
|
||||
</label>
|
||||
|
||||
<div className="grid grid-cols-4 gap-3 items-end">
|
||||
<div className="space-y-1">
|
||||
<Label>Keyer engine</Label>
|
||||
<Label>{t('wk.engine')}</Label>
|
||||
{/* Picking the serial engine defaults PTT-keying ON: without RTS held,
|
||||
the rig drops to RX between words (semi-break-in). */}
|
||||
<Select value={wk.engine} onValueChange={(v) => setWkField(v === 'serial' ? { engine: v, use_ptt: true } : { engine: v })}>
|
||||
<SelectTrigger className="h-8"><SelectValue /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="winkeyer">WinKeyer (K1EL, serial)</SelectItem>
|
||||
<SelectItem value="serial">Serial port (DTR=CW / RTS=PTT)</SelectItem>
|
||||
<SelectItem value="icom">Icom CI-V (rig keyer)</SelectItem>
|
||||
<SelectItem value="flex">FlexRadio (CWX)</SelectItem>
|
||||
<SelectItem value="tci" disabled>TCI (coming soon)</SelectItem>
|
||||
<SelectItem value="winkeyer">{t('wk.engWinkeyer')}</SelectItem>
|
||||
<SelectItem value="serial">{t('wk.engSerial')}</SelectItem>
|
||||
<SelectItem value="icom">{t('wk.engIcom')}</SelectItem>
|
||||
<SelectItem value="flex">{t('wk.engFlex')}</SelectItem>
|
||||
<SelectItem value="tci" disabled>{t('wk.engTci')}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<label className="flex items-center gap-2 text-sm cursor-pointer col-span-3 pb-1.5">
|
||||
<Checkbox checked={wk.esc_clears_call} onCheckedChange={(c) => setWkField({ esc_clears_call: !!c })} />
|
||||
ESC clears the callsign too (otherwise ESC only stops transmission)
|
||||
{t('wk.escClears')}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{wk.engine === 'icom' ? (
|
||||
<>
|
||||
<p className="text-xs text-muted-foreground -mt-2">
|
||||
Icom CI-V keys CW through the rig's own keyer over the existing CAT connection (command 0x17) — it reuses the CAT COM port set in Settings → CAT, so there's nothing else to wire up here. Put the rig in CW mode. Weight, ratio, sidetone, paddle mode… are configured on the radio; only the speed is set from here (the rig's KEY SPEED).
|
||||
{t('wk.icomNote')}
|
||||
</p>
|
||||
{(!catCfg.enabled || catCfg.backend !== 'icom') && (
|
||||
<p className="text-xs font-medium text-warning -mt-1 flex items-start gap-1.5">
|
||||
<span aria-hidden>⚠</span>
|
||||
<span>
|
||||
Your CAT backend is set to <strong>{catCfg.enabled ? (catCfg.backend || 'none') : 'disabled'}</strong>. Icom CI-V CW needs the CAT backend set to <strong>Icom</strong> and connected — change it under Settings → CAT interface, otherwise sending CW will fail.
|
||||
</span>
|
||||
<span>{t('wk.catWarnIcom', { backend: catCfg.enabled ? (catCfg.backend || 'none') : 'disabled' })}</span>
|
||||
</p>
|
||||
)}
|
||||
<div className="grid grid-cols-4 gap-3">
|
||||
<div className="space-y-1">
|
||||
<Label>Speed (WPM)</Label>
|
||||
<Label>{t('wk.speed')}</Label>
|
||||
<Input type="number" min={6} max={48} value={wk.wpm} onChange={(e) => setWkField({ wpm: num(e.target.value, 25) })} className="font-mono" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -3069,103 +3067,98 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
|
||||
) : wk.engine === 'flex' ? (
|
||||
<>
|
||||
<p className="text-xs text-muted-foreground -mt-2">
|
||||
FlexRadio keys CW through the radio's <strong>CWX</strong> keyer over the existing SmartSDR CAT connection — no WinKeyer or SmartCAT needed. It reuses the connection set in Settings → CAT, so there's nothing else to wire up here. Put a slice in CW mode. Only the speed is set from here; weight, sidetone and break-in are configured on the radio (break-in must be on for CW to actually transmit).
|
||||
{t('wk.flexNote')}
|
||||
</p>
|
||||
{(!catCfg.enabled || catCfg.backend !== 'flex') && (
|
||||
<p className="text-xs font-medium text-warning -mt-1 flex items-start gap-1.5">
|
||||
<span aria-hidden>⚠</span>
|
||||
<span>
|
||||
Your CAT backend is set to <strong>{catCfg.enabled ? (catCfg.backend || 'none') : 'disabled'}</strong>. Flex CWX needs the CAT backend set to <strong>FlexRadio</strong> and connected — change it under Settings → CAT interface, otherwise sending CW will fail.
|
||||
</span>
|
||||
<span>{t('wk.catWarnFlex', { backend: catCfg.enabled ? (catCfg.backend || 'none') : 'disabled' })}</span>
|
||||
</p>
|
||||
)}
|
||||
<div className="grid grid-cols-4 gap-3">
|
||||
<div className="space-y-1">
|
||||
<Label>Speed (WPM)</Label>
|
||||
<Label>{t('wk.speed')}</Label>
|
||||
<Input type="number" min={6} max={48} value={wk.wpm} onChange={(e) => setWkField({ wpm: num(e.target.value, 25) })} className="font-mono" />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : wk.engine === 'serial' ? (
|
||||
<>
|
||||
<p className="text-xs text-muted-foreground -mt-2">
|
||||
OpsLog keys the Morse itself by toggling a serial control line — the "hardware CW keying" a Yaesu <strong>SCU-17</strong> and generic interfaces use (like N1MM: DTR = CW, RTS = PTT). No WinKeyer chip. Put the rig in CW mode and pick the interface's <strong>keying COM port</strong> below (this is the CW/PTT port, <em>not</em> the CAT port). Speed, Farnsworth, lead-in/tail and PTT apply; paddle mode, ratio and sidetone are WinKeyer-only.
|
||||
</p>
|
||||
<div className="grid grid-cols-4 gap-3">
|
||||
<div className="space-y-1 col-span-2">
|
||||
<Label>Keying COM port</Label>
|
||||
<Label>{t('wk.keyingPort')}</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
<Select value={wk.port || '_'} onValueChange={(v) => setWkField({ port: v === '_' ? '' : v })}>
|
||||
<SelectTrigger className="h-8 flex-1"><SelectValue placeholder="— COM port —" /></SelectTrigger>
|
||||
<SelectTrigger className="h-8 flex-1"><SelectValue placeholder={t('wk.comPort')} /></SelectTrigger>
|
||||
<SelectContent>
|
||||
{wkPorts.length === 0 && <SelectItem value="_" disabled>No ports found</SelectItem>}
|
||||
{wkPorts.length === 0 && <SelectItem value="_" disabled>{t('wk.noPorts')}</SelectItem>}
|
||||
{wkPorts.map((p) => <SelectItem key={p} value={p}>{p}</SelectItem>)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button variant="ghost" size="sm" className="h-8 px-2" title="Reload ports"
|
||||
<Button variant="ghost" size="sm" className="h-8 px-2" title={t('wk.reloadPorts')}
|
||||
onClick={() => ListSerialPorts().then((p) => setWkPorts((p ?? []) as string[])).catch(() => {})}>
|
||||
<ArrowDown className="size-3.5 rotate-90" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label>CW key line</Label>
|
||||
<Label>{t('wk.cwKeyLine')}</Label>
|
||||
<Select value={wk.cw_key_line === 'rts' ? 'rts' : 'dtr'} onValueChange={(v) => setWkField({ cw_key_line: v })}>
|
||||
<SelectTrigger className="h-8"><SelectValue /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="dtr">DTR = CW, RTS = PTT</SelectItem>
|
||||
<SelectItem value="rts">RTS = CW, DTR = PTT</SelectItem>
|
||||
<SelectItem value="dtr">{t('wk.lineDtr')}</SelectItem>
|
||||
<SelectItem value="rts">{t('wk.lineRts')}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label>Speed (WPM)</Label>
|
||||
<Label>{t('wk.speed')}</Label>
|
||||
<Input type="number" min={5} max={99} value={wk.wpm} onChange={(e) => setWkField({ wpm: num(e.target.value, 25) })} className="font-mono" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-4 gap-3">
|
||||
<div className="space-y-1">
|
||||
<Label>Farnsworth</Label>
|
||||
<Label>{t('wk.farnsworth')}</Label>
|
||||
<Input type="number" min={0} max={99} value={wk.farnsworth} onChange={(e) => setWkField({ farnsworth: num(e.target.value, 0) })} className="font-mono" />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label>Lead-in (ms)</Label>
|
||||
<Label>{t('wk.leadIn')}</Label>
|
||||
<Input type="number" min={0} value={wk.lead_in_ms} onChange={(e) => setWkField({ lead_in_ms: num(e.target.value, 10) })} className="font-mono" />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label>Tail (ms)</Label>
|
||||
<Label>{t('wk.tail')}</Label>
|
||||
<Input type="number" min={0} value={wk.tail_ms} onChange={(e) => setWkField({ tail_ms: num(e.target.value, 50) })} className="font-mono" />
|
||||
</div>
|
||||
<label className="flex items-center gap-2 text-sm cursor-pointer pb-1.5 self-end">
|
||||
<Checkbox checked={wk.use_ptt} onCheckedChange={(c) => setWkField({ use_ptt: !!c })} /> Key PTT line
|
||||
<Checkbox checked={wk.use_ptt} onCheckedChange={(c) => setWkField({ use_ptt: !!c })} /> {t('wk.keyPtt')}
|
||||
</label>
|
||||
</div>
|
||||
<label className="flex items-center gap-2 text-sm cursor-pointer">
|
||||
<Checkbox checked={wk.cw_invert} onCheckedChange={(c) => setWkField({ cw_invert: !!c })} />
|
||||
Invert keying (active-LOW) — tick this only if the rig transmits at rest / keys backwards
|
||||
{t('wk.invert')}
|
||||
</label>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="grid grid-cols-4 gap-3">
|
||||
<div className="space-y-1 col-span-2">
|
||||
<Label>Serial port</Label>
|
||||
<Label>{t('wk.serialPort')}</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
<Select value={wk.port || '_'} onValueChange={(v) => setWkField({ port: v === '_' ? '' : v })}>
|
||||
<SelectTrigger className="h-8 flex-1"><SelectValue placeholder="— COM port —" /></SelectTrigger>
|
||||
<SelectTrigger className="h-8 flex-1"><SelectValue placeholder={t('wk.comPort')} /></SelectTrigger>
|
||||
<SelectContent>
|
||||
{wkPorts.length === 0 && <SelectItem value="_" disabled>No ports found</SelectItem>}
|
||||
{wkPorts.length === 0 && <SelectItem value="_" disabled>{t('wk.noPorts')}</SelectItem>}
|
||||
{wkPorts.map((p) => <SelectItem key={p} value={p}>{p}</SelectItem>)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button variant="ghost" size="sm" className="h-8 px-2" title="Reload ports"
|
||||
<Button variant="ghost" size="sm" className="h-8 px-2" title={t('wk.reloadPorts')}
|
||||
onClick={() => ListSerialPorts().then((p) => setWkPorts((p ?? []) as string[])).catch(() => {})}>
|
||||
<ArrowDown className="size-3.5 rotate-90" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label>Baud</Label>
|
||||
<Label>{t('wk.baud')}</Label>
|
||||
<Select value={String(wk.baud)} onValueChange={(v) => setWkField({ baud: num(v, 1200) })}>
|
||||
<SelectTrigger className="h-8"><SelectValue /></SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -3174,38 +3167,38 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label>Speed (WPM)</Label>
|
||||
<Label>{t('wk.speed')}</Label>
|
||||
<Input type="number" min={5} max={99} value={wk.wpm} onChange={(e) => setWkField({ wpm: num(e.target.value, 25) })} className="font-mono" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-4 gap-3">
|
||||
<div className="space-y-1">
|
||||
<Label>Weight</Label>
|
||||
<Label>{t('wk.weight')}</Label>
|
||||
<Input type="number" min={10} max={90} value={wk.weight} onChange={(e) => setWkField({ weight: num(e.target.value, 50) })} className="font-mono" />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label>Lead-in (ms)</Label>
|
||||
<Label>{t('wk.leadIn')}</Label>
|
||||
<Input type="number" min={0} value={wk.lead_in_ms} onChange={(e) => setWkField({ lead_in_ms: num(e.target.value, 10) })} className="font-mono" />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label>Tail (ms)</Label>
|
||||
<Label>{t('wk.tail')}</Label>
|
||||
<Input type="number" min={0} value={wk.tail_ms} onChange={(e) => setWkField({ tail_ms: num(e.target.value, 50) })} className="font-mono" />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label>Ratio (33-66)</Label>
|
||||
<Label>{t('wk.ratio')}</Label>
|
||||
<Input type="number" min={33} max={66} value={wk.ratio} onChange={(e) => setWkField({ ratio: num(e.target.value, 50) })} className="font-mono" />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label>Farnsworth</Label>
|
||||
<Label>{t('wk.farnsworth')}</Label>
|
||||
<Input type="number" min={0} max={99} value={wk.farnsworth} onChange={(e) => setWkField({ farnsworth: num(e.target.value, 0) })} className="font-mono" />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label>Sidetone (Hz)</Label>
|
||||
<Label>{t('wk.sidetone')}</Label>
|
||||
<Input type="number" min={0} value={wk.sidetone_hz} onChange={(e) => setWkField({ sidetone_hz: num(e.target.value, 600) })} className="font-mono" />
|
||||
</div>
|
||||
<div className="space-y-1 col-span-2">
|
||||
<Label>Paddle mode</Label>
|
||||
<Label>{t('wk.paddleMode')}</Label>
|
||||
<Select value={wk.mode} onValueChange={(v) => setWkField({ mode: v })}>
|
||||
<SelectTrigger className="h-8"><SelectValue /></SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -3220,16 +3213,16 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
|
||||
|
||||
<div className="flex flex-wrap gap-x-5 gap-y-2">
|
||||
<label className="flex items-center gap-2 text-sm cursor-pointer">
|
||||
<Checkbox checked={wk.swap} onCheckedChange={(c) => setWkField({ swap: !!c })} /> Swap paddles
|
||||
<Checkbox checked={wk.swap} onCheckedChange={(c) => setWkField({ swap: !!c })} /> {t('wk.swapPaddles')}
|
||||
</label>
|
||||
<label className="flex items-center gap-2 text-sm cursor-pointer">
|
||||
<Checkbox checked={wk.autospace} onCheckedChange={(c) => setWkField({ autospace: !!c })} /> Auto-space
|
||||
<Checkbox checked={wk.autospace} onCheckedChange={(c) => setWkField({ autospace: !!c })} /> {t('wk.autospace')}
|
||||
</label>
|
||||
<label className="flex items-center gap-2 text-sm cursor-pointer">
|
||||
<Checkbox checked={wk.use_ptt} onCheckedChange={(c) => setWkField({ use_ptt: !!c })} /> Key PTT
|
||||
<Checkbox checked={wk.use_ptt} onCheckedChange={(c) => setWkField({ use_ptt: !!c })} /> {t('wk.keyPttShort')}
|
||||
</label>
|
||||
<label className="flex items-center gap-2 text-sm cursor-pointer">
|
||||
<Checkbox checked={wk.serial_echo} onCheckedChange={(c) => setWkField({ serial_echo: !!c })} /> Serial echo
|
||||
<Checkbox checked={wk.serial_echo} onCheckedChange={(c) => setWkField({ serial_echo: !!c })} /> {t('wk.serialEcho')}
|
||||
</label>
|
||||
</div>
|
||||
</>
|
||||
@@ -3237,9 +3230,9 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
|
||||
|
||||
{/* Macro editor */}
|
||||
<div className="border-t border-border/60 pt-3">
|
||||
<Label className="text-sm font-medium">CW message macros (F1…)</Label>
|
||||
<Label className="text-sm font-medium">{t('wk.macroTitle')}</Label>
|
||||
<p className="text-[11px] text-muted-foreground mb-2">
|
||||
Use variables: <span className="font-mono"><MY_CALL> <CALL> <STX> <STRX> <MY_NAME> <HIS_NAME> <MY_QTH> <GRID> <CONT_TX> <n></span> (cut numbers: 9→N, 0→T). <span className="font-mono">*</span>=my call, <span className="font-mono">!</span>=his call. <span className="font-mono"><LOGQSO></span> = log the QSO when the macro is sent (e.g. <span className="font-mono">73 TU <LOGQSO></span>).
|
||||
{t('wk.macroVars')} <span className="font-mono"><MY_CALL> <CALL> <STX> <STRX> <MY_NAME> <HIS_NAME> <MY_QTH> <GRID> <CONT_TX> <n></span> {t('wk.macroCut')} <span className="font-mono">*</span>={t('wk.macroMyCall')}, <span className="font-mono">!</span>={t('wk.macroHisCall')}. <span className="font-mono"><LOGQSO></span> = {t('wk.macroLog')} (<span className="font-mono">73 TU <LOGQSO></span>).
|
||||
</p>
|
||||
<div className="space-y-1.5 max-h-[34vh] overflow-y-auto pr-1">
|
||||
{Array.from({ length: 12 }).map((_, i) => {
|
||||
@@ -3247,7 +3240,7 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
|
||||
return (
|
||||
<div key={i} className="flex items-center gap-2">
|
||||
<span className="font-mono text-[10px] text-primary font-semibold w-6 shrink-0">F{i + 1}</span>
|
||||
<Input className="h-8 w-28 shrink-0 text-xs" placeholder="Label" value={m.label} onChange={(e) => setMacro(i, { label: e.target.value })} />
|
||||
<Input className="h-8 w-28 shrink-0 text-xs" placeholder={t('wk.label')} value={m.label} onChange={(e) => setMacro(i, { label: e.target.value })} />
|
||||
<Input className="h-8 flex-1 font-mono text-xs" placeholder="CQ CQ DE <MY_CALL> K" value={m.text} onChange={(e) => setMacro(i, { text: e.target.value })} />
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user