1
0
forked from baron/baron-sso

i18n 누락 정리 및 하드코딩 텍스트 치환

This commit is contained in:
2026-05-06 16:31:40 +09:00
parent 9a87af93f1
commit 64cdef81a6
6 changed files with 154 additions and 81 deletions

View File

@@ -14,34 +14,34 @@ export function ForbiddenMessage({ resourceToken }: Props) {
let explanation = t(
"msg.dev.forbidden.default",
"해당 리소스에 접근할 권한이 없습니다. 관리자에게 문의하세요.",
"You do not have permission to access this resource. Contact your administrator.",
);
if (role === "rp_admin") {
explanation = t(
"msg.dev.forbidden.rp_admin",
"RP 관리자는 담당 앱의 리소스만 조회할 수 있습니다.",
"RP administrators can only access resources for their assigned applications.",
);
} else if (role === "tenant_admin") {
explanation = t(
"msg.dev.forbidden.tenant_admin",
"테넌트 관리자 권한이 올바르게 설정되지 않았거나 만료되었습니다.",
"Your tenant administrator permission is missing, misconfigured, or expired.",
);
} else if (role === "user" || role === "tenant_member") {
if (resourceToken === "consents") {
explanation = t(
"msg.dev.forbidden.user.consents",
"해당 앱(RP)에 대한 동의 내역 조회는 'RP 관리자', '동의 조회', '동의 회수' 관계가 부여된 경우에만 사용할 수 있습니다. 권한이 필요하면 관리자에게 요청하세요.",
"Viewing consent records for this application requires an RP administrator, consent read, or consent revoke relationship. Request access from an administrator if needed.",
);
} else if (resourceToken === "audit") {
explanation = t(
"msg.dev.forbidden.user.audit",
"해당 앱(RP)에 대한 감사 로그 조회는 'RP 관리자', '감사 조회' 관계가 부여된 경우에만 사용할 수 있습니다. 권한이 필요하면 관리자에게 요청하세요.",
"Viewing audit logs for this application requires an RP administrator or audit read relationship. Request access from an administrator if needed.",
);
} else {
explanation = t(
"msg.dev.forbidden.user.clients",
"일반 사용자 계정은 담당 RP(앱)에 대한 운영 또는 관리 관계가 부여된 경우에만 해당 기능을 사용할 수 있습니다. 권한이 필요하면 관리자에게 요청하세요.",
"Standard user accounts can use this feature only when an operational or administrative relationship is granted for the target RP. Request access from an administrator if needed.",
);
}
}
@@ -51,9 +51,9 @@ export function ForbiddenMessage({ resourceToken }: Props) {
? t("ui.dev.audit.title", "Audit Logs")
: resourceToken === "consents"
? t("ui.dev.clients.consents.title", "User Consent Grants")
: t("ui.dev.clients.registry.subtitle", "연동 앱");
: t("ui.dev.clients.registry.subtitle", "Connected Applications");
const title = t("msg.dev.forbidden.title", "{{resource}} 접근 권한 없음", {
const title = t("msg.dev.forbidden.title", "Access denied: {{resource}}", {
resource: resourceLabel,
});

View File

@@ -32,7 +32,7 @@ const navItems = [
},
{
labelKey: "ui.dev.nav.developer_request",
labelFallback: "개발자 권한 신청",
labelFallback: "Developer Access Request",
to: "/developer-requests",
icon: ClipboardCheck,
},
@@ -71,7 +71,11 @@ function AppLayout() {
});
const handleLogout = () => {
if (window.confirm(t("msg.dev.logout_confirm", "로그아웃 하시겠습니까?"))) {
if (
window.confirm(
t("msg.dev.logout_confirm", "Are you sure you want to log out?"),
)
) {
auth.removeUser();
navigate("/login");
}
@@ -136,7 +140,7 @@ function AppLayout() {
try {
await auth.signinSilent();
} catch (error) {
console.error("세션 자동 연장에 실패했습니다.", error);
console.error("Silent session renewal failed.", error);
} finally {
isRenewInFlightRef.current = false;
}
@@ -184,7 +188,7 @@ function AppLayout() {
try {
await auth.signinSilent();
} catch (error) {
console.error("세션 무제한 유지 갱신에 실패했습니다.", error);
console.error("Unlimited session keepalive renewal failed.", error);
} finally {
isRenewInFlightRef.current = false;
}
@@ -241,7 +245,7 @@ function AppLayout() {
void auth
.signinSilent()
.catch((error) => {
console.error("세션 자동 연장에 실패했습니다.", error);
console.error("Silent session renewal failed.", error);
})
.finally(() => {
isRenewInFlightRef.current = false;
@@ -289,15 +293,15 @@ function AppLayout() {
let sessionToneClass =
"border-emerald-500/30 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300";
let sessionText = t("ui.dev.session.active", "세션 활성");
let sessionText = t("ui.dev.session.active", "Session active");
if (remainingMs === null) {
sessionToneClass = "border-border bg-card text-muted-foreground";
sessionText = t("ui.dev.session.unknown", "알 수 없음");
sessionText = t("ui.dev.session.unknown", "Unknown");
} else if (remainingMs <= 0) {
sessionToneClass =
"border-rose-500/30 bg-rose-500/10 text-rose-700 dark:text-rose-300";
sessionText = t("ui.dev.session.expired", "세션 만료");
sessionText = t("ui.dev.session.expired", "Session expired");
} else if (
remainingMinutes !== null &&
remainingSeconds !== null &&
@@ -307,7 +311,7 @@ function AppLayout() {
"border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300";
sessionText = t(
"ui.dev.session.expiring",
"만료 임박: {{minutes}} {{seconds}}초 남음",
"Expiring soon: {{minutes}}m {{seconds}}s left",
{
minutes: remainingMinutes,
seconds: remainingSeconds,
@@ -316,7 +320,7 @@ function AppLayout() {
} else {
sessionText = t(
"ui.dev.session.remaining",
"만료 예정: {{minutes}} {{seconds}}초 남음",
"Expires in {{minutes}}m {{seconds}}s",
{
minutes: remainingMinutes ?? 0,
seconds: remainingSeconds ?? 0,
@@ -343,7 +347,7 @@ function AppLayout() {
</div>
<div>
<p className="text-xs uppercase tracking-[0.18em] text-muted-foreground">
{t("ui.dev.brand", "Baron 로그인")}
{t("ui.dev.brand", "Baron Sign In")}
</p>
<h1 className="text-lg font-semibold">
{t("ui.dev.console_title", "Developer Console")}
@@ -423,7 +427,7 @@ function AppLayout() {
type="button"
onClick={toggleTheme}
className="inline-flex items-center gap-2 rounded-full border border-border px-3 py-2 text-muted-foreground transition hover:bg-muted/20"
aria-label={t("ui.common.theme_toggle", "테마 전환")}
aria-label={t("ui.common.theme_toggle", "Toggle theme")}
>
{theme === "light" ? <Sun size={16} /> : <Moon size={16} />}
{theme === "light"
@@ -447,7 +451,7 @@ function AppLayout() {
className="inline-flex items-center gap-3 rounded-full border border-border bg-card px-3 py-2 transition hover:bg-muted/20"
aria-haspopup="menu"
aria-expanded={isProfileMenuOpen}
aria-label={t("ui.dev.profile.menu_aria", "계정 메뉴 열기")}
aria-label={t("ui.dev.profile.menu_aria", "Open account menu")}
>
<div className="grid h-8 w-8 place-items-center rounded-full bg-primary/15 text-xs font-semibold text-primary">
{profileInitial}
@@ -496,14 +500,14 @@ function AppLayout() {
<div className="flex items-center justify-between gap-3">
<div>
<p className="text-sm font-medium text-foreground">
{t("ui.dev.session.auto_extend", "세션 만료 관리")}
{t("ui.dev.session.auto_extend", "Session expiry")}
</p>
<p className="text-xs text-muted-foreground">
{isSessionExpiryEnabled
? sessionText
: t(
"ui.dev.session.disabled",
"세션 만료 비활성화",
"Session expiry disabled",
)}
</p>
</div>
@@ -539,7 +543,7 @@ function AppLayout() {
}}
>
<UserIcon size={16} className="text-muted-foreground" />
<span>{t("ui.dev.profile.title", "내 정보")}</span>
<span>{t("ui.dev.profile.title", "My Profile")}</span>
</button>
<button
type="button"