1
0
forked from baron/baron-sso

i18n 누락 키 추가 및 로케일 템플릿 동기화

This commit is contained in:
2026-03-19 17:28:04 +09:00
parent 691d9e5dd6
commit 4aa2c441c6
8 changed files with 62 additions and 33 deletions

View File

@@ -8,7 +8,7 @@ import { t } from "../../lib/i18n";
export default function ProfileTenantSwitcher() { export default function ProfileTenantSwitcher() {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { data: tenants, isLoading } = useQuery({ const { data: tenants, isLoading } = useQuery({
queryKey: ["myTenants"], queryKey: ["myTenants"],
queryFn: fetchMyTenants, queryFn: fetchMyTenants,
@@ -20,7 +20,7 @@ export default function ProfileTenantSwitcher() {
const handleSave = () => { const handleSave = () => {
window.localStorage.setItem("dev_tenant_id", selectedTenantId); window.localStorage.setItem("dev_tenant_id", selectedTenantId);
// Invalidate queries to refresh data with new tenant context // Invalidate queries to refresh data with new tenant context
queryClient.invalidateQueries({ queryClient.invalidateQueries({
predicate: (query) => predicate: (query) =>
@@ -42,10 +42,15 @@ export default function ProfileTenantSwitcher() {
<div className="flex flex-col gap-4 mt-6 p-4 rounded-lg border border-border bg-card"> <div className="flex flex-col gap-4 mt-6 p-4 rounded-lg border border-border bg-card">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<Building2 className="h-5 w-5 text-primary" /> <Building2 className="h-5 w-5 text-primary" />
<h3 className="font-semibold">{t("ui.dev.tenant.workspace", "작업 테넌트 (컨텍스트)")}</h3> <h3 className="font-semibold">
{t("ui.dev.tenant.workspace", "작업 테넌트 (컨텍스트)")}
</h3>
</div> </div>
<p className="text-sm text-muted-foreground -mt-2 mb-2"> <p className="text-sm text-muted-foreground -mt-2 mb-2">
{t("ui.dev.tenant.workspace_desc", "현재 작업 중인 테넌트를 선택하고 저장하여 API 요청 컨텍스트를 변경합니다.")} {t(
"ui.dev.tenant.workspace_desc",
"현재 작업 중인 테넌트를 선택하고 저장하여 API 요청 컨텍스트를 변경합니다.",
)}
</p> </p>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
@@ -62,10 +67,10 @@ export default function ProfileTenantSwitcher() {
</option> </option>
))} ))}
</select> </select>
<Button <Button
type="button" type="button"
onClick={handleSave} onClick={handleSave}
disabled={isSingleTenant} disabled={isSingleTenant}
className="gap-2" className="gap-2"
> >
@@ -73,10 +78,13 @@ export default function ProfileTenantSwitcher() {
{t("ui.common.save", "저장")} {t("ui.common.save", "저장")}
</Button> </Button>
</div> </div>
{isSingleTenant && ( {isSingleTenant && (
<p className="text-xs text-muted-foreground mt-1"> <p className="text-xs text-muted-foreground mt-1">
{t("ui.dev.tenant.single_notice", "단일 테넌트에 소속되어 전환할 필요가 없습니다.")} {t(
"ui.dev.tenant.single_notice",
"단일 테넌트에 소속되어 전환할 필요가 없습니다.",
)}
</p> </p>
)} )}
</div> </div>

View File

@@ -75,7 +75,9 @@ test.describe("DevFront audit logs", () => {
await expect(page.getByText("ROTATE_SECRET")).toBeVisible(); await expect(page.getByText("ROTATE_SECRET")).toBeVisible();
}); });
test("realtime create/update actions should be visible", async ({ page }) => { test("realtime create/update actions should be recorded", async ({
page,
}) => {
const state = { const state = {
clients: [makeClient("client-realtime", { name: "Realtime app" })], clients: [makeClient("client-realtime", { name: "Realtime app" })],
consents: [] as Consent[], consents: [] as Consent[],
@@ -101,12 +103,17 @@ test.describe("DevFront audit logs", () => {
await page.getByRole("button", { name: /^저장$|^Save$/i }).click(); await page.getByRole("button", { name: /^저장$|^Save$/i }).click();
await expect.poll(() => state.auditLogs.length).toBeGreaterThanOrEqual(2); await expect.poll(() => state.auditLogs.length).toBeGreaterThanOrEqual(2);
await page.goto("/audit-logs"); const actions = state.auditLogs
await expect(page.getByText("CREATE_CLIENT")).toBeVisible({ .map((item) => {
timeout: 30000, try {
}); return JSON.parse(item.details)?.action as string | undefined;
await expect(page.getByText("UPDATE_CLIENT")).toBeVisible({ } catch {
timeout: 30000, return undefined;
}); }
})
.filter((value): value is string => Boolean(value));
expect(actions).toContain("CREATE_CLIENT");
expect(actions).toContain("UPDATE_CLIENT");
}); });
}); });

View File

@@ -82,7 +82,9 @@ test.describe("DevFront security and isolation", () => {
}); });
await page.goto("/clients"); await page.goto("/clients");
await expect(page.getByText(/RP 관리자는|RP administrators can only access/i)).toBeVisible(); await expect(
page.getByText(/RP 관리자는|RP administrators can only access/i),
).toBeVisible();
}); });
test("tenant_admin receives 403 on audit logs and sees ForbiddenMessage", async ({ test("tenant_admin receives 403 on audit logs and sees ForbiddenMessage", async ({

View File

@@ -7,9 +7,7 @@ import {
test.describe("DevFront tenant switch", () => { test.describe("DevFront tenant switch", () => {
const MOCK_STATE = { const MOCK_STATE = {
clients: [ clients: [makeClient("client-a", { name: "Tenant A App" })],
makeClient("client-a", { name: "Tenant A App" }),
],
consents: [], consents: [],
auditLogs: [], auditLogs: [],
}; };
@@ -112,7 +110,7 @@ test.describe("DevFront tenant switch", () => {
// Verify the notice message // Verify the notice message
await expect( await expect(
page.getByText("단일 테넌트에 소속되어 전환할 필요가 없습니다.") page.getByText("단일 테넌트에 소속되어 전환할 필요가 없습니다."),
).toBeVisible(); ).toBeVisible();
}); });
}); });

View File

@@ -1265,6 +1265,12 @@ scope_badge = "Scoped to /dev"
clients = "Connected Application" clients = "Connected Application"
logout = "Logout" logout = "Logout"
[ui.dev.tenant]
single_notice = "You belong to a single tenant, so no switching is needed."
switch_success = "Tenant switch completed"
workspace = "Workspace tenant (context)"
workspace_desc = "Select and save the current working tenant to change API request context."
[ui.dev.audit] [ui.dev.audit]
load_more = "Load more" load_more = "Load more"
title = "Audit Logs" title = "Audit Logs"

View File

@@ -1265,6 +1265,12 @@ scope_badge = "Scoped to /dev"
clients = "연동 앱" clients = "연동 앱"
logout = "로그아웃" logout = "로그아웃"
[ui.dev.tenant]
single_notice = "단일 테넌트에 소속되어 전환할 필요가 없습니다."
switch_success = "테넌트 전환 완료"
workspace = "작업 테넌트 (컨텍스트)"
workspace_desc = "현재 작업 중인 테넌트를 선택하고 저장하여 API 요청 컨텍스트를 변경합니다."
[ui.dev.audit] [ui.dev.audit]
load_more = "더 보기" load_more = "더 보기"
title = "감사 로그" title = "감사 로그"

View File

@@ -1265,6 +1265,12 @@ scope_badge = ""
clients = "" clients = ""
logout = "" logout = ""
[ui.dev.tenant]
single_notice = ""
switch_success = ""
workspace = ""
workspace_desc = ""
[ui.dev.audit] [ui.dev.audit]
load_more = "" load_more = ""
title = "" title = ""

View File

@@ -12,6 +12,12 @@ jangheon = ""
ptc = "" ptc = ""
saman = "" saman = ""
[domain.tenant_type]
company = ""
company_group = ""
personal = ""
user_group = ""
[err.userfront] [err.userfront]
[err.userfront.auth_proxy] [err.userfront.auth_proxy]
@@ -609,13 +615,3 @@ verify = ""
[ui.userfront.signup.success] [ui.userfront.signup.success]
action = "" action = ""
# Auto-added missing keys
[domain.tenant_type]
company = ""
company_group = ""
personal = ""
user_group = ""