sendoff panel keyed (503 -> 494)

The example paths move from <span> to <code>. A path is user content and must not be
translated; <code> says so semantically, matches how ApiKeys and the Google guide
already render paths, and the scanner skips code elements for that exact reason.

A second sentence about turning someone off was multi-line, so the scanner never saw
it — converted on sight.
This commit is contained in:
2026-08-23 14:21:54 +00:00
parent c8fb89122e
commit 3e07dad69c
@@ -2,6 +2,7 @@ import { useRef, useState } from 'react';
import { Laptop, Send, Upload, User, X } from 'lucide-react'; import { Laptop, Send, Upload, User, X } from 'lucide-react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { useSendOff, useSendOffSenders, useSendOffTargets } from './useSendOff'; import { useSendOff, useSendOffSenders, useSendOffTargets } from './useSendOff';
import { useTranslation } from '../../locales';
const label = (p: { name: string | null; email: string }): string => p.name || p.email.split('@')[0] || p.email; const label = (p: { name: string | null; email: string }): string => p.name || p.email.split('@')[0] || p.email;
const kb = (n: number): string => const kb = (n: number): string =>
@@ -17,6 +18,7 @@ type Target = { kind: 'machine'; name: string } | { kind: 'user'; id: number; na
* needed keeping in sync. * needed keeping in sync.
*/ */
export const SendOffPanel = () => { export const SendOffPanel = () => {
const { t } = useTranslation();
const { devices, people, myFolder, isLoading } = useSendOffTargets(); const { devices, people, myFolder, isLoading } = useSendOffTargets();
const send = useSendOff(); const send = useSendOff();
const [files, setFiles] = useState<File[]>([]); const [files, setFiles] = useState<File[]>([]);
@@ -41,7 +43,7 @@ export const SendOffPanel = () => {
<div className="flex h-full min-h-0 flex-col overflow-y-auto p-5"> <div className="flex h-full min-h-0 flex-col overflow-y-auto p-5">
<div className="mx-auto flex w-full max-w-2xl flex-col gap-5"> <div className="mx-auto flex w-full max-w-2xl flex-col gap-5">
<section> <section>
<h3 className="text-sm font-semibold text-foreground">Files</h3> <h3 className="text-sm font-semibold text-foreground">{t('sendOff.files')}</h3>
<input <input
ref={picker} ref={picker}
type="file" type="file"
@@ -70,7 +72,7 @@ export const SendOffPanel = () => {
<span className="shrink-0 text-xs text-muted-foreground">{kb(f.size)}</span> <span className="shrink-0 text-xs text-muted-foreground">{kb(f.size)}</span>
<button <button
type="button" type="button"
aria-label={`Remove ${f.name}`} aria-label={t('sendOff.removeFile', { name: f.name })}
className="shrink-0 text-muted-foreground hover:text-foreground" className="shrink-0 text-muted-foreground hover:text-foreground"
onClick={() => setFiles((prev) => prev.filter((_, k) => k !== i))} onClick={() => setFiles((prev) => prev.filter((_, k) => k !== i))}
> >
@@ -86,9 +88,9 @@ export const SendOffPanel = () => {
</section> </section>
<section> <section>
<h3 className="text-sm font-semibold text-foreground">Where</h3> <h3 className="text-sm font-semibold text-foreground">{t('sendOff.where')}</h3>
{isLoading ? ( {isLoading ? (
<p className="mt-2 text-sm text-muted-foreground">Loading…</p> <p className="mt-2 text-sm text-muted-foreground">{t('sendOff.loading')}</p>
) : ( ) : (
<div className="mt-2 flex flex-col gap-3"> <div className="mt-2 flex flex-col gap-3">
<TargetGroup <TargetGroup
@@ -124,19 +126,19 @@ export const SendOffPanel = () => {
<section className="flex flex-col gap-2"> <section className="flex flex-col gap-2">
<Button onClick={doSend} disabled={!ready || send.isPending} size="lg"> <Button onClick={doSend} disabled={!ready || send.isPending} size="lg">
<Send className="mr-2 h-4 w-4" /> <Send className="mr-2 h-4 w-4" />
{send.isPending ? 'Sending…' : target ? `Send to ${target.name}` : 'Send'} {send.isPending ? 'Sending…' : target ? t('sendOff.sendTo', { name: target.name }) : 'Send'}
</Button> </Button>
{/* The server's own message, not a generic one: a file over the limit and a recipient who has {/* The server's own message, not a generic one: a file over the limit and a recipient who has
revoked you are different problems with different answers. */} revoked you are different problems with different answers. */}
{target?.kind === 'user' && myFolder && ( {target?.kind === 'user' && myFolder && (
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">
Lands in their <span className="text-foreground">Documents/SendOff/{myFolder}</span>. {t('sendOff.landsIn')} <code className="text-foreground">Documents/SendOff/{myFolder}</code>.
</p> </p>
)} )}
{send.error && <p className="text-sm text-destructive">{send.error.message}</p>} {send.error && <p className="text-sm text-destructive">{send.error.message}</p>}
{send.data && ( {send.data && (
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Landed in <span className="text-foreground">{send.data.path}</span> —{' '} {t('sendOff.landedIn')} <span className="text-foreground">{send.data.path}</span> —{' '}
{send.data.files.map((f) => f.name).join(', ')} {send.data.files.map((f) => f.name).join(', ')}
</p> </p>
)} )}
@@ -195,15 +197,16 @@ const TargetGroup = ({ icon: Icon, heading, empty, options, selected, onSelect }
* the answer has to be visible in the same place the question was asked. * the answer has to be visible in the same place the question was asked.
*/ */
const WhoMaySend = () => { const WhoMaySend = () => {
const { t } = useTranslation();
const { candidates, isLoading, setAllowed, isSaving } = useSendOffSenders(); const { candidates, isLoading, setAllowed, isSaving } = useSendOffSenders();
if (isLoading || candidates.length === 0) return null; if (isLoading || candidates.length === 0) return null;
return ( return (
<section className="border-t border-border pt-4"> <section className="border-t border-border pt-4">
<h3 className="text-sm font-semibold text-foreground">Who may send to me</h3> <h3 className="text-sm font-semibold text-foreground">{t('sendOff.whoMaySend')}</h3>
<p className="mt-1 text-xs text-muted-foreground"> <p className="mt-1 text-xs text-muted-foreground">
Files from anyone you allow land in <span className="text-foreground">Documents/SendOff/their-name</span>. {t('sendOff.allowedLandIn')} <code className="text-foreground">Documents/SendOff/their-name</code>.{' '}
Turning someone off stops future sends and leaves what they already sent alone. {t('sendOff.turningOff')}
</p> </p>
<div className="mt-2 flex flex-col gap-1"> <div className="mt-2 flex flex-col gap-1">
{candidates.map((p) => ( {candidates.map((p) => (