feat: Complete translation in French

This commit is contained in:
2026-07-05 10:44:40 +02:00
parent 3a6afc28ac
commit 2d742be7df
32 changed files with 1841 additions and 1421 deletions
+24 -22
View File
@@ -1,5 +1,6 @@
import { useEffect } from 'react';
import { Globe2, RefreshCw, Upload, BadgeCheck, Mail, FileDown, PencilLine, Trash2 } from 'lucide-react';
import { useI18n } from '@/lib/i18n';
export type QSOMenuState = { x: number; y: number; ids: number[] } | null;
@@ -20,12 +21,12 @@ type Props = {
onDelete?: (ids: number[]) => void;
};
const UPLOAD_TARGETS: { service: string; label: string }[] = [
{ service: 'qrz', label: 'Send to QRZ.com' },
{ service: 'clublog', label: 'Send to Club Log' },
{ service: 'hrdlog', label: 'Send to HRDLog.net' },
{ service: 'eqsl', label: 'Send to eQSL.cc' },
{ service: 'lotw', label: 'Send to LoTW' },
const UPLOAD_TARGETS: { service: string; name: string }[] = [
{ service: 'qrz', name: 'QRZ.com' },
{ service: 'clublog', name: 'Club Log' },
{ service: 'hrdlog', name: 'HRDLog.net' },
{ service: 'eqsl', name: 'eQSL.cc' },
{ service: 'lotw', name: 'LoTW' },
];
// Lightweight right-click menu for the QSO grids. AG Grid's native context
@@ -35,6 +36,7 @@ const UPLOAD_TARGETS: { service: string; label: string }[] = [
// list auto-refreshes and AG Grid fires internal scroll events on refresh,
// which used to dismiss the menu the instant it appeared.)
export function QSOContextMenu({ menu, onClose, onUpdateFromCty, onUpdateFromQRZ, onUpdateFromClublog, onSendTo, onSendRecording, onSendEQSL, onBulkEdit, onExportSelected, onExportFiltered, onExportCabrilloSelected, onExportCabrilloFiltered, onDelete }: Props) {
const { t } = useI18n();
useEffect(() => {
if (!menu) return;
const close = () => onClose();
@@ -60,21 +62,21 @@ export function QSOContextMenu({ menu, onClose, onUpdateFromCty, onUpdateFromQRZ
onMouseDown={(e) => e.stopPropagation()}
>
<div className="px-3 py-1 text-[11px] uppercase tracking-wider text-muted-foreground">
{n} QSO{n > 1 ? 's' : ''} selected
{t('qctx.selected', { n })}
</div>
<button
className="flex w-full items-center gap-2 px-3 py-1.5 text-left hover:bg-accent/50"
onClick={() => { onUpdateFromCty(menu.ids); onClose(); }}
>
<Globe2 className="size-4 text-primary" />
<span>Fix country &amp; zones from cty.dat</span>
<span>{t('qctx.fixCountry')}</span>
</button>
<button
className="flex w-full items-center gap-2 px-3 py-1.5 text-left hover:bg-accent/50"
onClick={() => { onUpdateFromQRZ(menu.ids); onClose(); }}
>
<RefreshCw className="size-4 text-sky-600" />
<span>Update from QRZ.com</span>
<span>{t('qctx.updateQrz')}</span>
</button>
{onUpdateFromClublog && (
<button
@@ -82,7 +84,7 @@ export function QSOContextMenu({ menu, onClose, onUpdateFromCty, onUpdateFromQRZ
onClick={() => { onUpdateFromClublog(menu.ids); onClose(); }}
>
<BadgeCheck className="size-4 text-violet-600" />
<span>Update from ClubLog (exceptions)</span>
<span>{t('qctx.updateClublog')}</span>
</button>
)}
@@ -95,7 +97,7 @@ export function QSOContextMenu({ menu, onClose, onUpdateFromCty, onUpdateFromQRZ
onClick={() => { onSendEQSL(menu.ids); onClose(); }}
>
<Mail className="size-4 text-amber-600" />
<span>Send OpsLog QSL by e-mail</span>
<span>{t('qctx.sendQslEmail')}</span>
</button>
)}
{onSendRecording && (
@@ -104,7 +106,7 @@ export function QSOContextMenu({ menu, onClose, onUpdateFromCty, onUpdateFromQRZ
onClick={() => { onSendRecording(menu.ids); onClose(); }}
>
<Mail className="size-4 text-rose-600" />
<span>Send recording by e-mail</span>
<span>{t('qctx.sendRecording')}</span>
</button>
)}
</>
@@ -118,7 +120,7 @@ export function QSOContextMenu({ menu, onClose, onUpdateFromCty, onUpdateFromQRZ
onClick={() => { onBulkEdit(menu.ids); onClose(); }}
>
<PencilLine className="size-4 text-indigo-600" />
<span>Bulk edit field ({n})</span>
<span>{t('qctx.bulkEdit', { n })}</span>
</button>
</>
)}
@@ -132,7 +134,7 @@ export function QSOContextMenu({ menu, onClose, onUpdateFromCty, onUpdateFromQRZ
onClick={() => { onExportSelected(menu.ids); onClose(); }}
>
<FileDown className="size-4 text-sky-600" />
<span>Export selected to ADIF ({n})</span>
<span>{t('qctx.exportSelectedAdif', { n })}</span>
</button>
)}
{onExportFiltered && (
@@ -141,7 +143,7 @@ export function QSOContextMenu({ menu, onClose, onUpdateFromCty, onUpdateFromQRZ
onClick={() => { onExportFiltered(); onClose(); }}
>
<FileDown className="size-4 text-violet-600" />
<span>Export filtered view to ADIF (no limit)</span>
<span>{t('qctx.exportFilteredAdif')}</span>
</button>
)}
{onExportCabrilloSelected && (
@@ -150,7 +152,7 @@ export function QSOContextMenu({ menu, onClose, onUpdateFromCty, onUpdateFromQRZ
onClick={() => { onExportCabrilloSelected(menu.ids); onClose(); }}
>
<FileDown className="size-4 text-amber-600" />
<span>Export selected to Cabrillo ({n})</span>
<span>{t('qctx.exportSelectedCabrillo', { n })}</span>
</button>
)}
{onExportCabrilloFiltered && (
@@ -159,7 +161,7 @@ export function QSOContextMenu({ menu, onClose, onUpdateFromCty, onUpdateFromQRZ
onClick={() => { onExportCabrilloFiltered(); onClose(); }}
>
<FileDown className="size-4 text-amber-700" />
<span>Export filtered view to Cabrillo (no limit)</span>
<span>{t('qctx.exportFilteredCabrillo')}</span>
</button>
)}
</>
@@ -168,14 +170,14 @@ export function QSOContextMenu({ menu, onClose, onUpdateFromCty, onUpdateFromQRZ
{onSendTo && (
<>
<div className="my-1 border-t border-border" />
{UPLOAD_TARGETS.map((t) => (
{UPLOAD_TARGETS.map((target) => (
<button
key={t.service}
key={target.service}
className="flex w-full items-center gap-2 px-3 py-1.5 text-left hover:bg-accent/50"
onClick={() => { onSendTo(t.service, menu.ids); onClose(); }}
onClick={() => { onSendTo(target.service, menu.ids); onClose(); }}
>
<Upload className="size-4 text-emerald-600" />
<span>{t.label}</span>
<span>{t('qctx.sendTo', { name: target.name })}</span>
</button>
))}
</>
@@ -189,7 +191,7 @@ export function QSOContextMenu({ menu, onClose, onUpdateFromCty, onUpdateFromQRZ
onClick={() => { onDelete(menu.ids); onClose(); }}
>
<Trash2 className="size-4" />
<span>Delete {n} QSO{n > 1 ? 's' : ''}</span>
<span>{t('qctx.delete', { n })}</span>
</button>
</>
)}