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

+37 -32
View File
@@ -9,19 +9,20 @@ import {
RefreshCw,
X,
} from "lucide-react";
import { useTranslations } from "next-intl";
import { memo, useEffect, useMemo, useState } from "react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import type { BatchItemStatus, VerificationSummary } from "./studio-types";
const statusLabels: Record<BatchItemStatus["status"], string> = {
pending: "Waiting",
started: "Starting",
downloading: "Downloading assets",
converting: "Preparing assets",
writing_db: "Saving",
done: "Imported",
failed: "Failed",
const STATUS_KEYS: Record<BatchItemStatus["status"], string> = {
pending: "status.pending",
started: "status.started",
downloading: "status.downloading",
converting: "status.converting",
writing_db: "status.writingDb",
done: "status.done",
failed: "status.failed",
};
function formatDuration(ms: number): string {
@@ -65,6 +66,7 @@ export const BatchProgress = memo(function BatchProgress({
onDismiss?: () => void;
onRetry?: (classnames: string[]) => void;
}) {
const t = useTranslations("admin.studio.batch");
const [now, setNow] = useState(() => Date.now());
useEffect(() => {
@@ -99,20 +101,20 @@ export const BatchProgress = memo(function BatchProgress({
const rate = formatRate(completed, elapsed);
const heading = !done
? cancelling
? "Stopping import…"
: "Importing furniture…"
? t("headingStopping")
: t("headingImporting")
: unfinished
? "Import stopped"
? t("headingStopped")
: errors
? "Import completed with errors"
: "Import completed";
? t("headingCompletedErrors")
: t("headingCompleted");
const issues = verification
? verification.furniDataMissing + verification.furniDataConflicts
: 0;
return (
<section
aria-label="Import progress"
aria-label={t("ariaProgress")}
className="max-h-[45%] shrink-0 overflow-auto border-t border-[var(--admin-border)] bg-[var(--admin-surface)] p-4"
>
<div className="flex flex-wrap items-center justify-between gap-3">
@@ -128,7 +130,7 @@ export const BatchProgress = memo(function BatchProgress({
<span className="font-semibold text-[var(--admin-success)]">
{imported}
</span>{" "}
imported
{t("imported")}
</span>
<span className="text-[var(--admin-border)]">·</span>
<span>
@@ -140,13 +142,13 @@ export const BatchProgress = memo(function BatchProgress({
>
{errors}
</span>{" "}
failed
{t("failed")}
</span>
{unfinished > 0 && (
<>
<span className="text-[var(--admin-border)]">·</span>
<span>
{unfinished} {done ? "not completed" : "remaining"}
{unfinished} {done ? t("notCompleted") : t("remaining")}
</span>
</>
)}
@@ -173,12 +175,12 @@ export const BatchProgress = memo(function BatchProgress({
onClick={() => onRetry(failedClassnames)}
>
<RefreshCw size={14} />
Retry failed ({failedClassnames.length})
{t("retryFailed", { count: failedClassnames.length })}
</Button>
)}
<Button size="sm" variant="secondary" onClick={onDismiss}>
<Check size={14} />
Close summary
{t("closeSummary")}
</Button>
</>
) : (
@@ -193,14 +195,14 @@ export const BatchProgress = memo(function BatchProgress({
) : (
<Ban size={14} />
)}
Stop import
{t("stopImport")}
</Button>
)}
</div>
</div>
<div
role="progressbar"
aria-label="Furniture processed"
aria-label={t("ariaProcessed")}
aria-valuemin={0}
aria-valuemax={total || 1}
aria-valuenow={completed}
@@ -215,39 +217,42 @@ export const BatchProgress = memo(function BatchProgress({
<div className="mb-3 flex flex-wrap items-center gap-1.5">
<p className="mr-1 text-sm text-[var(--admin-text-muted)]">
{issues
? `${issues} catalog data issue(s) still need attention. Open Catalog audit to review them.`
: "Catalog data verification completed."}
? t("verificationIssues", { count: issues })
: t("verificationDone")}
</p>
<NodeChip
count={verification.furniDataFixedIds}
label="furnidata ids fixed"
label={t("chipFurnidataIds")}
/>
<NodeChip
count={verification.offerIdsFixed}
label="offer ids reconciled"
label={t("chipOfferIds")}
/>
<NodeChip
count={verification.ownershipFixed.length}
label="ownerships fixed"
label={t("chipOwnerships")}
/>
<NodeChip
count={verification.nitrosSynced.length}
label="nitros synced"
label={t("chipNitros")}
/>
<NodeChip
count={verification.iconsSynced.length}
label="icons synced"
label={t("chipIcons")}
/>
</div>
)}
<details>
<summary className="cursor-pointer text-xs font-medium">
View item details ({total})
{t("viewItemDetails", { count: total })}
</summary>
<div className="mt-2 max-h-44 space-y-2 overflow-auto">
{entries.length > VISIBLE_ITEMS && (
<p className="px-1 text-[0.65rem] text-[var(--admin-text-muted)]">
Showing {VISIBLE_ITEMS} of {entries.length} items
{t("showingItems", {
shown: VISIBLE_ITEMS,
total: entries.length,
})}
</p>
)}
{entries.slice(0, VISIBLE_ITEMS).map((item) => (
@@ -280,8 +285,8 @@ export const BatchProgress = memo(function BatchProgress({
</span>
<span className="shrink-0 text-[var(--admin-text-muted)]">
{done && item.status !== "done" && item.status !== "failed"
? "Not completed"
: statusLabels[item.status]}
? t("statusNotCompleted")
: t(STATUS_KEYS[item.status])}
</span>
</span>
{item.message && (