Fix theme consistency, i18n completeness, CSS variable naming, and hardcoded strings
This commit is contained in:
1 parent
0abd490f67
commit
b058a3827b
37 files changed
+431
-214
No files matched your search
@@ -72,7 +72,7 @@ export default async function AdminApplications() {
|
|||||||
{applications.length === 0 ? (
|
{applications.length === 0 ? (
|
||||||
<div className="admin-empty">{t("noApplications")}</div>
|
<div className="admin-empty">{t("noApplications")}</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid cols-2">
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
{applications.map((a) => {
|
{applications.map((a) => {
|
||||||
const username = userMap.get(a.userId);
|
const username = userMap.get(a.userId);
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ export default async function AdminCalendarCampaign({
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{rewards.map((r) => (
|
{rewards.map((r) => (
|
||||||
<tr key={r.id} className="border-t border-[var(--border)]">
|
<tr key={r.id} className="border-t border-[var(--border-subtle)]">
|
||||||
<td>{r.id}</td>
|
<td>{r.id}</td>
|
||||||
<td>{r.productName || <span className="text-sm text-gray-500 dark:text-gray-400">—</span>}</td>
|
<td>{r.productName || <span className="text-sm text-gray-500 dark:text-gray-400">—</span>}</td>
|
||||||
<td className="text-sm text-gray-500 dark:text-gray-400 whitespace-nowrap">{r.itemId > 0 ? r.itemId : "—"}</td>
|
<td className="text-sm text-gray-500 dark:text-gray-400 whitespace-nowrap">{r.itemId > 0 ? r.itemId : "—"}</td>
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export default async function AdminCalendar() {
|
|||||||
{campaigns.length === 0 ? (
|
{campaigns.length === 0 ? (
|
||||||
<div className="admin-empty">{t("noCampaigns")}</div>
|
<div className="admin-empty">{t("noCampaigns")}</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid cols-3">
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-3">
|
||||||
{campaigns.map((c) => {
|
{campaigns.map((c) => {
|
||||||
const enabled = c.enabled === "1";
|
const enabled = c.enabled === "1";
|
||||||
const count = rewardCounts.get(c.id) ?? 0;
|
const count = rewardCounts.get(c.id) ?? 0;
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ export default async function AdminCatalogPage({
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{items.map((it) => (
|
{items.map((it) => (
|
||||||
<tr key={it.id} style={{ borderTop: "1px solid var(--border)" }}>
|
<tr key={it.id} style={{ borderTop: "1px solid var(--border-subtle)" }}>
|
||||||
<td>{it.id}</td>
|
<td>{it.id}</td>
|
||||||
<td>
|
<td>
|
||||||
{it.catalogName}
|
{it.catalogName}
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ export default async function CommandoCentrum() {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* ── Server info + diagnostics ──────────────────────────── */}
|
{/* ── Server info + diagnostics ──────────────────────────── */}
|
||||||
<div className="grid cols-2">
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
<section className="admin-card p-5">
|
<section className="admin-card p-5">
|
||||||
<h2 className="admin-section-title">{t("serverInfo")}</h2>
|
<h2 className="admin-section-title">{t("serverInfo")}</h2>
|
||||||
<div className="grid grid-cols-[repeat(auto-fit,minmax(150px,1fr))] gap-2.5">
|
<div className="grid grid-cols-[repeat(auto-fit,minmax(150px,1fr))] gap-2.5">
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export function FaviconForm({ currentUrl }: { currentUrl: string | null }) {
|
|||||||
<img
|
<img
|
||||||
src={preview}
|
src={preview}
|
||||||
alt="Favicon preview"
|
alt="Favicon preview"
|
||||||
className="w-16 h-16 rounded-lg border-2 border-[var(--border)] object-contain bg-white"
|
className="w-16 h-16 rounded-lg border-2 border-[var(--border-subtle)] object-contain bg-white"
|
||||||
/>
|
/>
|
||||||
<div className="text-xs text-[var(--color-text-muted)] break-all">{preview}</div>
|
<div className="text-xs text-[var(--color-text-muted)] break-all">{preview}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export function FaviconGenerator({ onSave }: Props) {
|
|||||||
ref={canvasRef}
|
ref={canvasRef}
|
||||||
width={SIZE}
|
width={SIZE}
|
||||||
height={SIZE}
|
height={SIZE}
|
||||||
className="border-2 border-[var(--border)] rounded-xl shadow-lg"
|
className="border-2 border-[var(--border-subtle)] rounded-xl shadow-lg"
|
||||||
style={{ width: 128, height: 128, imageRendering: "pixelated" }}
|
style={{ width: 128, height: 128, imageRendering: "pixelated" }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -114,7 +114,7 @@ export function FaviconGenerator({ onSave }: Props) {
|
|||||||
type="color"
|
type="color"
|
||||||
value={bgColor}
|
value={bgColor}
|
||||||
onChange={(e) => setBgColor(e.target.value)}
|
onChange={(e) => setBgColor(e.target.value)}
|
||||||
className="w-10 h-10 rounded-lg cursor-pointer border border-[var(--border)]"
|
className="w-10 h-10 rounded-lg cursor-pointer border border-[var(--border-subtle)]"
|
||||||
/>
|
/>
|
||||||
<span className="text-xs text-[var(--color-text-muted)] font-mono">{bgColor}</span>
|
<span className="text-xs text-[var(--color-text-muted)] font-mono">{bgColor}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -142,7 +142,7 @@ export function FaviconGenerator({ onSave }: Props) {
|
|||||||
type="color"
|
type="color"
|
||||||
value={textColor}
|
value={textColor}
|
||||||
onChange={(e) => setTextColor(e.target.value)}
|
onChange={(e) => setTextColor(e.target.value)}
|
||||||
className="w-10 h-10 rounded-lg cursor-pointer border border-[var(--border)]"
|
className="w-10 h-10 rounded-lg cursor-pointer border border-[var(--border-subtle)]"
|
||||||
/>
|
/>
|
||||||
<span className="text-xs text-[var(--color-text-muted)] font-mono">{textColor}</span>
|
<span className="text-xs text-[var(--color-text-muted)] font-mono">{textColor}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export default async function AdminIp() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid cols-2">
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
{/* ── Whitelist ──────────────────── */}
|
{/* ── Whitelist ──────────────────── */}
|
||||||
<section className="mt-6">
|
<section className="mt-6">
|
||||||
<h2 className="admin-section-title">{t("whitelist")}</h2>
|
<h2 className="admin-section-title">{t("whitelist")}</h2>
|
||||||
|
|||||||
@@ -163,10 +163,10 @@ async function Topbar({ staff }: { staff: { id: number; username: string; rank:
|
|||||||
const t = await getTranslations("pages.admin.nav");
|
const t = await getTranslations("pages.admin.nav");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="flex items-center justify-between gap-4 bg-[var(--color-surface)] border border-[var(--border)] rounded-2xl px-5 py-3 mb-5 shadow-card">
|
<header className="flex items-center justify-between gap-4 bg-[var(--color-surface)] border border-[var(--border-subtle)] rounded-2xl px-5 py-3 mb-5 shadow-card">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<h2 className="m-0 text-base font-extrabold text-[var(--color-text)]">{t("housekeeping")}</h2>
|
<h2 className="m-0 text-base font-extrabold text-[var(--color-text)]">{t("housekeeping")}</h2>
|
||||||
<span className="hidden sm:inline-block w-px h-5 bg-[var(--border)]" />
|
<span className="hidden sm:inline-block w-px h-5 bg-[var(--border-subtle)]" />
|
||||||
<span className="hidden sm:inline text-xs text-[var(--color-text-muted)]">
|
<span className="hidden sm:inline text-xs text-[var(--color-text-muted)]">
|
||||||
{t("hotelManagementPanel")}
|
{t("hotelManagementPanel")}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ export default async function AdminDashboard() {
|
|||||||
) : (
|
) : (
|
||||||
<div className="space-y-0">
|
<div className="space-y-0">
|
||||||
{activity.map((a, i) => (
|
{activity.map((a, i) => (
|
||||||
<div key={String(a.id)} className={`flex items-center gap-3 py-2.5 ${i > 0 ? "border-t border-[var(--border)]" : ""}`}>
|
<div key={String(a.id)} className={`flex items-center gap-3 py-2.5 ${i > 0 ? "border-t border-[var(--border-subtle)]" : ""}`}>
|
||||||
<div className="w-7 h-7 rounded-full bg-[var(--color-primary)]/10 grid place-items-center flex-none">
|
<div className="w-7 h-7 rounded-full bg-[var(--color-primary)]/10 grid place-items-center flex-none">
|
||||||
<Activity size={12} className="text-[var(--color-primary)]" />
|
<Activity size={12} className="text-[var(--color-primary)]" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export default async function AdminPhotos() {
|
|||||||
{photos.length === 0 ? (
|
{photos.length === 0 ? (
|
||||||
<div className="admin-empty">{t("noPhotos")}</div>
|
<div className="admin-empty">{t("noPhotos")}</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid cols-3">
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-3">
|
||||||
{photos.map((p) => (
|
{photos.map((p) => (
|
||||||
<div key={String(p.id)} className="admin-card" style={{ padding: 0, overflow: "hidden" }}>
|
<div key={String(p.id)} className="admin-card" style={{ padding: 0, overflow: "hidden" }}>
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ export default async function AdminRadioApiKeysPage() {
|
|||||||
{t("apiKeysForm.generateHint")}
|
{t("apiKeysForm.generateHint")}
|
||||||
</p>
|
</p>
|
||||||
<form action={createApiKey} className="admin-card">
|
<form action={createApiKey} className="admin-card">
|
||||||
<div className="grid cols-2">
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="new_name" style={{ display: "block", fontWeight: 700 }}>
|
<label htmlFor="new_name" style={{ display: "block", fontWeight: 700 }}>
|
||||||
{t("apiKeysForm.name")}
|
{t("apiKeysForm.name")}
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ export default async function AdminRadioAutoDjPage() {
|
|||||||
<section className="mt-6">
|
<section className="mt-6">
|
||||||
<h2 className="admin-section-title">{t("autoDjForm.addTrack")}</h2>
|
<h2 className="admin-section-title">{t("autoDjForm.addTrack")}</h2>
|
||||||
<form action={createTrack} className="admin-card">
|
<form action={createTrack} className="admin-card">
|
||||||
<div className="grid cols-2">
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="new_title" style={{ display: 'block', fontWeight: 700 }}>
|
<label htmlFor="new_title" style={{ display: 'block', fontWeight: 700 }}>
|
||||||
{t("autoDjForm.title")}
|
{t("autoDjForm.title")}
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ export default async function AdminRadioBannersPage() {
|
|||||||
<section className="mt-6">
|
<section className="mt-6">
|
||||||
<h2 className="admin-section-title">{t("banners.addBanner")}</h2>
|
<h2 className="admin-section-title">{t("banners.addBanner")}</h2>
|
||||||
<form action={createRadioBanner} className="admin-card">
|
<form action={createRadioBanner} className="admin-card">
|
||||||
<div className="grid cols-2">
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="new_imagePath" style={{ display: 'block', fontWeight: 700 }}>
|
<label htmlFor="new_imagePath" style={{ display: 'block', fontWeight: 700 }}>
|
||||||
{t("banners.form.imagePath")}
|
{t("banners.form.imagePath")}
|
||||||
@@ -185,7 +185,7 @@ export default async function AdminRadioBannersPage() {
|
|||||||
|
|
||||||
<form action={updateRadioBanner}>
|
<form action={updateRadioBanner}>
|
||||||
<input type="hidden" name="id" value={String(b.id)} />
|
<input type="hidden" name="id" value={String(b.id)} />
|
||||||
<div className="grid cols-2">
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
htmlFor={`img_${b.id}`}
|
htmlFor={`img_${b.id}`}
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ export default async function AdminRadioPointsPage({
|
|||||||
<section className="mt-6">
|
<section className="mt-6">
|
||||||
<h2 className="admin-section-title">{t("pointsForm.settings")}</h2>
|
<h2 className="admin-section-title">{t("pointsForm.settings")}</h2>
|
||||||
<form action={savePoints}>
|
<form action={savePoints}>
|
||||||
<div className="grid cols-2">
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="radio_points_enabled" style={{ display: "block", fontWeight: 700 }}>
|
<label htmlFor="radio_points_enabled" style={{ display: "block", fontWeight: 700 }}>
|
||||||
{t("pointsForm.pointsEnabled")}
|
{t("pointsForm.pointsEnabled")}
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ export default async function AdminRadioRanksPage() {
|
|||||||
<section className="mt-6">
|
<section className="mt-6">
|
||||||
<h2 className="admin-section-title">{t("ranksForm.addRank")}</h2>
|
<h2 className="admin-section-title">{t("ranksForm.addRank")}</h2>
|
||||||
<form action={createRadioRank} className="admin-card">
|
<form action={createRadioRank} className="admin-card">
|
||||||
<div className="grid cols-3">
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-3">
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="new_name" style={{ display: 'block', fontWeight: 700 }}>
|
<label htmlFor="new_name" style={{ display: 'block', fontWeight: 700 }}>
|
||||||
{t("ranksForm.name")}
|
{t("ranksForm.name")}
|
||||||
|
|||||||
@@ -279,7 +279,7 @@ export default async function AdminRadioSettingsPage() {
|
|||||||
{groups.map((group) => (
|
{groups.map((group) => (
|
||||||
<section key={group.title} className="admin-card" style={{ marginBottom: '1.25rem' }}>
|
<section key={group.title} className="admin-card" style={{ marginBottom: '1.25rem' }}>
|
||||||
<h2 style={{ marginTop: 0 }}>{group.title}</h2>
|
<h2 style={{ marginTop: 0 }}>{group.title}</h2>
|
||||||
<div className="grid cols-2">
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
{group.fields.map((field) => (
|
{group.fields.map((field) => (
|
||||||
<div key={field.key}>
|
<div key={field.key}>
|
||||||
<label
|
<label
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export default async function AdminTagsPage() {
|
|||||||
|
|
||||||
<form action={createTag} className="admin-card mb-6">
|
<form action={createTag} className="admin-card mb-6">
|
||||||
<h3 style={{ marginTop: 0 }}>{t("addTag")}</h3>
|
<h3 style={{ marginTop: 0 }}>{t("addTag")}</h3>
|
||||||
<div className="grid cols-2">
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="new_name" style={{ display: 'block', fontWeight: 700 }}>
|
<label htmlFor="new_name" style={{ display: 'block', fontWeight: 700 }}>
|
||||||
{t("name")}
|
{t("name")}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export default async function AdminTeams() {
|
|||||||
<div className="admin-card" style={{ marginBottom: "1.5rem" }}>
|
<div className="admin-card" style={{ marginBottom: "1.5rem" }}>
|
||||||
<h3 style={{ marginTop: 0 }}>{t("newTeamRank")}</h3>
|
<h3 style={{ marginTop: 0 }}>{t("newTeamRank")}</h3>
|
||||||
<form action={createTeam} style={{ display: "grid", gap: "0.6rem" }}>
|
<form action={createTeam} style={{ display: "grid", gap: "0.6rem" }}>
|
||||||
<div className="grid cols-2">
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
<label style={{ display: "grid", gap: "0.25rem" }}>
|
<label style={{ display: "grid", gap: "0.25rem" }}>
|
||||||
<span className="text-sm text-gray-500 dark:text-gray-400">{t("rankName")}</span>
|
<span className="text-sm text-gray-500 dark:text-gray-400">{t("rankName")}</span>
|
||||||
<input name="rankName" placeholder={t("rankNamePlaceholder")} required />
|
<input name="rankName" placeholder={t("rankNamePlaceholder")} required />
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ export default async function AdminUserEdit({
|
|||||||
<form action={updateUser} className="admin-card">
|
<form action={updateUser} className="admin-card">
|
||||||
<input type="hidden" name="id" value={user.id} />
|
<input type="hidden" name="id" value={user.id} />
|
||||||
|
|
||||||
<div className="grid cols-2">
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
<label>
|
<label>
|
||||||
{t("email")}
|
{t("email")}
|
||||||
<input name="mail" type="email" defaultValue={user.mail ?? ""} maxLength={500} />
|
<input name="mail" type="email" defaultValue={user.mail ?? ""} maxLength={500} />
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ export default async function AdminUserDetail({
|
|||||||
|
|
||||||
<section className="mt-6">
|
<section className="mt-6">
|
||||||
<h2 className="admin-section-title">{t("detail.actions")}</h2>
|
<h2 className="admin-section-title">{t("detail.actions")}</h2>
|
||||||
<div className="grid cols-2">
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
<form action={giveCurrency} className="admin-card">
|
<form action={giveCurrency} className="admin-card">
|
||||||
<input type="hidden" name="userId" value={user.id} />
|
<input type="hidden" name="userId" value={user.id} />
|
||||||
<h3 className="text-sm font-bold text-[var(--color-text)] m-0 mb-3">{t("detail.giveCurrency")}</h3>
|
<h3 className="text-sm font-bold text-[var(--color-text)] m-0 mb-3">{t("detail.giveCurrency")}</h3>
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ export default async function AdminVouchers() {
|
|||||||
|
|
||||||
<form action={createVoucher} className="admin-card mb-6">
|
<form action={createVoucher} className="admin-card mb-6">
|
||||||
<h3 style={{ marginTop: 0 }}>{t("newVoucher")}</h3>
|
<h3 style={{ marginTop: 0 }}>{t("newVoucher")}</h3>
|
||||||
<div className="grid cols-2" style={{ marginBottom: "0.75rem" }}>
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-2" style={{ marginBottom: "0.75rem" }}>
|
||||||
<label style={{ display: "flex", flexDirection: "column", gap: "0.3rem" }}>
|
<label style={{ display: "flex", flexDirection: "column", gap: "0.3rem" }}>
|
||||||
<span className="text-sm text-gray-500 dark:text-gray-400">{t("form.code")}</span>
|
<span className="text-sm text-gray-500 dark:text-gray-400">{t("form.code")}</span>
|
||||||
<input name="code" placeholder={t("form.codePlaceholder")} maxLength={255} required />
|
<input name="code" placeholder={t("form.codePlaceholder")} maxLength={255} required />
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export default async function AdminVpn({
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid cols-2">
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
htmlFor="vpn_provider"
|
htmlFor="vpn_provider"
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export default async function AdminWriteableBoxes() {
|
|||||||
|
|
||||||
<section className="mt-6">
|
<section className="mt-6">
|
||||||
<h2 className="admin-section-title">{t("newBox")}</h2>
|
<h2 className="admin-section-title">{t("newBox")}</h2>
|
||||||
<form action={createBox} className="grid cols-2">
|
<form action={createBox} className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="new_title" style={{ display: "block", fontWeight: 700 }}>
|
<label htmlFor="new_title" style={{ display: "block", fontWeight: 700 }}>
|
||||||
{t("form.name")}
|
{t("form.name")}
|
||||||
@@ -127,7 +127,7 @@ export default async function AdminWriteableBoxes() {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form action={updateBox} className="grid cols-2">
|
<form action={updateBox} className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
<input type="hidden" name="id" value={String(box.id)} />
|
<input type="hidden" name="id" value={String(box.id)} />
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
|
|||||||
@@ -334,7 +334,7 @@ function EndpointRow({ endpoint }: { endpoint: Endpoint }) {
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: "0.5rem 0.75rem",
|
gap: "0.5rem 0.75rem",
|
||||||
padding: "0.7rem 0",
|
padding: "0.7rem 0",
|
||||||
borderTop: "1px solid var(--border)",
|
borderTop: "1px solid var(--border-subtle)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<code
|
<code
|
||||||
@@ -346,7 +346,7 @@ function EndpointRow({ endpoint }: { endpoint: Endpoint }) {
|
|||||||
"ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace",
|
"ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace",
|
||||||
fontSize: "0.85rem",
|
fontSize: "0.85rem",
|
||||||
background: "color-mix(in srgb, var(--color-text-muted) 8%, transparent)",
|
background: "color-mix(in srgb, var(--color-text-muted) 8%, transparent)",
|
||||||
border: "1px solid var(--border)",
|
border: "1px solid var(--border-subtle)",
|
||||||
borderRadius: "var(--radius-sm)",
|
borderRadius: "var(--radius-sm)",
|
||||||
padding: "0.3rem 0.55rem",
|
padding: "0.3rem 0.55rem",
|
||||||
whiteSpace: "nowrap",
|
whiteSpace: "nowrap",
|
||||||
@@ -396,7 +396,7 @@ export default function DevelopersPage() {
|
|||||||
"ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace",
|
"ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace",
|
||||||
fontSize: "0.85rem",
|
fontSize: "0.85rem",
|
||||||
background: "color-mix(in srgb, var(--color-text-muted) 8%, transparent)",
|
background: "color-mix(in srgb, var(--color-text-muted) 8%, transparent)",
|
||||||
border: "1px solid var(--border)",
|
border: "1px solid var(--border-subtle)",
|
||||||
borderRadius: "var(--radius-sm)",
|
borderRadius: "var(--radius-sm)",
|
||||||
padding: "0.6rem 0.75rem",
|
padding: "0.6rem 0.75rem",
|
||||||
whiteSpace: "pre-wrap",
|
whiteSpace: "pre-wrap",
|
||||||
|
|||||||
+4
-20
@@ -66,7 +66,7 @@
|
|||||||
--input-focus-color: #eeb425;
|
--input-focus-color: #eeb425;
|
||||||
--navbar-height: 56px;
|
--navbar-height: 56px;
|
||||||
--muted: var(--color-text-muted);
|
--muted: var(--color-text-muted);
|
||||||
--border: color-mix(in srgb, var(--color-text-muted) 18%, transparent);
|
--border-subtle: color-mix(in srgb, var(--color-text-muted) 18%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dark mode (toggled by html.dark; wins over :root + ThemeVars via specificity). */
|
/* Dark mode (toggled by html.dark; wins over :root + ThemeVars via specificity). */
|
||||||
@@ -250,22 +250,6 @@ a:hover {
|
|||||||
transform: translateY(-3px);
|
transform: translateY(-3px);
|
||||||
box-shadow: var(--shadow-card-hover);
|
box-shadow: var(--shadow-card-hover);
|
||||||
}
|
}
|
||||||
.grid {
|
|
||||||
display: grid;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
.grid.cols-2 {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
}
|
|
||||||
.grid.cols-3 {
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
}
|
|
||||||
@media (max-width: 720px) {
|
|
||||||
.grid.cols-2,
|
|
||||||
.grid.cols-3 {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.article-img {
|
.article-img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 140px;
|
height: 140px;
|
||||||
@@ -408,7 +392,7 @@ tbody tr:hover {
|
|||||||
.admin-card {
|
.admin-card {
|
||||||
background: var(--color-navbar);
|
background: var(--color-navbar);
|
||||||
color: var(--color-navbar-text);
|
color: var(--color-navbar-text);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border-subtle);
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
box-shadow: var(--shadow-card);
|
box-shadow: var(--shadow-card);
|
||||||
padding: 1.25rem;
|
padding: 1.25rem;
|
||||||
@@ -452,7 +436,7 @@ tbody tr:hover {
|
|||||||
.admin-page table {
|
.admin-page table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: var(--color-navbar);
|
background: var(--color-navbar);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border-subtle);
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-shadow: var(--shadow-card);
|
box-shadow: var(--shadow-card);
|
||||||
@@ -462,7 +446,7 @@ tbody tr:hover {
|
|||||||
.admin-page thead th {
|
.admin-page thead th {
|
||||||
background: color-mix(in srgb, var(--color-primary) 8%, var(--color-navbar));
|
background: color-mix(in srgb, var(--color-primary) 8%, var(--color-navbar));
|
||||||
color: var(--color-text-muted);
|
color: var(--color-text-muted);
|
||||||
border-bottom: 1px solid var(--border);
|
border-bottom: 1px solid var(--border-subtle);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
font-size: 0.68rem;
|
font-size: 0.68rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export function StatusCard({
|
|||||||
icon?: ReactNode;
|
icon?: ReactNode;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="bg-[var(--color-surface)] border border-[var(--border)] rounded-2xl p-5 shadow-card hover:shadow-card-hover transition-shadow duration-200">
|
<div className="bg-[var(--color-surface)] border border-[var(--border-subtle)] rounded-2xl p-5 shadow-card hover:shadow-card-hover transition-shadow duration-200">
|
||||||
<div className="flex items-center justify-between gap-3 mb-3">
|
<div className="flex items-center justify-between gap-3 mb-3">
|
||||||
<span className="text-[0.65rem] font-bold uppercase tracking-widest text-[var(--color-text-muted)]">{label}</span>
|
<span className="text-[0.65rem] font-bold uppercase tracking-widest text-[var(--color-text-muted)]">{label}</span>
|
||||||
{icon ? (
|
{icon ? (
|
||||||
@@ -62,7 +62,7 @@ export function DiagnosticRow({
|
|||||||
"bg-[var(--color-primary)]/18";
|
"bg-[var(--color-primary)]/18";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2.5 py-2 px-0.5 border-t border-[var(--border)] first:border-t-0">
|
<div className="flex items-center gap-2.5 py-2 px-0.5 border-t border-[var(--border-subtle)] first:border-t-0">
|
||||||
<span className={`flex-none w-[9px] h-[9px] rounded-full ${dotColor}`} aria-hidden />
|
<span className={`flex-none w-[9px] h-[9px] rounded-full ${dotColor}`} aria-hidden />
|
||||||
<span className="font-bold text-sm">{label}</span>
|
<span className="font-bold text-sm">{label}</span>
|
||||||
<span className="flex-1 text-[0.82rem] text-[var(--color-text-muted)]">{detail}</span>
|
<span className="flex-1 text-[0.82rem] text-[var(--color-text-muted)]">{detail}</span>
|
||||||
@@ -102,7 +102,7 @@ export function OnlineUsersWidget({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-[var(--color-surface)] rounded-xl border border-[var(--border)] p-6">
|
<div className="bg-[var(--color-surface)] rounded-xl border border-[var(--border-subtle)] p-6">
|
||||||
<div className="flex justify-between items-center mb-4">
|
<div className="flex justify-between items-center mb-4">
|
||||||
<h3 className="m-0 text-lg font-semibold text-[var(--color-text)]">Online Now</h3>
|
<h3 className="m-0 text-lg font-semibold text-[var(--color-text)]">Online Now</h3>
|
||||||
<span className="text-sm text-[var(--color-text-muted)] bg-[var(--color-primary)]/10 px-3 py-1 rounded-full">
|
<span className="text-sm text-[var(--color-text-muted)] bg-[var(--color-primary)]/10 px-3 py-1 rounded-full">
|
||||||
@@ -125,7 +125,7 @@ export function OnlineUsersWidget({
|
|||||||
<img
|
<img
|
||||||
src={`https://www.habbo.com/habbo-imaging/avatarimage?width=50&height=62&direction=2&headonly=1&crop=face&action=wav&gender=m&look=${encodeURIComponent(user.look)}`}
|
src={`https://www.habbo.com/habbo-imaging/avatarimage?width=50&height=62&direction=2&headonly=1&crop=face&action=wav&gender=m&look=${encodeURIComponent(user.look)}`}
|
||||||
alt={user.username}
|
alt={user.username}
|
||||||
className="w-full h-full rounded object-cover border border-[var(--border)]"
|
className="w-full h-full rounded object-cover border border-[var(--border-subtle)]"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
{user.roomId && user.roomName && (
|
{user.roomId && user.roomName && (
|
||||||
|
|||||||
@@ -68,8 +68,8 @@ export default async function GuestView() {
|
|||||||
<img src="/assets/images/EnterHubbly.png" alt="Register" className="block" />
|
<img src="/assets/images/EnterHubbly.png" alt="Register" className="block" />
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
className="pointer-events-none absolute inset-0 flex items-center text-4xl font-extrabold text-white"
|
className="pointer-events-none absolute inset-0 flex items-center text-4xl font-extrabold text-white text-shadow"
|
||||||
style={{ textShadow: "1px 1px 2px rgba(0,0,0,0.8)", marginLeft: "15px" }}
|
style={{ marginLeft: "15px" }}
|
||||||
>
|
>
|
||||||
REGISTER NOW
|
REGISTER NOW
|
||||||
</span>
|
</span>
|
||||||
@@ -77,8 +77,8 @@ export default async function GuestView() {
|
|||||||
|
|
||||||
{recentUsers.length > 0 && (
|
{recentUsers.length > 0 && (
|
||||||
<div
|
<div
|
||||||
className="overflow-hidden rounded-lg border bg-white dark:bg-gray-800"
|
className="overflow-hidden rounded-lg border"
|
||||||
style={{ borderColor: "color-mix(in srgb, var(--color-text-muted) 22%, transparent)" }}
|
style={{ borderColor: "color-mix(in srgb, var(--color-text-muted) 22%, transparent)", backgroundColor: "var(--color-surface)" }}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="flex h-12 w-full items-center justify-center bg-repeat"
|
className="flex h-12 w-full items-center justify-center bg-repeat"
|
||||||
@@ -93,8 +93,8 @@ export default async function GuestView() {
|
|||||||
{recentUsers.map((u) => (
|
{recentUsers.map((u) => (
|
||||||
<div
|
<div
|
||||||
key={u.username}
|
key={u.username}
|
||||||
className="relative overflow-visible rounded-md bg-white p-0 shadow-lg dark:bg-gray-800"
|
className="relative overflow-visible rounded-md p-0 shadow-lg"
|
||||||
style={{ width: "50px", height: "50px", justifySelf: "center" }}
|
style={{ width: "50px", height: "50px", justifySelf: "center", backgroundColor: "var(--color-surface)" }}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={avatarImageUrl(imager, u.look, { headOnly: true, direction: 3 })}
|
src={avatarImageUrl(imager, u.look, { headOnly: true, direction: 3 })}
|
||||||
@@ -121,8 +121,8 @@ export default async function GuestView() {
|
|||||||
|
|
||||||
<div className="flex w-full flex-col gap-4 md:w-2/3">
|
<div className="flex w-full flex-col gap-4 md:w-2/3">
|
||||||
<div
|
<div
|
||||||
className="overflow-hidden rounded-lg border bg-white dark:bg-gray-800"
|
className="overflow-hidden rounded-lg border"
|
||||||
style={{ borderColor: "color-mix(in srgb, var(--color-text-muted) 22%, transparent)" }}
|
style={{ borderColor: "color-mix(in srgb, var(--color-text-muted) 22%, transparent)", backgroundColor: "var(--color-surface)" }}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="flex h-12 w-full items-center justify-center"
|
className="flex h-12 w-full items-center justify-center"
|
||||||
@@ -135,7 +135,7 @@ export default async function GuestView() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
{articles.length === 0 ? (
|
{articles.length === 0 ? (
|
||||||
<p className="py-8 text-center text-gray-500 dark:text-gray-400">No articles available</p>
|
<p className="py-8 text-center" style={{ color: "var(--color-text-muted)" }}>{t("noArticles")}</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||||
{articles.map((a) => (
|
{articles.map((a) => (
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ export default async function UserView({ userId, username, look }: UserViewProps
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3 px-3">
|
<div className="flex items-center gap-3 px-3">
|
||||||
{onlineFriends.length === 0 ? (
|
{onlineFriends.length === 0 ? (
|
||||||
<p className="mb-3 block w-full text-center text-xs font-medium text-gray-500 dark:text-white md:mb-0 md:text-left">
|
<p className="mb-3 block w-full text-center text-xs font-medium md:mb-0 md:text-left" style={{ color: "var(--color-text-muted)" }}>
|
||||||
{t("noFriendsOnline")}
|
{t("noFriendsOnline")}
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
@@ -178,7 +178,7 @@ export default async function UserView({ userId, username, look }: UserViewProps
|
|||||||
<div className="p-3">
|
<div className="p-3">
|
||||||
<h3 className="text-sm font-semibold">{latestArticle.title}</h3>
|
<h3 className="text-sm font-semibold">{latestArticle.title}</h3>
|
||||||
{latestArticle.shortStory && (
|
{latestArticle.shortStory && (
|
||||||
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
<p className="mt-1 text-xs" style={{ color: "var(--color-text-muted)" }}>
|
||||||
{excerpt(latestArticle.shortStory, 100)}
|
{excerpt(latestArticle.shortStory, 100)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -20,8 +20,7 @@ export async function Navigation() {
|
|||||||
<div className="flex items-center gap-x-1 md:gap-x-1 flex-1 md:flex-none">
|
<div className="flex items-center gap-x-1 md:gap-x-1 flex-1 md:flex-none">
|
||||||
<Link href="/" className="nav-item shrink-0">
|
<Link href="/" className="nav-item shrink-0">
|
||||||
<img src="/assets/images/icons/navigation/me.png" alt="" className="w-5 h-5 mr-1.5" />
|
<img src="/assets/images/icons/navigation/me.png" alt="" className="w-5 h-5 mr-1.5" />
|
||||||
<span className="hidden sm:inline">{t("home")}</span>
|
<span>{t("home")}</span>
|
||||||
<span className="sm:hidden">{t("home")}</span>
|
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export async function SiteHeader() {
|
|||||||
online = 0;
|
online = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bannerUrl = header && header !== "/assets/images/background.png" ? header : "/assets/images/mybobba_banner.png";
|
const bannerUrl = header && header !== "/assets/images/background.png" ? header : "/assets/images/banner.png";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="site-header relative w-full overflow-hidden" style={{ minHeight: "18rem" }}>
|
<div className="site-header relative w-full overflow-hidden" style={{ minHeight: "18rem" }}>
|
||||||
|
|||||||
+93
-35
@@ -696,7 +696,8 @@
|
|||||||
"settings": "Einstellungen",
|
"settings": "Einstellungen",
|
||||||
"backToSite": "Zurück zur Website",
|
"backToSite": "Zurück zur Website",
|
||||||
"hotelManagementPanel": "Hotel-Verwaltungsbereich",
|
"hotelManagementPanel": "Hotel-Verwaltungsbereich",
|
||||||
"rankLabel": "Rang {rank}"
|
"rankLabel": "Rang {rank}",
|
||||||
|
"favicon": "Favicon"
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"title": "Dashboard",
|
"title": "Dashboard",
|
||||||
@@ -1412,7 +1413,28 @@
|
|||||||
"apiKeys": "API-Schlüssel",
|
"apiKeys": "API-Schlüssel",
|
||||||
"points": "Punkte",
|
"points": "Punkte",
|
||||||
"autoDj": "AutoDJ",
|
"autoDj": "AutoDJ",
|
||||||
"monitoring": "Überwachung",
|
"monitoring": {
|
||||||
|
"title": "Radio-Überwachung",
|
||||||
|
"subtitle": "Read-only live probe of the configured radio endpoints (radio_now_playing_api_url, radio_listeners_api_url). Each request fails soft with a {timeout}s timeout — no data is written. Configure URLs under Settings.",
|
||||||
|
"stream": "Stream",
|
||||||
|
"online": "Online",
|
||||||
|
"offline": "Offline",
|
||||||
|
"notConfigured": "Not configured",
|
||||||
|
"nowPlaying": "Now playing",
|
||||||
|
"nowPlayingApi": "Now-playing API",
|
||||||
|
"listenersApi": "Listeners API",
|
||||||
|
"listeners": "Hörer",
|
||||||
|
"currentSong": "Aktueller Song",
|
||||||
|
"bitrate": "Bitrate",
|
||||||
|
"status": "Status",
|
||||||
|
"noNowPlayingHint": "no now-playing API set",
|
||||||
|
"noListenersHint": "no listeners API set",
|
||||||
|
"endpointDiagnostics": "Endpoint diagnostics",
|
||||||
|
"diagnosticsLegend": "<span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-green-600/20 text-[var(--color-text)]\">OK</span> reachable · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-[var(--color-primary)]/18 text-[var(--color-text)]\">WARN</span> not configured · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-red-500/22 text-[var(--color-text)]\">ERROR</span> fetch failed / timed out.",
|
||||||
|
"streamUrl": "Stream URL",
|
||||||
|
"unreachable": "unreachable",
|
||||||
|
"refresh": "Aktualisieren"
|
||||||
|
},
|
||||||
"user": "User",
|
"user": "User",
|
||||||
"message": "Message",
|
"message": "Message",
|
||||||
"posted": "Posted",
|
"posted": "Posted",
|
||||||
@@ -1455,7 +1477,7 @@
|
|||||||
},
|
},
|
||||||
"embedPage": {
|
"embedPage": {
|
||||||
"title": "Radio Embed",
|
"title": "Radio Embed",
|
||||||
"subtitle": "Copy-ready embed code for the live stream. Read-only \u2014 nothing here writes to the database. Configure the stream URL in Radio Settings (radio_stream_url).",
|
"subtitle": "Copy-ready embed code for the live stream. Read-only — nothing here writes to the database. Configure the stream URL in Radio Settings (radio_stream_url).",
|
||||||
"streamUrl": "Stream URL",
|
"streamUrl": "Stream URL",
|
||||||
"configured": "Configured",
|
"configured": "Configured",
|
||||||
"missing": "Missing",
|
"missing": "Missing",
|
||||||
@@ -1472,7 +1494,7 @@
|
|||||||
"audioSnippet": "Audio player snippet",
|
"audioSnippet": "Audio player snippet",
|
||||||
"audioSnippetDesc": "Drop-in HTML5 audio player pointed straight at the stream. Works anywhere plain HTML is allowed.",
|
"audioSnippetDesc": "Drop-in HTML5 audio player pointed straight at the stream. Works anywhere plain HTML is allowed.",
|
||||||
"iframeSnippet": "Iframe wrapper snippet",
|
"iframeSnippet": "Iframe wrapper snippet",
|
||||||
"iframeSnippetDesc": "Embeds the site\u2019s own radio player page in an iframe \u2014 keeps your styling and now-playing info. Adjust width/height to taste.",
|
"iframeSnippetDesc": "Embeds the site’s own radio player page in an iframe — keeps your styling and now-playing info. Adjust width/height to taste.",
|
||||||
"audioUnsupported": "Your browser does not support the audio element."
|
"audioUnsupported": "Your browser does not support the audio element."
|
||||||
},
|
},
|
||||||
"banners": {
|
"banners": {
|
||||||
@@ -1482,7 +1504,7 @@
|
|||||||
"addBanner": "Banner hinzufügen",
|
"addBanner": "Banner hinzufügen",
|
||||||
"createBanner": "Create banner",
|
"createBanner": "Create banner",
|
||||||
"deleteBanner": "Banner löschen",
|
"deleteBanner": "Banner löschen",
|
||||||
"noBanners": "No banners yet \u2014 add one above.",
|
"noBanners": "No banners yet — add one above.",
|
||||||
"existing": "Existing banners ({count})",
|
"existing": "Existing banners ({count})",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
@@ -1513,7 +1535,7 @@
|
|||||||
"description": "Description",
|
"description": "Description",
|
||||||
"createRank": "Create rank",
|
"createRank": "Create rank",
|
||||||
"existing": "Existing ranks ({count})",
|
"existing": "Existing ranks ({count})",
|
||||||
"noRanks": "No ranks yet \u2014 add one above.",
|
"noRanks": "No ranks yet — add one above.",
|
||||||
"activeLabel": "active",
|
"activeLabel": "active",
|
||||||
"inactiveLabel": "inactive",
|
"inactiveLabel": "inactive",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
@@ -1540,13 +1562,13 @@
|
|||||||
},
|
},
|
||||||
"apiKeysForm": {
|
"apiKeysForm": {
|
||||||
"title": "API-Schlüssel",
|
"title": "API-Schlüssel",
|
||||||
"subtitle": "API keys for external radio integrations (radio_api_keys). Keys are generated server-side and shown masked \u2014 copy the full value from the database when first issued.",
|
"subtitle": "API keys for external radio integrations (radio_api_keys). Keys are generated server-side and shown masked — copy the full value from the database when first issued.",
|
||||||
"dbError": "Could not load API keys (database unavailable).",
|
"dbError": "Could not load API keys (database unavailable).",
|
||||||
"totalKeys": "Total keys",
|
"totalKeys": "Total keys",
|
||||||
"active": "Active",
|
"active": "Active",
|
||||||
"inactive": "Inactive",
|
"inactive": "Inactive",
|
||||||
"generateKey": "Neuen Schlüssel generieren",
|
"generateKey": "Neuen Schlüssel generieren",
|
||||||
"generateHint": "The secret key is generated automatically with cryptographic randomness \u2014 you do not enter it.",
|
"generateHint": "The secret key is generated automatically with cryptographic randomness — you do not enter it.",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"namePlaceholder": "AzureCast bridge",
|
"namePlaceholder": "AzureCast bridge",
|
||||||
"rateLimit": "Rate limit (requests/min)",
|
"rateLimit": "Rate limit (requests/min)",
|
||||||
@@ -1554,7 +1576,7 @@
|
|||||||
"allowedIpsPlaceholder": "comma-separated, blank = any",
|
"allowedIpsPlaceholder": "comma-separated, blank = any",
|
||||||
"generate": "Generieren",
|
"generate": "Generieren",
|
||||||
"existingKeys": "Existing keys ({count})",
|
"existingKeys": "Existing keys ({count})",
|
||||||
"noKeys": "No API keys yet \u2014 generate one above.",
|
"noKeys": "No API keys yet — generate one above.",
|
||||||
"colName": "Name",
|
"colName": "Name",
|
||||||
"colKey": "Schlüssel",
|
"colKey": "Schlüssel",
|
||||||
"colAllowedIps": "Allowed IPs",
|
"colAllowedIps": "Allowed IPs",
|
||||||
@@ -1595,24 +1617,23 @@
|
|||||||
"save": "Punkteeinstellungen speichern"
|
"save": "Punkteeinstellungen speichern"
|
||||||
},
|
},
|
||||||
"autoDjForm": {
|
"autoDjForm": {
|
||||||
"title": "AutoDJ-Konfiguration",
|
"title": "Title",
|
||||||
"subtitle": "Fallback playlist (radio_auto_dj_playlist) the radio rotates through when no live DJ is streaming. Ordered by sort order then title.",
|
"subtitle": "Fallback playlist (radio_auto_dj_playlist) the radio rotates through when no live DJ is streaming. Ordered by sort order then title.",
|
||||||
"dbError": "Could not load the AutoDJ playlist (database unavailable).",
|
"dbError": "Could not load the AutoDJ playlist (database unavailable).",
|
||||||
"tracks": "Tracks",
|
"tracks": "Tracks",
|
||||||
"active": "Active",
|
"active": "Active",
|
||||||
"inactive": "Inactive",
|
"inactive": "Inactive",
|
||||||
"addTrack": "Add track",
|
"addTrack": "Add track",
|
||||||
"title": "Title",
|
|
||||||
"titlePlaceholder": "Track title",
|
"titlePlaceholder": "Track title",
|
||||||
"artist": "Artist",
|
"artist": "Artist",
|
||||||
"album": "Album",
|
"album": "Album",
|
||||||
"artworkUrl": "Artwork URL",
|
"artworkUrl": "Artwork URL",
|
||||||
"artworkUrlPlaceholder": "https://\u2026/cover.png",
|
"artworkUrlPlaceholder": "https://…/cover.png",
|
||||||
"duration": "Duration (seconds)",
|
"duration": "Duration (seconds)",
|
||||||
"durationPlaceholder": "e.g. 215",
|
"durationPlaceholder": "e.g. 215",
|
||||||
"sortOrder": "Sort order",
|
"sortOrder": "Sort order",
|
||||||
"playlist": "Playlist ({count})",
|
"playlist": "Playlist ({count})",
|
||||||
"noTracks": "No AutoDJ tracks yet \u2014 add one above.",
|
"noTracks": "No AutoDJ tracks yet — add one above.",
|
||||||
"colOrder": "Order",
|
"colOrder": "Order",
|
||||||
"colTitle": "Title",
|
"colTitle": "Title",
|
||||||
"colArtist": "Artist",
|
"colArtist": "Artist",
|
||||||
@@ -1628,28 +1649,6 @@
|
|||||||
"activate": "Activate",
|
"activate": "Activate",
|
||||||
"delete": "Delete"
|
"delete": "Delete"
|
||||||
},
|
},
|
||||||
"monitoring": {
|
|
||||||
"title": "Radio-Überwachung",
|
|
||||||
"subtitle": "Read-only live probe of the configured radio endpoints (radio_now_playing_api_url, radio_listeners_api_url). Each request fails soft with a {timeout}s timeout \u2014 no data is written. Configure URLs under Settings.",
|
|
||||||
"stream": "Stream",
|
|
||||||
"online": "Online",
|
|
||||||
"offline": "Offline",
|
|
||||||
"notConfigured": "Not configured",
|
|
||||||
"nowPlaying": "Now playing",
|
|
||||||
"nowPlayingApi": "Now-playing API",
|
|
||||||
"listenersApi": "Listeners API",
|
|
||||||
"listeners": "Hörer",
|
|
||||||
"currentSong": "Aktueller Song",
|
|
||||||
"bitrate": "Bitrate",
|
|
||||||
"status": "Status",
|
|
||||||
"noNowPlayingHint": "no now-playing API set",
|
|
||||||
"noListenersHint": "no listeners API set",
|
|
||||||
"endpointDiagnostics": "Endpoint diagnostics",
|
|
||||||
"diagnosticsLegend": "<span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-green-600/20 text-[var(--color-text)]\">OK</span> reachable \u00b7 <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-[var(--color-primary)]/18 text-[var(--color-text)]\">WARN</span> not configured \u00b7 <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-red-500/22 text-[var(--color-text)]\">ERROR</span> fetch failed / timed out.",
|
|
||||||
"streamUrl": "Stream URL",
|
|
||||||
"unreachable": "unreachable",
|
|
||||||
"refresh": "Aktualisieren"
|
|
||||||
},
|
|
||||||
"moderationPage": {
|
"moderationPage": {
|
||||||
"title": "DJ Moderation",
|
"title": "DJ Moderation",
|
||||||
"subtitle": "Review and remove radio shouts (50 most recent).",
|
"subtitle": "Review and remove radio shouts (50 most recent).",
|
||||||
@@ -1955,7 +1954,66 @@
|
|||||||
"saving": "Wird gespeichert…",
|
"saving": "Wird gespeichert…",
|
||||||
"cancel": "Abbrechen"
|
"cancel": "Abbrechen"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"favicon": {
|
||||||
|
"title": "Favicon",
|
||||||
|
"subtitle": "Upload and manage the site favicon",
|
||||||
|
"current": "Current favicon",
|
||||||
|
"uploadLabel": "Upload favicon",
|
||||||
|
"uploadHint": "PNG, JPEG, GIF, WebP, ICO or SVG. Max 2MB.",
|
||||||
|
"upload": "Upload",
|
||||||
|
"uploading": "Uploading…",
|
||||||
|
"delete": "Remove",
|
||||||
|
"deleting": "Removing…",
|
||||||
|
"saved": "Favicon saved successfully!",
|
||||||
|
"deleted": "Favicon removed successfully!",
|
||||||
|
"error": "Something went wrong",
|
||||||
|
"confirmDelete": "Remove the favicon? The site will no longer show a custom favicon.",
|
||||||
|
"generator": {
|
||||||
|
"title": "Generate favicon",
|
||||||
|
"text": "Text",
|
||||||
|
"bgColor": "Background color",
|
||||||
|
"textColor": "Text color",
|
||||||
|
"rounded": "Rounded corners",
|
||||||
|
"save": "Save as favicon"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"client": {
|
||||||
|
"title": "Nitro client",
|
||||||
|
"loading": "Loading client…",
|
||||||
|
"generatingTicket": "Generating SSO ticket…",
|
||||||
|
"connected": "Connected",
|
||||||
|
"welcome": "Welcome to {hotel}",
|
||||||
|
"openingClient": "Opening the client in a new tab…",
|
||||||
|
"fallbackLink": "If it doesn't open,",
|
||||||
|
"fallbackClick": "click here",
|
||||||
|
"errors": {
|
||||||
|
"unauthorized": "You need to be logged in to open the client.",
|
||||||
|
"generic": "Could not generate SSO ticket",
|
||||||
|
"loginRedirect": "Log in"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"radioRequests": {
|
||||||
|
"formTitle": "Submit a request",
|
||||||
|
"formSubtitle": "Ask the DJ to play your favourite track",
|
||||||
|
"songLabel": "Song",
|
||||||
|
"songPlaceholder": "Title of the song",
|
||||||
|
"artistLabel": "Artist",
|
||||||
|
"artistPlaceholder": "Name of the artist (optional)",
|
||||||
|
"submitButton": "Submit request",
|
||||||
|
"loginToRequest": "Log in to submit a request.",
|
||||||
|
"latestTitle": "Recent requests",
|
||||||
|
"latestSubtitle": "The most recently requested songs",
|
||||||
|
"empty": "No requests yet.",
|
||||||
|
"colSong": "Song",
|
||||||
|
"colArtist": "Artist",
|
||||||
|
"colRequester": "Requester",
|
||||||
|
"colDate": "Date",
|
||||||
|
"colStatus": "Status",
|
||||||
|
"statusPending": "Pending",
|
||||||
|
"statusApproved": "Approved",
|
||||||
|
"statusPlayed": "Played"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -724,7 +724,6 @@
|
|||||||
"logs": "Logs",
|
"logs": "Logs",
|
||||||
"wordFilter": "Word filter",
|
"wordFilter": "Word filter",
|
||||||
"maintenance": "Maintenance",
|
"maintenance": "Maintenance",
|
||||||
"housekeeping": "Housekeeping",
|
|
||||||
"rooms": "Rooms",
|
"rooms": "Rooms",
|
||||||
"emailTemplates": "Email templates",
|
"emailTemplates": "Email templates",
|
||||||
"theme": "Theme",
|
"theme": "Theme",
|
||||||
|
|||||||
+94
-36
@@ -696,7 +696,8 @@
|
|||||||
"settings": "Ajustes",
|
"settings": "Ajustes",
|
||||||
"backToSite": "Volver al sitio",
|
"backToSite": "Volver al sitio",
|
||||||
"hotelManagementPanel": "Panel de gestión del hotel",
|
"hotelManagementPanel": "Panel de gestión del hotel",
|
||||||
"rankLabel": "Rango {rank}"
|
"rankLabel": "Rango {rank}",
|
||||||
|
"favicon": "Favicon"
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"title": "Panel",
|
"title": "Panel",
|
||||||
@@ -1412,7 +1413,28 @@
|
|||||||
"apiKeys": "Claves de API",
|
"apiKeys": "Claves de API",
|
||||||
"points": "Puntos",
|
"points": "Puntos",
|
||||||
"autoDj": "AutoDJ",
|
"autoDj": "AutoDJ",
|
||||||
"monitoring": "Monitorización",
|
"monitoring": {
|
||||||
|
"title": "Monitorización de radio",
|
||||||
|
"subtitle": "Read-only live probe of the configured radio endpoints (radio_now_playing_api_url, radio_listeners_api_url). Each request fails soft with a {timeout}s timeout — no data is written. Configure URLs under Settings.",
|
||||||
|
"stream": "Stream",
|
||||||
|
"online": "En línea",
|
||||||
|
"offline": "Fuera de línea",
|
||||||
|
"notConfigured": "Not configured",
|
||||||
|
"nowPlaying": "Now playing",
|
||||||
|
"nowPlayingApi": "Now-playing API",
|
||||||
|
"listenersApi": "Listeners API",
|
||||||
|
"listeners": "Oyentes",
|
||||||
|
"currentSong": "Canción actual",
|
||||||
|
"bitrate": "Bitrate",
|
||||||
|
"status": "Estado",
|
||||||
|
"noNowPlayingHint": "no now-playing API set",
|
||||||
|
"noListenersHint": "no listeners API set",
|
||||||
|
"endpointDiagnostics": "Endpoint diagnostics",
|
||||||
|
"diagnosticsLegend": "<span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-green-600/20 text-[var(--color-text)]\">OK</span> reachable · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-[var(--color-primary)]/18 text-[var(--color-text)]\">WARN</span> not configured · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-red-500/22 text-[var(--color-text)]\">ERROR</span> fetch failed / timed out.",
|
||||||
|
"streamUrl": "Stream URL",
|
||||||
|
"unreachable": "unreachable",
|
||||||
|
"refresh": "Actualizar"
|
||||||
|
},
|
||||||
"user": "User",
|
"user": "User",
|
||||||
"message": "Message",
|
"message": "Message",
|
||||||
"posted": "Posted",
|
"posted": "Posted",
|
||||||
@@ -1455,7 +1477,7 @@
|
|||||||
},
|
},
|
||||||
"embedPage": {
|
"embedPage": {
|
||||||
"title": "Radio Embed",
|
"title": "Radio Embed",
|
||||||
"subtitle": "Copy-ready embed code for the live stream. Read-only \u2014 nothing here writes to the database. Configure the stream URL in Radio Settings (radio_stream_url).",
|
"subtitle": "Copy-ready embed code for the live stream. Read-only — nothing here writes to the database. Configure the stream URL in Radio Settings (radio_stream_url).",
|
||||||
"streamUrl": "Stream URL",
|
"streamUrl": "Stream URL",
|
||||||
"configured": "Configured",
|
"configured": "Configured",
|
||||||
"missing": "Missing",
|
"missing": "Missing",
|
||||||
@@ -1472,7 +1494,7 @@
|
|||||||
"audioSnippet": "Audio player snippet",
|
"audioSnippet": "Audio player snippet",
|
||||||
"audioSnippetDesc": "Drop-in HTML5 audio player pointed straight at the stream. Works anywhere plain HTML is allowed.",
|
"audioSnippetDesc": "Drop-in HTML5 audio player pointed straight at the stream. Works anywhere plain HTML is allowed.",
|
||||||
"iframeSnippet": "Iframe wrapper snippet",
|
"iframeSnippet": "Iframe wrapper snippet",
|
||||||
"iframeSnippetDesc": "Embeds the site\u2019s own radio player page in an iframe \u2014 keeps your styling and now-playing info. Adjust width/height to taste.",
|
"iframeSnippetDesc": "Embeds the site’s own radio player page in an iframe — keeps your styling and now-playing info. Adjust width/height to taste.",
|
||||||
"audioUnsupported": "Your browser does not support the audio element."
|
"audioUnsupported": "Your browser does not support the audio element."
|
||||||
},
|
},
|
||||||
"banners": {
|
"banners": {
|
||||||
@@ -1482,7 +1504,7 @@
|
|||||||
"addBanner": "Añadir banner",
|
"addBanner": "Añadir banner",
|
||||||
"createBanner": "Create banner",
|
"createBanner": "Create banner",
|
||||||
"deleteBanner": "Eliminar banner",
|
"deleteBanner": "Eliminar banner",
|
||||||
"noBanners": "No banners yet \u2014 add one above.",
|
"noBanners": "No banners yet — add one above.",
|
||||||
"existing": "Existing banners ({count})",
|
"existing": "Existing banners ({count})",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
@@ -1513,7 +1535,7 @@
|
|||||||
"description": "Description",
|
"description": "Description",
|
||||||
"createRank": "Create rank",
|
"createRank": "Create rank",
|
||||||
"existing": "Existing ranks ({count})",
|
"existing": "Existing ranks ({count})",
|
||||||
"noRanks": "No ranks yet \u2014 add one above.",
|
"noRanks": "No ranks yet — add one above.",
|
||||||
"activeLabel": "active",
|
"activeLabel": "active",
|
||||||
"inactiveLabel": "inactive",
|
"inactiveLabel": "inactive",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
@@ -1540,13 +1562,13 @@
|
|||||||
},
|
},
|
||||||
"apiKeysForm": {
|
"apiKeysForm": {
|
||||||
"title": "Claves de API",
|
"title": "Claves de API",
|
||||||
"subtitle": "API keys for external radio integrations (radio_api_keys). Keys are generated server-side and shown masked \u2014 copy the full value from the database when first issued.",
|
"subtitle": "API keys for external radio integrations (radio_api_keys). Keys are generated server-side and shown masked — copy the full value from the database when first issued.",
|
||||||
"dbError": "Could not load API keys (database unavailable).",
|
"dbError": "Could not load API keys (database unavailable).",
|
||||||
"totalKeys": "Total keys",
|
"totalKeys": "Total keys",
|
||||||
"active": "Active",
|
"active": "Active",
|
||||||
"inactive": "Inactive",
|
"inactive": "Inactive",
|
||||||
"generateKey": "Generar nueva clave",
|
"generateKey": "Generar nueva clave",
|
||||||
"generateHint": "The secret key is generated automatically with cryptographic randomness \u2014 you do not enter it.",
|
"generateHint": "The secret key is generated automatically with cryptographic randomness — you do not enter it.",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"namePlaceholder": "AzureCast bridge",
|
"namePlaceholder": "AzureCast bridge",
|
||||||
"rateLimit": "Rate limit (requests/min)",
|
"rateLimit": "Rate limit (requests/min)",
|
||||||
@@ -1554,7 +1576,7 @@
|
|||||||
"allowedIpsPlaceholder": "comma-separated, blank = any",
|
"allowedIpsPlaceholder": "comma-separated, blank = any",
|
||||||
"generate": "Generar",
|
"generate": "Generar",
|
||||||
"existingKeys": "Existing keys ({count})",
|
"existingKeys": "Existing keys ({count})",
|
||||||
"noKeys": "No API keys yet \u2014 generate one above.",
|
"noKeys": "No API keys yet — generate one above.",
|
||||||
"colName": "Name",
|
"colName": "Name",
|
||||||
"colKey": "Clave",
|
"colKey": "Clave",
|
||||||
"colAllowedIps": "Allowed IPs",
|
"colAllowedIps": "Allowed IPs",
|
||||||
@@ -1595,24 +1617,23 @@
|
|||||||
"save": "Guardar configuración de puntos"
|
"save": "Guardar configuración de puntos"
|
||||||
},
|
},
|
||||||
"autoDjForm": {
|
"autoDjForm": {
|
||||||
"title": "Configuración de AutoDJ",
|
"title": "Title",
|
||||||
"subtitle": "Fallback playlist (radio_auto_dj_playlist) the radio rotates through when no live DJ is streaming. Ordered by sort order then title.",
|
"subtitle": "Fallback playlist (radio_auto_dj_playlist) the radio rotates through when no live DJ is streaming. Ordered by sort order then title.",
|
||||||
"dbError": "Could not load the AutoDJ playlist (database unavailable).",
|
"dbError": "Could not load the AutoDJ playlist (database unavailable).",
|
||||||
"tracks": "Tracks",
|
"tracks": "Tracks",
|
||||||
"active": "Active",
|
"active": "Active",
|
||||||
"inactive": "Inactive",
|
"inactive": "Inactive",
|
||||||
"addTrack": "Add track",
|
"addTrack": "Add track",
|
||||||
"title": "Title",
|
|
||||||
"titlePlaceholder": "Track title",
|
"titlePlaceholder": "Track title",
|
||||||
"artist": "Artist",
|
"artist": "Artist",
|
||||||
"album": "Album",
|
"album": "Album",
|
||||||
"artworkUrl": "Artwork URL",
|
"artworkUrl": "Artwork URL",
|
||||||
"artworkUrlPlaceholder": "https://\u2026/cover.png",
|
"artworkUrlPlaceholder": "https://…/cover.png",
|
||||||
"duration": "Duration (seconds)",
|
"duration": "Duration (seconds)",
|
||||||
"durationPlaceholder": "e.g. 215",
|
"durationPlaceholder": "e.g. 215",
|
||||||
"sortOrder": "Sort order",
|
"sortOrder": "Sort order",
|
||||||
"playlist": "Playlist ({count})",
|
"playlist": "Playlist ({count})",
|
||||||
"noTracks": "No AutoDJ tracks yet \u2014 add one above.",
|
"noTracks": "No AutoDJ tracks yet — add one above.",
|
||||||
"colOrder": "Order",
|
"colOrder": "Order",
|
||||||
"colTitle": "Title",
|
"colTitle": "Title",
|
||||||
"colArtist": "Artist",
|
"colArtist": "Artist",
|
||||||
@@ -1628,28 +1649,6 @@
|
|||||||
"activate": "Activate",
|
"activate": "Activate",
|
||||||
"delete": "Delete"
|
"delete": "Delete"
|
||||||
},
|
},
|
||||||
"monitoring": {
|
|
||||||
"title": "Monitorización de radio",
|
|
||||||
"subtitle": "Read-only live probe of the configured radio endpoints (radio_now_playing_api_url, radio_listeners_api_url). Each request fails soft with a {timeout}s timeout \u2014 no data is written. Configure URLs under Settings.",
|
|
||||||
"stream": "Stream",
|
|
||||||
"online": "En línea",
|
|
||||||
"offline": "Fuera de línea",
|
|
||||||
"notConfigured": "Not configured",
|
|
||||||
"nowPlaying": "Now playing",
|
|
||||||
"nowPlayingApi": "Now-playing API",
|
|
||||||
"listenersApi": "Listeners API",
|
|
||||||
"listeners": "Oyentes",
|
|
||||||
"currentSong": "Canción actual",
|
|
||||||
"bitrate": "Bitrate",
|
|
||||||
"status": "Estado",
|
|
||||||
"noNowPlayingHint": "no now-playing API set",
|
|
||||||
"noListenersHint": "no listeners API set",
|
|
||||||
"endpointDiagnostics": "Endpoint diagnostics",
|
|
||||||
"diagnosticsLegend": "<span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-green-600/20 text-[var(--color-text)]\">OK</span> reachable \u00b7 <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-[var(--color-primary)]/18 text-[var(--color-text)]\">WARN</span> not configured \u00b7 <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-red-500/22 text-[var(--color-text)]\">ERROR</span> fetch failed / timed out.",
|
|
||||||
"streamUrl": "Stream URL",
|
|
||||||
"unreachable": "unreachable",
|
|
||||||
"refresh": "Actualizar"
|
|
||||||
},
|
|
||||||
"moderationPage": {
|
"moderationPage": {
|
||||||
"title": "DJ Moderation",
|
"title": "DJ Moderation",
|
||||||
"subtitle": "Review and remove radio shouts (50 most recent).",
|
"subtitle": "Review and remove radio shouts (50 most recent).",
|
||||||
@@ -1955,7 +1954,66 @@
|
|||||||
"saving": "Guardando…",
|
"saving": "Guardando…",
|
||||||
"cancel": "Cancelar"
|
"cancel": "Cancelar"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"favicon": {
|
||||||
|
"title": "Favicon",
|
||||||
|
"subtitle": "Upload and manage the site favicon",
|
||||||
|
"current": "Current favicon",
|
||||||
|
"uploadLabel": "Upload favicon",
|
||||||
|
"uploadHint": "PNG, JPEG, GIF, WebP, ICO or SVG. Max 2MB.",
|
||||||
|
"upload": "Upload",
|
||||||
|
"uploading": "Uploading…",
|
||||||
|
"delete": "Remove",
|
||||||
|
"deleting": "Removing…",
|
||||||
|
"saved": "Favicon saved successfully!",
|
||||||
|
"deleted": "Favicon removed successfully!",
|
||||||
|
"error": "Something went wrong",
|
||||||
|
"confirmDelete": "Remove the favicon? The site will no longer show a custom favicon.",
|
||||||
|
"generator": {
|
||||||
|
"title": "Generate favicon",
|
||||||
|
"text": "Text",
|
||||||
|
"bgColor": "Background color",
|
||||||
|
"textColor": "Text color",
|
||||||
|
"rounded": "Rounded corners",
|
||||||
|
"save": "Save as favicon"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"client": {
|
||||||
|
"title": "Nitro client",
|
||||||
|
"loading": "Loading client…",
|
||||||
|
"generatingTicket": "Generating SSO ticket…",
|
||||||
|
"connected": "Connected",
|
||||||
|
"welcome": "Welcome to {hotel}",
|
||||||
|
"openingClient": "Opening the client in a new tab…",
|
||||||
|
"fallbackLink": "If it doesn't open,",
|
||||||
|
"fallbackClick": "click here",
|
||||||
|
"errors": {
|
||||||
|
"unauthorized": "You need to be logged in to open the client.",
|
||||||
|
"generic": "Could not generate SSO ticket",
|
||||||
|
"loginRedirect": "Log in"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"radioRequests": {
|
||||||
|
"formTitle": "Submit a request",
|
||||||
|
"formSubtitle": "Ask the DJ to play your favourite track",
|
||||||
|
"songLabel": "Song",
|
||||||
|
"songPlaceholder": "Title of the song",
|
||||||
|
"artistLabel": "Artist",
|
||||||
|
"artistPlaceholder": "Name of the artist (optional)",
|
||||||
|
"submitButton": "Submit request",
|
||||||
|
"loginToRequest": "Log in to submit a request.",
|
||||||
|
"latestTitle": "Recent requests",
|
||||||
|
"latestSubtitle": "The most recently requested songs",
|
||||||
|
"empty": "No requests yet.",
|
||||||
|
"colSong": "Song",
|
||||||
|
"colArtist": "Artist",
|
||||||
|
"colRequester": "Requester",
|
||||||
|
"colDate": "Date",
|
||||||
|
"colStatus": "Status",
|
||||||
|
"statusPending": "Pending",
|
||||||
|
"statusApproved": "Approved",
|
||||||
|
"statusPlayed": "Played"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+94
-36
@@ -696,7 +696,8 @@
|
|||||||
"settings": "Paramètres",
|
"settings": "Paramètres",
|
||||||
"backToSite": "Retour au site",
|
"backToSite": "Retour au site",
|
||||||
"hotelManagementPanel": "Panneau de gestion de l'hôtel",
|
"hotelManagementPanel": "Panneau de gestion de l'hôtel",
|
||||||
"rankLabel": "Rang {rank}"
|
"rankLabel": "Rang {rank}",
|
||||||
|
"favicon": "Favicon"
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"title": "Tableau de bord",
|
"title": "Tableau de bord",
|
||||||
@@ -1412,7 +1413,28 @@
|
|||||||
"apiKeys": "Clés API",
|
"apiKeys": "Clés API",
|
||||||
"points": "Points",
|
"points": "Points",
|
||||||
"autoDj": "AutoDJ",
|
"autoDj": "AutoDJ",
|
||||||
"monitoring": "Surveillance",
|
"monitoring": {
|
||||||
|
"title": "Surveillance radio",
|
||||||
|
"subtitle": "Read-only live probe of the configured radio endpoints (radio_now_playing_api_url, radio_listeners_api_url). Each request fails soft with a {timeout}s timeout — no data is written. Configure URLs under Settings.",
|
||||||
|
"stream": "Stream",
|
||||||
|
"online": "En ligne",
|
||||||
|
"offline": "Hors ligne",
|
||||||
|
"notConfigured": "Not configured",
|
||||||
|
"nowPlaying": "Now playing",
|
||||||
|
"nowPlayingApi": "Now-playing API",
|
||||||
|
"listenersApi": "Listeners API",
|
||||||
|
"listeners": "Auditeurs",
|
||||||
|
"currentSong": "Chanson en cours",
|
||||||
|
"bitrate": "Débit",
|
||||||
|
"status": "Statut",
|
||||||
|
"noNowPlayingHint": "no now-playing API set",
|
||||||
|
"noListenersHint": "no listeners API set",
|
||||||
|
"endpointDiagnostics": "Endpoint diagnostics",
|
||||||
|
"diagnosticsLegend": "<span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-green-600/20 text-[var(--color-text)]\">OK</span> reachable · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-[var(--color-primary)]/18 text-[var(--color-text)]\">WARN</span> not configured · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-red-500/22 text-[var(--color-text)]\">ERROR</span> fetch failed / timed out.",
|
||||||
|
"streamUrl": "Stream URL",
|
||||||
|
"unreachable": "unreachable",
|
||||||
|
"refresh": "Actualiser"
|
||||||
|
},
|
||||||
"user": "User",
|
"user": "User",
|
||||||
"message": "Message",
|
"message": "Message",
|
||||||
"posted": "Posted",
|
"posted": "Posted",
|
||||||
@@ -1455,7 +1477,7 @@
|
|||||||
},
|
},
|
||||||
"embedPage": {
|
"embedPage": {
|
||||||
"title": "Radio Embed",
|
"title": "Radio Embed",
|
||||||
"subtitle": "Copy-ready embed code for the live stream. Read-only \u2014 nothing here writes to the database. Configure the stream URL in Radio Settings (radio_stream_url).",
|
"subtitle": "Copy-ready embed code for the live stream. Read-only — nothing here writes to the database. Configure the stream URL in Radio Settings (radio_stream_url).",
|
||||||
"streamUrl": "Stream URL",
|
"streamUrl": "Stream URL",
|
||||||
"configured": "Configured",
|
"configured": "Configured",
|
||||||
"missing": "Missing",
|
"missing": "Missing",
|
||||||
@@ -1472,7 +1494,7 @@
|
|||||||
"audioSnippet": "Audio player snippet",
|
"audioSnippet": "Audio player snippet",
|
||||||
"audioSnippetDesc": "Drop-in HTML5 audio player pointed straight at the stream. Works anywhere plain HTML is allowed.",
|
"audioSnippetDesc": "Drop-in HTML5 audio player pointed straight at the stream. Works anywhere plain HTML is allowed.",
|
||||||
"iframeSnippet": "Iframe wrapper snippet",
|
"iframeSnippet": "Iframe wrapper snippet",
|
||||||
"iframeSnippetDesc": "Embeds the site\u2019s own radio player page in an iframe \u2014 keeps your styling and now-playing info. Adjust width/height to taste.",
|
"iframeSnippetDesc": "Embeds the site’s own radio player page in an iframe — keeps your styling and now-playing info. Adjust width/height to taste.",
|
||||||
"audioUnsupported": "Your browser does not support the audio element."
|
"audioUnsupported": "Your browser does not support the audio element."
|
||||||
},
|
},
|
||||||
"banners": {
|
"banners": {
|
||||||
@@ -1482,7 +1504,7 @@
|
|||||||
"addBanner": "Ajouter une bannière",
|
"addBanner": "Ajouter une bannière",
|
||||||
"createBanner": "Create banner",
|
"createBanner": "Create banner",
|
||||||
"deleteBanner": "Supprimer la bannière",
|
"deleteBanner": "Supprimer la bannière",
|
||||||
"noBanners": "No banners yet \u2014 add one above.",
|
"noBanners": "No banners yet — add one above.",
|
||||||
"existing": "Existing banners ({count})",
|
"existing": "Existing banners ({count})",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
@@ -1513,7 +1535,7 @@
|
|||||||
"description": "Description",
|
"description": "Description",
|
||||||
"createRank": "Create rank",
|
"createRank": "Create rank",
|
||||||
"existing": "Existing ranks ({count})",
|
"existing": "Existing ranks ({count})",
|
||||||
"noRanks": "No ranks yet \u2014 add one above.",
|
"noRanks": "No ranks yet — add one above.",
|
||||||
"activeLabel": "active",
|
"activeLabel": "active",
|
||||||
"inactiveLabel": "inactive",
|
"inactiveLabel": "inactive",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
@@ -1540,13 +1562,13 @@
|
|||||||
},
|
},
|
||||||
"apiKeysForm": {
|
"apiKeysForm": {
|
||||||
"title": "Clés API",
|
"title": "Clés API",
|
||||||
"subtitle": "API keys for external radio integrations (radio_api_keys). Keys are generated server-side and shown masked \u2014 copy the full value from the database when first issued.",
|
"subtitle": "API keys for external radio integrations (radio_api_keys). Keys are generated server-side and shown masked — copy the full value from the database when first issued.",
|
||||||
"dbError": "Could not load API keys (database unavailable).",
|
"dbError": "Could not load API keys (database unavailable).",
|
||||||
"totalKeys": "Total keys",
|
"totalKeys": "Total keys",
|
||||||
"active": "Active",
|
"active": "Active",
|
||||||
"inactive": "Inactive",
|
"inactive": "Inactive",
|
||||||
"generateKey": "Générer une nouvelle clé",
|
"generateKey": "Générer une nouvelle clé",
|
||||||
"generateHint": "The secret key is generated automatically with cryptographic randomness \u2014 you do not enter it.",
|
"generateHint": "The secret key is generated automatically with cryptographic randomness — you do not enter it.",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"namePlaceholder": "AzureCast bridge",
|
"namePlaceholder": "AzureCast bridge",
|
||||||
"rateLimit": "Rate limit (requests/min)",
|
"rateLimit": "Rate limit (requests/min)",
|
||||||
@@ -1554,7 +1576,7 @@
|
|||||||
"allowedIpsPlaceholder": "comma-separated, blank = any",
|
"allowedIpsPlaceholder": "comma-separated, blank = any",
|
||||||
"generate": "Générer",
|
"generate": "Générer",
|
||||||
"existingKeys": "Existing keys ({count})",
|
"existingKeys": "Existing keys ({count})",
|
||||||
"noKeys": "No API keys yet \u2014 generate one above.",
|
"noKeys": "No API keys yet — generate one above.",
|
||||||
"colName": "Name",
|
"colName": "Name",
|
||||||
"colKey": "Clé",
|
"colKey": "Clé",
|
||||||
"colAllowedIps": "Allowed IPs",
|
"colAllowedIps": "Allowed IPs",
|
||||||
@@ -1595,24 +1617,23 @@
|
|||||||
"save": "Enregistrer les paramètres de points"
|
"save": "Enregistrer les paramètres de points"
|
||||||
},
|
},
|
||||||
"autoDjForm": {
|
"autoDjForm": {
|
||||||
"title": "Configuration AutoDJ",
|
"title": "Title",
|
||||||
"subtitle": "Fallback playlist (radio_auto_dj_playlist) the radio rotates through when no live DJ is streaming. Ordered by sort order then title.",
|
"subtitle": "Fallback playlist (radio_auto_dj_playlist) the radio rotates through when no live DJ is streaming. Ordered by sort order then title.",
|
||||||
"dbError": "Could not load the AutoDJ playlist (database unavailable).",
|
"dbError": "Could not load the AutoDJ playlist (database unavailable).",
|
||||||
"tracks": "Tracks",
|
"tracks": "Tracks",
|
||||||
"active": "Active",
|
"active": "Active",
|
||||||
"inactive": "Inactive",
|
"inactive": "Inactive",
|
||||||
"addTrack": "Add track",
|
"addTrack": "Add track",
|
||||||
"title": "Title",
|
|
||||||
"titlePlaceholder": "Track title",
|
"titlePlaceholder": "Track title",
|
||||||
"artist": "Artist",
|
"artist": "Artist",
|
||||||
"album": "Album",
|
"album": "Album",
|
||||||
"artworkUrl": "Artwork URL",
|
"artworkUrl": "Artwork URL",
|
||||||
"artworkUrlPlaceholder": "https://\u2026/cover.png",
|
"artworkUrlPlaceholder": "https://…/cover.png",
|
||||||
"duration": "Duration (seconds)",
|
"duration": "Duration (seconds)",
|
||||||
"durationPlaceholder": "e.g. 215",
|
"durationPlaceholder": "e.g. 215",
|
||||||
"sortOrder": "Sort order",
|
"sortOrder": "Sort order",
|
||||||
"playlist": "Playlist ({count})",
|
"playlist": "Playlist ({count})",
|
||||||
"noTracks": "No AutoDJ tracks yet \u2014 add one above.",
|
"noTracks": "No AutoDJ tracks yet — add one above.",
|
||||||
"colOrder": "Order",
|
"colOrder": "Order",
|
||||||
"colTitle": "Title",
|
"colTitle": "Title",
|
||||||
"colArtist": "Artist",
|
"colArtist": "Artist",
|
||||||
@@ -1628,28 +1649,6 @@
|
|||||||
"activate": "Activate",
|
"activate": "Activate",
|
||||||
"delete": "Delete"
|
"delete": "Delete"
|
||||||
},
|
},
|
||||||
"monitoring": {
|
|
||||||
"title": "Surveillance radio",
|
|
||||||
"subtitle": "Read-only live probe of the configured radio endpoints (radio_now_playing_api_url, radio_listeners_api_url). Each request fails soft with a {timeout}s timeout \u2014 no data is written. Configure URLs under Settings.",
|
|
||||||
"stream": "Stream",
|
|
||||||
"online": "En ligne",
|
|
||||||
"offline": "Hors ligne",
|
|
||||||
"notConfigured": "Not configured",
|
|
||||||
"nowPlaying": "Now playing",
|
|
||||||
"nowPlayingApi": "Now-playing API",
|
|
||||||
"listenersApi": "Listeners API",
|
|
||||||
"listeners": "Auditeurs",
|
|
||||||
"currentSong": "Chanson en cours",
|
|
||||||
"bitrate": "Débit",
|
|
||||||
"status": "Statut",
|
|
||||||
"noNowPlayingHint": "no now-playing API set",
|
|
||||||
"noListenersHint": "no listeners API set",
|
|
||||||
"endpointDiagnostics": "Endpoint diagnostics",
|
|
||||||
"diagnosticsLegend": "<span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-green-600/20 text-[var(--color-text)]\">OK</span> reachable \u00b7 <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-[var(--color-primary)]/18 text-[var(--color-text)]\">WARN</span> not configured \u00b7 <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-red-500/22 text-[var(--color-text)]\">ERROR</span> fetch failed / timed out.",
|
|
||||||
"streamUrl": "Stream URL",
|
|
||||||
"unreachable": "unreachable",
|
|
||||||
"refresh": "Actualiser"
|
|
||||||
},
|
|
||||||
"moderationPage": {
|
"moderationPage": {
|
||||||
"title": "DJ Moderation",
|
"title": "DJ Moderation",
|
||||||
"subtitle": "Review and remove radio shouts (50 most recent).",
|
"subtitle": "Review and remove radio shouts (50 most recent).",
|
||||||
@@ -1955,7 +1954,66 @@
|
|||||||
"saving": "Enregistrement…",
|
"saving": "Enregistrement…",
|
||||||
"cancel": "Annuler"
|
"cancel": "Annuler"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"favicon": {
|
||||||
|
"title": "Favicon",
|
||||||
|
"subtitle": "Upload and manage the site favicon",
|
||||||
|
"current": "Current favicon",
|
||||||
|
"uploadLabel": "Upload favicon",
|
||||||
|
"uploadHint": "PNG, JPEG, GIF, WebP, ICO or SVG. Max 2MB.",
|
||||||
|
"upload": "Upload",
|
||||||
|
"uploading": "Uploading…",
|
||||||
|
"delete": "Remove",
|
||||||
|
"deleting": "Removing…",
|
||||||
|
"saved": "Favicon saved successfully!",
|
||||||
|
"deleted": "Favicon removed successfully!",
|
||||||
|
"error": "Something went wrong",
|
||||||
|
"confirmDelete": "Remove the favicon? The site will no longer show a custom favicon.",
|
||||||
|
"generator": {
|
||||||
|
"title": "Generate favicon",
|
||||||
|
"text": "Text",
|
||||||
|
"bgColor": "Background color",
|
||||||
|
"textColor": "Text color",
|
||||||
|
"rounded": "Rounded corners",
|
||||||
|
"save": "Save as favicon"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"client": {
|
||||||
|
"title": "Nitro client",
|
||||||
|
"loading": "Loading client…",
|
||||||
|
"generatingTicket": "Generating SSO ticket…",
|
||||||
|
"connected": "Connected",
|
||||||
|
"welcome": "Welcome to {hotel}",
|
||||||
|
"openingClient": "Opening the client in a new tab…",
|
||||||
|
"fallbackLink": "If it doesn't open,",
|
||||||
|
"fallbackClick": "click here",
|
||||||
|
"errors": {
|
||||||
|
"unauthorized": "You need to be logged in to open the client.",
|
||||||
|
"generic": "Could not generate SSO ticket",
|
||||||
|
"loginRedirect": "Log in"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"radioRequests": {
|
||||||
|
"formTitle": "Submit a request",
|
||||||
|
"formSubtitle": "Ask the DJ to play your favourite track",
|
||||||
|
"songLabel": "Song",
|
||||||
|
"songPlaceholder": "Title of the song",
|
||||||
|
"artistLabel": "Artist",
|
||||||
|
"artistPlaceholder": "Name of the artist (optional)",
|
||||||
|
"submitButton": "Submit request",
|
||||||
|
"loginToRequest": "Log in to submit a request.",
|
||||||
|
"latestTitle": "Recent requests",
|
||||||
|
"latestSubtitle": "The most recently requested songs",
|
||||||
|
"empty": "No requests yet.",
|
||||||
|
"colSong": "Song",
|
||||||
|
"colArtist": "Artist",
|
||||||
|
"colRequester": "Requester",
|
||||||
|
"colDate": "Date",
|
||||||
|
"colStatus": "Status",
|
||||||
|
"statusPending": "Pending",
|
||||||
|
"statusApproved": "Approved",
|
||||||
|
"statusPlayed": "Played"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+94
-36
@@ -696,7 +696,8 @@
|
|||||||
"settings": "Impostazioni",
|
"settings": "Impostazioni",
|
||||||
"backToSite": "Torna al sito",
|
"backToSite": "Torna al sito",
|
||||||
"hotelManagementPanel": "Pannello di gestione hotel",
|
"hotelManagementPanel": "Pannello di gestione hotel",
|
||||||
"rankLabel": "Grado {rank}"
|
"rankLabel": "Grado {rank}",
|
||||||
|
"favicon": "Favicon"
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"title": "Dashboard",
|
"title": "Dashboard",
|
||||||
@@ -1412,7 +1413,28 @@
|
|||||||
"apiKeys": "Chiavi API",
|
"apiKeys": "Chiavi API",
|
||||||
"points": "Punti",
|
"points": "Punti",
|
||||||
"autoDj": "AutoDJ",
|
"autoDj": "AutoDJ",
|
||||||
"monitoring": "Monitoraggio",
|
"monitoring": {
|
||||||
|
"title": "Monitoraggio radio",
|
||||||
|
"subtitle": "Read-only live probe of the configured radio endpoints (radio_now_playing_api_url, radio_listeners_api_url). Each request fails soft with a {timeout}s timeout — no data is written. Configure URLs under Settings.",
|
||||||
|
"stream": "Stream",
|
||||||
|
"online": "Online",
|
||||||
|
"offline": "Offline",
|
||||||
|
"notConfigured": "Not configured",
|
||||||
|
"nowPlaying": "Now playing",
|
||||||
|
"nowPlayingApi": "Now-playing API",
|
||||||
|
"listenersApi": "Listeners API",
|
||||||
|
"listeners": "Ascoltatori",
|
||||||
|
"currentSong": "Brano in corso",
|
||||||
|
"bitrate": "Bitrate",
|
||||||
|
"status": "Stato",
|
||||||
|
"noNowPlayingHint": "no now-playing API set",
|
||||||
|
"noListenersHint": "no listeners API set",
|
||||||
|
"endpointDiagnostics": "Endpoint diagnostics",
|
||||||
|
"diagnosticsLegend": "<span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-green-600/20 text-[var(--color-text)]\">OK</span> reachable · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-[var(--color-primary)]/18 text-[var(--color-text)]\">WARN</span> not configured · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-red-500/22 text-[var(--color-text)]\">ERROR</span> fetch failed / timed out.",
|
||||||
|
"streamUrl": "Stream URL",
|
||||||
|
"unreachable": "unreachable",
|
||||||
|
"refresh": "Aggiorna"
|
||||||
|
},
|
||||||
"user": "User",
|
"user": "User",
|
||||||
"message": "Message",
|
"message": "Message",
|
||||||
"posted": "Posted",
|
"posted": "Posted",
|
||||||
@@ -1455,7 +1477,7 @@
|
|||||||
},
|
},
|
||||||
"embedPage": {
|
"embedPage": {
|
||||||
"title": "Radio Embed",
|
"title": "Radio Embed",
|
||||||
"subtitle": "Copy-ready embed code for the live stream. Read-only \u2014 nothing here writes to the database. Configure the stream URL in Radio Settings (radio_stream_url).",
|
"subtitle": "Copy-ready embed code for the live stream. Read-only — nothing here writes to the database. Configure the stream URL in Radio Settings (radio_stream_url).",
|
||||||
"streamUrl": "Stream URL",
|
"streamUrl": "Stream URL",
|
||||||
"configured": "Configured",
|
"configured": "Configured",
|
||||||
"missing": "Missing",
|
"missing": "Missing",
|
||||||
@@ -1472,7 +1494,7 @@
|
|||||||
"audioSnippet": "Audio player snippet",
|
"audioSnippet": "Audio player snippet",
|
||||||
"audioSnippetDesc": "Drop-in HTML5 audio player pointed straight at the stream. Works anywhere plain HTML is allowed.",
|
"audioSnippetDesc": "Drop-in HTML5 audio player pointed straight at the stream. Works anywhere plain HTML is allowed.",
|
||||||
"iframeSnippet": "Iframe wrapper snippet",
|
"iframeSnippet": "Iframe wrapper snippet",
|
||||||
"iframeSnippetDesc": "Embeds the site\u2019s own radio player page in an iframe \u2014 keeps your styling and now-playing info. Adjust width/height to taste.",
|
"iframeSnippetDesc": "Embeds the site’s own radio player page in an iframe — keeps your styling and now-playing info. Adjust width/height to taste.",
|
||||||
"audioUnsupported": "Your browser does not support the audio element."
|
"audioUnsupported": "Your browser does not support the audio element."
|
||||||
},
|
},
|
||||||
"banners": {
|
"banners": {
|
||||||
@@ -1482,7 +1504,7 @@
|
|||||||
"addBanner": "Aggiungi banner",
|
"addBanner": "Aggiungi banner",
|
||||||
"createBanner": "Create banner",
|
"createBanner": "Create banner",
|
||||||
"deleteBanner": "Elimina banner",
|
"deleteBanner": "Elimina banner",
|
||||||
"noBanners": "No banners yet \u2014 add one above.",
|
"noBanners": "No banners yet — add one above.",
|
||||||
"existing": "Existing banners ({count})",
|
"existing": "Existing banners ({count})",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
@@ -1513,7 +1535,7 @@
|
|||||||
"description": "Description",
|
"description": "Description",
|
||||||
"createRank": "Create rank",
|
"createRank": "Create rank",
|
||||||
"existing": "Existing ranks ({count})",
|
"existing": "Existing ranks ({count})",
|
||||||
"noRanks": "No ranks yet \u2014 add one above.",
|
"noRanks": "No ranks yet — add one above.",
|
||||||
"activeLabel": "active",
|
"activeLabel": "active",
|
||||||
"inactiveLabel": "inactive",
|
"inactiveLabel": "inactive",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
@@ -1540,13 +1562,13 @@
|
|||||||
},
|
},
|
||||||
"apiKeysForm": {
|
"apiKeysForm": {
|
||||||
"title": "Chiavi API",
|
"title": "Chiavi API",
|
||||||
"subtitle": "API keys for external radio integrations (radio_api_keys). Keys are generated server-side and shown masked \u2014 copy the full value from the database when first issued.",
|
"subtitle": "API keys for external radio integrations (radio_api_keys). Keys are generated server-side and shown masked — copy the full value from the database when first issued.",
|
||||||
"dbError": "Could not load API keys (database unavailable).",
|
"dbError": "Could not load API keys (database unavailable).",
|
||||||
"totalKeys": "Total keys",
|
"totalKeys": "Total keys",
|
||||||
"active": "Active",
|
"active": "Active",
|
||||||
"inactive": "Inactive",
|
"inactive": "Inactive",
|
||||||
"generateKey": "Genera nuova chiave",
|
"generateKey": "Genera nuova chiave",
|
||||||
"generateHint": "The secret key is generated automatically with cryptographic randomness \u2014 you do not enter it.",
|
"generateHint": "The secret key is generated automatically with cryptographic randomness — you do not enter it.",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"namePlaceholder": "AzureCast bridge",
|
"namePlaceholder": "AzureCast bridge",
|
||||||
"rateLimit": "Rate limit (requests/min)",
|
"rateLimit": "Rate limit (requests/min)",
|
||||||
@@ -1554,7 +1576,7 @@
|
|||||||
"allowedIpsPlaceholder": "comma-separated, blank = any",
|
"allowedIpsPlaceholder": "comma-separated, blank = any",
|
||||||
"generate": "Genera",
|
"generate": "Genera",
|
||||||
"existingKeys": "Existing keys ({count})",
|
"existingKeys": "Existing keys ({count})",
|
||||||
"noKeys": "No API keys yet \u2014 generate one above.",
|
"noKeys": "No API keys yet — generate one above.",
|
||||||
"colName": "Name",
|
"colName": "Name",
|
||||||
"colKey": "Chiave",
|
"colKey": "Chiave",
|
||||||
"colAllowedIps": "Allowed IPs",
|
"colAllowedIps": "Allowed IPs",
|
||||||
@@ -1595,24 +1617,23 @@
|
|||||||
"save": "Salva impostazioni punti"
|
"save": "Salva impostazioni punti"
|
||||||
},
|
},
|
||||||
"autoDjForm": {
|
"autoDjForm": {
|
||||||
"title": "Configurazione AutoDJ",
|
"title": "Title",
|
||||||
"subtitle": "Fallback playlist (radio_auto_dj_playlist) the radio rotates through when no live DJ is streaming. Ordered by sort order then title.",
|
"subtitle": "Fallback playlist (radio_auto_dj_playlist) the radio rotates through when no live DJ is streaming. Ordered by sort order then title.",
|
||||||
"dbError": "Could not load the AutoDJ playlist (database unavailable).",
|
"dbError": "Could not load the AutoDJ playlist (database unavailable).",
|
||||||
"tracks": "Tracks",
|
"tracks": "Tracks",
|
||||||
"active": "Active",
|
"active": "Active",
|
||||||
"inactive": "Inactive",
|
"inactive": "Inactive",
|
||||||
"addTrack": "Add track",
|
"addTrack": "Add track",
|
||||||
"title": "Title",
|
|
||||||
"titlePlaceholder": "Track title",
|
"titlePlaceholder": "Track title",
|
||||||
"artist": "Artist",
|
"artist": "Artist",
|
||||||
"album": "Album",
|
"album": "Album",
|
||||||
"artworkUrl": "Artwork URL",
|
"artworkUrl": "Artwork URL",
|
||||||
"artworkUrlPlaceholder": "https://\u2026/cover.png",
|
"artworkUrlPlaceholder": "https://…/cover.png",
|
||||||
"duration": "Duration (seconds)",
|
"duration": "Duration (seconds)",
|
||||||
"durationPlaceholder": "e.g. 215",
|
"durationPlaceholder": "e.g. 215",
|
||||||
"sortOrder": "Sort order",
|
"sortOrder": "Sort order",
|
||||||
"playlist": "Playlist ({count})",
|
"playlist": "Playlist ({count})",
|
||||||
"noTracks": "No AutoDJ tracks yet \u2014 add one above.",
|
"noTracks": "No AutoDJ tracks yet — add one above.",
|
||||||
"colOrder": "Order",
|
"colOrder": "Order",
|
||||||
"colTitle": "Title",
|
"colTitle": "Title",
|
||||||
"colArtist": "Artist",
|
"colArtist": "Artist",
|
||||||
@@ -1628,28 +1649,6 @@
|
|||||||
"activate": "Activate",
|
"activate": "Activate",
|
||||||
"delete": "Delete"
|
"delete": "Delete"
|
||||||
},
|
},
|
||||||
"monitoring": {
|
|
||||||
"title": "Monitoraggio radio",
|
|
||||||
"subtitle": "Read-only live probe of the configured radio endpoints (radio_now_playing_api_url, radio_listeners_api_url). Each request fails soft with a {timeout}s timeout \u2014 no data is written. Configure URLs under Settings.",
|
|
||||||
"stream": "Stream",
|
|
||||||
"online": "Online",
|
|
||||||
"offline": "Offline",
|
|
||||||
"notConfigured": "Not configured",
|
|
||||||
"nowPlaying": "Now playing",
|
|
||||||
"nowPlayingApi": "Now-playing API",
|
|
||||||
"listenersApi": "Listeners API",
|
|
||||||
"listeners": "Ascoltatori",
|
|
||||||
"currentSong": "Brano in corso",
|
|
||||||
"bitrate": "Bitrate",
|
|
||||||
"status": "Stato",
|
|
||||||
"noNowPlayingHint": "no now-playing API set",
|
|
||||||
"noListenersHint": "no listeners API set",
|
|
||||||
"endpointDiagnostics": "Endpoint diagnostics",
|
|
||||||
"diagnosticsLegend": "<span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-green-600/20 text-[var(--color-text)]\">OK</span> reachable \u00b7 <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-[var(--color-primary)]/18 text-[var(--color-text)]\">WARN</span> not configured \u00b7 <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-red-500/22 text-[var(--color-text)]\">ERROR</span> fetch failed / timed out.",
|
|
||||||
"streamUrl": "Stream URL",
|
|
||||||
"unreachable": "unreachable",
|
|
||||||
"refresh": "Aggiorna"
|
|
||||||
},
|
|
||||||
"moderationPage": {
|
"moderationPage": {
|
||||||
"title": "DJ Moderation",
|
"title": "DJ Moderation",
|
||||||
"subtitle": "Review and remove radio shouts (50 most recent).",
|
"subtitle": "Review and remove radio shouts (50 most recent).",
|
||||||
@@ -1955,7 +1954,66 @@
|
|||||||
"saving": "Salvataggio…",
|
"saving": "Salvataggio…",
|
||||||
"cancel": "Annulla"
|
"cancel": "Annulla"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"favicon": {
|
||||||
|
"title": "Favicon",
|
||||||
|
"subtitle": "Upload and manage the site favicon",
|
||||||
|
"current": "Current favicon",
|
||||||
|
"uploadLabel": "Upload favicon",
|
||||||
|
"uploadHint": "PNG, JPEG, GIF, WebP, ICO or SVG. Max 2MB.",
|
||||||
|
"upload": "Upload",
|
||||||
|
"uploading": "Uploading…",
|
||||||
|
"delete": "Remove",
|
||||||
|
"deleting": "Removing…",
|
||||||
|
"saved": "Favicon saved successfully!",
|
||||||
|
"deleted": "Favicon removed successfully!",
|
||||||
|
"error": "Something went wrong",
|
||||||
|
"confirmDelete": "Remove the favicon? The site will no longer show a custom favicon.",
|
||||||
|
"generator": {
|
||||||
|
"title": "Generate favicon",
|
||||||
|
"text": "Text",
|
||||||
|
"bgColor": "Background color",
|
||||||
|
"textColor": "Text color",
|
||||||
|
"rounded": "Rounded corners",
|
||||||
|
"save": "Save as favicon"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"client": {
|
||||||
|
"title": "Nitro client",
|
||||||
|
"loading": "Loading client…",
|
||||||
|
"generatingTicket": "Generating SSO ticket…",
|
||||||
|
"connected": "Connected",
|
||||||
|
"welcome": "Welcome to {hotel}",
|
||||||
|
"openingClient": "Opening the client in a new tab…",
|
||||||
|
"fallbackLink": "If it doesn't open,",
|
||||||
|
"fallbackClick": "click here",
|
||||||
|
"errors": {
|
||||||
|
"unauthorized": "You need to be logged in to open the client.",
|
||||||
|
"generic": "Could not generate SSO ticket",
|
||||||
|
"loginRedirect": "Log in"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"radioRequests": {
|
||||||
|
"formTitle": "Submit a request",
|
||||||
|
"formSubtitle": "Ask the DJ to play your favourite track",
|
||||||
|
"songLabel": "Song",
|
||||||
|
"songPlaceholder": "Title of the song",
|
||||||
|
"artistLabel": "Artist",
|
||||||
|
"artistPlaceholder": "Name of the artist (optional)",
|
||||||
|
"submitButton": "Submit request",
|
||||||
|
"loginToRequest": "Log in to submit a request.",
|
||||||
|
"latestTitle": "Recent requests",
|
||||||
|
"latestSubtitle": "The most recently requested songs",
|
||||||
|
"empty": "No requests yet.",
|
||||||
|
"colSong": "Song",
|
||||||
|
"colArtist": "Artist",
|
||||||
|
"colRequester": "Requester",
|
||||||
|
"colDate": "Date",
|
||||||
|
"colStatus": "Status",
|
||||||
|
"statusPending": "Pending",
|
||||||
|
"statusApproved": "Approved",
|
||||||
|
"statusPlayed": "Played"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -386,6 +386,7 @@ mv /var/log/emu/emulator.log /var/log/emu/\$file_name
|
|||||||
java -Dfile.encoding=UTF8 -Xmx2G -jar $jar
|
java -Dfile.encoding=UTF8 -Xmx2G -jar $jar
|
||||||
EOJ
|
EOJ
|
||||||
chmod +x emulator; ok "Launch script updated"
|
chmod +x emulator; ok "Launch script updated"
|
||||||
|
cd "$SCRIPT_DIR"
|
||||||
ROLLBACK_NEEDED=false
|
ROLLBACK_NEEDED=false
|
||||||
else
|
else
|
||||||
info "Emulator: already up to date"
|
info "Emulator: already up to date"
|
||||||
@@ -396,6 +397,7 @@ update_renderer() {
|
|||||||
step 3 8 "Update Nitro_Render_V3"
|
step 3 8 "Update Nitro_Render_V3"
|
||||||
if git_update "$NITRO_RENDERER" "$NITRO_BRANCH"; then
|
if git_update "$NITRO_RENDERER" "$NITRO_BRANCH"; then
|
||||||
HAD_UPDATES=true; UPDATED_REPOS+=("Nitro_Render_V3")
|
HAD_UPDATES=true; UPDATED_REPOS+=("Nitro_Render_V3")
|
||||||
|
cd "$NITRO_RENDERER"
|
||||||
spinner_start "Installing deps..."
|
spinner_start "Installing deps..."
|
||||||
yarn install --frozen-lockfile 2>&1 || { clean_node_modules && yarn install 2>&1; } || { spinner_stop fail; die "yarn install failed"; }
|
yarn install --frozen-lockfile 2>&1 || { clean_node_modules && yarn install 2>&1; } || { spinner_stop fail; die "yarn install failed"; }
|
||||||
spinner_stop ok; ok "Renderer dependencies installed"
|
spinner_stop ok; ok "Renderer dependencies installed"
|
||||||
@@ -408,6 +410,7 @@ update_client() {
|
|||||||
step 4 8 "Update & Build Nitro-V3"
|
step 4 8 "Update & Build Nitro-V3"
|
||||||
if git_update "$NITRO_CLIENT" "$NITRO_BRANCH"; then
|
if git_update "$NITRO_CLIENT" "$NITRO_BRANCH"; then
|
||||||
HAD_UPDATES=true; NITRO_BUILT=true; UPDATED_REPOS+=("Nitro-V3")
|
HAD_UPDATES=true; NITRO_BUILT=true; UPDATED_REPOS+=("Nitro-V3")
|
||||||
|
cd "$NITRO_CLIENT"
|
||||||
spinner_start "Installing deps..."
|
spinner_start "Installing deps..."
|
||||||
yarn install --frozen-lockfile 2>&1 || { clean_node_modules && yarn install 2>&1; } || { spinner_stop fail; die "yarn install failed"; }
|
yarn install --frozen-lockfile 2>&1 || { clean_node_modules && yarn install 2>&1; } || { spinner_stop fail; die "yarn install failed"; }
|
||||||
spinner_stop ok
|
spinner_stop ok
|
||||||
|
|||||||
Reference in new issue
Block a user