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

+18 -11
View File
@@ -10,6 +10,7 @@ import {
Search,
X,
} from "lucide-react";
import { useTranslations } from "next-intl";
import { memo, useMemo, useState, useTransition } from "react";
import {
refreshCatalogTree,
@@ -25,6 +26,7 @@ import type { TreeNode } from "@/types/catalog";
* member has no other signal that the import they just ran is not on screen.
*/
function LiveCatalogNotice() {
const t = useTranslations("admin.studio");
const { refreshError, refreshing } = useLiveCatalog();
const [retrying, startRetry] = useTransition();
if (!refreshError && !refreshing) return null;
@@ -41,7 +43,7 @@ function LiveCatalogNotice() {
{refreshError ? (
<>
<AlertTriangle size={14} className="shrink-0" />
<span className="min-w-0 flex-1">Catalog could not refresh.</span>
<span className="min-w-0 flex-1">{t("rail.refreshFailed")}</span>
<button
type="button"
disabled={retrying}
@@ -53,13 +55,13 @@ function LiveCatalogNotice() {
className="flex items-center gap-1 rounded px-1.5 py-0.5 font-medium underline disabled:opacity-50"
>
<RefreshCw size={12} className={cn(retrying && "animate-spin")} />
Retry
{t("rail.retry")}
</button>
</>
) : (
<>
<Loader2 size={14} className="shrink-0 animate-spin" />
<span className="min-w-0 flex-1">Updating catalog…</span>
<span className="min-w-0 flex-1">{t("rail.updating")}</span>
</>
)}
</p>
@@ -77,6 +79,7 @@ export const CatalogRail = memo(function CatalogRail({
onSelect: (id: number | null) => void;
onClose?: () => void;
}) {
const t = useTranslations("admin.studio");
const [collapsed, setCollapsed] = useState<Set<number>>(new Set());
const [search, setSearch] = useState("");
const visible = useMemo(() => {
@@ -100,7 +103,7 @@ export const CatalogRail = memo(function CatalogRail({
}
return (
<aside
aria-label="Catalog categories"
aria-label={t("aria.catalogCategories")}
className="absolute inset-y-0 left-0 z-30 flex w-64 max-w-[85%] flex-col border-r border-[var(--admin-border)] bg-[var(--admin-surface)] shadow-xl lg:static lg:z-auto lg:shrink-0 lg:shadow-none"
>
<div className="flex items-center gap-2 px-3 pt-3">
@@ -109,12 +112,12 @@ export const CatalogRail = memo(function CatalogRail({
style={{ fontSize: 14, margin: 0 }}
className="flex-1 text-sm font-semibold"
>
Categories
{t("rail.title")}
</h2>
<button
type="button"
onClick={onClose}
aria-label="Close categories"
aria-label={t("aria.closeCategories")}
className="grid h-9 w-9 place-items-center rounded-md hover:bg-[var(--admin-accent)]/10"
>
<X size={16} />
@@ -125,8 +128,8 @@ export const CatalogRail = memo(function CatalogRail({
<input
value={search}
onChange={(event) => setSearch(event.target.value)}
placeholder="Find a category…"
aria-label="Find a category"
placeholder={t("aria.findCategory")}
aria-label={t("aria.findCategory")}
className="h-9 min-w-0 w-full border-0 bg-transparent text-sm outline-none"
/>
</label>
@@ -136,13 +139,13 @@ export const CatalogRail = memo(function CatalogRail({
aria-pressed={selectedId === null}
className="mx-2 mb-2 rounded-md px-3 py-2 text-left text-sm hover:bg-[var(--admin-accent)]/10 aria-pressed:bg-[var(--admin-accent)]/15"
>
All categories
{t("rail.all")}
</button>
<LiveCatalogNotice />
<div className="min-h-0 flex-1 overflow-y-auto px-2 pb-3">
{visible.length === 0 && (
<p className="p-3 text-sm text-[var(--admin-text-muted)]">
No matching categories.
{t("rail.noMatches")}
</p>
)}
{visible.map((node) => (
@@ -159,7 +162,11 @@ export const CatalogRail = memo(function CatalogRail({
{node.childCount > 0 && !search.trim() ? (
<button
type="button"
aria-label={`${collapsed.has(node.id) ? "Expand" : "Collapse"} ${node.caption}`}
aria-label={
collapsed.has(node.id)
? t("aria.expandCategory", { name: node.caption })
: t("aria.collapseCategory", { name: node.caption })
}
aria-expanded={!collapsed.has(node.id)}
onClick={() => toggle(node.id)}
className="grid h-10 w-8 shrink-0 place-items-center rounded-md hover:bg-[var(--admin-accent)]/10"