feat: Complete translation in French
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Minus, Plus, Crosshair, X, PanelLeft, PanelRight } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
import { spotStatusKey, inferSpotMode, spotModeCategory } from '@/lib/spot';
|
||||
|
||||
// BandMap — vertical spectrum panel inspired by Log4OM.
|
||||
@@ -79,13 +80,13 @@ function LegendDot({ cls, label }: { cls: string; label: string }) {
|
||||
|
||||
// Human-readable label for a spot status — used in the pill hover tooltip
|
||||
// so the operator can see WHY a spot is coloured the way it is.
|
||||
function statusLabel(s: string): string {
|
||||
function statusLabel(s: string, t: (k: string) => string): string {
|
||||
switch (s) {
|
||||
case 'new': return 'NEW DXCC (entity never worked)';
|
||||
case 'new-band': return 'NEW BAND (entity not worked on this band)';
|
||||
case 'new-slot': return 'NEW SLOT (mode not worked on this band)';
|
||||
case 'worked': return 'Worked (this band + mode already in log)';
|
||||
default: return 'Entity not resolved';
|
||||
case 'new': return t('bmp.statusNew');
|
||||
case 'new-band': return t('bmp.statusNewBand');
|
||||
case 'new-slot': return t('bmp.statusNewSlot');
|
||||
case 'worked': return t('bmp.statusWorked');
|
||||
default: return t('bmp.statusUnresolved');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,6 +148,7 @@ const BOT_PAD = 14; // the top-most freq label isn't clipped at y=0
|
||||
const MAX_VISIBLE_SPOTS = 30;
|
||||
|
||||
export function BandMap({ band, spots, spotStatus, currentFreqHz, onSpotClick, onClose, side = 'right', onToggleSide }: Props) {
|
||||
const { t } = useI18n();
|
||||
const range = BAND_RANGES[band];
|
||||
const segments = SEGMENT_COLORS[band] ?? [];
|
||||
const [zoomIdx, setZoomIdx] = useState(0);
|
||||
@@ -330,8 +332,8 @@ export function BandMap({ band, spots, spotStatus, currentFreqHz, onSpotClick, o
|
||||
if (!range) {
|
||||
return (
|
||||
<div className="h-full w-full flex flex-col items-center justify-center text-xs text-muted-foreground p-3 bg-muted/20">
|
||||
<div className="text-sm font-semibold mb-1">Band map</div>
|
||||
Not configured for {band || '—'}.
|
||||
<div className="text-sm font-semibold mb-1">{t('bmp.bandMap')}</div>
|
||||
{t('bmp.notConfigured', { band: band || '—' })}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -364,34 +366,34 @@ export function BandMap({ band, spots, spotStatus, currentFreqHz, onSpotClick, o
|
||||
return (
|
||||
<div className="h-full w-full flex flex-col min-h-0 bg-card">
|
||||
<div className="px-3 py-1.5 text-xs font-semibold uppercase tracking-wider text-muted-foreground bg-muted/40 border-b border-border flex items-center gap-1 shrink-0">
|
||||
<span className="flex-1">Map · {band}</span>
|
||||
<span className="flex-1">{t('bmp.map')} · {band}</span>
|
||||
<button type="button" onClick={() => setZoomIdx((z) => Math.max(0, z - 1))} disabled={zoomIdx === 0}
|
||||
className="size-5 inline-flex items-center justify-center rounded hover:bg-muted disabled:opacity-30"
|
||||
title="Zoom out">
|
||||
title={t('bmp.zoomOut')}>
|
||||
<Minus className="size-3" />
|
||||
</button>
|
||||
<span className="font-mono text-[10px] w-12 text-center">{pxPerKHz}px/kHz</span>
|
||||
<button type="button" onClick={() => setZoomIdx((z) => Math.min(PX_PER_KHZ.length - 1, z + 1))} disabled={zoomIdx === PX_PER_KHZ.length - 1}
|
||||
className="size-5 inline-flex items-center justify-center rounded hover:bg-muted disabled:opacity-30"
|
||||
title="Zoom in">
|
||||
title={t('bmp.zoomIn')}>
|
||||
<Plus className="size-3" />
|
||||
</button>
|
||||
<button type="button" onClick={recenterOnRig}
|
||||
className="size-5 inline-flex items-center justify-center rounded hover:bg-muted"
|
||||
title="Scroll to current rig frequency">
|
||||
title={t('bmp.scrollToRig')}>
|
||||
<Crosshair className="size-3" />
|
||||
</button>
|
||||
{onToggleSide && (
|
||||
<button type="button" onClick={onToggleSide}
|
||||
className="size-5 inline-flex items-center justify-center rounded hover:bg-muted"
|
||||
title={side === 'right' ? 'Move band map to the left' : 'Move band map to the right'}>
|
||||
title={side === 'right' ? t('bmp.moveLeft') : t('bmp.moveRight')}>
|
||||
{side === 'right' ? <PanelLeft className="size-3" /> : <PanelRight className="size-3" />}
|
||||
</button>
|
||||
)}
|
||||
{onClose && (
|
||||
<button type="button" onClick={onClose}
|
||||
className="size-5 inline-flex items-center justify-center rounded hover:bg-muted"
|
||||
title="Hide band map">
|
||||
title={t('bmp.hide')}>
|
||||
<X className="size-3" />
|
||||
</button>
|
||||
)}
|
||||
@@ -496,7 +498,7 @@ export function BandMap({ band, spots, spotStatus, currentFreqHz, onSpotClick, o
|
||||
'hover:translate-x-0.5 hover:shadow',
|
||||
style.pill,
|
||||
)}
|
||||
title={`${p.spot.dx_call}${entry?.country ? ' · ' + entry.country : ''} · ${p.spot.freq_khz.toFixed(1)} kHz · ${statusLabel(st)}${p.spot.comment ? ' · ' + p.spot.comment : ''}${p.spot.spotter ? ' · de ' + p.spot.spotter : ''}`}
|
||||
title={`${p.spot.dx_call}${entry?.country ? ' · ' + entry.country : ''} · ${p.spot.freq_khz.toFixed(1)} kHz · ${statusLabel(st, t)}${p.spot.comment ? ' · ' + p.spot.comment : ''}${p.spot.spotter ? ' · de ' + p.spot.spotter : ''}`}
|
||||
>
|
||||
{/* Status accent strip on the left */}
|
||||
<span className={cn('w-1 shrink-0', style.bar)} aria-hidden />
|
||||
@@ -515,14 +517,14 @@ export function BandMap({ band, spots, spotStatus, currentFreqHz, onSpotClick, o
|
||||
</div>
|
||||
{/* Colour legend — what each pill colour means. */}
|
||||
<div className="px-3 py-1 flex flex-wrap items-center gap-x-2.5 gap-y-0.5 text-[9px] text-muted-foreground bg-muted/20 border-t border-border">
|
||||
<LegendDot cls="bg-rose-400" label="New DXCC" />
|
||||
<LegendDot cls="bg-amber-400" label="New band" />
|
||||
<LegendDot cls="bg-yellow-300" label="New slot (mode)" />
|
||||
<LegendDot cls="bg-muted-foreground/30" label="Worked" />
|
||||
<LegendDot cls="bg-rose-400" label={t('bmp.legendNewDxcc')} />
|
||||
<LegendDot cls="bg-amber-400" label={t('bmp.legendNewBand')} />
|
||||
<LegendDot cls="bg-yellow-300" label={t('bmp.legendNewSlot')} />
|
||||
<LegendDot cls="bg-muted-foreground/30" label={t('bmp.legendWorked')} />
|
||||
</div>
|
||||
<div className="px-3 py-1 text-[9px] text-muted-foreground bg-muted/30 border-t border-border font-mono text-center shrink-0">
|
||||
scroll · ctrl+wheel = zoom · ◎ = jump to rig
|
||||
{hidden > 0 && <span className="text-amber-600"> · {hidden} FT8/FT4 spot{hidden > 1 ? 's' : ''} hidden — top {MAX_VISIBLE_SPOTS} kept (CW/SSB all shown)</span>}
|
||||
{t('bmp.footerHint')}
|
||||
{hidden > 0 && <span className="text-amber-600"> · {t('bmp.spotsHidden', { n: hidden, max: MAX_VISIBLE_SPOTS })}</span>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user