feat(studio): localize catalog studio UI and polish responsive layout
CI / check (push) Failing after 43s
CI / tests-unit (push) Skipped
CI / tests-integration (push) Skipped
CI / tests-ui (push) Skipped
CI / preflight (push) Skipped
CI / deploy (push) Skipped

Route all Catalog Studio strings through the admin.studio namespace
(workspace, filters, review, inspector, quality, sync, scale32, organize
imports and page titles/subtitles) with English fallback, and add shared
StudioPage chrome plus a loading skeleton. Tighten mobile padding on the
header and sub-bars.
This commit is contained in:
openhands committed 2026-10-13 20:31:26 +02:00
1 parent d7d6382b60
commit 56676effc3
28 files changed
+1293 -634

No files matched your search

@@ -1,4 +1,5 @@
"use client";
import { useTranslations } from "next-intl";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { adminFetch } from "@/lib/admin-fetch";
@@ -11,6 +12,7 @@ export function NitroScale32Generator({
disabled: boolean;
onChanged: (metadata: Record<string, unknown>) => void;
}) {
const t = useTranslations("admin.studio.scale32");
const [preview, setPreview] = useState<{
hash: string;
before: string;
@@ -38,7 +40,7 @@ export function NitroScale32Generator({
}),
});
const data = await response.json();
if (!response.ok) throw Error(data.error || "Generation failed");
if (!response.ok) throw Error(data.error || t("generationFailed"));
if (action === "preview") setPreview(data);
else {
setPreview(null);
@@ -50,45 +52,37 @@ export function NitroScale32Generator({
onChanged(data.metadata);
}
} catch (e) {
setError(e instanceof Error ? e.message : "Generation failed");
setError(e instanceof Error ? e.message : t("generationFailed"));
} finally {
setBusy(false);
}
}
return (
<section className="space-y-3 rounded-lg border p-3">
<h3 className="font-semibold">Generate scale 32 from 64</h3>
<p className="text-sm text-muted-foreground">
Adds derived artwork using nearest-neighbor reduction. Existing scale 32
is preserved. Preview uses the saved bundle and shows one sprite at its
original pixel size.
</p>
{disabled && (
<p className="text-sm">Save or reset your pending edits first.</p>
)}
<h3 className="font-semibold">{t("title")}</h3>
<p className="text-sm text-muted-foreground">{t("intro")}</p>
{disabled && <p className="text-sm">{t("saveFirst")}</p>}
<Button
variant="outline"
disabled={busy || disabled}
onClick={() => run("preview")}
>
{busy ? "Working…" : "Preview generation"}
{busy ? t("working") : t("preview")}
</Button>
{preview && (
<>
<p className="text-sm">
{preview.count} sprite entries will be added. Check positioning and
animations in the client after saving.
{t("previewCount", { count: preview.count })}
</p>
{preview.skipped ? (
<p className="text-sm text-destructive" role="alert">
{preview.skipped} sprite(s) were skipped because the source bundle
has no usable artwork for them.
{t("skipped", { count: preview.skipped })}
</p>
) : null}
<div className="grid grid-cols-2 gap-3">
{[
["Original 64", preview.before],
["Derived 32", preview.after],
[t("original64"), preview.before],
[t("derived32"), preview.after],
].map(([label, src]) => (
<figure key={label} className="min-w-0">
<figcaption>{label}</figcaption>
@@ -103,28 +97,26 @@ export function NitroScale32Generator({
))}
</div>
<Button disabled={busy || disabled} onClick={() => run("apply")}>
Save with backup
{t("saveWithBackup")}
</Button>
<Button
variant="ghost"
disabled={busy}
onClick={() => setPreview(null)}
>
Discard preview
{t("discard")}
</Button>
</>
)}
{undo && (
<div>
<p className="text-sm">
Saved. A copy of the original bundle is preserved.
</p>
<p className="text-sm">{t("saved")}</p>
<Button
variant="outline"
disabled={busy || disabled}
onClick={() => run("restore")}
>
Restore original bundle
{t("restore")}
</Button>
</div>
)}