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:
@@ -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) => (
|
||||||
|
|||||||
Reference in New Issue
Block a user