1
0
forked from baron/baron-sso

최근 변경 앱 상세 다국어 정리

This commit is contained in:
2026-06-01 16:26:52 +09:00
parent d2a7ebd82f
commit d0f44de2d1
6 changed files with 128 additions and 46 deletions

View File

@@ -18,7 +18,6 @@ import {
OverviewMetric,
OverviewSelectionChips,
} from "../../../../common/core/components/overview";
import { formatAuditDateParts } from "../../../../common/core/audit";
import { DeveloperAccessRequestCard } from "../../components/common/DeveloperAccessRequestCard";
import { Badge } from "../../components/ui/badge";
import { Button } from "../../components/ui/button";
@@ -81,6 +80,7 @@ type UsageChartPalette = {
};
const deletedRecentChangeFilterId = "__deleted_recent_clients__";
const localeStorageKey = "locale";
type RecentChangePoint = {
date: string;
@@ -102,6 +102,73 @@ type RecentChangeSeries = {
points: RecentChangePoint[];
};
type AppLocale = "ko" | "en";
function resolveAppLocale(): AppLocale {
if (typeof window === "undefined") {
return "ko";
}
const stored = window.localStorage.getItem(localeStorageKey);
if (stored === "ko" || stored === "en") {
return stored;
}
const pathLocale = window.location.pathname.split("/")[1];
if (pathLocale === "ko" || pathLocale === "en") {
return pathLocale;
}
return window.navigator.language.toLowerCase().startsWith("ko")
? "ko"
: "en";
}
function formatRecentChangeTimestamp(value: string) {
if (!value) {
return { date: "-", time: "-" };
}
const parsed = new Date(value);
if (Number.isNaN(parsed.getTime())) {
return { date: value, time: "-" };
}
const locale = resolveAppLocale();
if (locale === "ko") {
const date = parsed.toISOString().slice(0, 10);
const timeParts = new Intl.DateTimeFormat("ko-KR", {
hour12: false,
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}).formatToParts(parsed);
const hour = timeParts.find((part) => part.type === "hour")?.value ?? "00";
const minute =
timeParts.find((part) => part.type === "minute")?.value ?? "00";
const second =
timeParts.find((part) => part.type === "second")?.value ?? "00";
return {
date,
time: `${hour}${minute}${second}`,
};
}
return {
date: new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "short",
day: "numeric",
}).format(parsed),
time: new Intl.DateTimeFormat("en-US", {
hour: "numeric",
minute: "2-digit",
second: "2-digit",
}).format(parsed),
};
}
const usageChartPalettes: UsageChartPalette[] = [
{ bar: "#7dd3fc", line: "#10b981", point: "#059669" },
{ bar: "#f9a8d4", line: "#f97316", point: "#ea580c" },
@@ -1068,7 +1135,7 @@ function GlobalOverviewPage() {
id: deletedRecentChangeFilterId,
label: t(
"ui.dev.dashboard.recent_changes.deleted_group",
"삭제된 RP",
"삭제된 ",
),
},
];
@@ -1399,9 +1466,9 @@ function GlobalOverviewPage() {
<OverviewMetric
icon={<Layers3 size={14} />}
label={t(
"ui.dev.dashboard.recent_changes.summary.deleted_clients",
"삭제된 RP 수",
)}
"ui.dev.dashboard.recent_changes.summary.deleted_clients",
"삭제된 수",
)}
value={deletedRecentChangedClientCount.toLocaleString()}
/>
<OverviewMetric
@@ -1466,7 +1533,8 @@ function GlobalOverviewPage() {
</div>
) : (
visibleRecentClientChanges.map((item) => {
const { date, time } = formatAuditDateParts(item.timestamp);
const { date, time } =
formatRecentChangeTimestamp(item.timestamp);
return (
<div
key={item.eventId}

View File

@@ -5,6 +5,7 @@ import {
type AuditDetails,
type CommonAuditLog,
} from "../../../../common/core/audit";
import { t } from "../../lib/i18n";
import type { ClientSummary, DevAuditLog } from "../../lib/devApi";
export type RecentClientChange = {
@@ -28,27 +29,6 @@ const recentClientActions = new Set([
"DELETE_CLIENT",
]);
const recentClientFieldLabels: Record<string, string> = {
name: "이름",
type: "유형",
status: "상태",
scopes: "스코프",
tenant_access_restricted: "테넌트 접근 제한",
allowed_tenants: "허용 테넌트",
id_token_claims: "커스텀 클레임",
token_endpoint_auth_method: "인증 방식",
jwks_uri: "JWKS URI",
backchannel_logout_uri: "Backchannel Logout URI",
backchannel_logout_session_required: "세션 필수",
headless_login_enabled: "헤드리스 로그인",
headless_token_endpoint_auth_method: "헤드리스 인증 방식",
headless_jwks_uri: "헤드리스 JWKS URI",
redirect_uri_count: "Redirect URI 수",
scope_count: "Scope 수",
relation: "관계",
subject: "대상",
};
function isRecord(value: unknown): value is Record<string, unknown> {
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}
@@ -56,24 +36,43 @@ function isRecord(value: unknown): value is Record<string, unknown> {
export function getRecentClientActionLabel(action: string) {
switch (action) {
case "CREATE_CLIENT":
return "클라이언트 생성";
return t("ui.dev.clients.recent_changes.guide.create", "앱 생성");
case "UPDATE_CLIENT":
return "설정 변경";
return t("ui.dev.clients.recent_changes.guide.settings", "설정 변경");
case "UPDATE_CLIENT_STATUS":
return "상태 변경";
return t("ui.dev.clients.recent_changes.guide.status", "상태 변경");
case "ROTATE_SECRET":
return "클라이언트 시크릿 재발급";
return t(
"ui.dev.clients.recent_changes.guide.secret",
"클라이언트 시크릿 재발급",
);
case "ADD_RELATION":
return "관계 추가";
return t("ui.dev.clients.relationships.add_title", "관계 추가");
case "REMOVE_RELATION":
return "관계 삭제";
return t("ui.common.remove", "Remove");
case "DELETE_CLIENT":
return "클라이언트 삭제";
return t("ui.dev.clients.recent_changes.guide.delete", "앱 삭제");
default:
return action;
}
}
function getRecentClientFieldLabel(key: string) {
switch (key) {
case "relation":
return t("ui.dev.clients.relationships.relation", "관계");
case "subject":
return t("ui.dev.clients.relationships.subject", "대상");
case "client_secret":
return t(
"ui.dev.clients.details.credentials.client_secret",
"클라이언트 시크릿",
);
default:
return key;
}
}
export function buildRecentClientChangeDetails(
action: string,
details: AuditDetails,
@@ -82,17 +81,32 @@ export function buildRecentClientChangeDetails(
const after = isRecord(details.after) ? details.after : {};
if (action === "ROTATE_SECRET") {
return [{ label: "클라이언트 시크릿", value: "재발급" }];
return [
{
label: getRecentClientFieldLabel("client_secret"),
value: t("msg.dev.clients.secret_rotated", "재발급"),
},
];
}
if (action === "ADD_RELATION" || action === "REMOVE_RELATION") {
const source = action === "ADD_RELATION" ? after : before;
return [
...(source.relation
? [{ label: "관계", value: formatAuditValue(source.relation) }]
? [
{
label: getRecentClientFieldLabel("relation"),
value: formatAuditValue(source.relation),
},
]
: []),
...(source.subject
? [{ label: "대상", value: formatAuditValue(source.subject) }]
? [
{
label: getRecentClientFieldLabel("subject"),
value: formatAuditValue(source.subject),
},
]
: []),
];
}
@@ -112,7 +126,7 @@ export function buildRecentClientChangeDetails(
}
}
const label = recentClientFieldLabels[key] ?? key;
const label = getRecentClientFieldLabel(key);
if (action === "CREATE_CLIENT") {
if (afterValue === undefined) {
return null;

View File

@@ -1746,7 +1746,7 @@ title = "Quick links"
title = "My Applications"
[ui.dev.dashboard.recent_changes]
deleted_group = "Deleted RPs"
deleted_group = "Deleted applications"
aria = "Recent application changes"
period = "Recent change aggregation period"
series = "Changes {{changes}} / Actors {{actors}}"
@@ -1755,7 +1755,7 @@ y_axis = "Y axis: change count"
[ui.dev.dashboard.recent_changes.summary]
changed_clients = "Changed apps"
deleted_clients = "Deleted RPs"
deleted_clients = "Deleted applications"
latest_change = "Latest change"
total_changes = "Recent changes"

View File

@@ -1745,7 +1745,7 @@ title = "빠른 이동"
title = "내 애플리케이션"
[ui.dev.dashboard.recent_changes]
deleted_group = "삭제된 RP"
deleted_group = "삭제된 "
aria = "최근 변경된 앱 현황"
period = "최근 변경 집계 단위"
series = "변경 {{changes}} / 작업자 {{actors}}"
@@ -1754,7 +1754,7 @@ y_axis = "Y축: 변경 수"
[ui.dev.dashboard.recent_changes.summary]
changed_clients = "변경된 앱 수"
deleted_clients = "삭제된 RP 수"
deleted_clients = "삭제된 수"
latest_change = "마지막 변경일"
total_changes = "최근 변경 건수"