feat(studio): localize catalog studio UI and polish responsive layout

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

+4
View File
@@ -2,6 +2,10 @@
`studio-client.tsx` composes the workspace, selection and actions. Keep reusable data handling outside its render tree.
## Shared chrome
- `studio-page.tsx`: consistent header (icon tile, title, subtitle, actions) and padding for every studio sub-page; wraps `AdminPageShell`.
## Client responsibilities
- `studio-filters.ts`: pure filtering and sorting of the loaded furniture list; never mutates the input array.
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -851,10 +851,10 @@ export function OrganizeImportsDialog({
</SelectTrigger>
<SelectContent>
<SelectItem value="0" className="text-xs">
Duckets
{t("pointsTypeDuckets")}
</SelectItem>
<SelectItem value="5" className="text-xs">
Diamonds
{t("pointsTypeDiamonds")}
</SelectItem>
</SelectContent>
</Select>
@@ -1571,7 +1571,7 @@ export function OrganizeImportsDialog({
</span>
</div>
<Badge variant="outline" className="text-[10px]">
{previewGroup.items.length} items
{previewGroup.items.length} {t("itemsUnit")}
</Badge>
</div>
{previewGroup.layout &&
@@ -1,6 +1,7 @@
"use client";
import { FolderTree } from "lucide-react";
import { useTranslations } from "next-intl";
import { lazy, Suspense, useState } from "react";
import { StudioPanelLoading } from "./studio-panel-loading";
@@ -11,6 +12,7 @@ const OrganizeImportsDialog = lazy(() =>
);
export function OrganizeImportsLauncher() {
const t = useTranslations("pages.admin.catalog.workspace.organizeImports");
const [open, setOpen] = useState(false);
const [requested, setRequested] = useState(false);
return (
@@ -24,7 +26,7 @@ export function OrganizeImportsLauncher() {
className="flex min-h-10 items-center gap-2 rounded-lg px-3 text-sm font-medium text-[var(--admin-text-muted)] hover:bg-[var(--admin-accent)]/10"
>
<FolderTree size={16} />
Organize imports
{t("title")}
</button>
{requested && (
<Suspense
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,37 @@
import type { LucideIcon } from "lucide-react";
import type { ReactNode } from "react";
import { AdminPageShell } from "@/components/admin/admin-page-shell";
interface StudioPageProps {
icon?: LucideIcon;
title: string;
subtitle?: string;
actions?: ReactNode;
children: ReactNode;
}
/**
* Consistent chrome for Catalog Studio sub-pages: the shared admin page
* header (icon tile, title, subtitle, optional actions) with the studio's
* standard padding, so every tool page lines up the same way.
*/
export function StudioPage({
icon,
title,
subtitle,
actions,
children,
}: StudioPageProps) {
return (
<div className="p-3 sm:p-4">
<AdminPageShell
icon={icon}
title={title}
subtitle={subtitle}
actions={actions}
>
{children}
</AdminPageShell>
</div>
);
}
@@ -1,4 +1,5 @@
"use client";
import { useTranslations } from "next-intl";
import { useState } from "react";
import { toast } from "sonner";
import { setFurnidataTranslateEnabled } from "@/actions/import-furni";
@@ -24,6 +25,7 @@ const TRANSLATE_LANGS = [
] as const;
export function useStudioTranslations(defaultTranslate: boolean) {
const t = useTranslations("admin.studio");
// Translation controls (per-import toggle + language selection, plus a global
// default that persists to the site setting and seeds the per-import toggle).
const ALL_LANGS = TRANSLATE_LANGS as readonly string[];
@@ -52,13 +54,16 @@ export function useStudioTranslations(defaultTranslate: boolean) {
const langs = data.languages ?? [];
const okCount = langs.filter((l) => l.ok).length;
toast.success(
`Languages translated (${okCount}/${langs.length} completed)`,
t("toasts.languagesTranslated", {
ok: okCount,
total: langs.length,
}),
);
} else {
toast.error(data.error ?? "Translation failed");
toast.error(data.error ?? t("toasts.translationFailed"));
}
} catch {
toast.error("Network error while translating languages");
toast.error(t("toasts.networkTranslating"));
} finally {
setBuildingLanguages(false);
}
@@ -75,7 +80,7 @@ export function useStudioTranslations(defaultTranslate: boolean) {
try {
await setFurnidataTranslateEnabled({ enabled: next });
} catch {
toast.error("Could not save the default translation setting");
toast.error(t("toasts.saveDefaultFailed"));
}
};