1
0
forked from baron/baron-sso

adminfront 상단 화면 i18n 정리

This commit is contained in:
2026-05-18 13:14:19 +09:00
parent 279bfae9ec
commit 222dc6f4a4
16 changed files with 1090 additions and 67 deletions

View File

@@ -7,8 +7,11 @@ import {
fetchMe,
fetchOrphanUserLoginIDs,
} from "../../lib/adminApi";
import { createI18nMock } from "../../test/i18nMock";
import DataIntegrityPage from "./DataIntegrityPage";
vi.mock("../../lib/i18n", () => createI18nMock());
let currentRole = "super_admin";
const integrityReport = {
@@ -92,6 +95,7 @@ describe("DataIntegrityPage", () => {
beforeEach(() => {
currentRole = "super_admin";
vi.clearAllMocks();
window.localStorage.setItem("locale", "ko");
});
it("renders integrity report for super_admin", async () => {
@@ -161,4 +165,20 @@ describe("DataIntegrityPage", () => {
expect(fetchMe).toHaveBeenCalled();
expect(fetchDataIntegrityReport).not.toHaveBeenCalled();
});
it("renders localized integrity labels in English", async () => {
window.localStorage.setItem("locale", "en");
renderPage();
expect(
await screen.findByText("Data Integrity Check"),
).toBeInTheDocument();
expect(await screen.findByText("Tenant integrity")).toBeInTheDocument();
expect(await screen.findByText("Duplicate tenant slug")).toBeInTheDocument();
expect(
await screen.findByText(
"Checks duplicate active tenant slugs using LOWER(TRIM(slug)).",
),
).toBeInTheDocument();
});
});

View File

@@ -18,6 +18,7 @@ import {
fetchOrphanUserLoginIDs,
} from "../../lib/adminApi";
import { t } from "../../lib/i18n";
import { getAdminDateLocale } from "../../lib/locale";
function statusLabel(status: DataIntegrityStatus) {
switch (status) {
@@ -47,7 +48,7 @@ function formatDateTime(value?: string) {
if (!value) return "-";
const date = new Date(value);
if (Number.isNaN(date.getTime())) return value;
return new Intl.DateTimeFormat("ko-KR", {
return new Intl.DateTimeFormat(getAdminDateLocale(), {
dateStyle: "medium",
timeStyle: "medium",
}).format(date);
@@ -78,6 +79,81 @@ function reasonLabel(reason: string) {
}
}
function integritySectionLabel(key: string, fallback: string) {
switch (key) {
case "tenant_integrity":
return t("ui.admin.integrity.section.tenant_integrity", fallback);
case "user_integrity":
return t("ui.admin.integrity.section.user_integrity", fallback);
default:
return fallback;
}
}
function integrityCheckLabel(key: string, fallback: string) {
switch (key) {
case "duplicate_tenant_slugs":
return t(
"ui.admin.integrity.check.duplicate_tenant_slugs.title",
fallback,
);
case "orphan_tenant_parents":
return t(
"ui.admin.integrity.check.orphan_tenant_parents.title",
fallback,
);
case "orphan_user_tenant_memberships":
return t(
"ui.admin.integrity.check.orphan_user_tenant_memberships.title",
fallback,
);
case "orphan_user_login_id_tenants":
return t(
"ui.admin.integrity.check.orphan_user_login_id_tenants.title",
fallback,
);
case "orphan_user_login_id_users":
return t(
"ui.admin.integrity.check.orphan_user_login_id_users.title",
fallback,
);
default:
return fallback;
}
}
function integrityCheckDescription(key: string, fallback: string) {
switch (key) {
case "duplicate_tenant_slugs":
return t(
"msg.admin.integrity.check.duplicate_tenant_slugs.description",
fallback,
);
case "orphan_tenant_parents":
return t(
"msg.admin.integrity.check.orphan_tenant_parents.description",
fallback,
);
case "orphan_user_tenant_memberships":
return t(
"msg.admin.integrity.check.orphan_user_tenant_memberships.description",
fallback,
);
case "orphan_user_login_id_tenants":
return t(
"msg.admin.integrity.check.orphan_user_login_id_tenants.description",
fallback,
);
case "orphan_user_login_id_users":
return t(
"msg.admin.integrity.check.orphan_user_login_id_users.description",
fallback,
);
default:
return fallback;
}
}
function recheckStatusText(status: "idle" | "running" | "success" | "error") {
switch (status) {
case "running":
@@ -252,9 +328,6 @@ function DataIntegrityContent() {
<main className="space-y-6 p-6 md:p-8">
<div className="flex flex-wrap items-center justify-between gap-3">
<div>
<p className="text-sm text-muted-foreground">
{t("ui.admin.integrity.kicker", "System")}
</p>
<h2 className="text-2xl font-semibold tracking-tight">
{t("ui.admin.integrity.title", "데이터 정합성 검증")}
</h2>
@@ -369,7 +442,9 @@ function DataIntegrityContent() {
className="rounded-lg border border-border bg-card p-5"
>
<div className="mb-4 flex items-center justify-between gap-3">
<h3 className="text-base font-semibold">{section.label}</h3>
<h3 className="text-base font-semibold">
{integritySectionLabel(section.key, section.label)}
</h3>
<Badge variant={statusBadgeVariant(section.status)}>
{statusLabel(section.status)}
</Badge>
@@ -383,9 +458,11 @@ function DataIntegrityContent() {
<div className="flex gap-3">
<CheckIcon check={check} />
<div>
<div className="font-medium">{check.label}</div>
<div className="font-medium">
{integrityCheckLabel(check.key, check.label)}
</div>
<p className="mt-1 text-sm text-muted-foreground">
{check.description}
{integrityCheckDescription(check.key, check.description)}
</p>
</div>
</div>