Translate admin panel to all 6 languages

This commit is contained in:
openhands committed 2026-07-04 21:21:15 +02:00
1 parent f381aa987f
commit befa4ec282
63 files changed
+9353 -1443

No files matched your search

+21 -22
View File
@@ -1,3 +1,4 @@
import { getTranslations } from "next-intl/server";
import { Bell } from "lucide-react";
import { sendHotelAlert } from "@/actions/admin-alerts";
import { StatusCard } from "@/components/admin/dashboard";
@@ -8,16 +9,10 @@ export const dynamic = "force-dynamic";
const CRITICAL = new Set(["critical", "error", "danger"]);
const WARNING = new Set(["warning", "warn"]);
// Read-only feed of alert_logs (prisma.alertLogs delegate): the emulator and
// CMS write rows here when hotel/user alerts fire. Auto-gated by the admin
// layout (requireStaff); only the mutation re-asserts staff in the action.
function fromDate(d: Date | null | undefined): string {
return d ? d.toISOString().slice(0, 16).replace("T", " ") : "—";
}
// Map a free-text severity to a theme-token colour. Unknown severities fall
// back to muted grey rather than inventing a new palette.
function severityColor(severity: string): string {
switch (severity.toLowerCase()) {
case "critical":
@@ -59,6 +54,8 @@ function Badge({ label, color }: { label: string; color: string }) {
}
export default async function AdminAlerts() {
const t = await getTranslations("pages.admin.alerts");
let alerts: Awaited<ReturnType<typeof prisma.alertLogs.findMany>> = [];
try {
alerts = await prisma.alertLogs.findMany({
@@ -80,27 +77,27 @@ export default async function AdminAlerts() {
<Bell size={20} className="text-[var(--color-primary)]" />
</div>
<div>
<h1 className="m-0 text-xl font-extrabold text-[var(--color-text)]">Alerts</h1>
<p className="m-0 text-xs text-[var(--color-text-muted)] mt-0.5">Read-only · 100 most recent alert_logs (newest first).</p>
<h1 className="m-0 text-xl font-extrabold text-[var(--color-text)]">{t("title")}</h1>
<p className="m-0 text-xs text-[var(--color-text-muted)] mt-0.5">{t("subtitle")}</p>
</div>
</div>
<div className="grid grid-cols-[repeat(auto-fit,minmax(180px,1fr))] gap-3.5 mb-6">
<StatusCard label="Alerts shown" value={alerts.length} icon="🔔" />
<StatusCard label={t("alertsShown")} value={alerts.length} icon="🔔" />
<StatusCard
label="Critical"
label={t("critical")}
value={critical}
state={critical > 0 ? "danger" : "ok"}
icon="🚨"
/>
<StatusCard
label="Warnings"
label={t("warnings")}
value={warnings}
state={warnings > 0 ? "warn" : "neutral"}
icon="⚠️"
/>
<StatusCard
label="Unread"
label={t("unread")}
value={unread}
state={unread > 0 ? "warn" : "ok"}
icon="📨"
@@ -108,9 +105,11 @@ export default async function AdminAlerts() {
</div>
<form action={sendHotelAlert} className="admin-card mb-6">
<h3 className="mt-0">Send hotel alert</h3>
<h3 className="mt-0">{t("form.send")}</h3>
<p className="text-xs text-gray-500 dark:text-gray-400">
Broadcasts a message to every online user via RCON (<code>hotelalert</code>).
{t.rich("form.description", {
code: (chunks) => <code>{chunks}</code>,
})}
</p>
<div className="flex gap-2 flex-wrap items-start">
<textarea
@@ -118,28 +117,28 @@ export default async function AdminAlerts() {
required
maxLength={1000}
rows={2}
placeholder="Message to broadcast to the hotel…"
placeholder={t("form.messagePlaceholder")}
className="flex-1 min-w-[240px]"
/>
<button type="submit" className="btn btn-primary">
Send alert
{t("form.send")}
</button>
</div>
</form>
<section className="admin-card">
<h3 className="mt-0">Recent alerts ({alerts.length})</h3>
<h3 className="mt-0">{t("recentAlerts", { count: alerts.length })}</h3>
{alerts.length === 0 ? (
<div className="admin-empty">No alert logs.</div>
<div className="admin-empty">{t("noAlerts")}</div>
) : (
<div className="overflow-x-auto">
<table>
<thead>
<tr>
<th>When</th>
<th>Severity</th>
<th>Type</th>
<th>Message</th>
<th>{t("colWhen")}</th>
<th>{t("colSeverity")}</th>
<th>{t("colType")}</th>
<th>{t("colMessage")}</th>
</tr>
</thead>
<tbody>