From 66450303a1f7a6fd795b9cb9b5ae902625f961bc Mon Sep 17 00:00:00 2001 From: Anais Saraiva Date: Mon, 21 Sep 2026 16:53:15 +0200 Subject: [PATCH] translations :3 --- frontend/src/components/AdminCenter.tsx | 104 ++++++++++++------------ frontend/src/components/AdminPanel.tsx | 100 ++++++++++++----------- 2 files changed, 104 insertions(+), 100 deletions(-) diff --git a/frontend/src/components/AdminCenter.tsx b/frontend/src/components/AdminCenter.tsx index bf2aee0..ca97b07 100644 --- a/frontend/src/components/AdminCenter.tsx +++ b/frontend/src/components/AdminCenter.tsx @@ -5,6 +5,7 @@ import { AdminPanel } from "./AdminPanel.tsx"; import { AccessRequestsPanel } from "./AccessRequests.tsx"; import { Button, CopyButton, FlowerMark, Input, LoadingScreen, Spinner, Switch } from "./ui.tsx"; import { cn } from "../lib/cn.ts"; +import { useT } from "../lib/i18n.ts"; type Tab = "accounts" | "access" | "features" | "mail"; @@ -15,20 +16,21 @@ function applyTheme(theme: string) { root.style.colorScheme = dark ? "dark" : "light"; } -const TABS: { id: Tab; label: string; icon: typeof Users }[] = [ - { id: "accounts", label: "Accounts", icon: Users }, - { id: "access", label: "Access", icon: Inbox }, - { id: "features", label: "Features", icon: SlidersHorizontal }, - { id: "mail", label: "Mail", icon: Mail }, -]; - export function AdminCenter() { + const { t } = useT(); const [allowed, setAllowed] = useState(null); const [tab, setTab] = useState("accounts"); const [settings, setSettings] = useState<{ registrationEnabled: boolean; accessRequestEnabled: boolean; ocrEnabled: boolean } | null>(null); const [error, setError] = useState(""); const [saving, setSaving] = useState(false); + const TABS: { id: Tab; label: string; icon: typeof Users }[] = [ + { id: "accounts", label: t("center.tabAccounts"), icon: Users }, + { id: "access", label: t("center.tabAccess"), icon: Inbox }, + { id: "features", label: t("center.tabFeatures"), icon: SlidersHorizontal }, + { id: "mail", label: t("center.tabMail"), icon: Mail }, + ]; + useEffect(() => { api.verify() .then((r) => { @@ -38,7 +40,7 @@ export function AdminCenter() { } setAllowed(true); applyTheme(r.user.theme); - api.adminGetSettings().then((s) => setSettings(s.settings)).catch((e) => setError(e instanceof Error ? e.message : "Load failed")); + api.adminGetSettings().then((s) => setSettings(s.settings)).catch((e) => setError(e instanceof Error ? e.message : t("admin.loadFailed"))); }) .catch(() => setAllowed(false)); }, []); @@ -50,7 +52,7 @@ export function AdminCenter() { const res = await api.adminPatchSettings(patch); setSettings(res.settings); } catch (e) { - setError(e instanceof Error ? e.message : "Save failed"); + setError(e instanceof Error ? e.message : t("common.saveFailed")); } finally { setSaving(false); } @@ -65,9 +67,9 @@ export function AdminCenter() {
-

Admins only

-

Log in with the admin account to open the Admin Center.

- Back to login +

{t("center.locked")}

+

{t("center.lockedSub")}

+ {t("common.backToLogin")}
); @@ -77,13 +79,13 @@ export function AdminCenter() {
- +

- Admin Center + {t("center.title")}

@@ -126,25 +128,25 @@ export function AdminCenter() {
-

Public registration

-

Show the Register button on the login page. Off = admin-created accounts only. Turning it on retires the wishlist below.

+

{t("center.regTitle")}

+

{t("center.regDesc")}

- toggle({ registrationEnabled: v })} /> + toggle({ registrationEnabled: v })} />
-

Access requests (wishlist)

+

{t("center.accTitle")}

{settings?.registrationEnabled - ? "Unavailable while public registration is on — it replaces the wishlist." - : "Show Request access on the login page. Visitors reserve a username and plead their case; you triage them in the Access tab."} + ? t("center.accDescOn") + : t("center.accDescOff")}

toggle({ accessRequestEnabled: v })} @@ -152,13 +154,13 @@ export function AdminCenter() {
-

OCR tool

-

Server-side image transcription for attachments. Requires the tesseract binary.

+

{t("center.ocrTitle")}

+

{t("center.ocrDesc")}

- toggle({ ocrEnabled: v })} /> + toggle({ ocrEnabled: v })} />
- {saving &&

Saving…

} - {!settings && !error &&

Loading…

} + {saving &&

{t("common.saving")}

} + {!settings && !error &&

{t("common.loading")}

}
)} @@ -181,6 +183,7 @@ type SmtpInfo = { }; function MailPanel() { + const { t } = useT(); const [smtp, setSmtp] = useState(null); const [error, setError] = useState(""); const [showPass, setShowPass] = useState(false); @@ -190,7 +193,7 @@ function MailPanel() { const [result, setResult] = useState<{ ok: boolean; text: string } | null>(null); useEffect(() => { - api.adminMailStatus().then((r) => setSmtp(r.smtp)).catch((e) => setError(e instanceof Error ? e.message : "Load failed")); + api.adminMailStatus().then((r) => setSmtp(r.smtp)).catch((e) => setError(e instanceof Error ? e.message : t("admin.loadFailed"))); }, []); async function verify() { @@ -198,9 +201,9 @@ function MailPanel() { setResult(null); try { await api.adminMailVerify(); - setResult({ ok: true, text: "Connection OK — SMTP host reachable, auth accepted." }); + setResult({ ok: true, text: t("center.connOk") }); } catch (e) { - setResult({ ok: false, text: e instanceof Error ? e.message : "Verification failed" }); + setResult({ ok: false, text: e instanceof Error ? e.message : t("center.verifyFailed") }); } finally { setVerifying(false); } @@ -211,9 +214,9 @@ function MailPanel() { setResult(null); try { const r = await api.adminMailTest(to.trim()); - setResult({ ok: true, text: `Test mail sent to ${to.trim()}${r.messageId ? ` (${r.messageId})` : ""}.` }); + setResult({ ok: true, text: t("center.testSent", { to: to.trim(), tail: r.messageId ? ` (${r.messageId})` : "" }) }); } catch (e) { - setResult({ ok: false, text: e instanceof Error ? e.message : "Send failed" }); + setResult({ ok: false, text: e instanceof Error ? e.message : t("center.mailSendFailed") }); } finally { setSending(false); } @@ -236,40 +239,40 @@ function MailPanel() {
-

Key recovery emails

+

{t("center.mailTitle")}

- {smtp == null ? "Checking…" : smtp.enabled ? `Enabled — sent from ${smtp.from}` : "Disabled — set SMTP_HOST on the server to enable"} + {smtp == null ? t("center.mailChecking") : smtp.enabled ? t("center.mailFrom", { from: smtp.from }) : t("center.mailOff")}

- {smtp?.enabled ? "On" : "Off"} + {smtp?.enabled ? t("center.on") : t("center.off")}
{error &&

{error}

} - {!smtp && !error &&

Loading…

} + {!smtp && !error &&

{t("common.loading")}

} {smtp && (
- {smtp.host || (not set)} - {smtp.port} · {smtp.secure ? "implicit TLS (465)" : "STARTTLS (587)"} - {smtp.user || (no auth)} - + {smtp.host || {t("center.notSet")}} + {smtp.port} · {smtp.secure ? t("center.tlsImplicit") : t("center.tlsStarttls")} + {smtp.user || {t("center.noAuth")}} + {!smtp.hasPass ? ( - (not set) + {t("center.notSet")} ) : showPass ? ( {smtp.pass} ) : ( - •••••••••• + •••••••••• )} {smtp.hasPass && ( <>
)}

- Credentials live in the server environment (see docs/OPERATIONS.md), read-only here. - Users set their recovery address in profile settings or at account creation. + {t("center.credsNote")}

-

Connectivity test

-

Verify the connection, then send a real test mail to an address you control.

+

{t("center.mailTestTitle")}

+

{t("center.mailTestDesc")}

setTo(e.target.value)} className="flex-1" />
{result && ( diff --git a/frontend/src/components/AdminPanel.tsx b/frontend/src/components/AdminPanel.tsx index f492fce..8812b44 100644 --- a/frontend/src/components/AdminPanel.tsx +++ b/frontend/src/components/AdminPanel.tsx @@ -162,7 +162,7 @@ export function OllamaModelsModal({ open, onClose }: { open: boolean; onClose: ( )} ); @@ -173,8 +173,8 @@ export function OllamaModelsModal({ open, onClose }: { open: boolean; onClose: ( setDeleteModel(null)} - title={`Delete ${deleteModel}?`} - message="The model is removed from the Ollama host. Pull it again anytime." + title={t("ollama.delTitle", { name: deleteModel ?? "" })} + message={t("ollama.delMsg")} onConfirm={() => deleteModel && removeModel(deleteModel)} /> @@ -182,6 +182,7 @@ export function OllamaModelsModal({ open, onClose }: { open: boolean; onClose: ( } export function AdminPanel({ open, onClose, bare }: { open: boolean; onClose: () => void; bare?: boolean }) { + const { t } = useT(); const [users, setUsers] = useState([]); const [busy, setBusy] = useState(false); const [error, setError] = useState(""); @@ -215,7 +216,7 @@ export function AdminPanel({ open, onClose, bare }: { open: boolean; onClose: () setPages(res.pages); if (res.page !== page) setPage(res.page); } catch (err) { - setError(err instanceof Error ? err.message : "Load failed"); + setError(err instanceof Error ? err.message : t("admin.loadFailed")); } finally { setBusy(false); } @@ -256,7 +257,7 @@ export function AdminPanel({ open, onClose, bare }: { open: boolean; onClose: () setShowCreate(false); await refresh(); } catch (err) { - setError(err instanceof Error ? err.message : "Create failed"); + setError(err instanceof Error ? err.message : t("admin.createFailed")); } } @@ -266,7 +267,7 @@ export function AdminPanel({ open, onClose, bare }: { open: boolean; onClose: () const res = await api.adminRegenerate(u.id); setFreshKey({ username: u.username, key: res.key }); } catch (err) { - setError(err instanceof Error ? err.message : "Regenerate failed"); + setError(err instanceof Error ? err.message : t("admin.regenFailed")); } } @@ -275,7 +276,7 @@ export function AdminPanel({ open, onClose, bare }: { open: boolean; onClose: () await api.adminDelete(u.id); await refresh(); } catch (err) { - setError(err instanceof Error ? err.message : "Delete failed"); + setError(err instanceof Error ? err.message : t("admin.deleteFailed")); } } @@ -283,19 +284,19 @@ export function AdminPanel({ open, onClose, bare }: { open: boolean; onClose: () // of a modal — no logic duplicated. const Shell: FC<{ children: ReactNode }> = bare ? ({ children }) =>
{children}
- : ({ children }) => {children}; + : ({ children }) => {children}; return ( <>

- No-KYC accounts: you create a username, hand the access key to its owner once. Keys are stored hashed — regenerate to rotate. + {t("admin.intro")}

{error &&

{error}

} {freshKey && (
-

Key for @{freshKey.username} — shown once, copy it now:

+

{t("admin.keyOnce", { user: freshKey.username })}

{freshKey.key} @@ -306,62 +307,62 @@ export function AdminPanel({ open, onClose, bare }: { open: boolean; onClose: () {!showCreate ? (
) : (
- setUsername(e.target.value)} placeholder="alice" required /> + setUsername(e.target.value)} placeholder="alice" required />
- setDisplayName(e.target.value)} placeholder="Alice" /> + setDisplayName(e.target.value)} placeholder="Alice" />
- setEmail(e.target.value)} placeholder="alice@example.com" inputMode="email" /> + setEmail(e.target.value)} placeholder="alice@example.com" inputMode="email" />
- - + +
)}
- setQ(e.target.value)} placeholder="Search accounts…" aria-label="Search accounts" className="pl-10" /> + setQ(e.target.value)} placeholder={t("admin.searchPh")} aria-label={t("admin.searchAria")} className="pl-10" />
{ setSort(v); setPage(1); }} align="right" options={[ - { value: "newest", label: "Newest first" }, - { value: "oldest", label: "Oldest first" }, - { value: "az", label: "A → Z" }, - { value: "za", label: "Z → A" }, + { value: "newest", label: t("admin.sortNewest") }, + { value: "oldest", label: t("admin.sortOldest") }, + { value: "az", label: t("admin.sortAz") }, + { value: "za", label: t("admin.sortZa") }, ]} /> { setFilter(v); setPage(1); }} align="right" options={[ - { value: "all", label: "All accounts" }, - { value: "with-email", label: "With recovery email" }, - { value: "no-email", label: "No recovery email" }, + { value: "all", label: t("admin.filterAll") }, + { value: "with-email", label: t("admin.filterWith") }, + { value: "no-email", label: t("admin.filterNo") }, ]} />
{busy ? ( -

Loading…

+

{t("common.loading")}

) : (
    {users.map((u) => ( @@ -369,13 +370,13 @@ export function AdminPanel({ open, onClose, bare }: { open: boolean; onClose: ()

    {u.displayName} @{u.username}

    -

    {u.email || "no recovery email"} · {new Date(u.createdAt).toLocaleDateString()}

    +

    {u.email || t("admin.noEmail")} · {new Date(u.createdAt).toLocaleDateString()}

    {!u.isAdmin && (
    - - - + + +
    )} @@ -383,18 +384,18 @@ export function AdminPanel({ open, onClose, bare }: { open: boolean; onClose: ()
)}
- {total} account{total === 1 ? "" : "s"} · page {page} of {pages} + {total === 1 ? t("admin.countOne", { total, page, pages }) : t("admin.countMany", { total, page, pages })} {pages > 1 && (
setPage((p) => Math.max(1, p - 1))} > = pages} onClick={() => setPage((p) => p + 1)} > @@ -408,8 +409,8 @@ export function AdminPanel({ open, onClose, bare }: { open: boolean; onClose: () setDeleteTarget(null)} - title={`Delete @${deleteTarget?.username}?`} - message="This permanently removes the account, all its conversations and its shares." + title={t("admin.deleteTitle", { user: deleteTarget?.username ?? "" })} + message={t("admin.deleteMsg")} onConfirm={() => deleteTarget && remove(deleteTarget)} /> @@ -420,6 +421,7 @@ export function AdminPanel({ open, onClose, bare }: { open: boolean; onClose: () } function EditUserDialog({ user, onClose, onSaved }: { user: User | null; onClose: () => void; onSaved: () => void }) { + const { t } = useT(); const [displayName, setDisplayName] = useState(""); const [avatarUrl, setAvatarUrl] = useState(""); const [email, setEmail] = useState(""); @@ -444,33 +446,33 @@ function EditUserDialog({ user, onClose, onSaved }: { user: User | null; onClose onSaved(); onClose(); } catch (err) { - setError(err instanceof Error ? err.message : "Save failed"); + setError(err instanceof Error ? err.message : t("common.saveFailed")); } } return ( - +
- setDisplayName(e.target.value)} maxLength={60} required /> - setAvatarUrl(e.target.value)} placeholder="https://…" /> - setEmail(e.target.value)} placeholder="alice@example.com" inputMode="email" /> - + setDisplayName(e.target.value)} maxLength={60} required /> + setAvatarUrl(e.target.value)} placeholder="https://…" /> + setEmail(e.target.value)} placeholder="alice@example.com" inputMode="email" /> + {error &&

{error}

}
- - + +