forked from baron/baron-sso
devfront 설정 화면 로케일 누락 수정
This commit is contained in:
@@ -1178,14 +1178,14 @@ function ClientGeneralPage() {
|
||||
if (!trimmedJwksUri) {
|
||||
validationErrors.push(
|
||||
t(
|
||||
"msg.dev.clients.general.public_key.validation.missing_jwks_uri",
|
||||
"ui.dev.clients.general.public_key.validation.missing_jwks_uri",
|
||||
"JWKS URI를 입력해야 합니다.",
|
||||
),
|
||||
);
|
||||
} else if (!isValidUrl(trimmedJwksUri)) {
|
||||
validationErrors.push(
|
||||
t(
|
||||
"msg.dev.clients.general.public_key.validation.invalid_jwks_uri",
|
||||
"ui.dev.clients.general.public_key.validation.invalid_jwks_uri",
|
||||
"JWKS URI 형식이 올바르지 않습니다.",
|
||||
),
|
||||
);
|
||||
@@ -1193,7 +1193,7 @@ function ClientGeneralPage() {
|
||||
if (unsupportedParsedAlgorithms.length > 0) {
|
||||
validationErrors.push(
|
||||
t(
|
||||
"msg.dev.clients.general.public_key.validation.unsupported_parsed_algorithms",
|
||||
"ui.dev.clients.general.public_key.validation.unsupported_parsed_algorithms",
|
||||
"JWKS에 지원하지 않는 알고리즘이 있습니다: {{details}}",
|
||||
{ details: unsupportedParsedAlgorithmSummary },
|
||||
),
|
||||
@@ -1202,7 +1202,7 @@ function ClientGeneralPage() {
|
||||
if (missingParsedAlgorithms.length > 0) {
|
||||
validationErrors.push(
|
||||
t(
|
||||
"msg.dev.clients.general.public_key.validation.missing_parsed_algorithms",
|
||||
"ui.dev.clients.general.public_key.validation.missing_parsed_algorithms",
|
||||
"JWKS에 알고리즘(`alg`)이 선언되지 않은 키가 있습니다: {{details}}",
|
||||
{ details: missingParsedAlgorithmSummary },
|
||||
),
|
||||
@@ -2050,13 +2050,13 @@ function ClientGeneralPage() {
|
||||
<p className="text-sm font-semibold">
|
||||
{t(
|
||||
"ui.dev.clients.general.scopes.picker_title",
|
||||
"추가할 scope 선택",
|
||||
"Add a scope",
|
||||
)}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t(
|
||||
"msg.dev.clients.general.scopes.picker_help",
|
||||
"지원 scope와 Custom Claim key를 선택해 scope 목록에 추가합니다.",
|
||||
"ui.dev.clients.general.scopes.picker_help",
|
||||
"Choose a supported scope or custom claim key to add it to the scope list.",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
@@ -2427,13 +2427,13 @@ function ClientGeneralPage() {
|
||||
{tenantAccessRestricted ? (
|
||||
<div className="grid gap-4 lg:grid-cols-[0.8fr_1.2fr]">
|
||||
<div className="space-y-3">
|
||||
<Label className="text-sm font-semibold">
|
||||
{t(
|
||||
"ui.dev.clients.general.tenant_access.picker_label",
|
||||
"허용 테넌트 추가",
|
||||
)}{" "}
|
||||
<span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Label className="text-sm font-semibold">
|
||||
{t(
|
||||
"ui.dev.clients.general.tenant_access.picker_label",
|
||||
"Add allowed tenant",
|
||||
)}{" "}
|
||||
<span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<TenantAccessPicker
|
||||
disabled={isGeneralSettingsReadOnly}
|
||||
selectedCount={allowedTenantIds.length}
|
||||
@@ -3069,8 +3069,8 @@ function ClientGeneralPage() {
|
||||
</Label>
|
||||
<p className="text-[10px] text-muted-foreground">
|
||||
{t(
|
||||
"msg.dev.clients.general.security.headless_login_enable_help",
|
||||
"Baron SSO 로그인 창 대신 RP 자체 로그인 UI를 사용하고, RP backend의 서명 키로 클라이언트를 검증하려는 경우 활성화합니다.",
|
||||
"ui.dev.clients.general.security.headless_login_enable_help",
|
||||
"Enable this when the RP uses its own login UI instead of the Baron SSO login page and the RP backend validates the client with a signing key.",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -57,7 +57,7 @@ export function TenantAccessPicker({
|
||||
aria-modal="true"
|
||||
aria-label={t(
|
||||
"ui.dev.clients.general.tenant_access.picker_title",
|
||||
"테넌트 선택",
|
||||
"Select tenant",
|
||||
)}
|
||||
>
|
||||
<div className="flex h-[92vh] w-[min(96vw,1200px)] flex-col overflow-hidden rounded-2xl border border-border bg-background p-4 shadow-2xl">
|
||||
@@ -66,13 +66,13 @@ export function TenantAccessPicker({
|
||||
<h2 className="text-lg font-semibold">
|
||||
{t(
|
||||
"ui.dev.clients.general.tenant_access.picker_title",
|
||||
"테넌트 선택",
|
||||
"Select tenant",
|
||||
)}
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t(
|
||||
"msg.dev.clients.general.tenant_access.picker_description",
|
||||
"orgfront 조직도에서 허용할 테넌트를 선택하면 목록에 추가됩니다.",
|
||||
"ui.dev.clients.general.tenant_access.picker_description",
|
||||
"Choose the tenants to allow from the orgfront org chart and add them to the list.",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
@@ -83,7 +83,7 @@ export function TenantAccessPicker({
|
||||
className="shrink-0"
|
||||
onClick={() => setPickerOpen(false)}
|
||||
>
|
||||
{t("ui.common.close", "닫기")}
|
||||
{t("ui.common.close", "Close")}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-4 min-h-0 flex-1 overflow-hidden rounded-md border">
|
||||
@@ -102,7 +102,7 @@ export function TenantAccessPicker({
|
||||
variant="outline"
|
||||
onClick={() => setPickerOpen(false)}
|
||||
>
|
||||
{t("ui.common.close", "닫기")}
|
||||
{t("ui.common.close", "Close")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -123,7 +123,7 @@ export function TenantAccessPicker({
|
||||
<Building2 className="h-4 w-4" />
|
||||
{t(
|
||||
"ui.dev.clients.general.tenant_access.open_picker",
|
||||
"테넌트 선택기 열기",
|
||||
"Open tenant picker",
|
||||
)}
|
||||
</Button>
|
||||
|
||||
@@ -132,13 +132,13 @@ export function TenantAccessPicker({
|
||||
<div className="rounded-xl border border-dashed border-border bg-muted/20 px-4 py-3 text-sm text-muted-foreground">
|
||||
{selectedCount > 0
|
||||
? t(
|
||||
"msg.dev.clients.general.tenant_access.picker_hint_with_count",
|
||||
"현재 {{count}}개가 선택되어 있습니다.",
|
||||
"ui.dev.clients.general.tenant_access.picker_hint_with_count",
|
||||
"{{count}} tenants selected.",
|
||||
{ count: selectedCount },
|
||||
)
|
||||
: t(
|
||||
"msg.dev.clients.general.tenant_access.picker_hint",
|
||||
"선택기를 열어 허용 테넌트를 추가하세요.",
|
||||
"ui.dev.clients.general.tenant_access.picker_hint",
|
||||
"Open the picker to add allowed tenants.",
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { normalizeDeveloperAccessPageSelection } from "./developerAccessPages";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
developerAccessPagesToLabel,
|
||||
getDeveloperAccessPageLabel,
|
||||
normalizeDeveloperAccessPageSelection,
|
||||
} from "./developerAccessPages";
|
||||
|
||||
beforeEach(() => {
|
||||
window.localStorage.clear();
|
||||
window.localStorage.setItem("locale", "ko");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.localStorage.clear();
|
||||
});
|
||||
|
||||
describe("developer access pages", () => {
|
||||
it("collapses all non-all pages into all", () => {
|
||||
@@ -21,4 +34,20 @@ describe("developer access pages", () => {
|
||||
it("keeps explicit all selection", () => {
|
||||
expect(normalizeDeveloperAccessPageSelection(["all"])).toEqual(["all"]);
|
||||
});
|
||||
|
||||
it("returns localized labels for access pages", () => {
|
||||
expect(getDeveloperAccessPageLabel("all")).toBe("전체");
|
||||
expect(developerAccessPagesToLabel(["overview", "audit"])).toBe(
|
||||
"개요, 감사로그",
|
||||
);
|
||||
|
||||
window.localStorage.setItem("locale", "en");
|
||||
|
||||
expect(getDeveloperAccessPageLabel("client_create")).toBe(
|
||||
"Add linked app",
|
||||
);
|
||||
expect(developerAccessPagesToLabel(["overview", "audit"])).toBe(
|
||||
"Overview, Audit Logs",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { t } from "../../lib/i18n";
|
||||
|
||||
export type DeveloperAccessPage =
|
||||
| "all"
|
||||
| "overview"
|
||||
@@ -10,15 +12,40 @@ export const developerAccessPageOrder: DeveloperAccessPage[] = [
|
||||
"audit",
|
||||
];
|
||||
|
||||
export const developerAccessPageOptions: Array<{
|
||||
export function getDeveloperAccessPageLabel(page: DeveloperAccessPage): string {
|
||||
switch (page) {
|
||||
case "all":
|
||||
return t("ui.dev.access_pages.all", "전체");
|
||||
case "overview":
|
||||
return t("ui.dev.access_pages.overview", "개요");
|
||||
case "client_create":
|
||||
return t("ui.dev.access_pages.client_create", "연동 앱 추가");
|
||||
case "audit":
|
||||
return t("ui.dev.access_pages.audit", "감사로그");
|
||||
default:
|
||||
return page;
|
||||
}
|
||||
}
|
||||
|
||||
export function getDeveloperAccessPageOptions(): Array<{
|
||||
value: DeveloperAccessPage;
|
||||
label: string;
|
||||
}> = [
|
||||
{ value: "all", label: "전체" },
|
||||
{ value: "overview", label: "개요" },
|
||||
{ value: "client_create", label: "연동 앱 추가" },
|
||||
{ value: "audit", label: "감사로그" },
|
||||
];
|
||||
}> {
|
||||
return developerAccessPageOrder.length > 0
|
||||
? [
|
||||
{ value: "all", label: getDeveloperAccessPageLabel("all") },
|
||||
{
|
||||
value: "overview",
|
||||
label: getDeveloperAccessPageLabel("overview"),
|
||||
},
|
||||
{
|
||||
value: "client_create",
|
||||
label: getDeveloperAccessPageLabel("client_create"),
|
||||
},
|
||||
{ value: "audit", label: getDeveloperAccessPageLabel("audit") },
|
||||
]
|
||||
: [];
|
||||
}
|
||||
|
||||
export function normalizeDeveloperAccessPages(
|
||||
pages: Array<string | undefined | null>,
|
||||
@@ -61,20 +88,11 @@ export function normalizeDeveloperAccessPageSelection(
|
||||
export function developerAccessPagesToLabel(pages?: Array<string | null>) {
|
||||
const normalized = normalizeDeveloperAccessPages(pages ?? []);
|
||||
if (normalized.length === 0 || normalized.includes("all")) {
|
||||
return "전체";
|
||||
return getDeveloperAccessPageLabel("all");
|
||||
}
|
||||
return normalized
|
||||
.map((page) => {
|
||||
switch (page) {
|
||||
case "overview":
|
||||
return "개요";
|
||||
case "client_create":
|
||||
return "연동 앱 추가";
|
||||
case "audit":
|
||||
return "감사로그";
|
||||
default:
|
||||
return page;
|
||||
}
|
||||
return getDeveloperAccessPageLabel(page);
|
||||
})
|
||||
.join(", ");
|
||||
}
|
||||
|
||||
@@ -38,7 +38,8 @@ import { resolveProfileRole } from "../../lib/role";
|
||||
import { fetchMe } from "../auth/authApi";
|
||||
import {
|
||||
type DeveloperAccessPage,
|
||||
developerAccessPageOptions,
|
||||
developerAccessPagesToLabel,
|
||||
getDeveloperAccessPageOptions,
|
||||
normalizeDeveloperAccessPageSelection,
|
||||
normalizeDeveloperAccessPages,
|
||||
} from "../developer-access/developerAccessPages";
|
||||
@@ -62,6 +63,7 @@ export default function DeveloperGrantsPage() {
|
||||
});
|
||||
const profileRole = me?.role?.trim() || role;
|
||||
const isSuperAdmin = profileRole === "super_admin";
|
||||
const developerAccessPageOptions = getDeveloperAccessPageOptions();
|
||||
|
||||
const [userSearch, setUserSearch] = useState("");
|
||||
const deferredUserSearch = useDeferredValue(userSearch.trim());
|
||||
@@ -621,9 +623,7 @@ export default function DeveloperGrantsPage() {
|
||||
: ["all"]
|
||||
).map((page) => (
|
||||
<Badge key={page} variant="outline">
|
||||
{developerAccessPageOptions.find(
|
||||
(option) => option.value === page,
|
||||
)?.label ?? page}
|
||||
{developerAccessPagesToLabel([page])}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -49,7 +49,8 @@ import { resolveProfileRole } from "../../lib/role";
|
||||
import { fetchMe } from "../auth/authApi";
|
||||
import {
|
||||
type DeveloperAccessPage,
|
||||
developerAccessPageOptions,
|
||||
developerAccessPagesToLabel,
|
||||
getDeveloperAccessPageOptions,
|
||||
normalizeDeveloperAccessPageSelection,
|
||||
normalizeDeveloperAccessPages,
|
||||
} from "../developer-access/developerAccessPages";
|
||||
@@ -287,9 +288,7 @@ export default function DeveloperRequestPage() {
|
||||
req.accessPages,
|
||||
).map((page) => (
|
||||
<Badge key={page} variant="outline">
|
||||
{developerAccessPageOptions.find(
|
||||
(option) => option.value === page,
|
||||
)?.label ?? page}
|
||||
{developerAccessPagesToLabel([page])}
|
||||
</Badge>
|
||||
))
|
||||
) : (
|
||||
@@ -479,6 +478,7 @@ function RequestAccessModal({
|
||||
const [accessPages, setAccessPages] = useState<DeveloperAccessPage[]>([
|
||||
"all",
|
||||
]);
|
||||
const developerAccessPageOptions = getDeveloperAccessPageOptions();
|
||||
const organizationDisplay = organization.trim() || t("ui.common.na", "없음");
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user