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

+30 -38
View File
@@ -4,6 +4,7 @@ import { siteSettings } from '@/lib/services/site-settings';
import { requireStaff } from '@/lib/admin/guard';
import { EmbedSnippet } from './embed-snippet';
import { Code } from 'lucide-react';
import { getTranslations } from "next-intl/server";
export const dynamic = 'force-dynamic';
@@ -12,9 +13,8 @@ export const metadata = { title: 'Radio Embed' };
export default async function AdminRadioEmbedPage() {
await requireStaff();
// Read radio embed-related website_settings. siteSettings.get() already
// swallows DB errors and serves fallbacks, but wrap defensively anyway so a
// throw never blanks the page.
const t = await getTranslations("pages.admin.radio");
let streamUrl = '';
let radioName = 'Radio';
let nowPlayingApiUrl = '';
@@ -33,10 +33,8 @@ export default async function AdminRadioEmbedPage() {
nowPlayingApiUrl = nowPlayingApiUrl.trim();
const hasStream = streamUrl !== '';
// Copy-ready embed snippets. Built server-side from the configured stream URL
// so what staff copy matches what the preview renders.
const audioSnippet = hasStream
? `<audio controls preload="none" src="${streamUrl}">\n Your browser does not support the audio element.\n</audio>`
? `<audio controls preload="none" src="${streamUrl}">\n ${t("embedPage.audioUnsupported")}\n</audio>`
: '';
const iframeSnippet = hasStream
@@ -46,12 +44,12 @@ export default async function AdminRadioEmbedPage() {
return (
<main>
<nav className="text-xs text-gray-500 dark:text-gray-400 mb-2">
<Link href="/admin/radio">Radio</Link> ·{' '}
<Link href="/admin/radio/settings">Settings</Link> ·{' '}
<Link href="/admin/radio/embed">Embed</Link> ·{' '}
<Link href="/admin/radio/banners">Banners</Link> ·{' '}
<Link href="/admin/radio/ranks">Ranks</Link> ·{' '}
<Link href="/admin/radio/history">History</Link>
<Link href="/admin/radio">{t("title")}</Link> ·{' '}
<Link href="/admin/radio/settings">{t("settings")}</Link> ·{' '}
<Link href="/admin/radio/embed">{t("embed")}</Link> ·{' '}
<Link href="/admin/radio/banners">{t("banners.title")}</Link> ·{' '}
<Link href="/admin/radio/ranks">{t("ranks")}</Link> ·{' '}
<Link href="/admin/radio/history">{t("history")}</Link>
</nav>
<section className="mt-6">
@@ -60,73 +58,67 @@ export default async function AdminRadioEmbedPage() {
<Code size={20} className="text-[var(--color-primary)]" />
</div>
<div>
<h1 className="m-0 text-xl font-extrabold text-[var(--color-text)]">Radio Embed</h1>
<p className="m-0 text-xs text-[var(--color-text-muted)] mt-0.5">Copy-ready embed code for the live stream. Read-only — nothing here writes to the database. Configure the stream URL in <Link href="/admin/radio/settings">Radio Settings</Link> (<code>radio_stream_url</code>).</p>
<h1 className="m-0 text-xl font-extrabold text-[var(--color-text)]">{t("embedPage.title")}</h1>
<p className="m-0 text-xs text-[var(--color-text-muted)] mt-0.5">{t("embedPage.subtitle")}</p>
</div>
</div>
<div className="grid grid-cols-[repeat(auto-fit,minmax(180px,1fr))] gap-3.5 mb-6">
<StatusCard
label="Stream URL"
value={hasStream ? 'Configured' : 'Missing'}
label={t("embedPage.streamUrl")}
value={hasStream ? t("embedPage.configured") : t("embedPage.missing")}
state={hasStream ? 'ok' : 'danger'}
icon="📻"
icon="\u{1F4BB}"
/>
<StatusCard label="Radio name" value={radioName} icon="🎙️" />
<StatusCard label={t("embedPage.radioName")} value={radioName} icon="\u{1F399}\uFE0F" />
<StatusCard
label="Now-playing API"
value={nowPlayingApiUrl ? 'Set' : 'Not set'}
label={t("embedPage.nowPlayingApi")}
value={nowPlayingApiUrl ? t("embedPage.set") : t("embedPage.notSet")}
state={nowPlayingApiUrl ? 'ok' : 'neutral'}
icon="🎵"
icon="\u{1F3B5}"
/>
</div>
</section>
{!hasStream ? (
<section className="mt-6">
<h2 className="admin-section-title">No stream URL configured</h2>
<h2 className="admin-section-title">{t("embedPage.noStreamUrl")}</h2>
<p className="text-xs text-gray-500 dark:text-gray-400">
There is no <code>radio_stream_url</code> set, so there is nothing to
embed yet. Open{' '}
<Link href="/admin/radio/settings">Radio Settings</Link> and fill in
the <strong>Main stream URL</strong> field under{' '}
<em>AzureCast &amp; Stream</em>. Once saved, return here to copy the
embed snippet and preview the player.
{t("embedPage.noStreamUrlText")}
</p>
<p>
<Link href="/admin/radio/settings" className="btn btn-primary">
Go to Radio Settings
{t("embedPage.goToSettings")}
</Link>
</p>
</section>
) : (
<>
<section className="mt-6">
<h2 className="admin-section-title">Live preview</h2>
<h2 className="admin-section-title">{t("embedPage.livePreview")}</h2>
<p className="text-xs text-gray-500 dark:text-gray-400">
This is the bare <code>&lt;audio&gt;</code> player as visitors will
see it. Press play to confirm the stream is reachable.
{t("embedPage.livePreviewText")}
</p>
<div className="admin-card">
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
<audio controls preload="none" src={streamUrl} style={{ width: '100%' }}>
Your browser does not support the audio element.
{t("embedPage.audioUnsupported")}
</audio>
<p className="text-xs text-gray-500 dark:text-gray-400" style={{ margin: '0.5rem 0 0', wordBreak: 'break-all' }}>
Source: <code>{streamUrl}</code>
{t("embedPage.source")}: <code>{streamUrl}</code>
</p>
</div>
</section>
<EmbedSnippet
title="Audio player snippet"
description="Drop-in HTML5 audio player pointed straight at the stream. Works anywhere plain HTML is allowed."
title={t("embedPage.audioSnippet")}
description={t("embedPage.audioSnippetDesc")}
snippet={audioSnippet}
/>
<EmbedSnippet
title="Iframe wrapper snippet"
description="Embeds the site's own radio player page in an iframe — keeps your styling and now-playing info. Adjust width/height to taste."
title={t("embedPage.iframeSnippet")}
description={t("embedPage.iframeSnippetDesc")}
snippet={iframeSnippet}
/>
</>