diff --git a/adminfront/src/app/routes.tsx b/adminfront/src/app/routes.tsx index 5603a013..7b80353d 100644 --- a/adminfront/src/app/routes.tsx +++ b/adminfront/src/app/routes.tsx @@ -14,11 +14,11 @@ import UserProjectionPage from "../features/projections/UserProjectionPage"; import { TenantAdminsAndOwnersTab } from "../features/tenants/routes/TenantAdminsAndOwnersTab"; import TenantCreatePage from "../features/tenants/routes/TenantCreatePage"; import TenantDetailPage from "../features/tenants/routes/TenantDetailPage"; +import { TenantFineGrainedPermissionsPage } from "../features/tenants/routes/TenantFineGrainedPermissionsPage"; +import { TenantFineGrainedPermissionsTab } from "../features/tenants/routes/TenantFineGrainedPermissionsTab"; import TenantListPage from "../features/tenants/routes/TenantListPage"; import { TenantProfilePage } from "../features/tenants/routes/TenantProfilePage"; import { TenantSchemaPage } from "../features/tenants/routes/TenantSchemaPage"; -import { TenantFineGrainedPermissionsTab } from "../features/tenants/routes/TenantFineGrainedPermissionsTab"; -import { TenantFineGrainedPermissionsPage } from "../features/tenants/routes/TenantFineGrainedPermissionsPage"; import { TenantWorksmobilePage } from "../features/tenants/routes/TenantWorksmobilePage"; import TenantUserGroupsTab from "../features/user-groups/routes/TenantUserGroupsTab"; import GlobalCustomClaimsPage from "../features/users/GlobalCustomClaimsPage"; @@ -53,7 +53,10 @@ export const adminRoutes: RouteObject[] = [ { path: "tenants", element: }, { path: "tenants/new", element: }, { path: "worksmobile", element: }, - { path: "permissions-direct", element: }, + { + path: "permissions-direct", + element: , + }, { path: "tenants/:tenantId", element: , @@ -62,7 +65,10 @@ export const adminRoutes: RouteObject[] = [ { path: "permissions", element: }, { path: "organization", element: }, { path: "schema", element: }, - { path: "relations", element: }, + { + path: "relations", + element: , + }, ], }, { diff --git a/adminfront/src/components/layout/AppLayout.tsx b/adminfront/src/components/layout/AppLayout.tsx index 92c8d00e..77494dc9 100644 --- a/adminfront/src/components/layout/AppLayout.tsx +++ b/adminfront/src/components/layout/AppLayout.tsx @@ -270,9 +270,11 @@ function AppLayout() { if (item.to === "/permissions-direct") return false; if (item.to === "/tenants") return permissions.tenants; if (item.to === orgfrontUrl) return permissions.org_chart; - if (item.to === "/worksmobile") return permissions.worksmobile && showWorksmobile; + if (item.to === "/worksmobile") + return permissions.worksmobile && showWorksmobile; if (item.to === "/system/ory-ssot") return permissions.ory_ssot; - if (item.to === "/system/data-integrity") return permissions.data_integrity; + if (item.to === "/system/data-integrity") + return permissions.data_integrity; return true; }); diff --git a/adminfront/src/features/tenants/components/ParentTenantSelector.tsx b/adminfront/src/features/tenants/components/ParentTenantSelector.tsx index 17fbcd33..d48095e1 100644 --- a/adminfront/src/features/tenants/components/ParentTenantSelector.tsx +++ b/adminfront/src/features/tenants/components/ParentTenantSelector.tsx @@ -144,7 +144,12 @@ export function ParentTenantSelector({ {localPickerLabel && ( - + {localPickerLabel} diff --git a/adminfront/src/features/tenants/components/TenantPermissionGuard.tsx b/adminfront/src/features/tenants/components/TenantPermissionGuard.tsx index 33c32697..20cd7132 100644 --- a/adminfront/src/features/tenants/components/TenantPermissionGuard.tsx +++ b/adminfront/src/features/tenants/components/TenantPermissionGuard.tsx @@ -1,5 +1,8 @@ import type React from "react"; -import { useTenantPermission, type TenantPermissionKey } from "../hooks/useTenantPermission"; +import { + type TenantPermissionKey, + useTenantPermission, +} from "../hooks/useTenantPermission"; interface TenantPermissionGuardProps { tenantId: string; diff --git a/adminfront/src/features/tenants/hooks/useTenantPermission.test.tsx b/adminfront/src/features/tenants/hooks/useTenantPermission.test.tsx index d5233b6a..9f9d20f3 100644 --- a/adminfront/src/features/tenants/hooks/useTenantPermission.test.tsx +++ b/adminfront/src/features/tenants/hooks/useTenantPermission.test.tsx @@ -1,11 +1,10 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { render, screen, waitFor } from "@testing-library/react"; -import { renderHook } from "@testing-library/react"; +import { render, renderHook, screen, waitFor } from "@testing-library/react"; import type React from "react"; import { describe, expect, it, vi } from "vitest"; -import { fetchTenant, fetchMe } from "../../../lib/adminApi"; -import { useTenantPermission } from "./useTenantPermission"; +import { fetchMe, fetchTenant } from "../../../lib/adminApi"; import { TenantPermissionGuard } from "../components/TenantPermissionGuard"; +import { useTenantPermission } from "./useTenantPermission"; vi.mock("../../../lib/adminApi", () => ({ fetchMe: vi.fn(), @@ -88,10 +87,14 @@ describe("TenantPermissionGuard", () => { } as any); render( - Access Denied}> + Access Denied} + > Access Granted , - { wrapper: createWrapper() } + { wrapper: createWrapper() }, ); await waitFor(() => { @@ -112,10 +115,14 @@ describe("TenantPermissionGuard", () => { } as any); render( - Access Denied}> + Access Denied} + > Access Granted , - { wrapper: createWrapper() } + { wrapper: createWrapper() }, ); await waitFor(() => { diff --git a/adminfront/src/features/tenants/hooks/useTenantPermission.ts b/adminfront/src/features/tenants/hooks/useTenantPermission.ts index 95d2673d..b7c3c5e8 100644 --- a/adminfront/src/features/tenants/hooks/useTenantPermission.ts +++ b/adminfront/src/features/tenants/hooks/useTenantPermission.ts @@ -1,5 +1,5 @@ import { useQuery } from "@tanstack/react-query"; -import { fetchTenant, fetchMe } from "../../../lib/adminApi"; +import { fetchMe, fetchTenant } from "../../../lib/adminApi"; import { normalizeAdminRole } from "../../../lib/roles"; export type TenantPermissionKey = diff --git a/adminfront/src/features/tenants/routes/TenantAdminsAndOwnersTab.tsx b/adminfront/src/features/tenants/routes/TenantAdminsAndOwnersTab.tsx index c7b9817b..c58857a2 100644 --- a/adminfront/src/features/tenants/routes/TenantAdminsAndOwnersTab.tsx +++ b/adminfront/src/features/tenants/routes/TenantAdminsAndOwnersTab.tsx @@ -11,7 +11,6 @@ import { import { useState } from "react"; import { useAuth } from "react-oidc-context"; import { useNavigate, useParams } from "react-router-dom"; -import { useTenantPermission } from "../hooks/useTenantPermission"; import { commonStickyTableHeaderClass } from "../../../../../common/ui/table"; import { Badge } from "../../../components/ui/badge"; import { Button } from "../../../components/ui/button"; @@ -50,6 +49,7 @@ import { type TenantAdmin, } from "../../../lib/adminApi"; import { t } from "../../../lib/i18n"; +import { useTenantPermission } from "../hooks/useTenantPermission"; type DialogMode = "owner" | "admin"; @@ -71,7 +71,8 @@ export function TenantAdminsAndOwnersTab() { const { tenantId: tenantIdParam } = useParams<{ tenantId: string }>(); const tenantId = tenantIdParam ?? ""; const { hasPermission } = useTenantPermission(tenantId); - const isWritable = hasPermission("manage_permissions") || hasPermission("manage_admins"); + const isWritable = + hasPermission("manage_permissions") || hasPermission("manage_admins"); const canView = hasPermission("view_permissions") || hasPermission("view"); const queryClient = useQueryClient(); const [searchTerm, setSearchTerm] = useState(""); diff --git a/adminfront/src/features/tenants/routes/TenantDetailPage.tsx b/adminfront/src/features/tenants/routes/TenantDetailPage.tsx index c59a2bea..e7f97f5f 100644 --- a/adminfront/src/features/tenants/routes/TenantDetailPage.tsx +++ b/adminfront/src/features/tenants/routes/TenantDetailPage.tsx @@ -2,9 +2,8 @@ import { useQuery } from "@tanstack/react-query"; import { Copy } from "lucide-react"; import { Link, Outlet, useLocation, useParams } from "react-router-dom"; import { Button } from "../../../components/ui/button"; -import { fetchMe, fetchTenant } from "../../../lib/adminApi"; +import { fetchTenant } from "../../../lib/adminApi"; import { t } from "../../../lib/i18n"; -import { normalizeAdminRole } from "../../../lib/roles"; import { useTenantPermission } from "../hooks/useTenantPermission"; function TenantDetailPage() { diff --git a/adminfront/src/features/tenants/routes/TenantFineGrainedPermissionsPage.tsx b/adminfront/src/features/tenants/routes/TenantFineGrainedPermissionsPage.tsx index 0e5d3747..96a85a5f 100644 --- a/adminfront/src/features/tenants/routes/TenantFineGrainedPermissionsPage.tsx +++ b/adminfront/src/features/tenants/routes/TenantFineGrainedPermissionsPage.tsx @@ -1,45 +1,27 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { useNavigate } from "react-router-dom"; import type { AxiosError } from "axios"; -import { useState, useEffect } from "react"; import { - fetchAllTenants, - fetchMe, - fetchUsers, - fetchSystemRelations, - addSystemRelation, - removeSystemRelation, - type TenantRelation, -} from "../../../lib/adminApi"; -import { t } from "../../../lib/i18n"; -import { TenantFineGrainedPermissionsTab } from "./TenantFineGrainedPermissionsTab"; -import { - ShieldCheck, - Search, - Plus, - UserPlus, - Trash2, - Settings, - Shield, - LayoutDashboard, Building2, - Network, Database, - Users, - KeyRound, Key, + KeyRound, + LayoutDashboard, + Network, NotebookTabs, + Plus, + Search, Share2, + Shield, + ShieldCheck, + Trash2, + Users, } from "lucide-react"; +import { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { Avatar, AvatarFallback } from "../../../components/ui/avatar"; import { Badge } from "../../../components/ui/badge"; import { Button } from "../../../components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "../../../components/ui/card"; +import { Card, CardContent } from "../../../components/ui/card"; import { Dialog, DialogContent, @@ -48,24 +30,33 @@ import { DialogTitle, } from "../../../components/ui/dialog"; import { Input } from "../../../components/ui/input"; -import { Avatar, AvatarFallback } from "../../../components/ui/avatar"; import { ScrollArea } from "../../../components/ui/scroll-area"; -import { Separator } from "../../../components/ui/separator"; -import { Switch } from "../../../components/ui/switch"; import { toast } from "../../../components/ui/use-toast"; +import { + addSystemRelation, + fetchAllTenants, + fetchMe, + fetchSystemRelations, + fetchUsers, + removeSystemRelation, + type TenantRelation, +} from "../../../lib/adminApi"; +import { t } from "../../../lib/i18n"; export function TenantFineGrainedPermissionsPage() { const queryClient = useQueryClient(); const navigate = useNavigate(); - const [activeTab, setActiveTab] = useState<"tenant" | "system">("system"); - const [selectedTenantId, setSelectedTenantId] = useState(""); + const [activeTab, _setActiveTab] = useState<"tenant" | "system">("system"); + const [_selectedTenantId, _setSelectedTenantId] = useState(""); const [searchTerm, setSearchTerm] = useState(""); const [isDialogOpen, setIsDialogOpen] = useState(false); const [activeUserId, setActiveUserId] = useState(null); const [userSearchTerm, setUserSearchTerm] = useState(""); // ๐ ๊ธ๋ก๋ฒ ์์คํ ๋๋กญ๋ค์ด ์ฆ๊ฐ ๋ณ๊ฒฝ์ ์ํ ์์ ๋ก์ปฌ ๋งต ์ ์ธ - const [localSystemPermissions, setLocalSystemPermissions] = useState>>({}); + const [localSystemPermissions, setLocalSystemPermissions] = useState< + Record> + >({}); const { data: profile } = useQuery({ queryKey: ["me"], @@ -74,26 +65,13 @@ export function TenantFineGrainedPermissionsPage() { const isSuperAdmin = profile?.role === "super_admin"; - if (profile && !isSuperAdmin) { - return ( - - - {t("msg.admin.common.forbidden", "์ ๊ทผ ๊ถํ์ด ์์ต๋๋ค.")} - - navigate("/")}> - {t("ui.common.go_home", "ํ์ผ๋ก ์ด๋")} - - - ); - } - const tenantsQuery = useQuery({ queryKey: ["tenants", "list-all"], queryFn: () => fetchAllTenants(), enabled: isSuperAdmin, }); - const tenants = isSuperAdmin + const _tenants = isSuperAdmin ? (tenantsQuery.data?.items ?? []) : (profile?.manageableTenants ?? []); @@ -108,18 +86,33 @@ export function TenantFineGrainedPermissionsPage() { // ๐ ์๋ฒ ๋ฐ์ดํฐ๋ฅผ ์์ ํ๋ฉด ๋ก์ปฌ ๋ณ๊ฒฝ ์ํ ๋งต์ ์ค์๊ฐ ๋๊ธฐํ useEffect(() => { if (systemRelationsQuery.data) { - const initialMap: Record> = {}; + const initialMap: Record< + string, + Record + > = {}; for (const user of systemRelationsQuery.data) { initialMap[user.userId] = {}; const menus = [ - "overview", "audit_logs", "tenants", "org_chart", "users", - "worksmobile", "api_keys", "ory_ssot", "data_integrity", - "auth_guard", "permissions_direct" + "overview", + "audit_logs", + "tenants", + "org_chart", + "users", + "worksmobile", + "api_keys", + "ory_ssot", + "data_integrity", + "auth_guard", + "permissions_direct", ]; for (const m of menus) { const isWrite = user.relations.includes(`${m}_managers`); const isRead = user.relations.includes(`${m}_viewers`); - initialMap[user.userId][m] = isWrite ? "write" : isRead ? "read" : "none"; + initialMap[user.userId][m] = isWrite + ? "write" + : isRead + ? "read" + : "none"; } } setLocalSystemPermissions(initialMap); @@ -131,30 +124,41 @@ export function TenantFineGrainedPermissionsPage() { addSystemRelation(payload.userId, payload.relation), onMutate: async (newRelation) => { await queryClient.cancelQueries({ queryKey: ["system-relations"] }); - const previousRelations = queryClient.getQueryData(["system-relations"]); + const previousRelations = queryClient.getQueryData([ + "system-relations", + ]); - queryClient.setQueryData(["system-relations"], (old) => { - if (!old) return []; - return old.map((user) => { - if (user.userId === newRelation.userId) { - return { - ...user, - relations: user.relations.includes(newRelation.relation) - ? user.relations - : [...user.relations, newRelation.relation], - }; - } - return user; - }); - }); + queryClient.setQueryData( + ["system-relations"], + (old) => { + if (!old) return []; + return old.map((user) => { + if (user.userId === newRelation.userId) { + return { + ...user, + relations: user.relations.includes(newRelation.relation) + ? user.relations + : [...user.relations, newRelation.relation], + }; + } + return user; + }); + }, + ); return { previousRelations }; }, onError: (err: AxiosError<{ error?: string }>, _, context) => { if (context?.previousRelations) { - queryClient.setQueryData(["system-relations"], context.previousRelations); + queryClient.setQueryData( + ["system-relations"], + context.previousRelations, + ); } - toast.error(err.response?.data?.error || t("msg.common.error", "์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.")); + toast.error( + err.response?.data?.error || + t("msg.common.error", "์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค."), + ); }, onSuccess: () => { // Quiet mutate @@ -174,28 +178,41 @@ export function TenantFineGrainedPermissionsPage() { removeSystemRelation(payload.userId, payload.relation), onMutate: async (targetRelation) => { await queryClient.cancelQueries({ queryKey: ["system-relations"] }); - const previousRelations = queryClient.getQueryData(["system-relations"]); + const previousRelations = queryClient.getQueryData([ + "system-relations", + ]); - queryClient.setQueryData(["system-relations"], (old) => { - if (!old) return []; - return old.map((user) => { - if (user.userId === targetRelation.userId) { - return { - ...user, - relations: user.relations.filter((r) => r !== targetRelation.relation), - }; - } - return user; - }); - }); + queryClient.setQueryData( + ["system-relations"], + (old) => { + if (!old) return []; + return old.map((user) => { + if (user.userId === targetRelation.userId) { + return { + ...user, + relations: user.relations.filter( + (r) => r !== targetRelation.relation, + ), + }; + } + return user; + }); + }, + ); return { previousRelations }; }, onError: (err: AxiosError<{ error?: string }>, _, context) => { if (context?.previousRelations) { - queryClient.setQueryData(["system-relations"], context.previousRelations); + queryClient.setQueryData( + ["system-relations"], + context.previousRelations, + ); } - toast.error(err.response?.data?.error || t("msg.common.error", "์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.")); + toast.error( + err.response?.data?.error || + t("msg.common.error", "์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค."), + ); }, onSuccess: () => { // Quiet mutate @@ -220,26 +237,53 @@ export function TenantFineGrainedPermissionsPage() { try { if (currentVal === "read") { - await removeSystemRelationMutation.mutateAsync({ userId, relation: `${menuKey}_viewers` }); + await removeSystemRelationMutation.mutateAsync({ + userId, + relation: `${menuKey}_viewers`, + }); } else if (currentVal === "write") { - await removeSystemRelationMutation.mutateAsync({ userId, relation: `${menuKey}_managers` }); + await removeSystemRelationMutation.mutateAsync({ + userId, + relation: `${menuKey}_managers`, + }); } if (newVal === "read") { - await addSystemRelationMutation.mutateAsync({ userId, relation: `${menuKey}_viewers` }); + await addSystemRelationMutation.mutateAsync({ + userId, + relation: `${menuKey}_viewers`, + }); } else if (newVal === "write") { - await addSystemRelationMutation.mutateAsync({ userId, relation: `${menuKey}_managers` }); + await addSystemRelationMutation.mutateAsync({ + userId, + relation: `${menuKey}_managers`, + }); } // ๐ Trigger a single consolidated success toast at the very end - toast.success(t("msg.admin.system.relations.update_success", "์์คํ ๋ฉ๋ด ๊ถํ์ด ์ฑ๊ณต์ ์ผ๋ก ๋ณ๊ฒฝ๋์์ต๋๋ค.")); + toast.success( + t( + "msg.admin.system.relations.update_success", + "์์คํ ๋ฉ๋ด ๊ถํ์ด ์ฑ๊ณต์ ์ผ๋ก ๋ณ๊ฒฝ๋์์ต๋๋ค.", + ), + ); } catch { // Individual mutations handle error toast via onError } }; - const handleRemoveAllSystemRelations = async (userId: string, userRelations: string[]) => { - if (!window.confirm(t("msg.admin.system.relations.remove_all_confirm", "์ด ์ฌ์ฉ์์ ๋ชจ๋ ์์คํ ๋ฉ๋ด ๊ถํ์ ์ญ์ ํ์๊ฒ ์ต๋๊น?"))) { + const handleRemoveAllSystemRelations = async ( + userId: string, + userRelations: string[], + ) => { + if ( + !window.confirm( + t( + "msg.admin.system.relations.remove_all_confirm", + "์ด ์ฌ์ฉ์์ ๋ชจ๋ ์์คํ ๋ฉ๋ด ๊ถํ์ ์ญ์ ํ์๊ฒ ์ต๋๊น?", + ), + ) + ) { return; } for (const rel of userRelations) { @@ -268,36 +312,133 @@ export function TenantFineGrainedPermissionsPage() { // Categorized system menus with descriptions and icons const systemMenuCategories = [ { - title: t("ui.admin.permissions_direct.cat_dashboard", "ํต์ฌ ๋์๋ณด๋ ๋ฐ ๋ถ์"), + title: t( + "ui.admin.permissions_direct.cat_dashboard", + "ํต์ฌ ๋์๋ณด๋ ๋ฐ ๋ถ์", + ), menus: [ - { label: t("ui.admin.nav.overview", "๊ฐ์"), relation: "overview", desc: t("msg.admin.permissions_direct.desc_overview", "๋ฐ๋ก ์ ์ฒด ์ฌ์ ๋ฐ ์์คํ ์ํ ๊ฐ์ ์ ๋ณด"), icon: LayoutDashboard }, - { label: t("ui.admin.nav.audit_logs", "๊ฐ์ฌ ๋ก๊ทธ"), relation: "audit_logs", desc: t("msg.admin.permissions_direct.desc_audit_logs", "์์คํ ์ ์ญ ๋ณด์ ๊ฐ์ฌ ๋ฐ ์ ์ ์ด๋ ฅ ๋ก๊ทธ"), icon: NotebookTabs }, - ] + { + label: t("ui.admin.nav.overview", "๊ฐ์"), + relation: "overview", + desc: t( + "msg.admin.permissions_direct.desc_overview", + "๋ฐ๋ก ์ ์ฒด ์ฌ์ ๋ฐ ์์คํ ์ํ ๊ฐ์ ์ ๋ณด", + ), + icon: LayoutDashboard, + }, + { + label: t("ui.admin.nav.audit_logs", "๊ฐ์ฌ ๋ก๊ทธ"), + relation: "audit_logs", + desc: t( + "msg.admin.permissions_direct.desc_audit_logs", + "์์คํ ์ ์ญ ๋ณด์ ๊ฐ์ฌ ๋ฐ ์ ์ ์ด๋ ฅ ๋ก๊ทธ", + ), + icon: NotebookTabs, + }, + ], }, { title: t("ui.admin.permissions_direct.cat_resources", "ํต์ฌ ๋ฆฌ์์ค ๊ด๋ฆฌ"), menus: [ - { label: t("ui.admin.nav.tenants", "ํ ๋ํธ"), relation: "tenants", desc: t("msg.admin.permissions_direct.desc_tenants", "๊ณ ๊ฐ ํ ๋ํธ ๋ชฉ๋ก, ์ ๊ท ๋ถ๋ชจ-์์ ํ ๋ํธ ๊ด๋ฆฌ"), icon: Building2 }, - { label: t("ui.admin.nav.org_chart", "์กฐ์ง๋"), relation: "org_chart", desc: t("msg.admin.permissions_direct.desc_org_chart", "์กฐ์ง๋ ๊ฐ์ํ ๋ฐ ํธ๋ฆฌ ๋ฐฐ์น ํ์ธ"), icon: Network }, - { label: t("ui.admin.nav.users", "์ฌ์ฉ์"), relation: "users", desc: t("msg.admin.permissions_direct.desc_users", "๊ฐ์ ์ฌ์ฉ์ ๋ชฉ๋ก, ์น์ธ ๋ฐ ์ปค์คํ ํด๋ ์ ์๋ ์ฃผ์ "), icon: Users }, - ] + { + label: t("ui.admin.nav.tenants", "ํ ๋ํธ"), + relation: "tenants", + desc: t( + "msg.admin.permissions_direct.desc_tenants", + "๊ณ ๊ฐ ํ ๋ํธ ๋ชฉ๋ก, ์ ๊ท ๋ถ๋ชจ-์์ ํ ๋ํธ ๊ด๋ฆฌ", + ), + icon: Building2, + }, + { + label: t("ui.admin.nav.org_chart", "์กฐ์ง๋"), + relation: "org_chart", + desc: t( + "msg.admin.permissions_direct.desc_org_chart", + "์กฐ์ง๋ ๊ฐ์ํ ๋ฐ ํธ๋ฆฌ ๋ฐฐ์น ํ์ธ", + ), + icon: Network, + }, + { + label: t("ui.admin.nav.users", "์ฌ์ฉ์"), + relation: "users", + desc: t( + "msg.admin.permissions_direct.desc_users", + "๊ฐ์ ์ฌ์ฉ์ ๋ชฉ๋ก, ์น์ธ ๋ฐ ์ปค์คํ ํด๋ ์ ์๋ ์ฃผ์ ", + ), + icon: Users, + }, + ], }, { - title: t("ui.admin.permissions_direct.cat_integrations", "์ธํ๋ผ ์ฐ๋ ๋ฐ ๋ณด์"), + title: t( + "ui.admin.permissions_direct.cat_integrations", + "์ธํ๋ผ ์ฐ๋ ๋ฐ ๋ณด์", + ), menus: [ - { label: t("ui.admin.nav.worksmobile", "Worksmobile"), relation: "worksmobile", desc: t("msg.admin.permissions_direct.desc_worksmobile", "๋ผ์ธ์์ค ์ฐ๋ ๋ฐ ์ฌ๋ด ์์ง์ ํจ์ค์๋ ๊ฐ์ ๋๊ธฐํ"), icon: Share2 }, - { label: t("ui.admin.nav.api_keys", "API ํค"), relation: "api_keys", desc: t("msg.admin.permissions_direct.desc_api_keys", "์กฐ์ง๋ ์ฐ๋์ ์ํ ์ ์ญ ์๋ํํฐ ํ ํฐ ๊ด๋ฆฌ"), icon: Key }, - ] + { + label: t("ui.admin.nav.worksmobile", "Worksmobile"), + relation: "worksmobile", + desc: t( + "msg.admin.permissions_direct.desc_worksmobile", + "๋ผ์ธ์์ค ์ฐ๋ ๋ฐ ์ฌ๋ด ์์ง์ ํจ์ค์๋ ๊ฐ์ ๋๊ธฐํ", + ), + icon: Share2, + }, + { + label: t("ui.admin.nav.api_keys", "API ํค"), + relation: "api_keys", + desc: t( + "msg.admin.permissions_direct.desc_api_keys", + "์กฐ์ง๋ ์ฐ๋์ ์ํ ์ ์ญ ์๋ํํฐ ํ ํฐ ๊ด๋ฆฌ", + ), + icon: Key, + }, + ], }, { - title: t("ui.admin.permissions_direct.cat_system", "์์ด๋ดํฐํฐ ๋ฐ ๊ฒ์ดํธ ๊ด๋ฆฌ"), + title: t( + "ui.admin.permissions_direct.cat_system", + "์์ด๋ดํฐํฐ ๋ฐ ๊ฒ์ดํธ ๊ด๋ฆฌ", + ), menus: [ - { label: t("ui.admin.nav.ory_ssot", "Ory SSOT ์์คํ "), relation: "ory_ssot", desc: t("msg.admin.permissions_direct.desc_ory_ssot", "Redis ์์ด๋ดํฐํฐ ๋ฏธ๋ฌ ์บ์ ๋ฐ PostgreSQL read model ์ ํฉ์ฑ ๊ฐฑ์ "), icon: Database }, - { label: t("ui.admin.nav.data_integrity", "๋ฐ์ดํฐ ์ ํฉ์ฑ"), relation: "data_integrity", desc: t("msg.admin.permissions_direct.desc_data_integrity", "๊ณ ์ ๋ ์ฝ๋ ๊ฒ์ถ ๋ฐ DB ์ ํฉ์ฑ ์ต์ข ๊ฒ์ฆ๊ธฐ"), icon: ShieldCheck }, - { label: t("ui.admin.nav.auth_guard", "์ธ์ฆ ๊ฐ๋"), relation: "auth_guard", desc: t("msg.admin.permissions_direct.desc_auth_guard", "์ ์ฑ ์์ง ๊ธฐ์ค์ผ๋ก Keto ReBAC ๊ด๊ณ ๊ฒ์ฆ ์๋ฎฌ๋ ์ดํฐ"), icon: KeyRound }, - { label: t("ui.admin.nav.permissions_direct", "๊ถํ ๋ถ์ฌ"), relation: "permissions_direct", desc: t("msg.admin.permissions_direct.desc_permissions_direct", "๋ณธ ์ฌ์ด๋๋ฐ ๋ฉ๋ด ์ธ๋ถ ๊ถํ ๊ฒฉ์ ๋ฐ ํ ๋ํธ ์ธ๊ฐ ์ค์ ํจ๋"), icon: Shield }, - ] - } + { + label: t("ui.admin.nav.ory_ssot", "Ory SSOT ์์คํ "), + relation: "ory_ssot", + desc: t( + "msg.admin.permissions_direct.desc_ory_ssot", + "Redis ์์ด๋ดํฐํฐ ๋ฏธ๋ฌ ์บ์ ๋ฐ PostgreSQL read model ์ ํฉ์ฑ ๊ฐฑ์ ", + ), + icon: Database, + }, + { + label: t("ui.admin.nav.data_integrity", "๋ฐ์ดํฐ ์ ํฉ์ฑ"), + relation: "data_integrity", + desc: t( + "msg.admin.permissions_direct.desc_data_integrity", + "๊ณ ์ ๋ ์ฝ๋ ๊ฒ์ถ ๋ฐ DB ์ ํฉ์ฑ ์ต์ข ๊ฒ์ฆ๊ธฐ", + ), + icon: ShieldCheck, + }, + { + label: t("ui.admin.nav.auth_guard", "์ธ์ฆ ๊ฐ๋"), + relation: "auth_guard", + desc: t( + "msg.admin.permissions_direct.desc_auth_guard", + "์ ์ฑ ์์ง ๊ธฐ์ค์ผ๋ก Keto ReBAC ๊ด๊ณ ๊ฒ์ฆ ์๋ฎฌ๋ ์ดํฐ", + ), + icon: KeyRound, + }, + { + label: t("ui.admin.nav.permissions_direct", "๊ถํ ๋ถ์ฌ"), + relation: "permissions_direct", + desc: t( + "msg.admin.permissions_direct.desc_permissions_direct", + "๋ณธ ์ฌ์ด๋๋ฐ ๋ฉ๋ด ์ธ๋ถ ๊ถํ ๊ฒฉ์ ๋ฐ ํ ๋ํธ ์ธ๊ฐ ์ค์ ํจ๋", + ), + icon: Shield, + }, + ], + }, ]; const filteredRelations = systemRelations.filter( @@ -308,6 +449,19 @@ export function TenantFineGrainedPermissionsPage() { const selectedUser = systemRelations.find((r) => r.userId === activeUserId); + if (profile && !isSuperAdmin) { + return ( + + + {t("msg.admin.common.forbidden", "์ ๊ทผ ๊ถํ์ด ์์ต๋๋ค.")} + + navigate("/")}> + {t("ui.common.go_home", "ํ์ผ๋ก ์ด๋")} + + + ); + } + return ( @@ -325,204 +479,260 @@ export function TenantFineGrainedPermissionsPage() { {/* ์์คํ ๋ฉ๋ด ๊ถํ (Admin Control) Split Screen Panel */} - {/* Left Panel: User List */} - - - - - {t("ui.admin.permissions_direct.user_list", "๋์ ์ฌ์ฉ์")} ({filteredRelations.length}) - + {/* Left Panel: User List */} + + + + + {t("ui.admin.permissions_direct.user_list", "๋์ ์ฌ์ฉ์")} ( + {filteredRelations.length}) + + setIsDialogOpen(true)} + > + + + + + + setUserSearchTerm(e.target.value)} + name="user-search" + className="pl-8 h-8 text-xs" + /> + + + + + {filteredRelations.length === 0 ? ( + + {t( + "msg.admin.permissions_direct.no_users_found", + "๋ฑ๋ก๋ ์ฌ์ฉ์๊ฐ ์์ต๋๋ค.", + )} + + ) : ( + filteredRelations.map((user) => { + const isSelected = activeUserId === user.userId; + const activeCount = user.relations.length; + + return ( + setActiveUserId(user.userId)} + className={`w-full flex items-center justify-between p-3 rounded-lg text-left transition-all ${ + isSelected + ? "bg-primary/10 text-primary border-l-4 border-primary shadow-sm" + : "hover:bg-muted/50 text-foreground" + }`} + > + + + + {user.name.charAt(0)} + + + + + {user.name} + + + {user.email} + + + + + {activeCount} + + + ); + }) + )} + + + + + {/* Right Panel: Toggle settings grid */} + + {selectedUser ? ( + <> + {/* User Detail Header */} + + + + + {selectedUser.name.charAt(0)} + + + + + {selectedUser.name} + + {selectedUser.relations.length}{" "} + {t("ui.admin.permissions_direct.allowed", "๊ฐ ํ์ฉ๋จ")} + + + + {selectedUser.email} + + + setIsDialogOpen(true)} + className="text-destructive border-destructive/20 hover:bg-destructive/10" + onClick={() => + handleRemoveAllSystemRelations( + selectedUser.userId, + selectedUser.relations, + ) + } > - + + {t( + "ui.admin.permissions_direct.revoke_all", + "๋ชจ๋ ๊ถํ ํ์", + )} - - - setUserSearchTerm(e.target.value)} - name="user-search" - className="pl-8 h-8 text-xs" - /> + + {/* Categorized Toggle Grid */} + + + {systemMenuCategories.map((category) => ( + + + {category.title} + + + + {category.menus.map((menu) => { + const isWrite = selectedUser.relations.includes( + `${menu.relation}_managers`, + ); + const isRead = selectedUser.relations.includes( + `${menu.relation}_viewers`, + ); + const serverValue: "none" | "read" | "write" = + isWrite ? "write" : isRead ? "read" : "none"; + const permissionValue = + localSystemPermissions[selectedUser.userId]?.[ + menu.relation + ] ?? serverValue; + const Icon = menu.icon; + + return ( + + + + + + + + + {menu.label} + + {(menu.relation === "ory_ssot" || + menu.relation === "data_integrity") && ( + + {t( + "ui.admin.permissions_direct.super_admin_only", + "Super Admin ์ ์ฉ", + )} + + )} + + + {menu.desc} + + + + { + const nextVal = e.target.value as + | "none" + | "read" + | "write"; + // ๐ 1๋จ๊ณ: ๋ก์ปฌ ์์ ์ํ ์ฆ์ ๊ฐฑ์ (0ms ๋ฐ์ ๋ณด์ฅ) + setLocalSystemPermissions((prev) => ({ + ...prev, + [selectedUser.userId]: { + ...(prev[selectedUser.userId] ?? {}), + [menu.relation]: nextVal, + }, + })); + // ๐ 2๋จ๊ณ: ๋ฐฑ๊ทธ๋ผ์ด๋ ๋น๋๊ธฐ API ์์ฒญ ์ํ + handleSystemRelationChange( + selectedUser.userId, + menu.relation, + permissionValue, + nextVal, + ); + }} + className="flex h-9 w-[180px] rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50" + > + + {t("ui.common.none", "๊ถํ ์์")} + + + {t("ui.common.read", "์กฐํ ๊ฐ๋ฅ (Read)")} + + + {t("ui.common.write", "์์ ๊ฐ๋ฅ (Write)")} + + + + ); + })} + + + + ))} + + + > + ) : ( + + + + + {t( + "ui.admin.permissions_direct.no_user_selected", + "์ฌ์ฉ์๊ฐ ์ ํ๋์ง ์์์ต๋๋ค.", + )} + + + {t( + "msg.admin.permissions_direct.no_user_selected_desc", + "์ผ์ชฝ์ ์ฌ์ฉ์ ๋ฆฌ์คํธ์์ ๊ถํ์ ๋ณ๊ฒฝํ ์ธ์์ ์ ํํด ์ฃผ์ธ์.", + )} + - - - {filteredRelations.length === 0 ? ( - - {t("msg.admin.permissions_direct.no_users_found", "๋ฑ๋ก๋ ์ฌ์ฉ์๊ฐ ์์ต๋๋ค.")} - - ) : ( - filteredRelations.map((user) => { - const isSelected = activeUserId === user.userId; - const activeCount = user.relations.length; - - return ( - setActiveUserId(user.userId)} - className={`w-full flex items-center justify-between p-3 rounded-lg text-left transition-all ${ - isSelected - ? "bg-primary/10 text-primary border-l-4 border-primary shadow-sm" - : "hover:bg-muted/50 text-foreground" - }`} - > - - - - {user.name.charAt(0)} - - - - {user.name} - - {user.email} - - - - - {activeCount} - - - ); - }) - )} - - - - - {/* Right Panel: Toggle settings grid */} - - {selectedUser ? ( - <> - {/* User Detail Header */} - - - - - {selectedUser.name.charAt(0)} - - - - - {selectedUser.name} - - {selectedUser.relations.length} {t("ui.admin.permissions_direct.allowed", "๊ฐ ํ์ฉ๋จ")} - - - {selectedUser.email} - - - handleRemoveAllSystemRelations(selectedUser.userId, selectedUser.relations)} - > - - {t("ui.admin.permissions_direct.revoke_all", "๋ชจ๋ ๊ถํ ํ์")} - - - - {/* Categorized Toggle Grid */} - - - {systemMenuCategories.map((category) => ( - - - {category.title} - - - - {category.menus.map((menu) => { - const isWrite = selectedUser.relations.includes(`${menu.relation}_managers`); - const isRead = selectedUser.relations.includes(`${menu.relation}_viewers`); - const serverValue: "none" | "read" | "write" = isWrite ? "write" : isRead ? "read" : "none"; - const permissionValue = localSystemPermissions[selectedUser.userId]?.[menu.relation] ?? serverValue; - const Icon = menu.icon; - - return ( - - - - - - - - {menu.label} - {(menu.relation === "ory_ssot" || menu.relation === "data_integrity") && ( - - {t("ui.admin.permissions_direct.super_admin_only", "Super Admin ์ ์ฉ")} - - )} - - - {menu.desc} - - - - { - const nextVal = e.target.value as "none" | "read" | "write"; - // ๐ 1๋จ๊ณ: ๋ก์ปฌ ์์ ์ํ ์ฆ์ ๊ฐฑ์ (0ms ๋ฐ์ ๋ณด์ฅ) - setLocalSystemPermissions(prev => ({ - ...prev, - [selectedUser.userId]: { - ...(prev[selectedUser.userId] ?? {}), - [menu.relation]: nextVal - } - })); - // ๐ 2๋จ๊ณ: ๋ฐฑ๊ทธ๋ผ์ด๋ ๋น๋๊ธฐ API ์์ฒญ ์ํ - handleSystemRelationChange( - selectedUser.userId, - menu.relation, - permissionValue, - nextVal, - ); - }} - className="flex h-9 w-[180px] rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50" - > - {t("ui.common.none", "๊ถํ ์์")} - {t("ui.common.read", "์กฐํ ๊ฐ๋ฅ (Read)")} - {t("ui.common.write", "์์ ๊ฐ๋ฅ (Write)")} - - - ); - })} - - - - ))} - - - > - ) : ( - - - - - {t("ui.admin.permissions_direct.no_user_selected", "์ฌ์ฉ์๊ฐ ์ ํ๋์ง ์์์ต๋๋ค.")} - - - {t("msg.admin.permissions_direct.no_user_selected_desc", "์ผ์ชฝ์ ์ฌ์ฉ์ ๋ฆฌ์คํธ์์ ๊ถํ์ ๋ณ๊ฒฝํ ์ธ์์ ์ ํํด ์ฃผ์ธ์.")} - - - - )} - + )} + {/* User Search Dialog for System relations */} - {t("ui.admin.permissions_direct.dialog_title_system", "์์คํ ๊ถํ ๊ด๋ฆฌ ์ ์ ์ถ๊ฐ")} + {t( + "ui.admin.permissions_direct.dialog_title_system", + "์์คํ ๊ถํ ๊ด๋ฆฌ ์ ์ ์ถ๊ฐ", + )} {t( diff --git a/adminfront/src/features/tenants/routes/TenantFineGrainedPermissionsTab.tsx b/adminfront/src/features/tenants/routes/TenantFineGrainedPermissionsTab.tsx index e119b31a..779237b1 100644 --- a/adminfront/src/features/tenants/routes/TenantFineGrainedPermissionsTab.tsx +++ b/adminfront/src/features/tenants/routes/TenantFineGrainedPermissionsTab.tsx @@ -1,14 +1,8 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import type { AxiosError } from "axios"; -import { - Plus, - Search, - ShieldCheck, - UserPlus, -} from "lucide-react"; -import { useState, useEffect } from "react"; +import { Plus, Search, ShieldCheck, Trash2, UserPlus } from "lucide-react"; +import { useEffect, useState } from "react"; import { useParams } from "react-router-dom"; -import { useTenantPermission } from "../hooks/useTenantPermission"; import { Badge } from "../../../components/ui/badge"; import { Button } from "../../../components/ui/button"; import { @@ -36,20 +30,22 @@ import { } from "../../../components/ui/table"; import { toast } from "../../../components/ui/use-toast"; import { - fetchUsers, - fetchTenantRelations, addTenantRelation, + fetchTenantRelations, + fetchUsers, removeTenantRelation, type TenantRelation, } from "../../../lib/adminApi"; import { t } from "../../../lib/i18n"; -import { Trash2 } from "lucide-react"; +import { useTenantPermission } from "../hooks/useTenantPermission"; interface TenantFineGrainedPermissionsTabProps { tenantIdProp?: string; } -export function TenantFineGrainedPermissionsTab({ tenantIdProp }: TenantFineGrainedPermissionsTabProps = {}) { +export function TenantFineGrainedPermissionsTab({ + tenantIdProp, +}: TenantFineGrainedPermissionsTabProps = {}) { const { tenantId: tenantIdParam } = useParams<{ tenantId: string }>(); const tenantId = tenantIdProp || tenantIdParam || ""; const { hasPermission } = useTenantPermission(tenantId); @@ -59,26 +55,35 @@ export function TenantFineGrainedPermissionsTab({ tenantIdProp }: TenantFineGrai const [isDialogOpen, setIsDialogOpen] = useState(false); // ๐ ํ ๋ํธ ํญ๋ณ ๋๋กญ๋ค์ด ์ฆ๊ฐ ๋ณ๊ฒฝ์ ์ํ ์์ ๋ก์ปฌ ๋งต ์ ์ธ - const [localTenantPermissions, setLocalTenantPermissions] = useState>>({}); + const [localTenantPermissions, setLocalTenantPermissions] = useState< + Record> + >({}); const relationsQuery = useQuery({ queryKey: ["tenant-relations", tenantId], queryFn: () => fetchTenantRelations(tenantId), enabled: !!tenantId, }); - const relationsData = relationsQuery.data ?? []; + const _relationsData = relationsQuery.data ?? []; // ๐ ์๋ฒ ๋ฐ์ดํฐ๋ฅผ ์์ ํ๋ฉด ๋ก์ปฌ ๋ณ๊ฒฝ ์ํ ๋งต์ ์ค์๊ฐ ๋๊ธฐํ useEffect(() => { if (relationsQuery.data) { - const initialMap: Record> = {}; + const initialMap: Record< + string, + Record + > = {}; for (const user of relationsQuery.data) { initialMap[user.userId] = {}; const tabs = ["profile", "permissions", "organization", "schema"]; for (const tab of tabs) { const isWrite = user.relations.includes(`${tab}_managers`); const isRead = user.relations.includes(`${tab}_viewers`); - initialMap[user.userId][tab] = isWrite ? "write" : isRead ? "read" : "none"; + initialMap[user.userId][tab] = isWrite + ? "write" + : isRead + ? "read" + : "none"; } } setLocalTenantPermissions(initialMap); @@ -91,7 +96,9 @@ export function TenantFineGrainedPermissionsTab({ tenantIdProp }: TenantFineGrai queryClient.invalidateQueries({ queryKey: ["tenant", tenantId] }); queryClient.invalidateQueries({ queryKey: ["me"] }); setTimeout(() => { - queryClient.invalidateQueries({ queryKey: ["tenant-relations", tenantId] }); + queryClient.invalidateQueries({ + queryKey: ["tenant-relations", tenantId], + }); queryClient.invalidateQueries({ queryKey: ["tenant", tenantId] }); queryClient.invalidateQueries({ queryKey: ["me"] }); }, 500); @@ -101,31 +108,45 @@ export function TenantFineGrainedPermissionsTab({ tenantIdProp }: TenantFineGrai mutationFn: (payload: { userId: string; relation: string }) => addTenantRelation(tenantId, payload.userId, payload.relation), onMutate: async (newRelation) => { - await queryClient.cancelQueries({ queryKey: ["tenant-relations", tenantId] }); - const previousRelations = queryClient.getQueryData(["tenant-relations", tenantId]); - - queryClient.setQueryData(["tenant-relations", tenantId], (old) => { - if (!old) return []; - return old.map((user) => { - if (user.userId === newRelation.userId) { - return { - ...user, - relations: user.relations.includes(newRelation.relation) - ? user.relations - : [...user.relations, newRelation.relation], - }; - } - return user; - }); + await queryClient.cancelQueries({ + queryKey: ["tenant-relations", tenantId], }); + const previousRelations = queryClient.getQueryData([ + "tenant-relations", + tenantId, + ]); + + queryClient.setQueryData( + ["tenant-relations", tenantId], + (old) => { + if (!old) return []; + return old.map((user) => { + if (user.userId === newRelation.userId) { + return { + ...user, + relations: user.relations.includes(newRelation.relation) + ? user.relations + : [...user.relations, newRelation.relation], + }; + } + return user; + }); + }, + ); return { previousRelations }; }, onError: (err: AxiosError<{ error?: string }>, _, context) => { if (context?.previousRelations) { - queryClient.setQueryData(["tenant-relations", tenantId], context.previousRelations); + queryClient.setQueryData( + ["tenant-relations", tenantId], + context.previousRelations, + ); } - toast.error(err.response?.data?.error || t("msg.common.error", "์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.")); + toast.error( + err.response?.data?.error || + t("msg.common.error", "์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค."), + ); }, onSuccess: () => { // Quiet mutate @@ -136,29 +157,45 @@ export function TenantFineGrainedPermissionsTab({ tenantIdProp }: TenantFineGrai mutationFn: (payload: { userId: string; relation: string }) => removeTenantRelation(tenantId, payload.userId, payload.relation), onMutate: async (targetRelation) => { - await queryClient.cancelQueries({ queryKey: ["tenant-relations", tenantId] }); - const previousRelations = queryClient.getQueryData(["tenant-relations", tenantId]); - - queryClient.setQueryData(["tenant-relations", tenantId], (old) => { - if (!old) return []; - return old.map((user) => { - if (user.userId === targetRelation.userId) { - return { - ...user, - relations: user.relations.filter((r) => r !== targetRelation.relation), - }; - } - return user; - }); + await queryClient.cancelQueries({ + queryKey: ["tenant-relations", tenantId], }); + const previousRelations = queryClient.getQueryData([ + "tenant-relations", + tenantId, + ]); + + queryClient.setQueryData( + ["tenant-relations", tenantId], + (old) => { + if (!old) return []; + return old.map((user) => { + if (user.userId === targetRelation.userId) { + return { + ...user, + relations: user.relations.filter( + (r) => r !== targetRelation.relation, + ), + }; + } + return user; + }); + }, + ); return { previousRelations }; }, onError: (err: AxiosError<{ error?: string }>, _, context) => { if (context?.previousRelations) { - queryClient.setQueryData(["tenant-relations", tenantId], context.previousRelations); + queryClient.setQueryData( + ["tenant-relations", tenantId], + context.previousRelations, + ); } - toast.error(err.response?.data?.error || t("msg.common.error", "์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.")); + toast.error( + err.response?.data?.error || + t("msg.common.error", "์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค."), + ); }, onSuccess: () => { // Quiet mutate @@ -180,7 +217,10 @@ export function TenantFineGrainedPermissionsTab({ tenantIdProp }: TenantFineGrai if (currentVal === "read") { await removeRelationMutation.mutateAsync({ userId, relation: readRel }); } else if (currentVal === "write") { - await removeRelationMutation.mutateAsync({ userId, relation: writeRel }); + await removeRelationMutation.mutateAsync({ + userId, + relation: writeRel, + }); } if (newVal === "read") { @@ -192,14 +232,29 @@ export function TenantFineGrainedPermissionsTab({ tenantIdProp }: TenantFineGrai invalidateAllQueries(); // ๐ Trigger a single consolidated success toast at the very end - toast.success(t("msg.admin.tenants.relations.update_success", "์ธ๋ถ ๊ถํ์ด ์ฑ๊ณต์ ์ผ๋ก ๋ณ๊ฒฝ๋์์ต๋๋ค.")); + toast.success( + t( + "msg.admin.tenants.relations.update_success", + "์ธ๋ถ ๊ถํ์ด ์ฑ๊ณต์ ์ผ๋ก ๋ณ๊ฒฝ๋์์ต๋๋ค.", + ), + ); } catch { // Individual mutations handle error toast via onError } }; - const handleRemoveAllRelations = async (userId: string, userRelations: string[]) => { - if (!window.confirm(t("msg.admin.tenants.relations.remove_all_confirm", "์ด ์ฌ์ฉ์์ ๋ชจ๋ ์ธ๋ถ ๊ถํ์ ์ญ์ ํ์๊ฒ ์ต๋๊น?"))) { + const handleRemoveAllRelations = async ( + userId: string, + userRelations: string[], + ) => { + if ( + !window.confirm( + t( + "msg.admin.tenants.relations.remove_all_confirm", + "์ด ์ฌ์ฉ์์ ๋ชจ๋ ์ธ๋ถ ๊ถํ์ ์ญ์ ํ์๊ฒ ์ต๋๊น?", + ), + ) + ) { return; } for (const rel of userRelations) { @@ -215,11 +270,14 @@ export function TenantFineGrainedPermissionsTab({ tenantIdProp }: TenantFineGrai }); const handleAddUser = (userId: string) => { - addRelationMutation.mutate({ userId, relation: "profile_viewers" }, { - onSettled: () => { - invalidateAllQueries(); - } - }); + addRelationMutation.mutate( + { userId, relation: "profile_viewers" }, + { + onSettled: () => { + invalidateAllQueries(); + }, + }, + ); setIsDialogOpen(false); setSearchTerm(""); }; @@ -235,7 +293,10 @@ export function TenantFineGrainedPermissionsTab({ tenantIdProp }: TenantFineGrai - {t("ui.admin.tenants.relations.title", "์ธ๋ถ ๊ถํ ์ค์ (Fine-grained Permissions)")} + {t( + "ui.admin.tenants.relations.title", + "์ธ๋ถ ๊ถํ ์ค์ (Fine-grained Permissions)", + )} {t( @@ -250,7 +311,10 @@ export function TenantFineGrainedPermissionsTab({ tenantIdProp }: TenantFineGrai disabled={!isWritable} > - {t("ui.admin.tenants.relations.add_button", "์ธ๋ถ ๊ถํ ์ฌ์ฉ์ ์ถ๊ฐ")} + {t( + "ui.admin.tenants.relations.add_button", + "์ธ๋ถ ๊ถํ ์ฌ์ฉ์ ์ถ๊ฐ", + )} @@ -258,58 +322,96 @@ export function TenantFineGrainedPermissionsTab({ tenantIdProp }: TenantFineGrai - {t("ui.common.name", "์ด๋ฆ")} - {t("ui.admin.tenants.detail.tab_profile", "ํ ๋ํธ ํ๋กํ")} - {t("ui.admin.tenants.detail.tab_permissions", "๊ถํ ๊ด๋ฆฌ")} - {t("ui.admin.tenants.detail.tab_organization", "์กฐ์ง ๊ด๋ฆฌ")} - {t("ui.admin.tenants.detail.tab_schema", "์ฌ์ฉ์ ์คํค๋ง")} - {t("ui.common.action", "์์ ")} + + {t("ui.common.name", "์ด๋ฆ")} + + + {t("ui.admin.tenants.detail.tab_profile", "ํ ๋ํธ ํ๋กํ")} + + + {t("ui.admin.tenants.detail.tab_permissions", "๊ถํ ๊ด๋ฆฌ")} + + + {t("ui.admin.tenants.detail.tab_organization", "์กฐ์ง ๊ด๋ฆฌ")} + + + {t("ui.admin.tenants.detail.tab_schema", "์ฌ์ฉ์ ์คํค๋ง")} + + + {t("ui.common.action", "์์ ")} + {relations.length === 0 ? ( - - {t("msg.admin.tenants.relations.empty", "์ธ๋ถ ๊ถํ์ด ์ง์ ๋ ์ฌ์ฉ์๊ฐ ์์ต๋๋ค. ์ฌ์ฉ์๋ฅผ ์ถ๊ฐํด ์ค์ ํ์ธ์.")} + + {t( + "msg.admin.tenants.relations.empty", + "์ธ๋ถ ๊ถํ์ด ์ง์ ๋ ์ฌ์ฉ์๊ฐ ์์ต๋๋ค. ์ฌ์ฉ์๋ฅผ ์ถ๊ฐํด ์ค์ ํ์ธ์.", + )} ) : ( relations.map((user) => { - const profileVal = user.relations.includes("profile_managers") + const profileVal = user.relations.includes( + "profile_managers", + ) ? "write" : user.relations.includes("profile_viewers") - ? "read" - : "none"; + ? "read" + : "none"; - const permissionsVal = user.relations.includes("permissions_managers") + const permissionsVal = user.relations.includes( + "permissions_managers", + ) ? "write" : user.relations.includes("permissions_viewers") - ? "read" - : "none"; + ? "read" + : "none"; - const organizationVal = user.relations.includes("organization_managers") + const organizationVal = user.relations.includes( + "organization_managers", + ) ? "write" : user.relations.includes("organization_viewers") - ? "read" - : "none"; + ? "read" + : "none"; const schemaVal = user.relations.includes("schema_managers") ? "write" : user.relations.includes("schema_viewers") - ? "read" - : "none"; + ? "read" + : "none"; - const curProfileVal = localTenantPermissions[user.userId]?.profile ?? profileVal; - const curPermissionsVal = localTenantPermissions[user.userId]?.permissions ?? permissionsVal; - const curOrganizationVal = localTenantPermissions[user.userId]?.organization ?? organizationVal; - const curSchemaVal = localTenantPermissions[user.userId]?.schema ?? schemaVal; + const curProfileVal = + localTenantPermissions[user.userId]?.profile ?? + profileVal; + const curPermissionsVal = + localTenantPermissions[user.userId]?.permissions ?? + permissionsVal; + const curOrganizationVal = + localTenantPermissions[user.userId]?.organization ?? + organizationVal; + const curSchemaVal = + localTenantPermissions[user.userId]?.schema ?? schemaVal; return ( - + - {user.name} - {user.email} + + {user.name} + + + {user.email} + @@ -319,13 +421,16 @@ export function TenantFineGrainedPermissionsTab({ tenantIdProp }: TenantFineGrai disabled={!isWritable} name={`tenant-fine-grained-profile-${user.userId}`} onChange={(e) => { - const nextVal = e.target.value as "none" | "read" | "write"; - setLocalTenantPermissions(prev => ({ + const nextVal = e.target.value as + | "none" + | "read" + | "write"; + setLocalTenantPermissions((prev) => ({ ...prev, [user.userId]: { ...(prev[user.userId] ?? {}), - profile: nextVal - } + profile: nextVal, + }, })); handleRelationChange( user.userId, @@ -335,9 +440,15 @@ export function TenantFineGrainedPermissionsTab({ tenantIdProp }: TenantFineGrai ); }} > - {t("ui.common.none", "๊ถํ ์์")} - {t("ui.common.read", "์กฐํ ๊ฐ๋ฅ (Read)")} - {t("ui.common.write", "์์ ๊ฐ๋ฅ (Write)")} + + {t("ui.common.none", "๊ถํ ์์")} + + + {t("ui.common.read", "์กฐํ ๊ฐ๋ฅ (Read)")} + + + {t("ui.common.write", "์์ ๊ฐ๋ฅ (Write)")} + @@ -347,13 +458,16 @@ export function TenantFineGrainedPermissionsTab({ tenantIdProp }: TenantFineGrai disabled={!isWritable} name={`tenant-fine-grained-permissions-${user.userId}`} onChange={(e) => { - const nextVal = e.target.value as "none" | "read" | "write"; - setLocalTenantPermissions(prev => ({ + const nextVal = e.target.value as + | "none" + | "read" + | "write"; + setLocalTenantPermissions((prev) => ({ ...prev, [user.userId]: { ...(prev[user.userId] ?? {}), - permissions: nextVal - } + permissions: nextVal, + }, })); handleRelationChange( user.userId, @@ -363,9 +477,15 @@ export function TenantFineGrainedPermissionsTab({ tenantIdProp }: TenantFineGrai ); }} > - {t("ui.common.none", "๊ถํ ์์")} - {t("ui.common.read", "์กฐํ ๊ฐ๋ฅ (Read)")} - {t("ui.common.write", "์์ ๊ฐ๋ฅ (Write)")} + + {t("ui.common.none", "๊ถํ ์์")} + + + {t("ui.common.read", "์กฐํ ๊ฐ๋ฅ (Read)")} + + + {t("ui.common.write", "์์ ๊ฐ๋ฅ (Write)")} + @@ -375,13 +495,16 @@ export function TenantFineGrainedPermissionsTab({ tenantIdProp }: TenantFineGrai disabled={!isWritable} name={`tenant-fine-grained-organization-${user.userId}`} onChange={(e) => { - const nextVal = e.target.value as "none" | "read" | "write"; - setLocalTenantPermissions(prev => ({ + const nextVal = e.target.value as + | "none" + | "read" + | "write"; + setLocalTenantPermissions((prev) => ({ ...prev, [user.userId]: { ...(prev[user.userId] ?? {}), - organization: nextVal - } + organization: nextVal, + }, })); handleRelationChange( user.userId, @@ -391,9 +514,15 @@ export function TenantFineGrainedPermissionsTab({ tenantIdProp }: TenantFineGrai ); }} > - {t("ui.common.none", "๊ถํ ์์")} - {t("ui.common.read", "์กฐํ ๊ฐ๋ฅ (Read)")} - {t("ui.common.write", "์์ ๊ฐ๋ฅ (Write)")} + + {t("ui.common.none", "๊ถํ ์์")} + + + {t("ui.common.read", "์กฐํ ๊ฐ๋ฅ (Read)")} + + + {t("ui.common.write", "์์ ๊ฐ๋ฅ (Write)")} + @@ -403,13 +532,16 @@ export function TenantFineGrainedPermissionsTab({ tenantIdProp }: TenantFineGrai disabled={!isWritable} name={`tenant-fine-grained-schema-${user.userId}`} onChange={(e) => { - const nextVal = e.target.value as "none" | "read" | "write"; - setLocalTenantPermissions(prev => ({ + const nextVal = e.target.value as + | "none" + | "read" + | "write"; + setLocalTenantPermissions((prev) => ({ ...prev, [user.userId]: { ...(prev[user.userId] ?? {}), - schema: nextVal - } + schema: nextVal, + }, })); handleRelationChange( user.userId, @@ -419,9 +551,15 @@ export function TenantFineGrainedPermissionsTab({ tenantIdProp }: TenantFineGrai ); }} > - {t("ui.common.none", "๊ถํ ์์")} - {t("ui.common.read", "์กฐํ ๊ฐ๋ฅ (Read)")} - {t("ui.common.write", "์์ ๊ฐ๋ฅ (Write)")} + + {t("ui.common.none", "๊ถํ ์์")} + + + {t("ui.common.read", "์กฐํ ๊ฐ๋ฅ (Read)")} + + + {t("ui.common.write", "์์ ๊ฐ๋ฅ (Write)")} + @@ -429,7 +567,12 @@ export function TenantFineGrainedPermissionsTab({ tenantIdProp }: TenantFineGrai variant="ghost" size="icon" disabled={!isWritable} - onClick={() => handleRemoveAllRelations(user.userId, user.relations)} + onClick={() => + handleRemoveAllRelations( + user.userId, + user.relations, + ) + } > @@ -457,7 +600,10 @@ export function TenantFineGrainedPermissionsTab({ tenantIdProp }: TenantFineGrai - {t("ui.admin.tenants.relations.dialog_title", "์ธ๋ถ ๊ถํ ๊ด๋ฆฌ ์ ์ ์ถ๊ฐ")} + {t( + "ui.admin.tenants.relations.dialog_title", + "์ธ๋ถ ๊ถํ ๊ด๋ฆฌ ์ ์ ์ถ๊ฐ", + )} {t( @@ -533,8 +679,7 @@ export function TenantFineGrainedPermissionsTab({ tenantIdProp }: TenantFineGrai size="sm" variant={isAlreadyInMatrix ? "ghost" : "outline"} disabled={ - isAlreadyInMatrix || - addRelationMutation.isPending + isAlreadyInMatrix || addRelationMutation.isPending } onClick={() => handleAddUser(user.id)} > diff --git a/adminfront/src/features/tenants/routes/TenantGroupsPage.tsx b/adminfront/src/features/tenants/routes/TenantGroupsPage.tsx index c142577b..9ccf7aab 100644 --- a/adminfront/src/features/tenants/routes/TenantGroupsPage.tsx +++ b/adminfront/src/features/tenants/routes/TenantGroupsPage.tsx @@ -21,7 +21,6 @@ import { import type React from "react"; import { useState } from "react"; import { useParams } from "react-router-dom"; -import { useTenantPermission } from "../hooks/useTenantPermission"; import { commonStickyTableHeaderClass } from "../../../../../common/ui/table"; import { Badge } from "../../../components/ui/badge"; import { Button } from "../../../components/ui/button"; @@ -63,6 +62,7 @@ import { removeGroupMember, } from "../../../lib/adminApi"; import { t } from "../../../lib/i18n"; +import { useTenantPermission } from "../hooks/useTenantPermission"; type UserGroupNode = GroupSummary & { children: UserGroupNode[]; @@ -247,7 +247,8 @@ function TenantGroupsPage() { const _queryClient = useQueryClient(); const { hasPermission } = useTenantPermission(tenantId); - const isWritable = hasPermission("manage_organization") || hasPermission("manage"); + const isWritable = + hasPermission("manage_organization") || hasPermission("manage"); const canView = hasPermission("view_organization") || hasPermission("view"); const [newGroupName, setNewGroupName] = useState(""); @@ -502,7 +503,9 @@ function TenantGroupsPage() { createMutation.mutate()} - disabled={!newGroupName || createMutation.isPending || !isWritable} + disabled={ + !newGroupName || createMutation.isPending || !isWritable + } > {t("ui.admin.groups.form.submit", "์์ฑํ๊ธฐ")} diff --git a/adminfront/src/features/tenants/routes/TenantListPage.tsx b/adminfront/src/features/tenants/routes/TenantListPage.tsx index a57baf5c..b85c64f4 100644 --- a/adminfront/src/features/tenants/routes/TenantListPage.tsx +++ b/adminfront/src/features/tenants/routes/TenantListPage.tsx @@ -30,7 +30,6 @@ import { } from "../../../../../common/core/utils"; import { SearchFilterBar } from "../../../../../common/ui/search-filter-bar"; import { commonStickyTableHeaderClass } from "../../../../../common/ui/table"; -import { RoleGuard } from "../../../components/auth/RoleGuard"; import { Badge } from "../../../components/ui/badge"; import { Button } from "../../../components/ui/button"; import { @@ -378,7 +377,9 @@ function TenantListPage() { queryFn: fetchMe, }); const profileRole = normalizeAdminRole(profile?.role); - const isWritable = profileRole === "super_admin" || !!profile?.systemPermissions?.manage_tenants; + const isWritable = + profileRole === "super_admin" || + !!profile?.systemPermissions?.manage_tenants; const query = useInfiniteQuery({ queryKey: ["tenants", "lazy", debouncedSearch, scopeTenantId], @@ -583,7 +584,11 @@ function TenantListPage() { return () => window.removeEventListener("message", onMessage); }, [allTenants, scopePickerOpen]); - if (profile && profileRole !== "super_admin" && !profile?.systemPermissions?.tenants) { + if ( + profile && + profileRole !== "super_admin" && + !profile?.systemPermissions?.tenants + ) { return ( diff --git a/adminfront/src/features/tenants/routes/TenantProfilePage.tsx b/adminfront/src/features/tenants/routes/TenantProfilePage.tsx index ad25ff7d..3bb5c705 100644 --- a/adminfront/src/features/tenants/routes/TenantProfilePage.tsx +++ b/adminfront/src/features/tenants/routes/TenantProfilePage.tsx @@ -276,13 +276,21 @@ export function TenantProfilePage() { {t("ui.admin.tenants.profile.name", "ํ ๋ํธ ์ด๋ฆ")}{" "} * - setName(e.target.value)} disabled={!isWritable} /> + setName(e.target.value)} + disabled={!isWritable} + /> {t("ui.admin.tenants.profile.slug", "์ฌ๋ฌ๊ทธ (Slug)")} - setSlug(e.target.value)} disabled={!isWritable} /> + setSlug(e.target.value)} + disabled={!isWritable} + /> (); const queryClient = useQueryClient(); - const { hasPermission, isLoading: isPermissionLoading } = useTenantPermission(tenantId ?? ""); + const { hasPermission, isLoading: isPermissionLoading } = useTenantPermission( + tenantId ?? "", + ); const canView = hasPermission("view_schema") || hasPermission("view"); const isWritable = hasPermission("manage_schema") || hasPermission("manage"); @@ -393,7 +395,9 @@ export function TenantSchemaPage() { updateMutation.mutate(fields)} - disabled={updateMutation.isPending || tenantQuery.isLoading || !isWritable} + disabled={ + updateMutation.isPending || tenantQuery.isLoading || !isWritable + } className="px-8 h-11" > diff --git a/adminfront/src/features/users/UserCreatePage.tsx b/adminfront/src/features/users/UserCreatePage.tsx index e0d70f18..2a8fd288 100644 --- a/adminfront/src/features/users/UserCreatePage.tsx +++ b/adminfront/src/features/users/UserCreatePage.tsx @@ -158,7 +158,9 @@ function UserCreatePage() { queryFn: fetchMe, }); const profileRole = normalizeAdminRole(profile?.role); - const canManageUsers = canManageTenantScopedUsers(profile) || !!profile?.systemPermissions?.manage_users; + const canManageUsers = + canManageTenantScopedUsers(profile) || + !!profile?.systemPermissions?.manage_users; const { register, diff --git a/adminfront/src/features/users/UserDetailPage.tsx b/adminfront/src/features/users/UserDetailPage.tsx index 2c854648..6d13c449 100644 --- a/adminfront/src/features/users/UserDetailPage.tsx +++ b/adminfront/src/features/users/UserDetailPage.tsx @@ -653,8 +653,17 @@ function UserDetailPage() { const isAdmin = profileRole === "super_admin"; const isSelf = Boolean(profile?.id && user?.id && profile.id === user.id); const canManageCurrentUser = canManageUserInTenantScope({ profile, user }); - const isWritable = isAdmin || isSelf || canManageCurrentUser || !!profile?.systemPermissions?.manage_users; - const canViewUser = isAdmin || isSelf || canManageCurrentUser || !!profile?.systemPermissions?.users || !!profile?.systemPermissions?.manage_users; + const isWritable = + isAdmin || + isSelf || + canManageCurrentUser || + !!profile?.systemPermissions?.manage_users; + const canViewUser = + isAdmin || + isSelf || + canManageCurrentUser || + !!profile?.systemPermissions?.users || + !!profile?.systemPermissions?.manage_users; const watchedStatus = watch("status"); const [newSubEmail, setNewSubEmail] = React.useState(""); diff --git a/adminfront/src/features/users/UserListPage.tsx b/adminfront/src/features/users/UserListPage.tsx index 86757936..244f404a 100644 --- a/adminfront/src/features/users/UserListPage.tsx +++ b/adminfront/src/features/users/UserListPage.tsx @@ -381,7 +381,8 @@ function UserListPage() { queryFn: fetchMe, }); const profileRole = normalizeAdminRole(profile?.role); - const isWritable = profileRole === "super_admin" || !!profile?.systemPermissions?.manage_users; + const isWritable = + profileRole === "super_admin" || !!profile?.systemPermissions?.manage_users; const { data: tenantsData } = useQuery({ queryKey: ["tenants", "all"], diff --git a/adminfront/src/lib/adminApi.ts b/adminfront/src/lib/adminApi.ts index cf327c81..46a5a5d4 100644 --- a/adminfront/src/lib/adminApi.ts +++ b/adminfront/src/lib/adminApi.ts @@ -541,20 +541,14 @@ export async function fetchSystemRelations() { return data.items; } -export async function addSystemRelation( - userId: string, - relation: string, -) { +export async function addSystemRelation(userId: string, relation: string) { await apiClient.post(`/v1/admin/system/relations`, { userId, relation, }); } -export async function removeSystemRelation( - userId: string, - relation: string, -) { +export async function removeSystemRelation(userId: string, relation: string) { await apiClient.delete(`/v1/admin/system/relations`, { data: { userId, relation }, }); diff --git a/adminfront/tests/auth.spec.ts b/adminfront/tests/auth.spec.ts index 30222f8e..8bb37766 100644 --- a/adminfront/tests/auth.spec.ts +++ b/adminfront/tests/auth.spec.ts @@ -2,6 +2,10 @@ import { expect, test } from "@playwright/test"; test.describe("Authentication", () => { test.beforeEach(async ({ page }) => { + page.on("console", (msg) => console.log("BROWSER LOG:", msg.text())); + page.on("pageerror", (err) => + console.error("BROWSER EXCEPTION:", err.message), + ); // 1. Force state await page.addInitScript(() => { window.localStorage.setItem("locale", "ko"); @@ -70,8 +74,24 @@ test.describe("Authentication", () => { // 3. Catch-all for others await page.route(/.*\/api\/v1\/.*/, async (route) => { + if (route.request().url().includes("/user/me")) { + return route.fallback(); + } if (route.request().method() === "GET") { - await route.fulfill({ json: { items: [], total: 0 } }); + await route.fulfill({ + json: { + items: [], + total: 0, + summary: { + failures: 0, + warnings: 0, + pass: 0, + success: 0, + total: 0, + }, + sections: [], + }, + }); } else { await route.fulfill({ status: 200, json: {} }); } diff --git a/adminfront/tests/security_roles.spec.ts b/adminfront/tests/security_roles.spec.ts index a9f6ff4a..bc052877 100644 --- a/adminfront/tests/security_roles.spec.ts +++ b/adminfront/tests/security_roles.spec.ts @@ -297,7 +297,9 @@ test.describe("๋ณด์ ๋ฐ ์ ๊ทผ ์ ์ด: ์์คํ ๊ด๋ฆฌ์ vs ์ผ๋ฐ ์ฌ์ฉ์ }); test.describe("์ธ๋ถ ๊ธฐ๋ฅ ๊ถํ(System Permissions)์ ๊ฐ์ง ๋น-์ํผ์ด๋๋ฏผ", () => { - test("ํ ๋ํธ ์กฐํ ๊ถํ(tenants)์ด ์์ ๋ ํ ๋ํธ ๋ชฉ๋ก ํ์ด์ง ์ง์ ๊ฐ๋ฅ ๋ฐ ์ฐ๊ธฐ ๊ธฐ๋ฅ ์ ํ ํ์ธ", async ({ page }) => { + test("ํ ๋ํธ ์กฐํ ๊ถํ(tenants)์ด ์์ ๋ ํ ๋ํธ ๋ชฉ๋ก ํ์ด์ง ์ง์ ๊ฐ๋ฅ ๋ฐ ์ฐ๊ธฐ ๊ธฐ๋ฅ ์ ํ ํ์ธ", async ({ + page, + }) => { await setupAuth(page, "tenant_admin", { tenantId: "t1", tenantSlug: "t1", @@ -315,18 +317,24 @@ test.describe("๋ณด์ ๋ฐ ์ ๊ทผ ์ ์ด: ์์คํ ๊ด๋ฆฌ์ vs ์ผ๋ฐ ์ฌ์ฉ์ // ์ฐจ๋จ ๋ฉ์์ง ๋น๋ ธ์ถ ํ์ธ await expect( - page.getByText(/์ ๊ทผ ๊ถํ์ด ์์ต๋๋ค|์ด ์์ ์ ์ํํ ๊ถํ์ด ์์ต๋๋ค/i), + page.getByText( + /์ ๊ทผ ๊ถํ์ด ์์ต๋๋ค|์ด ์์ ์ ์ํํ ๊ถํ์ด ์์ต๋๋ค/i, + ), ).not.toBeVisible(); // "ํ ๋ํธ 1" ๋ชฉ๋ก ๋ ธ์ถ ํ์ธ await expect(page.getByText("ํ ๋ํธ 1")).toBeVisible(); // ์์ ๊ถํ(manage_tenants)์ด ์์ผ๋ฏ๋ก ์ฐ๊ธฐ ๋ฒํผ ๋น๋ ธ์ถ ํ์ธ - await expect(page.getByRole("link", { name: /ํ ๋ํธ ์ถ๊ฐ/i })).not.toBeVisible(); + await expect( + page.getByRole("link", { name: /ํ ๋ํธ ์ถ๊ฐ/i }), + ).not.toBeVisible(); await expect(page.getByTestId("tenant-data-mgmt-btn")).not.toBeVisible(); }); - test("ํ ๋ํธ ๊ด๋ฆฌ ๊ถํ(manage_tenants)๊น์ง ์์ ๋ ํ ๋ํธ ์ถ๊ฐ ๋ฐ ๋ฐ์ดํฐ ๊ด๋ฆฌ ๋ฒํผ ํ์ฑํ ํ์ธ", async ({ page }) => { + test("ํ ๋ํธ ๊ด๋ฆฌ ๊ถํ(manage_tenants)๊น์ง ์์ ๋ ํ ๋ํธ ์ถ๊ฐ ๋ฐ ๋ฐ์ดํฐ ๊ด๋ฆฌ ๋ฒํผ ํ์ฑํ ํ์ธ", async ({ + page, + }) => { await setupAuth(page, "tenant_admin", { tenantId: "t1", tenantSlug: "t1", @@ -341,7 +349,9 @@ test.describe("๋ณด์ ๋ฐ ์ ๊ทผ ์ ์ด: ์์คํ ๊ด๋ฆฌ์ vs ์ผ๋ฐ ์ฌ์ฉ์ await expect(page.getByText("ํ ๋ํธ 1")).toBeVisible(); // ์์ ๊ถํ(manage_tenants)์ด ์์ผ๋ฏ๋ก ์ฐ๊ธฐ ๋ฒํผ(ํ ๋ํธ ์ถ๊ฐ, ๋ฐ์ดํฐ ๊ด๋ฆฌ) ๋ ธ์ถ ํ์ธ - await expect(page.getByRole("link", { name: /ํ ๋ํธ ์ถ๊ฐ/i })).toBeVisible(); + await expect( + page.getByRole("link", { name: /ํ ๋ํธ ์ถ๊ฐ/i }), + ).toBeVisible(); await expect(page.getByTestId("tenant-data-mgmt-btn")).toBeVisible(); }); }); diff --git a/backend/internal/domain/auth_models.go b/backend/internal/domain/auth_models.go index 015d8028..45e184b5 100644 --- a/backend/internal/domain/auth_models.go +++ b/backend/internal/domain/auth_models.go @@ -70,17 +70,17 @@ type SignupRequest struct { // User Profile Models type SystemPermissions struct { - Overview bool `json:"overview"` - Tenants bool `json:"tenants"` - OrgChart bool `json:"org_chart"` - Worksmobile bool `json:"worksmobile"` - OrySSOT bool `json:"ory_ssot"` - DataIntegrity bool `json:"data_integrity"` - Users bool `json:"users"` - PermissionsDirect bool `json:"permissions_direct"` - AuthGuard bool `json:"auth_guard"` - ApiKeys bool `json:"api_keys"` - AuditLogs bool `json:"audit_logs"` + Overview bool `json:"overview"` + Tenants bool `json:"tenants"` + OrgChart bool `json:"org_chart"` + Worksmobile bool `json:"worksmobile"` + OrySSOT bool `json:"ory_ssot"` + DataIntegrity bool `json:"data_integrity"` + Users bool `json:"users"` + PermissionsDirect bool `json:"permissions_direct"` + AuthGuard bool `json:"auth_guard"` + ApiKeys bool `json:"api_keys"` + AuditLogs bool `json:"audit_logs"` ManageOverview bool `json:"manage_overview"` ManageTenants bool `json:"manage_tenants"` diff --git a/backend/internal/handler/tenant_handler.go b/backend/internal/handler/tenant_handler.go index 0674155f..729519a8 100644 --- a/backend/internal/handler/tenant_handler.go +++ b/backend/internal/handler/tenant_handler.go @@ -3263,14 +3263,14 @@ func (h *TenantHandler) ListRelations(c *fiber.Ctx) error { } allowedRelations := map[string]bool{ - "profile_viewers": true, - "profile_managers": true, - "permissions_viewers": true, - "permissions_managers": true, - "organization_viewers": true, - "organization_managers": true, - "schema_viewers": true, - "schema_managers": true, + "profile_viewers": true, + "profile_managers": true, + "permissions_viewers": true, + "permissions_managers": true, + "organization_viewers": true, + "organization_managers": true, + "schema_viewers": true, + "schema_managers": true, } type userRelationInfo struct { @@ -3308,7 +3308,7 @@ func (h *TenantHandler) ListRelations(c *fiber.Ctx) error { } } } - + if name == "Unknown" && email == "Unknown" && h.UserRepo != nil { user, err := h.UserRepo.FindByID(c.Context(), userID) if err == nil && user != nil { @@ -3349,14 +3349,14 @@ func (h *TenantHandler) AddRelation(c *fiber.Ctx) error { } allowedRelations := map[string]bool{ - "profile_viewers": true, - "profile_managers": true, - "permissions_viewers": true, - "permissions_managers": true, - "organization_viewers": true, - "organization_managers": true, - "schema_viewers": true, - "schema_managers": true, + "profile_viewers": true, + "profile_managers": true, + "permissions_viewers": true, + "permissions_managers": true, + "organization_viewers": true, + "organization_managers": true, + "schema_viewers": true, + "schema_managers": true, } if !allowedRelations[req.Relation] { diff --git a/common/locales/en.toml b/common/locales/en.toml index 995518c1..9a81d789 100644 --- a/common/locales/en.toml +++ b/common/locales/en.toml @@ -1,11 +1,19 @@ + +[msg] + +[msg.admin] + +[msg.admin.audit] +subtitle = "View administrator activity history." + [msg.common] -loading_more = "Loading more logs..." copied = "Copied." error = "Error" forbidden = "Access denied." loading = "Loading..." -no_results = "No results found." +loading_more = "Loading more logs..." no_description = "No Description." +no_results = "No results found." parsing = "Parsing data..." requesting = "Requesting..." saving = "Saving..." @@ -20,20 +28,38 @@ loading = "Loading audit logs..." [msg.common.audit.registry] count = "{{count}} logs" -[msg.admin.audit] -subtitle = "View administrator activity history." +[msg.dev] [msg.dev.audit] subtitle = "View developer activity history within the current app scope." +[ui] + +[ui.admin] + +[ui.admin.integrity] +fetch_error = "Unable to load the final integrity check result." + +[ui.admin.integrity.section] +tenant_integrity = "Tenant integrity" +user_integrity = "User integrity" + +[ui.admin.integrity.summary] +failures_text = "Failures {{count}}" +title = "Final integrity check" + +[ui.admin.overview] + +[ui.admin.overview.chart] +description = "Check the graph by all or selected organizations." +title = "Login request status by company and app" + [ui.common] -no_results = "No results to display." -apply = "Apply" +action = "Action" actions = "Actions" add = "Add" -all = "All" -apply = "Apply" admin_only = "Admin Only" +all = "All" apply = "Apply" approve = "Approve" assign = "Assign" @@ -61,42 +87,45 @@ export_with_ids = "Include UUID" export_without_ids = "Export without UUID" fail = "Fail" go_home = "Go Home" -info = "Info" -view = "View" hyphen = "-" +info = "Info" +language = "Language" +language_en = "English" +language_ko = "Korean" +load_more = "Load more" loading = "Loading..." manage = "Manage" move = "Move" move_org = "Move to another organization" na = "N/A" +name = "Name" never = "Never" next = "Next" +no_results = "No results to display." none = "None" page_of = "Page {{page}} of {{total}}" prev = "Prev" previous = "Previous" qr = "QR" -reject = "Reject" -rejected = "Rejected" -reset = "Reset" +read = "Read" read_only = "Read Only" refresh = "Refresh" +reject = "Reject" +rejected = "Rejected" remove = "Remove" remove_org = "Remove from organization" resend = "Resend" +reset = "Reset" retry = "Retry" row = "Row" save = "Save" search = "Search" search_group = "Search groups..." +searching = "Searching..." select = "Select" select_file = "Select File" select_placeholder = "Select Placeholder" -load_more = "Load more" show_more = "Show More" -language = "Language" -language_ko = "Korean" -language_en = "English" submit = "Submit" submitting = "Submitting..." success = "Success" @@ -105,6 +134,8 @@ theme_light = "Light" theme_toggle = "Theme Toggle" unassigned = "Unassigned" unknown = "Unknown" +view = "View" +write = "Write" [ui.common.audit] load_more = "Load more" @@ -114,12 +145,6 @@ title = "Audit Logs" actor_id = "Copy User ID" target = "Copy Client ID" -[ui.common.audit.filters] -user_id = "Filter by User ID" -client_id = "Filter by Client ID" -action = "Filter by Action (e.g. ROTATE_SECRET)" -status_all = "All Status" - [ui.common.audit.details] actor = "User ID" actor_id = "User ID ยท {{value}}" @@ -135,24 +160,38 @@ path = "Path ยท {{value}}" request = "Request" request_id = "Request ID ยท {{value}}" result = "Result" -tenant = "Tenant ยท {{value}}" target = "Client ID ยท {{value}}" +tenant = "Tenant ยท {{value}}" + +[ui.common.audit.filters] +action = "Filter by Action (e.g. ROTATE_SECRET)" +client_id = "Filter by Client ID" +status_all = "All Status" +user_id = "Filter by User ID" [ui.common.audit.registry] title = "Audit registry" [ui.common.audit.table] -no_logs = "No logs to display." action = "Action" actor = "User ID" client_id = "Client ID" -user_id = "User ID" +no_logs = "No logs to display." status = "Status" target = "Client ID" time = "Time" +user_id = "User ID" -[ui.common.overview] -title = "Operational Status" +[ui.common.badge] +admin_only = "Admin only" +command_only = "Command only" +system = "System" + +[ui.common.chart] + +[ui.common.chart.axis] +x = "X-axis: Period" +y = "Y-axis: Login Requests" [ui.common.chart.period] day = "Day" @@ -162,29 +201,12 @@ week = "Week" [ui.common.chart.series_summary] login_users = "Login {{login}} / Users {{subjects}}" -[ui.common.chart.axis] -x = "X-axis: Period" -y = "Y-axis: Login Requests" - -[ui.admin.integrity] -fetch_error = "Unable to load the final integrity check result." - -[ui.admin.integrity.summary] -failures_text = "Failures {{count}}" -title = "Final integrity check" - -[ui.admin.integrity.section] -tenant_integrity = "Tenant integrity" -user_integrity = "User integrity" - -[ui.admin.overview.chart] -description = "Check the graph by all or selected organizations." -title = "Login request status by company and app" - -[ui.common.badge] +[ui.common.custom_claim_permission] admin_only = "Admin only" -command_only = "Command only" -system = "System" +user_and_admin = "User and admin" + +[ui.common.overview] +title = "Operational Status" [ui.common.status] active = "Active" @@ -197,10 +219,3 @@ pending = "Pending" success = "Success" unchanged = "Unchanged" updated = "Updated" - -[ui.common] -searching = "Searching..." - -[ui.common.custom_claim_permission] -admin_only = "Admin only" -user_and_admin = "User and admin" diff --git a/common/locales/ko.toml b/common/locales/ko.toml index 42b716d2..dc3fc8ed 100644 --- a/common/locales/ko.toml +++ b/common/locales/ko.toml @@ -1,11 +1,19 @@ + +[msg] + +[msg.admin] + +[msg.admin.audit] +subtitle = "๊ด๋ฆฌ์ ์์ ์ด๋ ฅ์ ์กฐํํฉ๋๋ค." + [msg.common] -loading_more = "์ถ๊ฐ ๋ก๊ทธ๋ฅผ ๋ถ๋ฌ์ค๋ ์ค..." copied = "๋ณต์ฌ๋์์ต๋๋ค." error = "์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค." forbidden = "์ ๊ทผ ๊ถํ์ด ์์ต๋๋ค." loading = "๋ก๋ฉ ์ค..." -no_results = "๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค." +loading_more = "์ถ๊ฐ ๋ก๊ทธ๋ฅผ ๋ถ๋ฌ์ค๋ ์ค..." no_description = "์ค๋ช ์ด ์์ต๋๋ค." +no_results = "๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค." parsing = "๋ฐ์ดํฐ ํ์ฑ ์ค..." requesting = "์์ฒญ ์ค..." saving = "์ ์ฅ ์ค..." @@ -20,20 +28,38 @@ loading = "Loading audit logs..." [msg.common.audit.registry] count = "์ด {{count}}๊ฐ ๋ก๊ทธ" -[msg.admin.audit] -subtitle = "๊ด๋ฆฌ์ ์์ ์ด๋ ฅ์ ์กฐํํฉ๋๋ค." +[msg.dev] [msg.dev.audit] subtitle = "ํ์ฌ ์ฑ ๋ฒ์์ ๊ฐ๋ฐ์ ์์ ์ด๋ ฅ์ ์กฐํํฉ๋๋ค." +[ui] + +[ui.admin] + +[ui.admin.integrity] +fetch_error = "์ ํฉ์ฑ ์ต์ข ๊ฒ์ฆ ๊ฒฐ๊ณผ๋ฅผ ๋ถ๋ฌ์ค์ง ๋ชปํ์ต๋๋ค." + +[ui.admin.integrity.section] +tenant_integrity = "ํ ๋ํธ ์ ํฉ์ฑ" +user_integrity = "์ฌ์ฉ์ ์ ํฉ์ฑ" + +[ui.admin.integrity.summary] +failures_text = "์คํจ {{count}}๊ฑด" +title = "์ ํฉ์ฑ ์ต์ข ๊ฒ์ฆ" + +[ui.admin.overview] + +[ui.admin.overview.chart] +description = "์ ์ฒด ๋๋ ์ ํํ ์กฐ์ง ๊ธฐ์ค์ผ๋ก ๊ทธ๋ํ๋ฅผ ํ์ธํฉ๋๋ค." +title = "ํ์ฌ๋ณ ์ฑ๋ณ ๋ก๊ทธ์ธ ์์ฒญ ํํฉ" + [ui.common] -no_results = "ํ์ํ ๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค." -apply = "์ ์ฉ" +action = "์์ " actions = "์ก์ " add = "์ถ๊ฐ" -all = "์ ์ฒด" -apply = "์ ์ฉ" admin_only = "๊ด๋ฆฌ์ ์ ์ฉ" +all = "์ ์ฒด" apply = "์ ์ฉ" approve = "์น์ธ" assign = "ํ ๋น" @@ -61,42 +87,45 @@ export_with_ids = "UUID ํฌํจ" export_without_ids = "UUID ์ ์ธ ๋ด๋ณด๋ด๊ธฐ" fail = "์คํจ" go_home = "ํ์ผ๋ก" -info = "์์ธ ์๋ด" -view = "๋ณด๊ธฐ" hyphen = "-" +info = "์์ธ ์๋ด" +language = "์ธ์ด" +language_en = "English" +language_ko = "ํ๊ตญ์ด" +load_more = "๋ ๋ณด๊ธฐ" loading = "๋ก๋ฉ ์ค..." manage = "๊ด๋ฆฌ" move = "์ด๋" move_org = "ํ ์กฐ์ง์ผ๋ก ์ด๋" na = "N/A" +name = "์ด๋ฆ" never = "Never" next = "๋ค์" +no_results = "ํ์ํ ๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค." none = "์์" page_of = "Page {{page}} of {{total}}" prev = "์ด์ " previous = "์ด์ " qr = "QR" -reject = "๋ฐ๋ ค" -rejected = "๋ฐ๋ ค๋จ" -reset = "์ด๊ธฐํ" +read = "์กฐํ ๊ฐ๋ฅ (Read)" read_only = "์ฝ๊ธฐ ์ ์ฉ" refresh = "์๋ก๊ณ ์นจ" +reject = "๋ฐ๋ ค" +rejected = "๋ฐ๋ ค๋จ" remove = "์ ์ธ" remove_org = "์กฐ์ง์์ ์ ์ธ" resend = "์ฌ๋ฐ์ก" +reset = "์ด๊ธฐํ" retry = "๋ค์ ์๋" row = "ํ" save = "์ ์ฅ" search = "๊ฒ์" search_group = "๊ทธ๋ฃน ๊ฒ์..." +searching = "๊ฒ์ ์ค..." select = "์ ํ" select_file = "ํ์ผ ์ ํ" select_placeholder = "์ ํํ์ธ์" -load_more = "๋ ๋ณด๊ธฐ" show_more = "+ ๋๋ณด๊ธฐ" -language = "์ธ์ด" -language_ko = "ํ๊ตญ์ด" -language_en = "English" submit = "์ ์ฒญํ๊ธฐ" submitting = "์ ์ถ ์ค..." success = "์ฑ๊ณต" @@ -105,6 +134,8 @@ theme_light = "Light" theme_toggle = "ํ ๋ง ์ ํ" unassigned = "๋ฏธ๋ฐฐ์ " unknown = "Unknown" +view = "๋ณด๊ธฐ" +write = "์์ ๊ฐ๋ฅ (Write)" [ui.common.audit] load_more = "๋ ๋ณด๊ธฐ" @@ -114,12 +145,6 @@ title = "๊ฐ์ฌ ๋ก๊ทธ" actor_id = "์ฌ์ฉ์ ID ๋ณต์ฌ" target = "ํด๋ผ์ด์ธํธ ID ๋ณต์ฌ" -[ui.common.audit.filters] -user_id = "์ฌ์ฉ์ ID๋ก ๊ฒ์" -client_id = "ํด๋ผ์ด์ธํธ ID๋ก ๊ฒ์" -action = "์ก์ ์ผ๋ก ๊ฒ์ (์: ROTATE_SECRET)" -status_all = "์ ์ฒด ์ํ" - [ui.common.audit.details] actor = "์ฌ์ฉ์ ID" actor_id = "์ฌ์ฉ์ ID ยท {{value}}" @@ -135,24 +160,38 @@ path = "Path ยท {{value}}" request = "Request" request_id = "Request ID ยท {{value}}" result = "Result" -tenant = "Tenant ยท {{value}}" target = "ํด๋ผ์ด์ธํธ ID ยท {{value}}" +tenant = "Tenant ยท {{value}}" + +[ui.common.audit.filters] +action = "์ก์ ์ผ๋ก ๊ฒ์ (์: ROTATE_SECRET)" +client_id = "ํด๋ผ์ด์ธํธ ID๋ก ๊ฒ์" +status_all = "์ ์ฒด ์ํ" +user_id = "์ฌ์ฉ์ ID๋ก ๊ฒ์" [ui.common.audit.registry] title = "๊ฐ์ฌ ๋ก๊ทธ ๋ ์ง์คํธ๋ฆฌ" [ui.common.audit.table] -no_logs = "ํ์ํ ๋ก๊ทธ๊ฐ ์์ต๋๋ค." action = "์์ " actor = "์ฌ์ฉ์ ID" client_id = "ํด๋ผ์ด์ธํธ ID" -user_id = "์ฌ์ฉ์ ID" +no_logs = "ํ์ํ ๋ก๊ทธ๊ฐ ์์ต๋๋ค." status = "์ํ" target = "ํด๋ผ์ด์ธํธ ID" time = "์๊ฐ" +user_id = "์ฌ์ฉ์ ID" -[ui.common.overview] -title = "์ด์ ํํฉ" +[ui.common.badge] +admin_only = "Admin only" +command_only = "Command only" +system = "System" + +[ui.common.chart] + +[ui.common.chart.axis] +x = "X์ถ: ๊ธฐ๊ฐ" +y = "Y์ถ: ๋ก๊ทธ์ธ ์์ฒญ ์" [ui.common.chart.period] day = "์ผ" @@ -162,29 +201,12 @@ week = "์ฃผ" [ui.common.chart.series_summary] login_users = "๋ก๊ทธ์ธ {{login}} / ์ฌ์ฉ์ {{subjects}}" -[ui.common.chart.axis] -x = "X์ถ: ๊ธฐ๊ฐ" -y = "Y์ถ: ๋ก๊ทธ์ธ ์์ฒญ ์" +[ui.common.custom_claim_permission] +admin_only = "๊ด๋ฆฌ์๋ง ๊ฐ๋ฅ" +user_and_admin = "์ฌ์ฉ์์ ๊ด๋ฆฌ์" -[ui.admin.integrity] -fetch_error = "์ ํฉ์ฑ ์ต์ข ๊ฒ์ฆ ๊ฒฐ๊ณผ๋ฅผ ๋ถ๋ฌ์ค์ง ๋ชปํ์ต๋๋ค." - -[ui.admin.integrity.summary] -failures_text = "์คํจ {{count}}๊ฑด" -title = "์ ํฉ์ฑ ์ต์ข ๊ฒ์ฆ" - -[ui.admin.integrity.section] -tenant_integrity = "ํ ๋ํธ ์ ํฉ์ฑ" -user_integrity = "์ฌ์ฉ์ ์ ํฉ์ฑ" - -[ui.admin.overview.chart] -description = "์ ์ฒด ๋๋ ์ ํํ ์กฐ์ง ๊ธฐ์ค์ผ๋ก ๊ทธ๋ํ๋ฅผ ํ์ธํฉ๋๋ค." -title = "ํ์ฌ๋ณ ์ฑ๋ณ ๋ก๊ทธ์ธ ์์ฒญ ํํฉ" - -[ui.common.badge] -admin_only = "Admin only" -command_only = "Command only" -system = "System" +[ui.common.overview] +title = "์ด์ ํํฉ" [ui.common.status] active = "ํ์ฑ" @@ -197,10 +219,3 @@ pending = "์ค๋น ์ค" success = "์ฑ๊ณต" unchanged = "๋์ผ" updated = "์์ " - -[ui.common] -searching = "๊ฒ์ ์ค..." - -[ui.common.custom_claim_permission] -admin_only = "๊ด๋ฆฌ์๋ง ๊ฐ๋ฅ" -user_and_admin = "์ฌ์ฉ์์ ๊ด๋ฆฌ์" diff --git a/common/locales/template.toml b/common/locales/template.toml index 7b3a6de3..c956f461 100644 --- a/common/locales/template.toml +++ b/common/locales/template.toml @@ -1,11 +1,19 @@ + +[msg] + +[msg.admin] + +[msg.admin.audit] +subtitle = "" + [msg.common] -loading_more = "" copied = "" error = "" forbidden = "" loading = "" -no_results = "" +loading_more = "" no_description = "" +no_results = "" parsing = "" requesting = "" saving = "" @@ -20,20 +28,38 @@ loading = "" [msg.common.audit.registry] count = "" -[msg.admin.audit] -subtitle = "" +[msg.dev] [msg.dev.audit] subtitle = "" +[ui] + +[ui.admin] + +[ui.admin.integrity] +fetch_error = "" + +[ui.admin.integrity.section] +tenant_integrity = "" +user_integrity = "" + +[ui.admin.integrity.summary] +failures_text = "" +title = "" + +[ui.admin.overview] + +[ui.admin.overview.chart] +description = "" +title = "" + [ui.common] -no_results = "" -apply = "Apply" +action = "" actions = "" add = "" -all = "" -apply = "" admin_only = "" +all = "" apply = "" approve = "" assign = "" @@ -61,42 +87,45 @@ export_with_ids = "" export_without_ids = "" fail = "" go_home = "" -info = "" -view = "" hyphen = "" +info = "" +language = "" +language_en = "" +language_ko = "" +load_more = "" loading = "" manage = "" move = "" move_org = "" na = "" +name = "" never = "" next = "" +no_results = "" none = "" page_of = "" prev = "" previous = "" qr = "" -reject = "" -rejected = "" -reset = "" +read = "" read_only = "" refresh = "" +reject = "" +rejected = "" remove = "" remove_org = "" resend = "" +reset = "" retry = "" row = "" save = "" search = "" search_group = "" +searching = "" select = "" select_file = "" select_placeholder = "" -load_more = "" show_more = "" -language = "" -language_ko = "" -language_en = "" submit = "" submitting = "" success = "" @@ -105,6 +134,8 @@ theme_light = "" theme_toggle = "" unassigned = "" unknown = "" +view = "" +write = "" [ui.common.audit] load_more = "" @@ -114,12 +145,6 @@ title = "" actor_id = "" target = "" -[ui.common.audit.filters] -user_id = "" -client_id = "" -action = "" -status_all = "" - [ui.common.audit.details] actor = "" actor_id = "" @@ -135,24 +160,38 @@ path = "" request = "" request_id = "" result = "" -tenant = "" target = "" +tenant = "" + +[ui.common.audit.filters] +action = "" +client_id = "" +status_all = "" +user_id = "" [ui.common.audit.registry] title = "" [ui.common.audit.table] -no_logs = "" action = "" actor = "" client_id = "" -user_id = "" +no_logs = "" status = "" target = "" time = "" +user_id = "" -[ui.common.overview] -title = "" +[ui.common.badge] +admin_only = "" +command_only = "" +system = "" + +[ui.common.chart] + +[ui.common.chart.axis] +x = "" +y = "" [ui.common.chart.period] day = "" @@ -162,29 +201,12 @@ week = "" [ui.common.chart.series_summary] login_users = "" -[ui.common.chart.axis] -x = "" -y = "" - -[ui.admin.integrity] -fetch_error = "" - -[ui.admin.integrity.summary] -failures_text = "" -title = "" - -[ui.admin.integrity.section] -tenant_integrity = "" -user_integrity = "" - -[ui.admin.overview.chart] -description = "" -title = "" - -[ui.common.badge] +[ui.common.custom_claim_permission] admin_only = "" -command_only = "" -system = "" +user_and_admin = "" + +[ui.common.overview] +title = "" [ui.common.status] active = "" @@ -197,10 +219,3 @@ pending = "" success = "" unchanged = "" updated = "" - -[ui.common] -searching = "" - -[ui.common.custom_claim_permission] -admin_only = "" -user_and_admin = "" diff --git a/devfront/e2e-evidence/tenant-access-allowed-tenant-added.png b/devfront/e2e-evidence/tenant-access-allowed-tenant-added.png index 4efe45e2..c480b3a7 100644 Binary files a/devfront/e2e-evidence/tenant-access-allowed-tenant-added.png and b/devfront/e2e-evidence/tenant-access-allowed-tenant-added.png differ diff --git a/devfront/e2e-evidence/tenant-access-allowed-tenant-deleted.png b/devfront/e2e-evidence/tenant-access-allowed-tenant-deleted.png index 11269d9d..d44faeff 100644 Binary files a/devfront/e2e-evidence/tenant-access-allowed-tenant-deleted.png and b/devfront/e2e-evidence/tenant-access-allowed-tenant-deleted.png differ diff --git a/devfront/src/features/clients/ClientGeneralPage.tsx b/devfront/src/features/clients/ClientGeneralPage.tsx index abde3287..172e9366 100644 --- a/devfront/src/features/clients/ClientGeneralPage.tsx +++ b/devfront/src/features/clients/ClientGeneralPage.tsx @@ -2798,6 +2798,7 @@ function ClientGeneralPage() { ) : ( { .getByPlaceholder(/๊ธฐ๋ณธ๊ฐ์ ์ ๋ ฅํ์ธ์|Enter the default value/i) .first() .fill("3.14"); + const responsePromise = page.waitForResponse( + "**/api/v1/dev/clients/client-claims", + ); await page.getByRole("button", { name: /^์ ์ฅ$|^Save$/i }).click(); + await responsePromise; await expect .poll( diff --git a/locales/en.toml b/locales/en.toml index 4fad3b26..9a36296a 100644 --- a/locales/en.toml +++ b/locales/en.toml @@ -91,17 +91,22 @@ notice_emphasis = "Store it in a secure location." notice_suffix = "Rotate the key immediately if you think it has been exposed." [msg.admin.api_keys.list] -edit_scopes_desc = "Keep the CLIENT_ID unchanged and modify scopes only." -rotate_confirm = "API key \"{{name}}\"'s secret will be rotated. The existing secret will no longer work." -rotate_secret_notice = "The new secret is shown only once. The CLIENT_ID has not changed." delete_confirm = "Are you sure you want to delete this API key?" +edit_scopes_desc = "Edit the scopes granted to this API key." empty = "No API keys have been issued yet." fetch_error = "Failed to load the API key list." +rotate_confirm = "Rotate the secret for this API key?" +rotate_secret_notice = "The new secret is shown only once." subtitle = "View and manage the API keys issued for server-to-server communication." [msg.admin.api_keys.list.registry] count = "{{count}} API keys loaded." +[msg.admin.apikeys] + +[msg.admin.apikeys.registry] +count = "There are {{count}} active keys registered." + [msg.admin.audit] empty = "No audit logs have been collected yet." end = "End of audit feed" @@ -163,6 +168,55 @@ remove_success = "Role revoked successfully." [msg.admin.header] subtitle = "Tenant isolation & least privilege by default" +[msg.admin.integrity] +subtitle = "Review integrity status and inspect checks across the admin data model." + +[msg.admin.integrity.check] + +[msg.admin.integrity.check.duplicate_tenant_slugs] +description = "Checks duplicate active tenant slugs using LOWER(TRIM(slug))." + +[msg.admin.integrity.check.orphan_tenant_parents] +description = "Checks whether tenants.parent_id points to a missing or soft-deleted tenant." + +[msg.admin.integrity.check.orphan_user_login_id_tenants] +description = "Checks whether user_login_ids.tenant_id points to a missing or soft-deleted tenant." + +[msg.admin.integrity.check.orphan_user_login_id_users] +description = "Checks whether user_login_ids.user_id points to a missing or soft-deleted user." + +[msg.admin.integrity.check.orphan_user_tenant_memberships] +description = "Checks whether users.tenant_id points to a missing or soft-deleted tenant." + +[msg.admin.integrity.forbidden] +description = "This screen is available only to super_admin." + +[msg.admin.integrity.orphan_login_ids] +delete_confirm = "Delete {{count}} selected orphan login IDs?" +delete_success = "Deleted {{count}} orphan login IDs." +description = "Review login IDs that reference deleted or missing users/tenants, then delete selected rows." +empty = "No orphan login IDs to delete." +load_error = "Failed to load orphan login ID targets." + +[msg.admin.integrity.read_model] +description = "Checks anomalies in the backend DB read model without overwriting the Ory SoT." + +[msg.admin.integrity.recheck] +error = "Check failed." +running = "Running integrity check." +success = "Check completed." + +[msg.admin.integrity.report] +load_error = "Failed to load the integrity report." + +[msg.admin.integrity.section] + +[msg.admin.integrity.section.tenant_integrity] +description = "Check duplicate tenant slugs and invalid parent relationships." + +[msg.admin.integrity.section.user_integrity] +description = "Check orphan records in user and login ID references." + [msg.admin.notice] idp_policy = "IDP management keys are only used through server-side wrapper APIs with audit logging and rate limits enabled." scope = "Administrative features are exposed only within the /admin namespace." @@ -171,8 +225,19 @@ scope = "Administrative features are exposed only within the /admin namespace." hover_member_info = "Hover to see member details." import_description = "Upload a CSV file to bulk register the organization chart." import_error = "An error occurred during organization chart import." +import_partial_success = "Imported some organization data successfully." import_success = "Organization chart imported successfully." +[msg.admin.ory_ssot] +flush_confirm = "Flush only Redis identity cache keys?" +flush_error = "Redis identity cache flush failed." +flush_success = "Flushed {{count}} Redis identity cache keys." +load_error = "Failed to load Ory SSOT system status." +subtitle = "Review Kratos source-of-truth and Redis identity cache status separately." + +[msg.admin.ory_ssot.forbidden] +description = "This screen is only available to super_admin users." + [msg.admin.overview] description = "Review shared metrics and policy status across all tenants in one place." idp_fallback = "Fallback: Descope" @@ -192,36 +257,52 @@ description = "Jump to the most frequently used administrative workflows." audit_events_24h = "24h Audit Events" oidc_clients = "OIDC Clients" policy_gate = "Policy Gate Status" -total_users = "Total Users" total_tenants = "Total Tenants" +total_users = "Total Users" + +[msg.admin.permissions_direct] +desc_api_keys = "Desc API Keys" +desc_audit_logs = "Desc Audit Logs" +desc_auth_guard = "Desc Auth Guard" +desc_data_integrity = "Desc Data Integrity" +desc_org_chart = "Desc Org Chart" +desc_ory_ssot = "Desc Ory Ssot" +desc_overview = "Desc Overview" +desc_permissions_direct = "Desc Permissions Direct" +desc_tenants = "Desc Tenants" +desc_users = "Desc Users" +desc_worksmobile = "Desc Worksmobile" +description = "Assign and grant fine-grained functional permissions for tenants and global sidebar menu tab access." +no_user_selected_desc = "No User Selected Desc" +no_users_found = "No Users Found" + +[msg.admin.system] + +[msg.admin.system.relations] +remove_all_confirm = "Remove All Confirm" +update_success = "Update Success" [msg.admin.tenants] approve_confirm = "Do you want to approve this tenant?" approve_success = "Tenant approved successfully." -delete_confirm = "Delete Tenant \\\\\\\"{{name}}\\\\\\\"?" +delete_bulk_confirm = "Delete {{count}} selected tenants?" +delete_confirm = "Delete Tenant \\\\\\\\\\\\\\\"{{name}}\\\\\\\\\\\\\\\"?" delete_success = "Tenant deleted." empty = "No tenants have been registered yet." -fetch_error = "Failed to load the tenant list." +empty_scope = "There are no child tenants to display in the selected scope." +empty_search = "No tenants match the current search." export_error = "Failed to export tenants." +fetch_error = "Failed to load the tenant list." import_empty = "There are no tenant rows to import." import_error = "Failed to import tenants." import_result = "Created {{created}}, updated {{updated}}, failed {{failed}}" missing_id = "No Tenant ID." not_found = "Tenant not found." -remove_sub_confirm = "Remove tenant \\\"{{name}}\\\" from sub-tenants?" +remove_sub_confirm = "Remove tenant \\\\\\\"{{name}}\\\\\\\" from sub-tenants?" +seed_delete_blocked = "Seed tenants cannot be deleted." +status_error = "temp" subtitle = "Review registered tenants and manage their current status." -[msg.admin.tenants.import_preview] -description = "Rows without tenant_id are compared with existing tenant candidates, then imported as new tenants or updates." - -[msg.admin.tenants.parent] -local_picker_description = "Select the tenant to use as the parent from the tenant list." -local_picker_empty = "No selectable tenants are available." -picker_description = "Select a tenant in org-chart to apply it as the parent tenant." - -[msg.admin.tenants.scope] -description = "Select a parent tenant to filter the list to its descendants." - [msg.admin.tenants.admins] add_success = "Tenant admin added successfully." empty = "No tenant admins are assigned yet." @@ -231,6 +312,10 @@ remove_self = "Cannot remove yourself." remove_success = "Tenant admin removed successfully." subtitle = "Manage the administrators assigned to this tenant." +[msg.admin.tenants.bulk] +update_error = "temp" +update_success = "temp" + [msg.admin.tenants.create] pick_parent_first = "Select the parent tenant first." subtitle = "Enter the minimum required information to create a tenant." @@ -245,7 +330,12 @@ subtitle = "Capture internal policy notes for administrators." [msg.admin.tenants.create.profile] subtitle = "Set the basic tenant profile information." +[msg.admin.tenants.import_preview] +description = "Rows without tenant_id are compared with existing tenant candidates, then imported as new tenants or updates." + [msg.admin.tenants.members] +add_error = "Failed to add members" +add_success = "Added {{count}} members." desc = "View the list of users belonging to this organization." empty = "No members found." limit_notice = "Showing members from the first 10 descendant organizations due to size limits." @@ -262,6 +352,11 @@ remove_self = "Cannot remove yourself." remove_success = "Owner permission revoked." subtitle = "List of owners with top-level permissions for this tenant." +[msg.admin.tenants.parent] +local_picker_description = "Select the tenant to use as the parent from the tenant list." +local_picker_empty = "No selectable tenants are available." +picker_description = "Select a tenant in org-chart to apply it as the parent tenant." + [msg.admin.tenants.registry] count = "{{count}} tenants loaded." scope_results = "{{count}} tenants under {{name}}" @@ -270,25 +365,42 @@ search_results = "{{count}} search results" table_hint = "Compare IDs, status, and size quickly in the sortable flat list." tree_hint = "Review parent-child relationships and subtree coverage in the hierarchy." -[msg.admin.tenants] -empty_scope = "There are no child tenants to display in the selected scope." -empty_search = "No tenants match the current search." +[msg.admin.tenants.relations] +empty = "There are no users with designated fine-grained permissions. Please add a user to configure." +remove_all_confirm = "Remove All Confirm" +subtitle = "Isolate and assign fine-grained view and edit permissions for each tab on a per-user basis. Parent inherited permissions are automatically preserved." +update_success = "Update Success" [msg.admin.tenants.schema] -empty = "No custom fields defined. Click \\\\\\\"Add Field\\\\\\\" to begin." +empty = "No custom fields defined. Click \\\\\\\\\\\\\\\"Add Field\\\\\\\\\\\\\\\" to begin." forbidden_desc = "Only administrators can access user schema settings." missing_id = "Tenant ID missing" subtitle = "Define custom attributes for users in this tenant." update_error = "Failed to update schema" update_success = "Schema updated successfully" +[msg.admin.tenants.scope] +description = "Select a parent tenant to filter the list to its descendants." + [msg.admin.tenants.sub] empty = "No child tenants are connected." subtitle = "Review and manage child tenants linked under this tenant." +[msg.admin.user_projection] +action_error = "Projection operation failed." +action_success = "Refreshed the projection for {{count}} users." +forbidden_description = "This screen is only available to super_admin users." +load_error = "Failed to load projection status." +reset_confirm = "Rebuild user projection from the Kratos source of truth?" +subtitle = "Review and sync the Kratos user read model." + +[msg.admin.user_projection.forbidden] +description = "This screen is only available to super_admin users." + [msg.admin.users] confirm_remove_org = "Do you want to remove this user from the organization?" export_error = "Failed to export users." +self_delete_blocked = "You cannot delete your own account." status_error = "Failed to update user status." [msg.admin.users.bulk] @@ -299,10 +411,10 @@ move_description = "Bulk move selected users to another tenant." move_error = "Error moving users." move_success = "{{count}} users moved successfully." parsed_count = "Parsed {{count}} rows." +permission_placeholder = "Select permission" schema_incompatible = "Fields not in target schema may be lost:" schema_missing = "Missing required fields for target tenant:" status_placeholder = "Select status" -permission_placeholder = "Select permission" update_success = "User info updated successfully." [msg.admin.users.create] @@ -362,6 +474,11 @@ name_required = "Name is required." [msg.admin.users.detail.security] password_hint = "Password Hint" +[msg.admin.users.global_custom_claims] +description = "Manage user claim definitions shared by all RPs and default read/write permissions." +empty = "No global claims are defined." +registry = "Only defined claim keys are available for global claim value management on user details." + [msg.admin.users.list] delete_confirm = "Are you sure you want to delete the selected user?" empty = "No users match the current filters." @@ -401,81 +518,6 @@ loading = "Loading audit logs..." registry_description = "Filter recent audit logs by search criteria and review action history quickly." subtitle = "Shows DevFront activity history within current tenant/app scope." -[msg.dev.request] -admin_desc = "A super admin can review developer access requests and approve or reject them." -approved = "Approved." -cancelled = "Approval cancelled." -empty = "No requests found." -need_cancel_notes = "Please enter a reason for cancelling the approval." -need_notes = "Please enter a rejection reason." -rejected = "Rejected." -user_desc = "Request developer access and check the review result." - -[msg.dev.request.modal] -desc = "Review the information below and enter a request reason to apply for developer access." -email = "Email" -name = "Name" -org = "Organization" -phone = "Phone" -reason = "Request Reason" -reason_placeholder = "Explain why you need developer access." -role = "Role" -pages_hint = "If you select All, Overview, Add linked app, and Audit Logs are all included." -title = "Developer Registration Request" - -[msg.dev.grants] -admin_notes_description = "Leaving a short note for the direct grant helps later reviews and revocations." -admin_notes_hint = "Revocations are handled from the list below." -admin_notes_placeholder = "e.g. Grant access after verifying the test environment" -approved = "Approved" -count = "Total {{count}}" -create_success = "Developer access has been granted directly." -description = "Directly grant developer access to users and revoke granted access." -empty = "There are no granted permissions." -forbidden = "Only super admin can directly grant developer access." -forbidden_desc = "This screen is available only to super admin." -form.description = "Select a user to view their current tenant, email, and phone, then grant developer access immediately." -list.description = "Current developer access grants." -load_error = "Failed to load developer access grants." -pages_hint = "If you select All, Overview, Add linked app, and Audit Logs are all included." -phone_missing = "No phone number is registered." -reason = "Grant reason" -revoke = "Revoke" -revoke_success = "Developer access has been revoked." -search_empty = "No users found." -search_loading = "Searching users..." -selected_info_description = "Review the selected user's tenant, email, and phone." -selected_user = "Selected user: {{user}}" -tenant_missing = "No tenant information is available for the selected user." -tenant_required = "The selected user's tenant information is unavailable." -user_required = "Select a user before granting access." -user_section_description = "Enter a search term to select a user. The next-step information stays empty until a user is chosen." - -[msg.dev.request.status] -approved = "Approved" -cancelled = "Approval Cancelled" -pending = "Pending" -rejected = "Rejected" - -[msg.dev.request.table] -actions = "Actions" -date = "Requested At" -org = "Organization" -reason = "Request Reason" -status = "Status" -user = "User" - -[msg.dev.request.list] -approved_count = "{{count}} users have been approved." -title = "Request History" - -[msg.dev.request.admin] -notes_placeholder = "Enter a reason for approval or rejection." - -[msg.dev.request.cancel] -approval = "Cancel Approval" -notes_placeholder = "Enter a reason for cancelling the approval." - [msg.dev.auth] access_denied_description = "DevFront is for administrators only. Request access from your administrator." access_denied_title = "Access denied." @@ -612,12 +654,6 @@ access_pending = "Your developer access request is under review." access_pending_detail = "You can use the overview and developer features after a super admin approves it." description = "Review RP composition and authentication operations in one place." -[msg.dev.dashboard.hero] -body = "Monitor RP readiness, consent activity, and operational status for the current developer workspace." -title_emphasis = "Title Emphasis" -title_prefix = "Title Prefix" -title_suffix = "Title Suffix" - [msg.dev.dashboard.chart] empty = "No RP usage aggregates are available." filter_description = "View the graph for all apps or only the selected apps." @@ -629,6 +665,17 @@ unavailable_with_reason = "RP usage statistics are unavailable. {{reason}}" [msg.dev.dashboard.distribution] description = "Quickly review application types and headless login usage." +[msg.dev.dashboard.hero] +body = "Monitor RP readiness, consent activity, and operational status for the current developer workspace." +title_emphasis = "Title Emphasis" +title_prefix = "Title Prefix" +title_suffix = "Title Suffix" + +[msg.dev.dashboard.notice] +consent_audit = "Consent Audit" +dev_scope = "Dev Scope" +hydra_health = "Hydra Health" + [msg.dev.dashboard.recent] empty = "Review the RPs this account can access." none = "No linked applications are available." @@ -637,11 +684,6 @@ none = "No linked applications are available." description = "Review trends for changed or deleted applications on the dashboard." empty = "There are no recent change logs yet." -[msg.dev.dashboard.notice] -consent_audit = "Consent Audit" -dev_scope = "Dev Scope" -hydra_health = "Hydra Health" - [msg.dev.forbidden] default = "You do not have permission to access this resource. Please contact an administrator." rp_admin = "RP administrators can only access resources for the apps they manage." @@ -652,6 +694,85 @@ user.audit = "Viewing audit logs for this App (RP) is only available when grante user.clients = "General user accounts can only use this feature if they have been granted operational or management relationships for the relevant RP (App). If you need access, please request it from an administrator." user.consents = "Viewing consent history for this App (RP) is only available when granted 'RP Admin', 'Consent View', or 'Consent Revoke' relationships. If you need access, please request it from an administrator." +[msg.dev.grants] +admin_notes_description = "Leaving a short note for the direct grant helps later reviews and revocations." +admin_notes_hint = "Revocations are handled from the list below." +admin_notes_placeholder = "e.g. Grant access after verifying the test environment" +approved = "Approved" +count = "Total {{count}}" +create_success = "Developer access has been granted directly." +description = "Directly grant developer access to users and revoke granted access." +empty = "There are no granted permissions." +forbidden = "Only super admin can directly grant developer access." +forbidden_desc = "This screen is available only to super admin." +load_error = "Failed to load developer access grants." +pages_hint = "If you select All, Overview, Add linked app, and Audit Logs are all included." +phone_missing = "No phone number is registered." +reason = "Grant reason" +revoke = "Revoke" +revoke_success = "Developer access has been revoked." +search_empty = "No users found." +search_loading = "Searching users..." +selected_info_description = "Review the selected user's tenant, email, and phone." +selected_user = "Selected user: {{user}}" +tenant_missing = "No tenant information is available for the selected user." +tenant_required = "The selected user's tenant information is unavailable." +user_required = "Select a user before granting access." +user_section_description = "Enter a search term to select a user. The next-step information stays empty until a user is chosen." + +[msg.dev.grants.form] +description = "Select a user to view their current tenant, email, and phone, then grant developer access immediately." + +[msg.dev.grants.list] +description = "Current developer access grants." + +[msg.dev.request] +admin_desc = "A super admin can review developer access requests and approve or reject them." +approved = "Approved." +cancelled = "Approval cancelled." +empty = "No requests found." +need_cancel_notes = "Please enter a reason for cancelling the approval." +need_notes = "Please enter a rejection reason." +rejected = "Rejected." +user_desc = "Request developer access and check the review result." + +[msg.dev.request.admin] +notes_placeholder = "Enter a reason for approval or rejection." + +[msg.dev.request.cancel] +approval = "Cancel Approval" +notes_placeholder = "Enter a reason for cancelling the approval." + +[msg.dev.request.list] +approved_count = "{{count}} users have been approved." +title = "Request History" + +[msg.dev.request.modal] +desc = "Review the information below and enter a request reason to apply for developer access." +email = "Email" +name = "Name" +org = "Organization" +pages_hint = "If you select All, Overview, Add linked app, and Audit Logs are all included." +phone = "Phone" +reason = "Request Reason" +reason_placeholder = "Explain why you need developer access." +role = "Role" +title = "Developer Registration Request" + +[msg.dev.request.status] +approved = "Approved" +cancelled = "Approval Cancelled" +pending = "Pending" +rejected = "Rejected" + +[msg.dev.request.table] +actions = "Actions" +date = "Requested At" +org = "Organization" +reason = "Request Reason" +status = "Status" +user = "User" + [msg.dev.sidebar] notice = "Developer Console" notice_detail = "Register and manage client applications." @@ -674,11 +795,14 @@ result = "Result: {{value}}" session_id = "Session ID: {{value}}" status = "Status: pending" +[msg.userfront.audit.filter] +description = "Toggle to view only active sessions." + [msg.userfront.consent] accept_error = "Failed to process consent: {{error}}" client_id = "Client ID: {{id}}" client_unknown = "Unknown application" -description = "The service below is requesting access to your account information.\\\\nPlease choose whether to continue." +description = "The service below is requesting access to your account information.\\\\\\\\nPlease choose whether to continue." load_error = "Failed to load consent information: {{error}}" missing_redirect = "Consent was processed, but the redirect URL was missing." redirect_notice = "After consent, you will be redirected automatically." @@ -700,15 +824,15 @@ approved_device = "Approved device: {{device}}" approved_ip = "Approved IP: {{ip}}" audit_empty = "No recent sign-in activity." audit_load_error = "Could not load sign-in history." -auto_login_supported = "You can sign in without an extra login when opening this linked app." auth_method = "Auth method: {{method}}" +auto_login_supported = "You can sign in without an extra login when opening this linked app." client_id = "Client ID: {{id}}" client_id_missing = "No client ID available." current_status = "Current status: {{status}}" last_auth = "Last signed in: {{value}}" -link_status = "Link status: {{status}}" link_missing = "This app does not have a launch URL configured." link_open_error = "Could not open the app link." +link_status = "Link status: {{status}}" render_error = "Dashboard render error: {{error}}" session_id_copied = "Session ID copied." @@ -717,6 +841,19 @@ empty = "No linked apps yet." empty_detail = "Linked apps and their latest activity will appear here." error = "Could not load linked apps." +[msg.userfront.dashboard.approved_session] +copy_click = "{{label}}: {{id}}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\nClick to copy." +copy_tap = "{{label}}: {{id}}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\nTap to copy." +none = "No {{label}}" + +[msg.userfront.dashboard.revoke] +confirm = "Disconnect {{app}}?\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\nYou will need to grant access again the next time you sign in." +error = "Could not disconnect the app: {{error}}" +success = "{{app}} has been disconnected." + +[msg.userfront.dashboard.scopes] +empty = "No scopes were requested." + [msg.userfront.dashboard.sessions] browser = "Browser: {{value}}" empty = "No active sessions." @@ -727,23 +864,10 @@ recent_app = "Recent app: {{app}}" session_id = "Session ID: {{id}}" [msg.userfront.dashboard.sessions.revoke] -confirm = "End the session for {{target}}?\nThat device will need to sign in again." +confirm = "End the session for {{target}}?\\nThat device will need to sign in again." error = "Could not end the session: {{error}}" success = "The session has been ended." -[msg.userfront.dashboard.approved_session] -copy_click = "{{label}}: {{id}}\\\\\\\\\\\\\\\\nClick to copy." -copy_tap = "{{label}}: {{id}}\\\\\\\\\\\\\\\\nTap to copy." -none = "No {{label}}" - -[msg.userfront.dashboard.revoke] -confirm = "Disconnect {{app}}?\\\\\\\\\\\\\\\\nYou will need to grant access again the next time you sign in." -error = "Could not disconnect the app: {{error}}" -success = "{{app}} has been disconnected." - -[msg.userfront.dashboard.scopes] -empty = "No scopes were requested." - [msg.userfront.dashboard.timeline] load_error = "Could not load sign-in history." @@ -757,6 +881,22 @@ title_generic = "An error occurred." title_with_code = "Error: {{code}}" type = "Error type: {{type}}" +[msg.userfront.error.ory] +$normalizedCode = "{{error}}" +access_denied = "The user denied the consent request." +consent_required = "Consent is required to continue." +interaction_required = "Additional interaction is required. Please try again." +invalid_client = "Client authentication failed." +invalid_grant = "The authorization grant is invalid or expired." +invalid_request = "The request is invalid." +invalid_scope = "The requested scope is invalid." +login_required = "Login is required." +request_forbidden = "The request was forbidden." +server_error = "An authentication server error occurred." +temporarily_unavailable = "The authentication server is temporarily unavailable." +unauthorized_client = "The client is not authorized for this request." +unsupported_response_type = "The response type is not supported." + [msg.userfront.error.tenant] account = "Account" account_unknown = "Unknown" @@ -773,24 +913,8 @@ tenant = "Tenant" tenant_unknown = "Unknown" title = "Access restriction details" -[msg.userfront.error.ory] -"$normalizedCode" = "{{error}}" -access_denied = "The user denied the consent request." -consent_required = "Consent is required to continue." -interaction_required = "Additional interaction is required. Please try again." -invalid_client = "Client authentication failed." -invalid_grant = "The authorization grant is invalid or expired." -invalid_request = "The request is invalid." -invalid_scope = "The requested scope is invalid." -login_required = "Login is required." -request_forbidden = "The request was forbidden." -server_error = "An authentication server error occurred." -temporarily_unavailable = "The authentication server is temporarily unavailable." -unauthorized_client = "The client is not authorized for this request." -unsupported_response_type = "The response type is not supported." - [msg.userfront.error.whitelist] -"$normalizedCode" = "{{error}}" +$normalizedCode = "{{error}}" bad_request = "Please check your input." invalid_session = "Your session has expired. Please sign in again." not_found = "The requested page could not be found." @@ -844,14 +968,14 @@ scan_hint = "Scan it with the mobile app." invalid = "Enter the 2 letters and 6 digits from your code." [msg.userfront.login.unregistered] -body = "We could not find an account for that information.\\\\\\\\\\\\\\\\nPlease sign up before continuing." +body = "We could not find an account for that information.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\nPlease sign up before continuing." [msg.userfront.login.verification] approved = "Approved. Complete sign-in in the original window." approved_local = "Approved. This device is already signed in, and the remote window will be signed in shortly." approved_remote = "Your requested sign-in is complete." -pending_remote = "Checking the sign-in approval request. Please wait." close_hint = "You can close this window now." +pending_remote = "Checking the sign-in approval request. Please wait." success = "Sign-in approval completed." [msg.userfront.login_success] @@ -1005,6 +1129,17 @@ key = "Non-existent key" [test] key = "Test" +[this] + +[this.key] + +[this.key.truly] + +[this.key.truly.does] + +[this.key.truly.does.not] +exist = "Fallback" + [ui] [ui.admin] @@ -1030,10 +1165,10 @@ title = "API Key Created" [ui.admin.api_keys.list] add = "Add" -edit_scopes = "Edit Scopes" -rotate_secret = "Rotate Secret" -rotate_secret_done = "Secret Rotated" -save_scopes = "Save Scopes" +edit_scopes = "Edit scopes" +rotate_secret = "Rotate secret" +rotate_secret_done = "Secret rotated" +save_scopes = "Save scopes" title = "API Key Management" [ui.admin.api_keys.list.breadcrumb] @@ -1050,6 +1185,11 @@ last_used = "LAST USED" name = "NAME" scopes = "SCOPES" +[ui.admin.apikeys] + +[ui.admin.apikeys.registry] +title = "API Key Registry" + [ui.admin.audit] export_csv = "Export CSV" load_more = "Load more" @@ -1095,6 +1235,32 @@ request = "REQUEST" status = "STATUS" time = "TIME" +[ui.admin.auth_guard] +subtitle = "Verify admin privileges and ReBAC relationships against the policy engine." +title = "Auth Guard" + +[ui.admin.auth_guard.checker] +allowed = "Access ALLOWED" +allowed_description = "The subject has access to the requested resource, including inherited permissions." +check = "Check permission" +checking = "Checking..." +denied = "Access DENIED" +denied_description = "The subject does not have access to the requested resource." +description = "Check in real time whether a subject has access to a resource through Ory Keto." +namespace = "Namespace" +namespace.label = "Namespace" +namespace.relying_party = "RelyingParty" +namespace.system = "System" +namespace.tenant = "Tenant" +namespace.tenant_group = "TenantGroup" +object_id = "Object ID" +object_id_placeholder = "Tenant UUID, etc." +relation = "Relation" +relation_placeholder = "view, manage, admins..." +subject = "Subject (User:ID)" +subject_placeholder = "User:uuid or Namespace:ID#Relation" +title = "ReBAC permission checker" + [ui.admin.groups] import_csv = "Import Csv" @@ -1143,18 +1309,95 @@ name = "NAME" plane = "Admin Plane" subtitle = "Manage tenants, policies, and operators" +[ui.admin.integrity] +fetch_error = "Unable to load the final integrity check result." +kicker = "System" +loading = "Loading data integrity report..." +subtitle = "Review integrity status and inspect checks across the admin data model." +tab_checks = "Integrity Checks" +tab_ory_ssot = "Ory SSOT System" +tab_user_projection = "User Projection" +title = "Data Integrity Check" + +[ui.admin.integrity.check] + +[ui.admin.integrity.check.duplicate_tenant_slugs] +title = "Duplicate tenant slug" + +[ui.admin.integrity.check.orphan_tenant_parents] +title = "Orphan tenant parents" + +[ui.admin.integrity.check.orphan_user_login_id_tenants] +title = "Orphan user login ID tenants" + +[ui.admin.integrity.check.orphan_user_login_id_users] +title = "Orphan user login ID users" + +[ui.admin.integrity.check.orphan_user_tenant_memberships] +title = "Orphan user tenant memberships" + +[ui.admin.integrity.forbidden] +title = "Access denied" + +[ui.admin.integrity.orphan_login_ids] +delete = "Delete selected" +title = "Orphan Login ID Cleanup" + +[ui.admin.integrity.read_model] +title = "Read model integrity" + +[ui.admin.integrity.reason] +deleted_tenant = "Deleted tenant" +deleted_user = "Deleted user" +missing_tenant = "Missing tenant" +missing_user = "Missing user" + +[ui.admin.integrity.recheck] +run = "Run again" +running = "Checking" + +[ui.admin.integrity.section] +tenant_integrity = "Tenant integrity" +user_integrity = "User integrity" + +[ui.admin.integrity.status] +fail = "Failed" +pass = "Passed" +warning = "Warning" + +[ui.admin.integrity.summary] +checked_at = "Checked at" +failures = "Failures" +failures_text = "Failures {{count}}" +passed = "Passed" +title = "Final integrity check" +total_checks = "Checks" + +[ui.admin.integrity.table] +field = "Field" +login_id = "Login ID" +reason = "Reason" +select = "Select" +select_item = "Select {{loginId}}" +tenant = "Tenant" +user = "User" + [ui.admin.nav] -org_chart = "Org Chart" api_keys = "API Keys" audit_logs = "Audit Logs" auth_guard = "Auth Guard" +data_integrity = "Data Integrity" logout = "Logout" +org_chart = "Org Chart" +ory_ssot = "Ory Ssot" overview = "Overview" +permissions_direct = "Permissions Direct" relying_parties = "Apps (RP)" tenant_dashboard = "Tenant Dashboard" tenants = "Tenants" user_groups = "User Groups" users = "Users" +worksmobile = "Worksmobile" [ui.admin.org] download_template = "Download Template" @@ -1162,10 +1405,46 @@ import_btn = "Import" import_title = "Bulk Organization Import" start_import = "Start Import" +[ui.admin.ory_ssot] +loading = "Loading Ory SSOT status..." +title = "Ory SSOT System" + +[ui.admin.ory_ssot.actions] +flush_identity_cache = "Redis cache flush" + +[ui.admin.ory_ssot.cache_card] +description = "Redis mirror/cache status for Kratos identity list and lookup operations." +title = "Redis identity cache" + +[ui.admin.ory_ssot.forbidden] +title = "Access denied" + +[ui.admin.ory_ssot.projection_card] +description = "PostgreSQL read model status used by admin search and statistics." +title = "Backend user read model" + +[ui.admin.ory_ssot.status] +failed = "failed" +not_ready = "not ready" +ready = "ready" + +[ui.admin.ory_ssot.summary] +cache_keys = "Cache keys" +last_refreshed = "Last refreshed" +last_synced = "Last read-model refresh" +local_users = "Local users" +observed_identities = "Observed identities" +status = "Status" +updated_at = "Updated at" + [ui.admin.overview] kicker = "Global Overview" title = "Tenant-independent control plane" +[ui.admin.overview.chart] +description = "Check the graph by all or selected organizations." +title = "Login request status by company and app" + [ui.admin.overview.playbook] title = "Admin playbook" @@ -1180,8 +1459,20 @@ view_audit_logs = "View Audit Logs" audit_events_24h = "24h Events" oidc_clients = "OIDC Clients" policy_gate = "Policy Gate" -total_users = "Total Users" total_tenants = "Total Tenants" +total_users = "Total Users" + +[ui.admin.permissions_direct] +allowed = "Allowed" +cat_dashboard = "Cat Dashboard" +cat_integrations = "Cat Integrations" +cat_resources = "Cat Resources" +cat_system = "Cat System" +dialog_title_system = "Dialog Title System" +no_user_selected = "No User Selected" +revoke_all = "Revoke All" +super_admin_only = "Super Admin Only" +user_list = "User List" [ui.admin.profile] manageable_tenants = "Manageable Tenants" @@ -1195,57 +1486,17 @@ user = "TENANT MEMBER" [ui.admin.tenants] add = "Add Tenant" csv_template = "Template" -data_mgmt = "Data Management" +data_mgmt = "temp" delete_selected = "Delete Selected" export_with_ids = "Include UUIDs" export_without_ids = "Export without UUIDs" import = "Import" +search_match_badge = "Search match" seed_badge = "Seed" -path.root = "Root" title = "Tenant Registry" +toggle_status = "temp" view_org_chart = "View Full Org Chart" -[ui.admin.tenants.view] -hierarchy = "Hierarchy" -list = "List" -table = "Table" -tree = "Tree" - -[ui.admin.tenants.scope] -active = "{{name}} descendants" -pick = "Select parent scope" - -[ui.admin.tenants.domain_conflict] -description = "" -title = "Domain conflict" - -[ui.admin.tenants.import_result] -message = "Message" -modified = "Modified:" -status = "Status" -title = "Import Result Report" - -[ui.admin.tenants.import_preview] -candidates = "Candidates" -confirm = "Run import" -create_new_reset = "Create new (reset ID/slug)" -csv_parents = "CSV Parents" -external_id = "External ID" -match = "Match" -no_candidates = "No candidates" -parent = "Parent" -parent_companies = "Parent Companies" -parent_company_groups = "Parent Company Groups" -parent_organizations = "Parent Organizations" -parent_unresolved = "Parent needs review" -slug_exists = "slug conflict" -title = "Confirm CSV import" -csv_parents = "CSV import" -parent = "Parent" -parent_companies = "Companies" -parent_company_groups = "Company groups" -parent_organizations = "Organizations" - [ui.admin.tenants.admins] add_button = "Add Button" already_admin = "Already Admin" @@ -1264,6 +1515,10 @@ title = "Tenant Admins" list = "List" section = "Tenants" +[ui.admin.tenants.bulk] +selected_count = "temp" +status_placeholder = "temp" + [ui.admin.tenants.create] title = "Tenant Add" @@ -1286,15 +1541,15 @@ slug_placeholder = "tenant-slug" status = "Status" type = "Type" +[ui.admin.tenants.create.memo] +title = "Policy Memo" + [ui.admin.tenants.create.parent_context] general = "General child tenant" hanmac = "Hanmac Family child tenant" pick_required = "Parent tenant selection required" root = "Top-level tenant" -[ui.admin.tenants.create.memo] -title = "Policy Memo" - [ui.admin.tenants.create.profile] title = "Tenant Profile" @@ -1306,81 +1561,22 @@ tab_federation = "Tab Federation" tab_organization = "Organization Manage" tab_permissions = "Permissions" tab_profile = "Profile" +tab_relations = "Tab Relations" tab_schema = "Tab Schema" tab_worksmobile = "Worksmobile" title = "Details" -[ui.admin.tenants.worksmobile] -compare = "Baron / Works Comparison" -compare_description = "Users show entries that exist only in Baron or only in WORKS by default." -compare_groups = "Organizations / Groups" -compare_users = "Users" -dry_run = "Backfill Dry-run" -forbidden = "You do not have permission to manage the Worksmobile integration." -initial_password_csv = "Initial Password CSV" -recent_jobs = "Recent Jobs" -refresh = "Refresh" -single_sync = "Single-item Sync" -single_sync_description = "Create an organization or user sync job using a Baron UUID." -subtitle = "Review Hanmac Family Directory sync status for organizations and users, and retry failed jobs." -sync_orgunit = "Organization Sync" -sync_user = "User Sync" -title = "Worksmobile Integration" - -[ui.admin.tenants.list] -search_placeholder = "Search tenant by name or slug..." -select_placeholder = "Select a tenant" - -[ui.admin.tenants.members] -descendants = "Descendant Members" -direct = "Direct Members" -direct_label = "Direct" -list_title = "Member Management" -title = "Tenant Members ({{count}})" -total = "Total" -total_label = "Total" - -[msg.admin.apikeys.registry] -count = "There are {{count}} active keys registered." - -[msg.admin.org] -import_partial_success = "Imported some organization data successfully." - -[msg.admin.tenants] -delete_bulk_confirm = "Delete {{count}} selected tenants?" -seed_delete_blocked = "Seed tenants cannot be deleted." - -[msg.admin.users] -self_delete_blocked = "You cannot delete your own account." - -[ui.admin.apikeys.registry] -title = "API Key Registry" - -[ui.admin.tenants.members] -add_existing = "Assign Existing Member" -create_new = "Create New Member" -delete_selected = "Delete Selected" -remove = "Exclude from Organization" -org_picker_title = "Select Organization" -view_org_chart = "View Full Org Chart" -direct_label = "Direct" -list_title = "Member Management" -title = "Tenant Members ({{count}})" -total = "Total" -total_label = "Total" -view_profile = "View Profile" - -[ui.admin.tenants.import_result] -message = "Message" -modified = "Modified:" -status = "Status" -title = "Import Result Report" +[ui.admin.tenants.domain_conflict] +description = "ui.admin.tenants.domain_conflict.description" +title = "Domain conflict" [ui.admin.tenants.import_preview] candidates = "Candidates" confirm = "Confirm Import" create_new = "Create New" +create_new_reset = "Create new (reset ID/slug)" csv_parents = "CSV Parents" +external_id = "External ID" fixed_id = "Fixed ID" match = "Matched Tenant" no_candidates = "No matching tenants found." @@ -1388,8 +1584,43 @@ parent = "Parent" parent_companies = "Parent Companies" parent_company_groups = "Parent Company Groups" parent_organizations = "Parent Organizations" +parent_unresolved = "Parent needs review" +slug_exists = "slug conflict" title = "Import Preview" +[ui.admin.tenants.import_result] +message = "Message" +modified = "Modified:" +status = "Status" +title = "Import Result Report" + +[ui.admin.tenants.list] +search_placeholder = "Search tenant by name or slug..." +select_placeholder = "Select a tenant" + +[ui.admin.tenants.members] +add_existing = "Assign Existing Member" +add_existing_description = "Select search results into an add queue, then assign them in one operation." +add_queued = "Add selected members" +create_new = "Create New Member" +delete_selected = "Delete Selected" +descendants = "Descendant Members" +direct = "Direct Members" +direct_label = "Direct" +export = "Selected organization users CSV" +list_title = "Member Management" +org_picker_title = "Select Organization" +queue_empty = "Select members to add." +queue_remove = "Remove from add queue" +remove = "Exclude from Organization" +search_min_length = "Enter at least two characters." +search_placeholder = "Search by name or email" +title = "Tenant Members ({{count}})" +total = "Total" +total_label = "Total" +view_org_chart = "View Full Org Chart" +view_profile = "View Profile" + [ui.admin.tenants.members.table] actions = "ACTIONS" email = "EMAIL" @@ -1408,6 +1639,15 @@ table_email = "Email" table_name = "Name" title = "Tenant Owners" +[ui.admin.tenants.parent] +company_only = "Companies and groups only" +local_search_placeholder = "Search tenant name or slug" +pick_tenant = "Pick tenant" +search_placeholder = "Search by name or slug" + +[ui.admin.tenants.path] +root = "Root" + [ui.admin.tenants.profile] allowed_domains = "Allowed Domains" allowed_domains_help = "Users with these email domains will be automatically assigned to this tenant." @@ -1429,15 +1669,15 @@ worksmobile_sync = "Worksmobile Sync Status" parent = "Parent Tenant (Optional)" parent_help = "Select a parent tenant if this is a subsidiary or sub-organization." -[ui.admin.tenants.parent] -company_only = "Companies and groups only" -search_placeholder = "Search by name or slug" -local_search_placeholder = "Search tenant name or slug" -pick_tenant = "Pick tenant" - [ui.admin.tenants.registry] title = "Tenant registry" +[ui.admin.tenants.relations] +add_button = "Add Button" +already_added = "Already Added" +dialog_title = "Dialog Title" +title = "Fine-grained Permissions Settings" + [ui.admin.tenants.schema] add_field = "Add Field" save = "Save Schema" @@ -1462,12 +1702,16 @@ type_text = "Text Value" unsigned = "Unsigned" validation_placeholder = "Regex Pattern (Optional)" +[ui.admin.tenants.scope] +active = "{{name}} descendants" +pick = "Select parent scope" + [ui.admin.tenants.sub] add = "Add" add_dialog_desc = "Select a tenant to add as a sub-tenant." add_dialog_title = "Add Sub-tenant" add_existing = "Add Existing Tenant" -export = "Subtree CSV" +export = "Export" manage = "Manage" no_candidates = "No available tenants to add." search_placeholder = "Search..." @@ -1483,6 +1727,7 @@ status = "STATUS" [ui.admin.tenants.table] actions = "ACTIONS" context = "Parent Path" +created = "CREATED" id = "ID" id_copy = "Copy ID" members = "Members" @@ -1494,27 +1739,79 @@ slug = "SLUG" status = "STATUS" type = "TYPE" updated = "UPDATED" -created = "CREATED" + +[ui.admin.tenants.view] +hierarchy = "Hierarchy" +list = "List" +table = "Table" +tree = "Tree" + +[ui.admin.tenants.worksmobile] +compare = "Baron / Works Comparison" +compare_description = "Users show entries that exist only in Baron or only in WORKS by default." +compare_groups = "Organizations / Groups" +compare_users = "Users" +dry_run = "Backfill Dry-run" +forbidden = "You do not have permission to manage the Worksmobile integration." +initial_password_csv = "Initial Password CSV" +recent_jobs = "Recent Jobs" +refresh = "Refresh" +single_sync = "Single-item Sync" +single_sync_description = "Create an organization or user sync job using a Baron UUID." +subtitle = "Review Hanmac Family Directory sync status for organizations and users, and retry failed jobs." +sync_orgunit = "Organization Sync" +sync_user = "User Sync" +title = "Worksmobile Integration" + +[ui.admin.user_projection] +loading = "Loading user projection data..." +subtitle = "Review and sync the Kratos user read model." +title = "User Projection Management" + +[ui.admin.user_projection.actions] +reconcile = "Re-sync" +reset = "Reset and rebuild" + +[ui.admin.user_projection.card] +description = "Current user read model state referenced by backend DB statistics." +title = "Kratos users projection" + +[ui.admin.user_projection.forbidden] +title = "Access denied" + +[ui.admin.user_projection.status] +failed = "failed" +not_ready = "not ready" +ready = "ready" + +[ui.admin.user_projection.summary] +last_synced = "Last synced" +projected_users = "Projected users" +status = "Status" +updated_at = "Updated at" [ui.admin.users] csv_template = "Download Template" +data_mgmt = "temp" [ui.admin.users.bulk] acknowledge_warning = "I acknowledge the warning and will proceed." create_missing_tenant = "Create new" do_move = "Execute Move" download_template = "Download Template" +modified_fields = "Modified Fields:" move_group = "Bulk Tenant Move" move_title = "Bulk User Move" +no_changes = "No changes" no_department = "No Department" +permission_placeholder = "Select permission" schema_warning = "Schema Compatibility Warning" select_group = "Select Target Tenant" selected_count = "{{count}} users selected" start_upload = "Start Upload" +status_placeholder = "Select status" tenant_resolution = "Tenant mapping" title = "Bulk Actions" -status_placeholder = "Select status" -permission_placeholder = "Select permission" [ui.admin.users.create] back = "Back" @@ -1547,9 +1844,9 @@ name = "Name" name_placeholder = "Name Placeholder" password = "Password" password_placeholder = "********" -picker_description = "Search and select a tenant." phone = "Phone number" phone_placeholder = "010-1234-5678" +picker_description = "Search and select a tenant." position = "Position" position_placeholder = "e.g. Senior" role = "Role" @@ -1815,6 +2112,16 @@ role = "Role" status = "Status" tenant = "Tenant" +[ui.admin.users.global_custom_claims] +description_placeholder = "Optional claim description" +label_placeholder = "Display name" +manage_definitions = "Manage Global Definitions" +read_permission = "Read permission" +registry = "Global Claim Registry" +title = "Global Claim Settings" +value_type = "Claim type" +write_permission = "Write permission" + [ui.admin.users.list] add = "User Add" bulk_import = "Bulk Import" @@ -1824,8 +2131,8 @@ fetch_error = "Failed to load the user list." search_placeholder = "Search Placeholder" status_select = "{{name}} status" subtitle = "Browse and manage registered users." -toggle_status = "{{name}} active status" title = "User Manage" +toggle_status = "{{name}} active status" [ui.admin.users.list.breadcrumb] list = "List" @@ -2331,15 +2638,7 @@ policy_toggle = "Policy toggle enabled" registry = "RP registry" rp_synced = "RP registry synced" -[ui.dev.dashboard.distribution] -headless_hint = "{{count}} with Headless Login enabled" -pkce = "PKCE" -private = "Server side App" -title = "Application Distribution" - [ui.dev.dashboard.chart] -x_axis = "X-axis: Period" -y_axis = "Y-axis: Login requests" aria = "RP request overview" filter_all = "All" period_day = "Day" @@ -2347,6 +2646,14 @@ period_month = "Month" period_week = "Week" series = "Login {{login}} / Users {{subjects}}" title = "Login requests by application" +x_axis = "X-axis: Period" +y_axis = "Y-axis: Login requests" + +[ui.dev.dashboard.distribution] +headless_hint = "{{count}} with Headless Login enabled" +pkce = "PKCE" +private = "Server side App" +title = "Application Distribution" [ui.dev.dashboard.next] subtitle = "Ship the RP controls" @@ -2398,19 +2705,75 @@ active_sessions = "Active sessions" auth_failures_24h = "24h auth failures" total_clients = "Total RPs" +[ui.dev.grants] +actions = "Actions" +admin_notes = "Grant Reason" +approved = "Approved" +date = "Granted At" +email = "Email" +grant = "Grant Directly" +input_section = "Input" +pages = "Access Pages" +phone = "Phone Number" +read_only = "Read Only" +reason = "Grant Reason" +revoke = "Revoke" +revoke_notes_placeholder = "Revoke note (optional)..." +selected_info = "Selected User Info" +status = "Status" +tenant = "Affiliation" +user = "User" +user_search_placeholder = "Search by name or email..." +user_section = "User Selection" + +[ui.dev.grants.form] +title = "Direct Grant" + +[ui.dev.grants.list] +title = "Granted Access" + [ui.dev.header] plane = "Dev Plane" subtitle = "Manage your applications" [ui.dev.nav] -overview = "Overview" clients = "Connected Application" -logout = "Logout" -developer_request = "Developer Access Request" developer_grants = "Developer Access Grants" +developer_request = "Developer Access Request" +logout = "Logout" +overview = "Overview" -[ui.dev.welcome] -btn_request = "New Request" +[ui.dev.profile] +error = "Failed to load profile." +loading = "Loading profile..." +menu_aria = "Open account menu" +menu_title = "Account" +subtitle = "View user details and assigned roles." +title = "My Profile" +unknown_email = "unknown@example.com" +unknown_name = "Unknown User" + +[ui.dev.profile.basic] +email = "Email" +id = "Account ID" +name = "Name" +phone = "Phone Number" +title = "User Info" + +[ui.dev.profile.org] +company_code = "Company Code" +tenant = "Tenant" +tenant_slug = "Tenant slug" +title = "Organization Info" + +[ui.dev.profile.role] +current = "Current Role" +description = "The permission level granted to this account." +title = "System Role" + +[ui.dev.profile.tab] +basic = "Basic Info" +role = "Roles & Permissions" [ui.dev.request] admin_notes_placeholder = "Enter a reason for approval or rejection." @@ -2442,66 +2805,11 @@ rejected = "Rejected" actions = "Actions" date = "Requested At" org = "Organization" +pages = "Access Pages" reason = "Request Reason" -pages = "Access Pages" status = "Status" user = "User" -[ui.dev.grants] -actions = "Actions" -admin_notes = "Grant Reason" -approved = "Approved" -date = "Granted At" -email = "Email" -form.title = "Direct Grant" -grant = "Grant Directly" -input_section = "Input" -list.title = "Granted Access" -pages = "Access Pages" -read_only = "Read Only" -reason = "Grant Reason" -revoke = "Revoke" -revoke_notes_placeholder = "Revoke note (optional)..." -selected_info = "Selected User Info" -status = "Status" -phone = "Phone Number" -tenant = "Affiliation" -user = "User" -user_search_placeholder = "Search by name or email..." -user_section = "User Selection" - -[ui.dev.profile] -error = "Failed to load profile." -loading = "Loading profile..." -menu_aria = "Open account menu" -menu_title = "Account" -subtitle = "View user details and assigned roles." -title = "My Profile" -unknown_email = "unknown@example.com" -unknown_name = "Unknown User" - -[ui.dev.profile.basic] -email = "Email" -id = "Account ID" -name = "Name" -phone = "Phone Number" -title = "User Info" - -[ui.dev.profile.org] -company_code = "Company Code" -tenant = "Tenant" -tenant_slug = "Tenant Slug" -title = "Organization Info" - -[ui.dev.profile.role] -current = "Current Role" -description = "The permission level granted to this account." -title = "System Role" - -[ui.dev.profile.tab] -basic = "Basic Info" -role = "Roles & Permissions" - [ui.dev.session] active = "Checking expiration..." auto_extend = "Session expiry controls" @@ -2519,6 +2827,34 @@ 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.welcome] +btn_request = "New Request" + +[ui.shell] + +[ui.shell.nav] +logout = "Logout" +profile = "My Profile" + +[ui.shell.profile] +menu_aria = "Open account menu" +menu_title = "Account" +unknown_email = "unknown@example.com" +unknown_name = "Unknown User" + +[ui.shell.session] +active = "Session active" +auto_extend = "Session expiry" +disabled = "Session expiry disabled" +expired = "Session expired" +expiring = "Expiring soon: {{minutes}}m {{seconds}}s left" +remaining = "Expires in {{minutes}}m {{seconds}}s" +unknown = "Unknown" + +[ui.shell.sidebar] +collapse = "Collapse sidebar" +expand = "Expand sidebar" + [ui.userfront] app_title = "Baron SW Portal" @@ -2529,6 +2865,10 @@ dev_console = "Dev Console" [ui.userfront.audit] +[ui.userfront.audit.filter] +title = "Manage My Activity" +toggle_label = "Show active sessions only" + [ui.userfront.audit.table] action = "Action" app = "App" @@ -2563,17 +2903,6 @@ status_history = "Link details" [ui.userfront.dashboard.activity] linked = "Linked" -[ui.userfront.dashboard.sessions] -active_badge = "Active" -current_badge = "Current" -current_disabled = "Current session" -unknown_device = "Unknown device" -unknown_session = "Session" - -[ui.userfront.dashboard.sessions.revoke] -action = "End session" -title = "End session" - [ui.userfront.dashboard.approved_session] default = "Default" userfront = "Approved UserFront session ID" @@ -2585,6 +2914,17 @@ title = "Disconnect app" [ui.userfront.dashboard.scopes] title = "Consent scopes" +[ui.userfront.dashboard.sessions] +active_badge = "Active" +current_badge = "Current" +current_disabled = "Current session" +unknown_device = "Unknown device" +unknown_session = "Session" + +[ui.userfront.dashboard.sessions.revoke] +action = "End session" +title = "End session" + [ui.userfront.dashboard.status] revoked = "Revoked" @@ -2647,36 +2987,13 @@ title = "Account not found" [ui.userfront.login.verification] action_label = "Done" -action_label_remote = "Go to sign-in window" action_label_close = "Close Window" +action_label_remote = "Go to sign-in window" page_title = "Baron SW Portal" title = "Approval complete" title_pending = "Checking approval" title_remote = "Sign-in Approved" -[ui.shell.nav] -logout = "Logout" -profile = "My Profile" - -[ui.shell.sidebar] -collapse = "Collapse sidebar" -expand = "Expand sidebar" - -[ui.shell.profile] -menu_aria = "Open account menu" -menu_title = "Account" -unknown_email = "unknown@example.com" -unknown_name = "Unknown User" - -[ui.shell.session] -active = "Session active" -auto_extend = "Session expiry" -disabled = "Session expiry disabled" -expired = "Session expired" -expiring = "Expiring soon: {{minutes}}m {{seconds}}s left" -remaining = "Expires in {{minutes}}m {{seconds}}s" -unknown = "Unknown" - [ui.userfront.login_success] later = "Do this later (go to dashboard)" qr = "Use QR approval" @@ -2785,311 +3102,3 @@ verify = "Verification" [ui.userfront.signup.success] action = "Go to sign-in" - - -[ui.userfront.audit.filter] -title = "Manage My Activity" -toggle_label = "Show active sessions only" - -[msg.userfront.audit.filter] -description = "Toggle to view only active sessions." - -[msg.admin.integrity] -subtitle = "Review integrity status and inspect checks across the admin data model." - -[msg.admin.integrity.section.tenant_integrity] -description = "Check duplicate tenant slugs and invalid parent relationships." - -[msg.admin.integrity.section.user_integrity] -description = "Check orphan records in user and login ID references." - -[msg.admin.integrity.forbidden] -description = "This screen is available only to super_admin." - -[msg.admin.integrity.orphan_login_ids] -delete_confirm = "Delete {{count}} selected orphan login IDs?" -delete_success = "Deleted {{count}} orphan login IDs." -description = "Review login IDs that reference deleted or missing users/tenants, then delete selected rows." -empty = "No orphan login IDs to delete." -load_error = "Failed to load orphan login ID targets." - -[msg.admin.integrity.read_model] -description = "Checks anomalies in the backend DB read model without overwriting the Ory SoT." - -[msg.admin.integrity.recheck] -error = "Check failed." -running = "Running integrity check." -success = "Check completed." - -[msg.admin.integrity.report] -load_error = "Failed to load the integrity report." - -[msg.admin.integrity.check.duplicate_tenant_slugs] -description = "Checks duplicate active tenant slugs using LOWER(TRIM(slug))." - -[msg.admin.integrity.check.orphan_tenant_parents] -description = "Checks whether tenants.parent_id points to a missing or soft-deleted tenant." - -[msg.admin.integrity.check.orphan_user_login_id_tenants] -description = "Checks whether user_login_ids.tenant_id points to a missing or soft-deleted tenant." - -[msg.admin.integrity.check.orphan_user_login_id_users] -description = "Checks whether user_login_ids.user_id points to a missing or soft-deleted user." - -[msg.admin.integrity.check.orphan_user_tenant_memberships] -description = "Checks whether users.tenant_id points to a missing or soft-deleted tenant." - -[msg.admin.user_projection] -action_error = "Projection operation failed." -action_success = "Refreshed the projection for {{count}} users." -forbidden_description = "This screen is only available to super_admin users." -load_error = "Failed to load projection status." -reset_confirm = "Rebuild user projection from the Kratos source of truth?" -subtitle = "Review and sync the Kratos user read model." - -[msg.admin.user_projection.forbidden] -description = "This screen is only available to super_admin users." - -[ui.admin.integrity] -tab_checks = "Integrity Checks" -tab_user_projection = "User Projection" -fetch_error = "Unable to load the final integrity check result." -kicker = "System" -loading = "Loading data integrity report..." -subtitle = "Review integrity status and inspect checks across the admin data model." -title = "Data Integrity Check" - -[ui.admin.integrity.forbidden] -title = "Access denied" - -[ui.admin.integrity.orphan_login_ids] -delete = "Delete selected" -title = "Orphan Login ID Cleanup" - -[ui.admin.integrity.read_model] -title = "Read model integrity" - -[ui.admin.integrity.reason] -deleted_tenant = "Deleted tenant" -deleted_user = "Deleted user" -missing_tenant = "Missing tenant" -missing_user = "Missing user" - -[ui.admin.integrity.recheck] -run = "Run again" -running = "Checking" - -[ui.admin.integrity.status] -fail = "Failed" -pass = "Passed" -warning = "Warning" - -[ui.admin.integrity.summary] -checked_at = "Checked at" -failures = "Failures" -failures_text = "Failures {{count}}" -passed = "Passed" -title = "Final integrity check" -total_checks = "Checks" - -[ui.admin.integrity.table] -field = "Field" -login_id = "Login ID" -reason = "Reason" -select = "Select" -select_item = "Select {{loginId}}" -tenant = "Tenant" -user = "User" - -[ui.admin.integrity.section] -tenant_integrity = "Tenant integrity" -user_integrity = "User integrity" - -[ui.admin.integrity.check.duplicate_tenant_slugs] -title = "Duplicate tenant slug" - -[ui.admin.integrity.check.orphan_tenant_parents] -title = "Orphan tenant parents" - -[ui.admin.integrity.check.orphan_user_login_id_tenants] -title = "Orphan user login ID tenants" - -[ui.admin.integrity.check.orphan_user_login_id_users] -title = "Orphan user login ID users" - -[ui.admin.integrity.check.orphan_user_tenant_memberships] -title = "Orphan user tenant memberships" - -[msg.admin.api_keys.list] -edit_scopes_desc = "Edit the scopes granted to this API key." -rotate_confirm = "Rotate the secret for this API key?" -rotate_secret_notice = "The new secret is shown only once." - -[msg.admin.tenants] -export_error = "Failed to export tenants." - -[ui.admin.api_keys.list] -edit_scopes = "Edit scopes" -rotate_secret = "Rotate secret" -rotate_secret_done = "Secret rotated" -save_scopes = "Save scopes" - -[ui.admin.user_projection] -loading = "Loading user projection data..." -subtitle = "Review and sync the Kratos user read model." -title = "User Projection Management" - -[ui.admin.user_projection.actions] -reconcile = "Re-sync" -reset = "Reset and rebuild" - -[ui.admin.user_projection.card] -description = "Current user read model state referenced by backend DB statistics." -title = "Kratos users projection" - -[ui.admin.user_projection.forbidden] -title = "Access denied" - -[ui.admin.user_projection.status] -failed = "failed" -not_ready = "not ready" -ready = "ready" - -[ui.admin.user_projection.summary] -last_synced = "Last synced" -projected_users = "Projected users" -status = "Status" -updated_at = "Updated at" - -[ui.admin.auth_guard] -subtitle = "Verify admin privileges and ReBAC relationships against the policy engine." -title = "Auth Guard" - -[ui.admin.auth_guard.checker] -check = "Check permission" -checking = "Checking..." -denied = "Access DENIED" -denied_description = "The subject does not have access to the requested resource." -description = "Check in real time whether a subject has access to a resource through Ory Keto." -object_id = "Object ID" -object_id_placeholder = "Tenant UUID, etc." -allowed = "Access ALLOWED" -allowed_description = "The subject has access to the requested resource, including inherited permissions." -namespace = "Namespace" -relation = "Relation" -relation_placeholder = "view, manage, admins..." -subject = "Subject (User:ID)" -subject_placeholder = "User:uuid or Namespace:ID#Relation" -title = "ReBAC permission checker" - -[ui.admin.auth_guard.checker.namespace] -label = "Namespace" -relying_party = "RelyingParty" -system = "System" -tenant = "Tenant" -tenant_group = "TenantGroup" - -[ui.admin.overview.summary] -total_users = "Total Users" - -[ui.admin.overview.chart] -description = "Check the graph by all or selected organizations." -title = "Login request status by company and app" - -[ui.admin.tenants.sub] -export = "Export" - -[ui.admin.users.bulk] -modified_fields = "Modified Fields:" -no_changes = "No changes" -permission_placeholder = "Select permission" -status_placeholder = "Select status" - -[ui.dev.profile.org] -tenant_slug = "Tenant slug" - -[] -"msg.admin.tenants.bulk.update_error" = "temp" -"msg.admin.tenants.bulk.update_success" = "temp" -"msg.admin.tenants.status_error" = "temp" -"ui.admin.tenants.bulk.selected_count" = "temp" -"ui.admin.tenants.bulk.status_placeholder" = "temp" -"ui.admin.tenants.data_mgmt" = "temp" -"ui.admin.tenants.toggle_status" = "temp" -"ui.admin.users.data_mgmt" = "temp" - -[msg.admin.ory_ssot] -flush_confirm = "Flush only Redis identity cache keys?" -flush_error = "Redis identity cache flush failed." -flush_success = "Flushed {{count}} Redis identity cache keys." -load_error = "Failed to load Ory SSOT system status." -subtitle = "Review Kratos source-of-truth and Redis identity cache status separately." - -[msg.admin.ory_ssot.forbidden] -description = "This screen is only available to super_admin users." - -[msg.admin.tenants.members] -add_error = "Failed to add members" -add_success = "Added {{count}} members." - -[msg.admin.users.global_custom_claims] -description = "Manage user claim definitions shared by all RPs and default read/write permissions." -empty = "No global claims are defined." -registry = "Only defined claim keys are available for global claim value management on user details." - -[ui.admin.integrity] -tab_ory_ssot = "Ory SSOT System" - -[ui.admin.ory_ssot] -loading = "Loading Ory SSOT status..." -title = "Ory SSOT System" - -[ui.admin.ory_ssot.actions] -flush_identity_cache = "Redis cache flush" - -[ui.admin.ory_ssot.cache_card] -description = "Redis mirror/cache status for Kratos identity list and lookup operations." -title = "Redis identity cache" - -[ui.admin.ory_ssot.forbidden] -title = "Access denied" - -[ui.admin.ory_ssot.projection_card] -description = "PostgreSQL read model status used by admin search and statistics." -title = "Backend user read model" - -[ui.admin.ory_ssot.status] -failed = "failed" -not_ready = "not ready" -ready = "ready" - -[ui.admin.ory_ssot.summary] -cache_keys = "Cache keys" -last_refreshed = "Last refreshed" -last_synced = "Last read-model refresh" -local_users = "Local users" -observed_identities = "Observed identities" -status = "Status" -updated_at = "Updated at" - -[ui.admin.tenants] -search_match_badge = "Search match" - -[ui.admin.tenants.members] -add_existing_description = "Select search results into an add queue, then assign them in one operation." -add_queued = "Add selected members" -export = "Selected organization users CSV" -queue_empty = "Select members to add." -queue_remove = "Remove from add queue" -search_min_length = "Enter at least two characters." -search_placeholder = "Search by name or email" - -[ui.admin.users.global_custom_claims] -description_placeholder = "Optional claim description" -label_placeholder = "Display name" -manage_definitions = "Manage Global Definitions" -read_permission = "Read permission" -registry = "Global Claim Registry" -title = "Global Claim Settings" -value_type = "Claim type" -write_permission = "Write permission" diff --git a/locales/ko.toml b/locales/ko.toml index 676f7743..0ee26103 100644 --- a/locales/ko.toml +++ b/locales/ko.toml @@ -74,563 +74,7 @@ scope_admin = "Scoped to /admin" session_ttl = "Session TTL: 15m admin" tenant_headers = "Tenant-aware headers" -[msg.admin.common] -forbidden = "์ด ์์ ์ ์ํํ ๊ถํ์ด ์์ต๋๋ค." - -[msg.admin.audit] -empty = "์์ง ์์ง๋ ๊ฐ์ฌ ๋ก๊ทธ๊ฐ ์์ต๋๋ค." -end = "๊ฐ์ฌ ๋ก๊ทธ์ ๋ง์ง๋ง์ ๋๋ค." -load_error = "๊ฐ์ฌ ๋ก๊ทธ๋ฅผ ๋ถ๋ฌ์ค์ง ๋ชปํ์ต๋๋ค: {{error}}" -loading = "๊ฐ์ฌ ๋ก๊ทธ๋ฅผ ๋ถ๋ฌ์ค๋ ์ค..." -subtitle = "Command ์์ฒญ ๊ธฐ๋ฐ ClickHouse ๋ก๊ทธ๋ฅผ ์กฐํํฉ๋๋ค. ์ฌ์ฉ์/ํ ๋ํธ๋ ์ถํ ์ธ์ ์ฐ๋ ์ ์๋ ์ฑ์์ง๋๋ค." - -[msg.admin.header] -subtitle = "Tenant isolation & least privilege by default" - -[msg.admin.notice] -idp_policy = "IDP ๊ด๋ฆฌ ํค๋ ์๋ฒ ๋ด๋ถ ๋ํ API๋ก๋ง ์ฌ์ฉํ๋ฉฐ, ๊ฐ์ฌยท๋ ์ดํธ๋ฆฌ๋ฐ์ ๊ธฐ๋ณธ ์ ์ฉํฉ๋๋ค." -scope = "๊ด๋ฆฌ ๊ธฐ๋ฅ์ /admin ๋ค์์คํ์ด์ค์์๋ง ๋ ธ์ถํฉ๋๋ค." - -[msg.admin.org] -hover_member_info = "๋ง์ฐ์ค๋ฅผ ์ฌ๋ฆฌ๋ฉด ์์ธ ์ ๋ณด๋ฅผ ํ์ธํ ์ ์์ต๋๋ค." -import_description = "CSV ํ์ผ์ ์ ๋ก๋ํ์ฌ ์กฐ์ง๋๋ฅผ ์ผ๊ด ๋ฑ๋กํฉ๋๋ค." -import_error = "์กฐ์ง๋ ์ํฌํธ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค." -import_success = "์กฐ์ง๋๊ฐ ์ฑ๊ณต์ ์ผ๋ก ์ํฌํธ๋์์ต๋๋ค." - -[msg.admin.overview] -description = "๋ชจ๋ ํ ๋ํธ ๊ณตํต ์งํ์ ์ ์ฑ ์ํ๋ฅผ ํ ๊ณณ์์ ํ์ธํฉ๋๋ค." -idp_fallback = "Fallback: Descope" -idp_primary = "IDP: Ory primary" - -[msg.admin.tenants] -approve_confirm = "์ด ํ ๋ํธ๋ฅผ ์น์ธํ์๊ฒ ์ต๋๊น?" -approve_success = "ํ ๋ํธ๊ฐ ์น์ธ๋์์ต๋๋ค." -delete_confirm = "ํ ๋ํธ \"{{name}}\"๋ฅผ ์ญ์ ํ ๊น์?" -delete_success = "ํ ๋ํธ๊ฐ ์ญ์ ๋์์ต๋๋ค." -empty = "์์ง ๋ฑ๋ก๋ ํ ๋ํธ๊ฐ ์์ต๋๋ค." -fetch_error = "ํ ๋ํธ ๋ชฉ๋ก ์กฐํ์ ์คํจํ์ต๋๋ค." -import_empty = "๊ฐ์ ธ์ฌ ํ ๋ํธ ํ์ด ์์ต๋๋ค." -import_error = "ํ ๋ํธ ๊ฐ์ ธ์ค๊ธฐ์ ์คํจํ์ต๋๋ค." -import_result = "์์ฑ {{created}}, ๊ฐฑ์ {{updated}}, ์คํจ {{failed}}" -missing_id = "ํ ๋ํธ ID๊ฐ ์์ต๋๋ค." -not_found = "ํ ๋ํธ๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค." -remove_sub_confirm = "ํ ๋ํธ \"{{name}}\"์(๋ฅผ) ํ์ ์กฐ์ง์์ ์ ์ธํ ๊น์?" -subtitle = "ํ์ฌ ๋ฑ๋ก๋ ํ ๋ํธ๋ฅผ ํ์ธํ๊ณ ์ํ๋ฅผ ๊ด๋ฆฌํฉ๋๋ค." - -[msg.admin.tenants.import_preview] -description = "tenant_id๊ฐ ์๋ ํ์ ๊ธฐ์กด ํ ๋ํธ ํ๋ณด์ ๋น๊ตํ ๋ค ์ ๊ท ์์ฑ ๋๋ ๊ธฐ์กด ํ ๋ํธ ๊ฐฑ์ ์ผ๋ก ์ฒ๋ฆฌํฉ๋๋ค." - -[msg.admin.tenants.parent] -local_picker_description = "ํ ๋ํธ ๋ชฉ๋ก์์ ์์ ํ ๋ํธ๋ก ์ฌ์ฉํ ํญ๋ชฉ์ ์ ํํฉ๋๋ค." -local_picker_empty = "์ ํํ ์ ์๋ ํ ๋ํธ๊ฐ ์์ต๋๋ค." -picker_description = "org-chart์์ ํ ๋ํธ๋ฅผ ์ ํํ๋ฉด ์์ ํ ๋ํธ์ ๋ฐ์๋ฉ๋๋ค." - -[msg.admin.tenants.scope] -description = "์์ ํ ๋ํธ๋ฅผ ์ ํํ๋ฉด ํด๋น ํ์ ํ ๋ํธ๋ง ๋ชฉ๋ก์ ํ์ํฉ๋๋ค." - -[msg.dev.auth] -access_denied_description = "DevFront๋ ๊ด๋ฆฌ์ ์ ์ฉ ํ๋ฉด์ ๋๋ค. ๊ถํ์ด ํ์ํ๋ฉด ๊ด๋ฆฌ์์๊ฒ ์์ฒญํด ์ฃผ์ธ์." -access_denied_title = "์ ๊ทผ ๊ถํ์ด ์์ต๋๋ค." - -[msg.dev.forbidden] -default = "ํด๋น ๋ฆฌ์์ค์ ์ ๊ทผํ ๊ถํ์ด ์์ต๋๋ค. ๊ด๋ฆฌ์์๊ฒ ๋ฌธ์ํ์ธ์." -rp_admin = "RP ๊ด๋ฆฌ์๋ ๋ด๋น ์ฑ์ ๋ฆฌ์์ค๋ง ์กฐํํ ์ ์์ต๋๋ค." -tenant_admin = "ํ ๋ํธ ๊ด๋ฆฌ์ ๊ถํ์ด ์ฌ๋ฐ๋ฅด๊ฒ ์ค์ ๋์ง ์์๊ฑฐ๋ ๋ง๋ฃ๋์์ต๋๋ค." -user = "์ผ๋ฐ ์ฌ์ฉ์๋ ๊ด๋ฆฌ์ ํ๋ฉด์ ์ ๊ทผํ ์ ์์ต๋๋ค." -title = "{{resource}} ์ ๊ทผ ๊ถํ ์์" - -[msg.dev.audit] -access_denied = "๊ฐ์ฌ ๋ก๊ทธ๋ ๊ฐ๋ฐ์ ๊ถํ์ด ์์ด์ผ ๋ณผ ์ ์์ต๋๋ค." -access_denied_detail = "๊ฐ๋ฐ์ ๊ถํ ์ ์ฒญ ํ์ด์ง์์ ์ ์ฒญ์ ๋ฑ๋กํ ๋ค ์น์ธ์ ๋ฐ์์ฃผ์ธ์." -empty = "์กฐํ๋ ๊ฐ์ฌ ๋ก๊ทธ๊ฐ ์์ต๋๋ค." -forbidden = "๊ฐ์ฌ ๋ก๊ทธ๋ฅผ ์กฐํํ ๊ถํ์ด ์์ต๋๋ค. ๊ด๋ฆฌ์์๊ฒ ๊ถํ์ ์์ฒญํด์ฃผ์ธ์." -load_error = "๊ฐ์ฌ ๋ก๊ทธ ์กฐํ ์คํจ: {{error}}" -loaded_count = "๋ก๋๋ ๋ก๊ทธ {{count}}๊ฑด" -loading = "๊ฐ์ฌ ๋ก๊ทธ๋ฅผ ๋ถ๋ฌ์ค๋ ์ค..." -registry_description = "์ต๊ทผ ๊ฐ์ฌ ๋ก๊ทธ๋ฅผ ๊ฒ์ ์กฐ๊ฑด์ ๋ง์ถฐ ํํฐ๋งํ๊ณ , ์์ ์ด๋ ฅ์ ๋น ๋ฅด๊ฒ ํ์ธํฉ๋๋ค." -subtitle = "ํ์ฌ ํ ๋ํธ/์ฑ ๋ฒ์์ DevFront ์์ ์ด๋ ฅ์ ์กฐํํฉ๋๋ค." - -[msg.dev.clients] -deleted = "์ฑ์ด ์ญ์ ๋์์ต๋๋ค." -delete_confirm = "์ ๋ง๋ก ์ด ์ฑ์ ์ญ์ ํ์๊ฒ ์ต๋๊น? ์ด ์์ ์ ๋๋๋ฆด ์ ์์ต๋๋ค." -delete_error = "์ญ์ ์คํจ: {{error}}" -load_error = "์ฑ ์ ๋ณด๋ฅผ ๋ถ๋ฌ์ค์ง ๋ชปํ์ต๋๋ค: {{error}}" -loading = "์ฑ ์ ๋ณด๋ฅผ ๋ถ๋ฌ์ค๋ ์ค..." -showing = "์ ์ฒด {{total}}๊ฐ ์ค {{shown}}๊ฐ๋ฅผ ํ์ํ๋ ์ค์ ๋๋ค." - -[msg.dev.sidebar] -notice = "๊ฐ๋ฐ์ ์ ์ฉ ์ฝ์์ ๋๋ค." -notice_detail = "์ฐ๋ ์ฑ ๋ฑ๋ก ๋ฐ ๊ด๋ฆฌ๋ฅผ ์ํํ ์ ์์ต๋๋ค." - -[msg.dev.clients.general.public_key] -auth_method_client_secret_basic_help = "์ผ๋ฐ์ ์ธ ์๋ฒ ์ฌ์ด๋ ์ฑ ์ธ์ฆ ๋ฐฉ์์ ๋๋ค." -auth_method_none_help = "PKCE ๊ธฐ๋ฐ public client์ ์ฌ์ฉํ๋ ๋ฐฉ์์ ๋๋ค." -auth_method_private_key_jwt_help = "Trusted RP bootstrap๊ณผ JAR ๊ฒ์ฆ์ ํ์ํ ์๋ช ํค ๊ธฐ๋ฐ ์ธ์ฆ ๋ฐฉ์์ ๋๋ค." -guide_example = "๊ถ์ฅ ์์: https://rp.example.com/.well-known/jwks.json" -guide_intro = "JWKS URI๋ Baron์ด ๋ง๋๋ ๊ฐ์ด ์๋๋ผ RP backend๊ฐ ๊ณต๊ฐํค๋ฅผ ๋ ธ์ถํ๋ URL์ ๋๋ค." -guide_step_1 = "RP ์๋ฒ์์ key pair๋ฅผ ์์ฑํ๊ณ private key๋ RP backend์๋ง ๋ณด๊ดํฉ๋๋ค." -guide_step_2 = "RP backend๊ฐ public key๋ฅผ JWKS(JSON Web Key Set) ํํ๋ก ์ ๊ณตํ๋ endpoint๋ฅผ ์ค๋นํฉ๋๋ค." -guide_step_3 = "์: https://rp.example.com/.well-known/jwks.json ๊ฐ์ URL์ DevFront์ ์ ๋ ฅํฉ๋๋ค." -headless_help = "์ ํ๋ฆฌ์ผ์ด์ ๊ณ ์ ์ ๋์์ธ์ผ๋ก ๋ก๊ทธ์ธ ํ๋ฉด์ ๊ตฌ์ฑํ ์ ์์ต๋๋ค. ์ค์ ์์ด๋/๋น๋ฐ๋ฒํธ ํ์ธ ๋ฐ ๋ณด์ ๊ฒ์ฆ ๋ก์ง์ Baron API๋ฅผ ํตํด ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์ฒ๋ฆฌ๋ฉ๋๋ค." -jwks_inline_help = "SSH-RSA ๊ณต๊ฐํค ํ์์ ์ฐ์ ๊ถ์ฅํฉ๋๋ค. 'ssh-rsa AAA...' ํ์์ผ๋ก ์ ๋ ฅํ๋ฉด Baron์ด OIDC ํ์ค์ธ JWKS(JSON)๋ก ์๋ ๋ณํํ์ฌ ์ ์ฅํฉ๋๋ค." -jwks_uri_help = "RP backend๊ฐ ์ ๊ณตํ๋ ๊ณต๊ฐํค endpoint URL์ ์ ๋ ฅํ์ธ์. ์: https://rp.example.com/.well-known/jwks.json" -request_object_alg_help = "Headless Login์ ์ฌ์ฉํ ๋ JAR(Request Object) ์๋ช ์๊ณ ๋ฆฌ์ฆ์ ๋ช ์ํฉ๋๋ค." -source_help = "์ ํ๋ฆฌ์ผ์ด์ ์ ๊ณต๊ฐํค(SSH-RSA)๋ฅผ ์ง์ ๋ฑ๋กํ๊ฑฐ๋, ์ด์ ํ๊ฒฝ์ด๋ผ๋ฉด JWKS URI๋ฅผ ํตํด ์๋์ผ๋ก ๊ฒ์ฆํ ์ ์์ต๋๋ค." -subtitle = "Trusted RP ํ์ ์ ํ์ํ ๊ณต๊ฐํค์ headless login ๊ด๋ จ ์ค์ ์ ๊ด๋ฆฌํฉ๋๋ค." - -[msg.dev.clients.general.public_key.validation] -headless_requires_alg = "Headless Login์ ์ฌ์ฉํ๋ ค๋ฉด Request Object Signing Algorithm์ ์ ๋ ฅํด์ผ ํฉ๋๋ค." -headless_requires_private_key_jwt = "Headless Login์ ์ฌ์ฉํ๋ ค๋ฉด token endpoint auth method๊ฐ private_key_jwt์ฌ์ผ ํฉ๋๋ค." -headless_requires_public_key = "Headless Login์ ์ฌ์ฉํ๋ ค๋ฉด JWKS URI๊ฐ ํ์ํฉ๋๋ค." -invalid_jwks_inline = "์ ๋ ฅ๊ฐ์ด ์ ํจํ JSON(JWKS) ํ์์ด ์๋๋๋ค. SSH-RSA์ ๊ฒฝ์ฐ 'ssh-rsa'๋ก ์์ํด์ผ ํฉ๋๋ค." -invalid_jwks_uri = "JWKS URI ํ์์ด ์ฌ๋ฐ๋ฅด์ง ์์ต๋๋ค." -missing_jwks_inline = "๊ณต๊ฐํค(SSH-RSA ๋๋ JWKS)๋ฅผ ์ ๋ ฅํด์ผ ํฉ๋๋ค." -missing_jwks_uri = "JWKS URI๋ฅผ ์ ๋ ฅํด์ผ ํฉ๋๋ค." -private_key_jwt_requires_public_key = "์๋ช ํค ๊ธฐ๋ฐ ์ธ์ฆ์ ์ฌ์ฉํ๋ ค๋ฉด JWKS URI๊ฐ ํ์ํฉ๋๋ค." - -[msg.userfront.audit] -browser = "๋ธ๋ผ์ฐ์ : {{value}}" -date = "์ ์์ผ์: {{value}}" -device = "์ ์ํ๊ฒฝ: {{value}}" -end = "๋ ์ด์ ํญ๋ชฉ์ด ์์ต๋๋ค." -filtered_empty = "ํ์ฑ ์ธ์ ์ผ๋ก ํํฐ๋ง๋ ์ ์ ์ด๋ ฅ์ด ์์ต๋๋ค." -ip = "์ ์ IP: {{value}}" -load_more_error = "๋ ๋ถ๋ฌ์ค์ง ๋ชปํ์ต๋๋ค." -result = "์ธ์ฆ๊ฒฐ๊ณผ: {{value}}" -session_id = "Session ID: {{value}}" -status = "ํํฉ: (์ค๋น์ค)" - -[msg.userfront.dashboard] -approved_device = "์น์ธ ๊ธฐ๊ธฐ: {{device}}" -approved_ip = "์น์ธ IP: {{ip}}" -audit_empty = "์ต๊ทผ ์ ์ ์ด๋ ฅ์ด ์์ต๋๋ค." -audit_load_error = "์ ์์ด๋ ฅ์ ๋ถ๋ฌ์ค์ง ๋ชปํ์ต๋๋ค." -auth_method = "์ธ์ฆ์๋จ: {{method}}" -client_id = "Client ID: {{id}}" -client_id_missing = "Client ID ์์" -current_status = "ํ์ฌ ์ํ: {{status}}" -last_auth = "์ต๊ทผ ์ธ์ฆ: {{value}}" -link_status = "์ฐ๋ ์ํ: {{status}}" -link_missing = "์ด๋ํ ํ์ด์ง ์ฃผ์(Client URI)๊ฐ ์ค์ ๋์ง ์์์ต๋๋ค." -link_open_error = "ํด๋น ๋งํฌ๋ฅผ ์ด ์ ์์ต๋๋ค." -render_error = "๋์๋ณด๋ ๋ ๋๋ง ์ค๋ฅ: {{error}}" -session_id_copied = "์ธ์ ID๊ฐ ๋ณต์ฌ๋์์ต๋๋ค." - -[msg.userfront.error] -detail_contact = "๊ด๋ฆฌ์์๊ฒ ๋ฌธ์ํด ์ฃผ์ธ์." -detail_generic = "์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค." -detail_request = "์์ฒญ์ ์ฒ๋ฆฌํ๋ ์ค ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค." -id = "์ค๋ฅ ID: {{id}}" -title = "์ธ์ฆ ๊ณผ์ ์์ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค" -title_generic = "์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค" -title_with_code = "์ค๋ฅ: {{code}}" -type = "์ค๋ฅ ์ข ๋ฅ: {{type}}" - -[msg.userfront.error.tenant] -account = "๊ณ์ " -account_unknown = "์ ์ ์์" -affiliated_tenants = "์ ์ฒด ์์ ํ ๋ํธ" -allowed_box_title = "์ ์ ๊ฐ๋ฅ ํ ๋ํธ" -allowed_tenants = "์ ์ ๊ฐ๋ฅ ํ ๋ํธ" -detail = "ํ์ฌ ๋ก๊ทธ์ธ๋ ๊ณ์ ์ ์ด ์ ํ๋ฆฌ์ผ์ด์ ์ ์ ๊ทผํ ์ ์์ต๋๋ค." -load_failed = "๊ณ์ ์ ๋ณด๋ฅผ ํ์ธํ์ง ๋ชปํ์ต๋๋ค. ๋ค์ ์๋ํด ์ฃผ์ธ์." -loading = "ํ์ฌ ๊ณ์ ์ ๋ณด๋ฅผ ๋ถ๋ฌ์ค๋ ์ค์ ๋๋ค." -lookup_fallback = "ํ์ ์ ๋ณด๊ฐ ์ถฉ๋ถํ์ง ์์ ์ผ๋ถ ํญ๋ชฉ์ ํ์ธ๋์ง ์์ ์ ์์ต๋๋ค." -page_title = "์ ํ๋ฆฌ์ผ์ด์ ์ ๊ทผ์ด ์ ํ๋์์ต๋๋ค" -primary_tenant = "๋ํ ์์ ํ ๋ํธ" -tenant = "์์ ํ ๋ํธ" -tenant_unknown = "์ ์ ์์" -title = "์ ๊ทผ ์ ํ ์ ๋ณด" - -[msg.userfront.forgot] -description = "๊ณ์ ๊ณผ ์ฐ๊ฒฐ๋ ์ด๋ฉ์ผ ์ฃผ์ ๋๋ ํด๋ํฐ ๋ฒํธ๋ฅผ ์ ๋ ฅํ์๋ฉด, ๋น๋ฐ๋ฒํธ๋ฅผ ์ฌ์ค์ ํ ์ ์๋ ๋งํฌ๋ฅผ ๋ณด๋ด๋๋ฆฝ๋๋ค." -dry_send = "drySend ๋ชจ๋: ์ค์ ์ด๋ฉ์ผ/SMS๋ ๋ฐ์ก๋์ง ์์ต๋๋ค." -error = "์ ์ก์ ์คํจํ์ต๋๋ค: {{error}}" -input_required = "์ด๋ฉ์ผ ๋๋ ํด๋ํฐ ๋ฒํธ๋ฅผ ์ ๋ ฅํด์ฃผ์ธ์." -sent = "๋น๋ฐ๋ฒํธ ์ฌ์ค์ ๋งํฌ๊ฐ ์ ์ก๋์์ต๋๋ค. ์ด๋ฉ์ผ ๋๋ SMS๋ฅผ ํ์ธํด์ฃผ์ธ์." - -[msg.userfront.login] -cookie_check_failed = "๋ก๊ทธ์ธ ํ์ธ ์คํจ: {{error}}" -dry_send = "drySend ๋ชจ๋: ์ค์ ์ด๋ฉ์ผ/SMS๋ ๋ฐ์ก๋์ง ์์ต๋๋ค." -link_failed = "์ค๋ฅ: {{error}}" -link_send_failed = "์ ์ก ์คํจ: {{error}}" -link_sent_email = "์ ๋ ฅํ์ ์ด๋ฉ์ผ๋ก ๋ก๊ทธ์ธ ๋งํฌ๋ฅผ ๋ณด๋์ต๋๋ค." -link_sent_phone = "์ ๋ ฅํ์ ๋ฒํธ๋ก ๋ก๊ทธ์ธ ๋งํฌ๋ฅผ ๋ณด๋์ต๋๋ค." -link_timeout = "์๊ฐ์ด ๊ฒฝ๊ณผ๋์์ต๋๋ค." -no_account = "๊ณ์ ์ด ์์ผ์ ๊ฐ์?" -oidc_failed = "OIDC ๋ก๊ทธ์ธ ์ฒ๋ฆฌ์ ์คํจํ์ต๋๋ค. ๋ค์ ์๋ํด ์ฃผ์ธ์." -qr_expired = "์๊ฐ์ด ๊ฒฝ๊ณผ๋์์ต๋๋ค." -qr_init_failed = "QR ์ด๊ธฐํ์ ์คํจํ์ต๋๋ค: {{error}}" -qr_login_required = "๋ก๊ทธ์ธ ํ ์ํ์ฌ์ผ QR ์ค์บ์ผ๋ก ๋ก๊ทธ์ธ ํ ์ ์์ต๋๋ค" -token_missing = "๋ก๊ทธ์ธ ํ ํฐ์ ํ์ธํ ์ ์์ต๋๋ค." -verification_failed = "์น์ธ ์ฒ๋ฆฌ์ ์คํจํ์ต๋๋ค: {{error}}" - -[msg.userfront.login_success] -subtitle = "์ฑ๊ณต์ ์ผ๋ก ๋ก๊ทธ์ธ๋์์ต๋๋ค." - -[msg.userfront.consent] -accept_error = "๋์ ์ฒ๋ฆฌ์ ์คํจํ์ต๋๋ค: {{error}}" -client_id = "ํด๋ผ์ด์ธํธ ID: {{id}}" -client_unknown = "์ ์ ์๋ ์ฑ" -description = "์๋ ์๋น์ค๊ฐ ํ์๋์ ๊ณ์ ์ ๋ณด์ ์ ๊ทผํ๋ ค๊ณ ํฉ๋๋ค.\n๊ณ์ ์งํํ๋ ค๋ฉด ๋์ ์ฌ๋ถ๋ฅผ ์ ํํด ์ฃผ์ธ์." -load_error = "๋์ ์ ๋ณด๋ฅผ ๋ถ๋ฌ์ค๋๋ฐ ์คํจํ์ต๋๋ค: {{error}}" -missing_redirect = "๋์๊ฐ ์ฒ๋ฆฌ๋์์ผ๋ ๋ฆฌ๋ค์ด๋ ํธ URL์ ๋ฐ์ง ๋ชปํ์ต๋๋ค." -redirect_notice = "๋์ ํ ์๋์ผ๋ก ์๋น์ค๋ก ์ด๋ํฉ๋๋ค." -scope_count = "์ด {{count}}๊ฐ" - -[msg.userfront.profile] -department_missing = "์์ ์ ๋ณด ์์" -department_required = "์์์ ์ ๋ ฅํด์ฃผ์ธ์." -email_missing = "์ด๋ฉ์ผ ์์" -greeting = "์๋ ํ์ธ์, {{name}}๋" -load_failed = "์ ๋ณด๋ฅผ ๋ถ๋ฌ์ฌ ์ ์์ต๋๋ค." -name_missing = "์ด๋ฆ ์์" -name_required = "์ด๋ฆ์ ์ ๋ ฅํด์ฃผ์ธ์." -phone_required = "ํด๋ํฐ ๋ฒํธ๋ฅผ ์ ๋ ฅํด์ฃผ์ธ์." -phone_verify_required = "ํด๋ํฐ ๋ฒํธ ์ธ์ฆ์ด ํ์ํฉ๋๋ค." -update_failed = "์์ ์คํจ: {{error}}" -update_success = "์ ๋ณด๊ฐ ์์ ๋์์ต๋๋ค." - -[msg.userfront.qr] -camera_error = "์นด๋ฉ๋ผ ์ค๋ฅ: {{error}}" -permission_error = "์นด๋ฉ๋ผ ๊ถํ ์์ฒญ์ ์คํจํ์ต๋๋ค. ๋ธ๋ผ์ฐ์ /OS ์ค์ ์ ํ์ธํด์ฃผ์ธ์." -permission_required = "์นด๋ฉ๋ผ ๊ถํ์ด ํ์ํฉ๋๋ค." - -[msg.userfront.reset] -invalid_body = "๋น๋ฐ๋ฒํธ ์ฌ์ค์ ๋งํฌ๊ฐ ๋ง๋ฃ๋์๊ฑฐ๋ ์๋ชป๋์์ต๋๋ค. ๋ค์ ์๋ํด์ฃผ์ธ์." -invalid_link = "์ ํจํ์ง ์์ ์ฌ์ค์ ๋งํฌ์ ๋๋ค. (loginId/token ๋๋ฝ)" -invalid_title = "์ ํจํ์ง ์์ ๋งํฌ์ ๋๋ค." -policy_loading = "๋น๋ฐ๋ฒํธ ์ ์ฑ ์ ๋ถ๋ฌ์ค๋ ์ค์ ๋๋ค..." -success = "๋น๋ฐ๋ฒํธ๊ฐ ์ฑ๊ณต์ ์ผ๋ก ๋ณ๊ฒฝ๋์์ต๋๋ค. ๋ค์ ๋ก๊ทธ์ธํด์ฃผ์ธ์." - -[msg.userfront.sections] -apps_subtitle = "ํ์ฌ ์ฐ๊ฒฐ๋ ์ฑ๊ณผ ์ต๊ทผ ์ธ์ฆ ์ํ์ ๋๋ค." -audit_subtitle = "Baron ๋ก๊ทธ์ธ ๊ธฐ์ค์ ์ต๊ทผ ์ ๊ทผ ๊ธฐ๋ก์ ๋๋ค." -sessions_subtitle = "ํ์ฌ ๋ก๊ทธ์ธ๋ ๊ธฐ๊ธฐ์ ๋ธ๋ผ์ฐ์ ์ธ์ ์ ๋๋ค." - -[msg.userfront.settings] -disabled = "ํ์ฌ ๊ณ์ ์ค์ ํ๋ฉด์ ์ค๋น ์ค์ ๋๋ค." - -[msg.userfront.signup] -failed = "๊ฐ์ ์คํจ: {{error}}" -privacy_full = "๊ฐ์ธ์ ๋ณด ์์ง ๋ฐ ์ด์ฉ ๋์ ์ ๋ฌธ..." -tos_full = "์๋น์ค ์ด์ฉ์ฝ๊ด ์ ๋ฌธ..." - -[ui.admin.audit] -export_csv = "Export CSV" -load_more = "Load more" -target = "Target ยท {{target}}" -title = "๊ฐ์ฌ ๋ก๊ทธ" - -[ui.admin.groups] -import_csv = "CSV ์ํฌํธ" - -[ui.admin.header] -plane = "Admin Plane" -subtitle = "๊ด๋ฆฌ ๋ฐ ์ ์ฑ ์ด์" - -[ui.admin.nav] -org_chart = "์กฐ์ง๋" -api_keys = "API ํค" -audit_logs = "๊ฐ์ฌ ๋ก๊ทธ" -auth_guard = "์ธ์ฆ ๊ฐ๋" -logout = "๋ก๊ทธ์์" -overview = "๊ฐ์" -relying_parties = "์ ํ๋ฆฌ์ผ์ด์ (RP)" -tenant_dashboard = "ํ ๋ํธ ๋์๋ณด๋" -user_groups = "์ ์ ๊ทธ๋ฃน" -tenants = "ํ ๋ํธ" -users = "์ฌ์ฉ์" - -[ui.admin.org] -download_template = "ํ ํ๋ฆฟ ๋ค์ด๋ก๋" -import_btn = "์ํฌํธ" -import_title = "์กฐ์ง๋ ๋๋ ๋ฑ๋ก" -start_import = "์ํฌํธ ์์" - -[ui.admin.overview] -kicker = "Global Overview" -title = "ํตํฉ ๋์๋ณด๋" - -[ui.admin.profile] -manageable_tenants = "๊ด๋ฆฌ ๊ฐ๋ฅํ ํ ๋ํธ" - -[ui.admin.role] -rp_admin = "RP ADMIN" -super_admin = "SUPER ADMIN" -tenant_admin = "TENANT ADMIN" -user = "TENANT MEMBER" - -[ui.admin.tenants] -add = "ํ ๋ํธ ์ถ๊ฐ" -csv_template = "ํ ํ๋ฆฟ" -delete_selected = "์ ํ ์ญ์ " -export_with_ids = "UUID ํฌํจ" -export_without_ids = "UUID ์ ์ธ ๋ด๋ณด๋ด๊ธฐ" -import = "๊ฐ์ ธ์ค๊ธฐ" -title = "ํ ๋ํธ ๋ชฉ๋ก" -view_org_chart = "์ ์ฒด ์กฐ์ง๋ ๋ณด๊ธฐ" - -[ui.admin.tenants.domain_conflict] -description = "" -title = "๋๋ฉ์ธ ์ถฉ๋" - -[ui.admin.tenants.import_result] -message = "์์ธ ๋ด์ฉ" -modified = "์์ ๋จ:" -status = "์ํ" -title = "๊ฐ์ ธ์ค๊ธฐ ๊ฒฐ๊ณผ ๋ฆฌํฌํธ" - -[ui.admin.tenants.import_preview] -candidates = "ํ๋ณด" -confirm = "๊ฐ์ ธ์ค๊ธฐ ์คํ" -create_new_reset = "์ ๊ท ์์ฑ (ID/slug ์ฌ์ค์ )" -csv_parents = "CSV ์์ ํ ๋ํธ" -external_id = "์ธ๋ถ ID" -match = "๋งค์นญ" -no_candidates = "ํ๋ณด ์์" -parent = "์์" -parent_companies = "์์ ํ์ฌ" -parent_company_groups = "์์ ๊ทธ๋ฃน์ฌ" -parent_organizations = "์์ ์กฐ์ง" -parent_unresolved = "๋ถ๋ชจ ํ์ธ ํ์" -slug_exists = "slug ์ถฉ๋" -title = "CSV ๊ฐ์ ธ์ค๊ธฐ ํ์ธ" -csv_parents = "๊ฐ์ ธ์ค๊ธฐ CSV" -parent = "์์" -parent_companies = "ํ์ฌ" -parent_company_groups = "๊ทธ๋ฃน์ฌ" -parent_organizations = "์กฐ์ง" - -[ui.common.badge] -admin_only = "Admin only" -command_only = "Command only" -system = "System" - -[ui.common.status] -active = "ํ์ฑ" -blocked = "์ฐจ๋จ๋จ" -failure = "์คํจ" -inactive = "๋นํ์ฑ" -ok = "์ ์" -pending = "์ค๋น ์ค" -success = "์ฑ๊ณต" - -[ui.dev.nav] -overview = "๊ฐ์" -clients = "์ฐ๋ ์ฑ" -logout = "๋ก๊ทธ์์" -developer_request = "๊ฐ๋ฐ์ ๊ถํ ์ ์ฒญ" -developer_grants = "๊ฐ๋ฐ์ ๊ถํ ๋ถ์ฌ" - -[ui.dev.welcome] -btn_request = "์ ๊ท ์ ์ฒญํ๊ธฐ" - -[ui.dev.request] -admin_notes_placeholder = "์น์ธ ๋๋ ๋ฐ๋ ค ์ฌ์ ๋ฅผ ์ ๋ ฅํ์ธ์." -cancel_approval = "์น์ธ ์ทจ์" -cancel_notes_placeholder = "์น์ธ ์ทจ์ ์ฌ์ ๋ฅผ ์ ๋ ฅํ์ธ์." - -[ui.dev.request.list] -title = "์ ์ฒญ ๋ด์ญ" - -[ui.dev.request.modal] -desc = "๊ฐ๋ฐ์ ๊ถํ์ ์ ์ฒญํ๋ ค๋ฉด ์๋ ์ ๋ณด๋ฅผ ํ์ธํ ๋ค ์ ์ฒญ ์ฌ์ ๋ฅผ ์ ๋ ฅํ์ธ์." -email = "์ด๋ฉ์ผ" -name = "์ฑํจ" -org = "์์" -pages = "๊ถํ ํ์ด์ง" -phone = "์ ํ๋ฒํธ" -reason = "์ ์ฒญ ์ฌ์ " -reason_placeholder = "๊ฐ๋ฐ์ ๊ถํ์ด ํ์ํ ์ด์ ๋ฅผ ์์ฑํด์ฃผ์ธ์." -role = "์ญํ " -title = "๊ฐ๋ฐ์ ๋ฑ๋ก ์ ์ฒญ" - -[ui.dev.request.status] -approved = "์น์ธ๋จ" -cancelled = "์น์ธ ์ทจ์๋จ" -pending = "๋๊ธฐ ์ค" -rejected = "๋ฐ๋ ค๋จ" - -[ui.dev.request.table] -actions = "๊ด๋ฆฌ" -date = "์ ์ฒญ์ผ" -org = "์์" -reason = "์ ์ฒญ ์ฌ์ " -pages = "๊ถํ ํ์ด์ง" -status = "์ํ" -user = "์ฌ์ฉ์" - -[ui.dev.grants] -actions = "๊ด๋ฆฌ" -admin_notes = "๋ถ์ฌ ์ฌ์ " -approved = "์น์ธ๋จ" -date = "๋ถ์ฌ์ผ" -email = "์ด๋ฉ์ผ" -form.title = "์ง์ ๋ถ์ฌ" -grant = "์ง์ ๋ถ์ฌ" -input_section = "์ ๋ ฅ" -list.title = "๋ถ์ฌ๋ ๊ถํ" -pages = "๊ถํ ํ์ด์ง" -read_only = "์ฝ๊ธฐ ์ ์ฉ" -reason = "๋ถ์ฌ ์ฌ์ " -revoke = "ํ์" -revoke_notes_placeholder = "ํ์ ๋ฉ๋ชจ (์ ํ)..." -selected_info = "์ ํ๋ ์ฌ์ฉ์ ์ ๋ณด" -status = "์ํ" -phone = "์ ํ๋ฒํธ" -tenant = "์์" -user = "์ฌ์ฉ์" -user_search_placeholder = "์ด๋ฆ ๋๋ ์ด๋ฉ์ผ ๊ฒ์..." -user_section = "์ฌ์ฉ์ ์ ํ" - -[ui.dev.grants] -actions = "๊ด๋ฆฌ" -admin_notes = "๋ถ์ฌ ์ฌ์ " -approved = "์น์ธ๋จ" -date = "๋ถ์ฌ์ผ" -form.title = "์ง์ ๋ถ์ฌ" -grant = "์ง์ ๋ถ์ฌ" -input_section = "์ ๋ ฅ" -list.title = "๋ถ์ฌ๋ ๊ถํ" -pages = "๊ถํ ํ์ด์ง" -read_only = "์ฝ๊ธฐ ์ ์ฉ" -reason = "๋ถ์ฌ ์ฌ์ " -revoke = "ํ์" -revoke_notes_placeholder = "ํ์ ๋ฉ๋ชจ (์ ํ)..." -selected_info = "์ ํ๋ ์ฌ์ฉ์ ์ ๋ณด" -status = "์ํ" -tenant = "์์" -user = "์ฌ์ฉ์" -user_search_placeholder = "์ด๋ฆ ๋๋ ์ด๋ฉ์ผ ๊ฒ์..." -user_section = "์ฌ์ฉ์ ์ ํ" - -[ui.dev.tenant] -single_notice = "๋จ์ผ ํ ๋ํธ์ ์์๋์ด ์ ํํ ํ์๊ฐ ์์ต๋๋ค." -switch_success = "ํ ๋ํธ ์ ํ ์๋ฃ" -workspace = "์์ ํ ๋ํธ (์ปจํ ์คํธ)" -workspace_desc = "ํ์ฌ ์์ ์ค์ธ ํ ๋ํธ๋ฅผ ์ ํํ๊ณ ์ ์ฅํ์ฌ API ์์ฒญ ์ปจํ ์คํธ๋ฅผ ๋ณ๊ฒฝํฉ๋๋ค." - -[ui.dev.audit] -load_more = "๋ ๋ณด๊ธฐ" -title = "๊ฐ์ฌ ๋ก๊ทธ" - -[ui.dev.profile] -menu_aria = "๊ณ์ ๋ฉ๋ด ์ด๊ธฐ" -menu_title = "๊ณ์ " -unknown_email = "unknown@example.com" -unknown_name = "Unknown User" -title = "๋ด ์ ๋ณด" -subtitle = "์ฌ์ฉ์ ์์ธ ์ ๋ณด ๋ฐ ํ ๋น๋ ์ญํ (Role)์ ํ์ธํฉ๋๋ค." -loading = "ํ๋กํ ์ ๋ณด๋ฅผ ๋ถ๋ฌ์ค๋ ์ค..." -error = "ํ๋กํ ์ ๋ณด๋ฅผ ๋ถ๋ฌ์ค์ง ๋ชปํ์ต๋๋ค." - -[ui.dev.clients] -new = "์ฐ๋ ์ฑ ์ถ๊ฐ" -search_placeholder = "์ฐ๋ ์ฑ ์ด๋ฆ/ID๋ก ๊ฒ์..." -tenant_scoped = "Tenant-scoped" -untitled = "Untitled" - -[ui.dev.dashboard] -ready_badge = "devfront ready" - -[ui.dev.header] -plane = "Dev Plane" -subtitle = "Manage your applications" - -[ui.dev.session] -auto_extend = "์ธ์ ๋ง๋ฃ ๊ด๋ฆฌ" -active = "์ธ์ ํ์ฑ" -disabled = "์๋ ์ฐ์ฅ ๋นํ์ฑํ" -unknown = "์ ์ ์์" -expired = "์ธ์ ๋ง๋ฃ" -expiring = "๋ง๋ฃ ์๋ฐ: {{minutes}}๋ถ {{seconds}}์ด ๋จ์" -remaining = "๋ง๋ฃ ์์ : {{minutes}}๋ถ {{seconds}}์ด ๋จ์" -refresh = "์ธ์ ๋ง๋ฃ ์๊ฐ ๊ฐฑ์ " -refreshing = "์ธ์ ๋ง๋ฃ ์๊ฐ ๊ฐฑ์ ์ค..." - -[ui.userfront.app_label] -admin_console = "Admin Console" -baron = "Baron ๋ก๊ทธ์ธ" -dev_console = "Dev Console" - -[ui.userfront.auth_method] -ory = "Ory ์ธ์ " -session = "์ธ์ " - -[ui.userfront.dashboard] -last_auth_label = "์ต๊ทผ ์ธ์ฆ" -link_status_label = "์ฐ๋ ์ํ" -status_history = "์ฐ๋ ์ ๋ณด" - -[ui.userfront.device] -android = "Mobile(Android)" -ios = "Mobile(iOS)" -linux = "Desktop(Linux)" -macos = "Desktop(macOS)" -windows = "Desktop(Windows)" - -[ui.userfront.error] -go_home = "ํ์ผ๋ก ์ด๋" -go_login = "๋ก๊ทธ์ธ์ผ๋ก ์ด๋" -switch_account = "๋ค๋ฅธ ๊ณ์ ์ผ๋ก ๋ก๊ทธ์ธ" - -[ui.userfront.forgot] -heading = "๋น๋ฐ๋ฒํธ๋ฅผ ์์ผ์ จ๋์?" -input_label = "์ด๋ฉ์ผ ๋๋ ํด๋ํฐ ๋ฒํธ" -submit = "์ฌ์ค์ ๋งํฌ ์ ์ก" -title = "๋น๋ฐ๋ฒํธ ์ฌ์ค์ " - -[ui.userfront.login] -forgot_password = "๋น๋ฐ๋ฒํธ๋ฅผ ์์ผ์ จ๋์?" -signup = "ํ์๊ฐ์ " - -[ui.userfront.login_success] -later = "๋์ค์ ํ๊ธฐ (๋์๋ณด๋๋ก ์ด๋)" -qr = "QR ์ธ์ฆ (์นด๋ฉ๋ผ ์ผ๊ธฐ)" -title = "๋ก๊ทธ์ธ ์๋ฃ" - -[ui.userfront.consent] -accept = "๋์ํ๊ณ ๊ณ์ํ๊ธฐ" -requested_scopes = "์์ฒญ๋ ๊ถํ" -title = "์ ๊ทผ ๊ถํ ์์ฒญ" - -[ui.userfront.nav] -dashboard = "๋์๋ณด๋" -logout = "๋ก๊ทธ์์" -profile = "๋ด ์ ๋ณด" -qr_scan = "QR ์ค์บ" - -[ui.userfront.profile] -department_empty = "์์ ์ ๋ณด ์์" -manage = "ํ๋กํ ๊ด๋ฆฌ" -user_fallback = "์ฌ์ฉ์" - -[ui.userfront.qr] -rescan = "๋ค์ ์ค์บ" -result_success = "์น์ธ ์๋ฃ" -title = "Scan QR Code" - -[ui.userfront.reset] -confirm_password = "์ ๋น๋ฐ๋ฒํธ ํ์ธ" -new_password = "์ ๋น๋ฐ๋ฒํธ" -submit = "๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ" -subtitle = "์๋ก์ด ๋น๋ฐ๋ฒํธ ์ค์ " -title = "์ ๋น๋ฐ๋ฒํธ ์ค์ " - -[ui.userfront.sections] -apps = "๋์ App ํํฉ" -audit = "์ ์์ด๋ ฅ" -sessions = "ํ์ฑ ์ธ์ " - -[ui.userfront.session] -active = "์ธ์ ํ์ฑ" -unknown = "์ ์ ์์" - -[ui.userfront.signup] -complete = "๊ฐ์ ์๋ฃ" -next_step = "๋ค์ ๋จ๊ณ" -title = "ํ์๊ฐ์ " +[msg.admin.api_keys] [msg.admin.api_keys.create] error = "API ํค ์์ฑ์ ์คํจํ์ต๋๋ค." @@ -647,17 +91,22 @@ notice_emphasis = "์ง๊ธ ํ ๋ฒ๋ง" notice_suffix = "ํ์๋ฉ๋๋ค." [msg.admin.api_keys.list] -edit_scopes_desc = "CLIENT_ID๋ ์ ์งํ๊ณ ๊ถํ๋ง ๋ณ๊ฒฝํฉ๋๋ค." -rotate_confirm = "API ํค \"{{name}}\"์ Secret์ ์ฌ๋ฐ๊ธํ ๊น์? ๊ธฐ์กด Secret์ ๋ ์ด์ ์ฌ์ฉํ ์ ์์ต๋๋ค." -rotate_secret_notice = "์ Secret์ ์ง๊ธ ํ ๋ฒ๋ง ํ์๋ฉ๋๋ค. CLIENT_ID๋ ๋ณ๊ฒฝ๋์ง ์์์ต๋๋ค." -delete_confirm = "API ํค \\\\\\\"{{name}}\\\\\\\"๋ฅผ ์ญ์ ํ ๊น์?" +delete_confirm = "API ํค \\\\\\\\\\\\\\\"{{name}}\\\\\\\\\\\\\\\"๋ฅผ ์ญ์ ํ ๊น์?" +edit_scopes_desc = "API ํค์ ๋ถ์ฌํ ๊ถํ ๋ฒ์๋ฅผ ์์ ํฉ๋๋ค." empty = "๋ฑ๋ก๋ API ํค๊ฐ ์์ต๋๋ค." fetch_error = "API ํค ๋ชฉ๋ก ์กฐํ์ ์คํจํ์ต๋๋ค." +rotate_confirm = "์ด API ํค์ Secret์ ์ฌ๋ฐ๊ธํ ๊น์?" +rotate_secret_notice = "์ Secret์ ์ง๊ธ ํ ๋ฒ๋ง ํ์๋ฉ๋๋ค." subtitle = "์๋ฒ ๊ฐ ํต์ (Machine-to-Machine)์ ์ํ API ํค๋ฅผ ๋ฐ๊ธํ๊ณ ๊ด๋ฆฌํฉ๋๋ค." [msg.admin.api_keys.list.registry] count = "์ด {{count}}๊ฐ API ํค" +[msg.admin.apikeys] + +[msg.admin.apikeys.registry] +count = "์ด {{count}}๊ฐ์ ํ์ฑ ํค๊ฐ ๋ฑ๋ก๋์ด ์์ต๋๋ค." + [msg.admin.audit] empty = "์์ง ์์ง๋ ๊ฐ์ฌ ๋ก๊ทธ๊ฐ ์์ต๋๋ค." end = "๊ฐ์ฌ ๋ก๊ทธ์ ๋ง์ง๋ง์ ๋๋ค." @@ -719,6 +168,55 @@ remove_success = "์ญํ ์ด ํ์๋์์ต๋๋ค." [msg.admin.header] subtitle = "Tenant isolation & least privilege by default" +[msg.admin.integrity] +subtitle = "์ ํฉ์ฑ ์ํ๋ฅผ ํ์ธํ๊ณ ๋ฐ์ดํฐ ๋ชจ๋ธ ์ ๋ฐ์ ๊ฒ์ฆ ๊ฒฐ๊ณผ๋ฅผ ์ดํด๋ด ๋๋ค." + +[msg.admin.integrity.check] + +[msg.admin.integrity.check.duplicate_tenant_slugs] +description = "์ญ์ ๋์ง ์์ tenant์ LOWER(TRIM(slug)) ๊ธฐ์ค ์ค๋ณต์ ๊ฒ์ฌํฉ๋๋ค." + +[msg.admin.integrity.check.orphan_tenant_parents] +description = "tenants.parent_id๊ฐ ์กด์ฌํ์ง ์๊ฑฐ๋ soft-deleted tenant๋ฅผ ์ฐธ์กฐํ๋์ง ๊ฒ์ฌํฉ๋๋ค." + +[msg.admin.integrity.check.orphan_user_login_id_tenants] +description = "user_login_ids.tenant_id๊ฐ ์กด์ฌํ์ง ์๊ฑฐ๋ soft-deleted tenant๋ฅผ ์ฐธ์กฐํ๋์ง ๊ฒ์ฌํฉ๋๋ค." + +[msg.admin.integrity.check.orphan_user_login_id_users] +description = "user_login_ids.user_id๊ฐ ์กด์ฌํ์ง ์๊ฑฐ๋ soft-deleted user๋ฅผ ์ฐธ์กฐํ๋์ง ๊ฒ์ฌํฉ๋๋ค." + +[msg.admin.integrity.check.orphan_user_tenant_memberships] +description = "users.tenant_id๊ฐ ์กด์ฌํ์ง ์๊ฑฐ๋ soft-deleted tenant๋ฅผ ์ฐธ์กฐํ๋์ง ๊ฒ์ฌํฉ๋๋ค." + +[msg.admin.integrity.forbidden] +description = "์ด ํ๋ฉด์ super_admin ๊ถํ์ผ๋ก๋ง ์ ๊ทผํ ์ ์์ต๋๋ค." + +[msg.admin.integrity.orphan_login_ids] +delete_confirm = "์ ํํ {{count}}๊ฐ์ ์ ๋ น ๋ก๊ทธ์ธ ID๋ฅผ ์ญ์ ํ์๊ฒ ์ต๋๊น?" +delete_success = "{{count}}๊ฐ์ ์ ๋ น ๋ก๊ทธ์ธ ID๋ฅผ ์ญ์ ํ์ต๋๋ค." +description = "์ญ์ ๋์๊ฑฐ๋ ์กด์ฌํ์ง ์๋ ์ฌ์ฉ์/ํ ๋ํธ๋ฅผ ์ฐธ์กฐํ๋ ๋ก๊ทธ์ธ ID๋ฅผ ํ์ธํ ๋ค ์ ํ ์ญ์ ํฉ๋๋ค." +empty = "์ญ์ ํ ์ ๋ น ๋ก๊ทธ์ธ ID๊ฐ ์์ต๋๋ค." +load_error = "์ ๋ น ๋ก๊ทธ์ธ ID ๋์์ ๋ถ๋ฌ์ค์ง ๋ชปํ์ต๋๋ค." + +[msg.admin.integrity.read_model] +description = "Ory SoT๋ฅผ ๋ฎ์ด์ฐ์ง ์๊ณ backend DB read model์ ์ด์ ์งํ๋ง ํ์ธํฉ๋๋ค." + +[msg.admin.integrity.recheck] +error = "๊ฒ์ฌ์ ์คํจํ์ต๋๋ค." +running = "์ ํฉ์ฑ ๊ฒ์ฌ๋ฅผ ์คํ ์ค์ ๋๋ค." +success = "๊ฒ์ฌ๊ฐ ์๋ฃ๋์์ต๋๋ค." + +[msg.admin.integrity.report] +load_error = "์ ํฉ์ฑ ๋ฆฌํฌํธ๋ฅผ ๋ถ๋ฌ์ค์ง ๋ชปํ์ต๋๋ค." + +[msg.admin.integrity.section] + +[msg.admin.integrity.section.tenant_integrity] +description = "ํ ๋ํธ slug ์ค๋ณต๊ณผ ๋ถ๋ชจ ๊ด๊ณ ์ด์์ ํ์ธํฉ๋๋ค." + +[msg.admin.integrity.section.user_integrity] +description = "์ฌ์ฉ์์ ๋ก๊ทธ์ธ ID ์ฐธ์กฐ์ ๊ณ ์ ๋ ์ฝ๋๋ฅผ ํ์ธํฉ๋๋ค." + [msg.admin.notice] idp_policy = "IDP ๊ด๋ฆฌ ํค๋ ์๋ฒ ๋ด๋ถ ๋ํ API๋ก๋ง ์ฌ์ฉํ๋ฉฐ, ๊ฐ์ฌยท๋ ์ดํธ๋ฆฌ๋ฐ์ ๊ธฐ๋ณธ ์ ์ฉํฉ๋๋ค." scope = "๊ด๋ฆฌ ๊ธฐ๋ฅ์ /admin ๋ค์์คํ์ด์ค์์๋ง ๋ ธ์ถํฉ๋๋ค." @@ -727,8 +225,19 @@ scope = "๊ด๋ฆฌ ๊ธฐ๋ฅ์ /admin ๋ค์์คํ์ด์ค์์๋ง ๋ ธ์ถํฉ๋๋ค." hover_member_info = "๋ง์ฐ์ค๋ฅผ ์ฌ๋ฆฌ๋ฉด ์์ธ ์ ๋ณด๋ฅผ ํ์ธํ ์ ์์ต๋๋ค." import_description = "CSV ํ์ผ์ ์ ๋ก๋ํ์ฌ ์กฐ์ง๋๋ฅผ ์ผ๊ด ๋ฑ๋กํฉ๋๋ค." import_error = "์กฐ์ง๋ ์ํฌํธ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค." +import_partial_success = "์ผ๋ถ ์กฐ์ง ์ ๋ณด๋ฅผ ๊ฐ์ ธ์์ต๋๋ค." import_success = "์กฐ์ง๋๊ฐ ์ฑ๊ณต์ ์ผ๋ก ์ํฌํธ๋์์ต๋๋ค." +[msg.admin.ory_ssot] +flush_confirm = "Redis identity cache ํค๋ง ๋น์ฐ์๊ฒ ์ต๋๊น?" +flush_error = "Redis identity cache flush์ ์คํจํ์ต๋๋ค." +flush_success = "Redis identity cache key {{count}}๊ฐ๋ฅผ ๋น์ ์ต๋๋ค." +load_error = "Ory SSOT ์์คํ ์ํ๋ฅผ ๋ถ๋ฌ์ค์ง ๋ชปํ์ต๋๋ค." +subtitle = "Kratos ์์ฅ๊ณผ Redis identity cache ์ํ๋ฅผ ๋ถ๋ฆฌํด์ ํ์ธํฉ๋๋ค." + +[msg.admin.ory_ssot.forbidden] +description = "์ด ํ๋ฉด์ super_admin ๊ถํ์ผ๋ก๋ง ์ ๊ทผํ ์ ์์ต๋๋ค." + [msg.admin.overview] description = "๋ชจ๋ ํ ๋ํธ ๊ณตํต ์งํ์ ์ ์ฑ ์ํ๋ฅผ ํ ๊ณณ์์ ํ์ธํฉ๋๋ค." idp_fallback = "Fallback: Descope" @@ -748,28 +257,52 @@ description = "์ฃผ์ ์ด์ ํ๋ฉด์ผ๋ก ๋ฐ๋ก ์ด๋ํฉ๋๋ค." audit_events_24h = "์ต๊ทผ 24์๊ฐ ๊ฐ์ฌ ๋ก๊ทธ" oidc_clients = "๋ฑ๋ก๋ OIDC ํด๋ผ์ด์ธํธ" policy_gate = "์ ์ฑ ๊ฐ์ดํธ ์ํ" -total_users = "์ ์ฒด ์ฌ์ฉ์ ์" total_tenants = "์ ์ฒด ํ ๋ํธ ์" +total_users = "์ ์ฒด ์ฌ์ฉ์ ์" + +[msg.admin.permissions_direct] +desc_api_keys = "์กฐ์ง๋ ์ฐ๋์ ์ํ ์ ์ญ ์๋ํํฐ ํ ํฐ ๊ด๋ฆฌ" +desc_audit_logs = "์์คํ ์ ์ญ ๋ณด์ ๊ฐ์ฌ ๋ฐ ์ ์ ์ด๋ ฅ ๋ก๊ทธ" +desc_auth_guard = "์ ์ฑ ์์ง ๊ธฐ์ค์ผ๋ก Keto ReBAC ๊ด๊ณ ๊ฒ์ฆ ์๋ฎฌ๋ ์ดํฐ" +desc_data_integrity = "๊ณ ์ ๋ ์ฝ๋ ๊ฒ์ถ ๋ฐ DB ์ ํฉ์ฑ ์ต์ข ๊ฒ์ฆ๊ธฐ" +desc_org_chart = "์กฐ์ง๋ ๊ฐ์ํ ๋ฐ ํธ๋ฆฌ ๋ฐฐ์น ํ์ธ" +desc_ory_ssot = "Redis ์์ด๋ดํฐํฐ ๋ฏธ๋ฌ ์บ์ ๋ฐ PostgreSQL read model ์ ํฉ์ฑ ๊ฐฑ์ " +desc_overview = "๋ฐ๋ก ์ ์ฒด ์ฌ์ ๋ฐ ์์คํ ์ํ ๊ฐ์ ์ ๋ณด" +desc_permissions_direct = "๋ณธ ์ฌ์ด๋๋ฐ ๋ฉ๋ด ์ธ๋ถ ๊ถํ ๊ฒฉ์ ๋ฐ ํ ๋ํธ ์ธ๊ฐ ์ค์ ํจ๋" +desc_tenants = "๊ณ ๊ฐ ํ ๋ํธ ๋ชฉ๋ก, ์ ๊ท ๋ถ๋ชจ-์์ ํ ๋ํธ ๊ด๋ฆฌ" +desc_users = "๊ฐ์ ์ฌ์ฉ์ ๋ชฉ๋ก, ์น์ธ ๋ฐ ์ปค์คํ ํด๋ ์ ์๋ ์ฃผ์ " +desc_worksmobile = "๋ผ์ธ์์ค ์ฐ๋ ๋ฐ ์ฌ๋ด ์์ง์ ํจ์ค์๋ ๊ฐ์ ๋๊ธฐํ" +description = "ํ ๋ํธ์ ์ธ๋ถ ๊ธฐ๋ฅ ๊ถํ ๋ฐ ๊ธ๋ก๋ฒ ์ฌ์ด๋๋ฐ ๋ฉ๋ด ํญ ์ ๊ทผ ๊ถํ์ ์ง์ ํ๊ณ ๋ถ์ฌํฉ๋๋ค." +no_user_selected_desc = "์ผ์ชฝ์ ์ฌ์ฉ์ ๋ฆฌ์คํธ์์ ๊ถํ์ ๋ณ๊ฒฝํ ์ธ์์ ์ ํํด ์ฃผ์ธ์." +no_users_found = "๋ฑ๋ก๋ ์ฌ์ฉ์๊ฐ ์์ต๋๋ค." + +[msg.admin.system] + +[msg.admin.system.relations] +remove_all_confirm = "์ด ์ฌ์ฉ์์ ๋ชจ๋ ์์คํ ๋ฉ๋ด ๊ถํ์ ์ญ์ ํ์๊ฒ ์ต๋๊น?" +update_success = "์์คํ ๋ฉ๋ด ๊ถํ์ด ์ฑ๊ณต์ ์ผ๋ก ๋ณ๊ฒฝ๋์์ต๋๋ค." [msg.admin.tenants] approve_confirm = "์ด ํ ๋ํธ๋ฅผ ์น์ธํ์๊ฒ ์ต๋๊น?" approve_success = "ํ ๋ํธ๊ฐ ์น์ธ๋์์ต๋๋ค." -delete_confirm = "ํ ๋ํธ \\\\\\\"{{name}}\\\\\\\"๋ฅผ ์ญ์ ํ ๊น์?" +delete_bulk_confirm = "์ ํํ {{count}}๊ฐ ํ ๋ํธ๋ฅผ ์ญ์ ํ ๊น์?" +delete_confirm = "ํ ๋ํธ \\\\\\\\\\\\\\\"{{name}}\\\\\\\\\\\\\\\"๋ฅผ ์ญ์ ํ ๊น์?" delete_success = "ํ ๋ํธ๊ฐ ์ญ์ ๋์์ต๋๋ค." empty = "์์ง ๋ฑ๋ก๋ ํ ๋ํธ๊ฐ ์์ต๋๋ค." -fetch_error = "ํ ๋ํธ ๋ชฉ๋ก ์กฐํ์ ์คํจํ์ต๋๋ค." +empty_scope = "์ ํํ ๋ฒ์์ ํ์ํ ํ์ ํ ๋ํธ๊ฐ ์์ต๋๋ค." +empty_search = "๊ฒ์ ์กฐ๊ฑด์ ๋ง๋ ํ ๋ํธ๊ฐ ์์ต๋๋ค." export_error = "ํ ๋ํธ ๋ด๋ณด๋ด๊ธฐ์ ์คํจํ์ต๋๋ค." +fetch_error = "ํ ๋ํธ ๋ชฉ๋ก ์กฐํ์ ์คํจํ์ต๋๋ค." import_empty = "์ํฌํธ ํ์ผ์ ํ ๋ํธ ํ์ด ์์ต๋๋ค." import_error = "ํ ๋ํธ ์ํฌํธ์ ์คํจํ์ต๋๋ค: {{error}}" import_result = "{{count}}๊ฐ์ ํ ๋ํธ ํ์ ์ฒ๋ฆฌํ์ต๋๋ค." missing_id = "ํ ๋ํธ ID๊ฐ ์์ต๋๋ค." not_found = "ํ ๋ํธ๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค." -remove_sub_confirm = "ํ ๋ํธ \\\\\\\"{{name}}\\\\\\\"์(๋ฅผ) ํ์ ์กฐ์ง์์ ์ ์ธํ ๊น์?" +remove_sub_confirm = "ํ ๋ํธ \\\\\\\\\\\\\\\"{{name}}\\\\\\\\\\\\\\\"์(๋ฅผ) ํ์ ์กฐ์ง์์ ์ ์ธํ ๊น์?" +seed_delete_blocked = "์ด๊ธฐ ์ค์ ํ ๋ํธ๋ ์ญ์ ํ ์ ์์ต๋๋ค." +status_error = "temp" subtitle = "ํ์ฌ ๋ฑ๋ก๋ ํ ๋ํธ๋ฅผ ํ์ธํ๊ณ ์ํ๋ฅผ ๊ด๋ฆฌํฉ๋๋ค." -[msg.admin.tenants.import_preview] -description = "์ํฌํธ ์ ์ ๊ฐ ํ์ ๋งค์นญ ๊ฒฐ๊ณผ๋ฅผ ๊ฒํ ํ๊ณ ์ฒ๋ฆฌ ๋ฐฉ์์ ์ ํํ์ธ์." - [msg.admin.tenants.admins] add_success = "๊ด๋ฆฌ์๊ฐ ์ถ๊ฐ๋์์ต๋๋ค." empty = "๋ฑ๋ก๋ ๊ด๋ฆฌ์๊ฐ ์์ต๋๋ค." @@ -779,6 +312,10 @@ remove_self = "๋ณธ์ธ์ ๊ถํ์ ํ์ํ ์ ์์ต๋๋ค." remove_success = "๊ถํ์ด ํ์๋์์ต๋๋ค." subtitle = "์ด ํ ๋ํธ์ ์์์ ๊ด๋ฆฌํ ์ ์๋ ์ฌ์ฉ์ ๋ชฉ๋ก์ ๋๋ค." +[msg.admin.tenants.bulk] +update_error = "temp" +update_success = "temp" + [msg.admin.tenants.create] pick_parent_first = "์์ ํ ๋ํธ๋ฅผ ๋จผ์ ์ ํํ์ธ์." subtitle = "๊ธ๋ก๋ฒ ์ด์ ๊ธฐ์ค์ ์ ๊ท ํ ๋ํธ๋ฅผ ๋ฑ๋กํฉ๋๋ค." @@ -793,7 +330,12 @@ subtitle = "Tenant ๊ถํ ์ ์ฑ ์ ์ถํ Keto ์ฐ๊ณ๋ก ํ์ฅ ์์ ์ ๋๋ค [msg.admin.tenants.create.profile] subtitle = "ํ์ ์ ๋ณด๋ง ์ ๋ ฅํด๋ ์์ฑ ๊ฐ๋ฅํฉ๋๋ค. Slug๋ ์์ผ๋ฉด ์๋ ์์ฑ๋ฉ๋๋ค." +[msg.admin.tenants.import_preview] +description = "์ํฌํธ ์ ์ ๊ฐ ํ์ ๋งค์นญ ๊ฒฐ๊ณผ๋ฅผ ๊ฒํ ํ๊ณ ์ฒ๋ฆฌ ๋ฐฉ์์ ์ ํํ์ธ์." + [msg.admin.tenants.members] +add_error = "๊ตฌ์ฑ์ ์ถ๊ฐ ์คํจ" +add_success = "{{count}}๋ช ์ ๊ตฌ์ฑ์์ด ์ถ๊ฐ๋์์ต๋๋ค." desc = "์กฐ์ง์ ์์๋ ์ฌ์ฉ์ ๋ชฉ๋ก์ ํ์ธํฉ๋๋ค." empty = "์์๋ ์ฌ์ฉ์๊ฐ ์์ต๋๋ค." limit_notice = "ํ์ ์กฐ์ง์ด ๋ง์ ์์ 10๊ฐ ์กฐ์ง์ ๋ฉค๋ฒ๋ง ํ์๋ฉ๋๋ค." @@ -810,6 +352,11 @@ remove_self = "๋ณธ์ธ์ ๊ถํ์ ํ์ํ ์ ์์ต๋๋ค." remove_success = "์์ ์ ๊ถํ์ด ํ์๋์์ต๋๋ค." subtitle = "์ด ํ ๋ํธ์ ์ต์์ ๊ถํ์ ๊ฐ์ง ์์ ์(์กฐ์ง์ฅ) ๋ชฉ๋ก์ ๋๋ค." +[msg.admin.tenants.parent] +local_picker_description = "ํ ๋ํธ ๋ชฉ๋ก์์ ์์ ํ ๋ํธ๋ก ์ฌ์ฉํ ํญ๋ชฉ์ ์ ํํฉ๋๋ค." +local_picker_empty = "์ ํํ ์ ์๋ ํ ๋ํธ๊ฐ ์์ต๋๋ค." +picker_description = "org-chart์์ ํ ๋ํธ๋ฅผ ์ ํํ๋ฉด ์์ ํ ๋ํธ์ ๋ฐ์๋ฉ๋๋ค." + [msg.admin.tenants.registry] count = "์ด {{count}}๊ฐ ํ ๋ํธ" scope_results = "{{name}} ํ์ {{count}}๊ฐ" @@ -818,9 +365,11 @@ search_results = "๊ฒ์ ๊ฒฐ๊ณผ {{count}}๊ฐ" table_hint = "์ ๋ ฌ ๊ฐ๋ฅํ ํ๋ฉด ๋ชฉ๋ก์์ ID, ์ํ, ๊ท๋ชจ๋ฅผ ๋น ๋ฅด๊ฒ ๋น๊ตํฉ๋๋ค." tree_hint = "๊ณ์ธต ๊ตฌ์กฐ๋ฅผ ๋ฐ๋ผ ๋ถ๋ชจ-์์ ๊ด๊ณ์ ํ์ ๋ฒ์๋ฅผ ํจ๊ป ํ์ธํฉ๋๋ค." -[msg.admin.tenants] -empty_scope = "์ ํํ ๋ฒ์์ ํ์ํ ํ์ ํ ๋ํธ๊ฐ ์์ต๋๋ค." -empty_search = "๊ฒ์ ์กฐ๊ฑด์ ๋ง๋ ํ ๋ํธ๊ฐ ์์ต๋๋ค." +[msg.admin.tenants.relations] +empty = "์ธ๋ถ ๊ถํ์ด ์ง์ ๋ ์ฌ์ฉ์๊ฐ ์์ต๋๋ค. ์ฌ์ฉ์๋ฅผ ์ถ๊ฐํด ์ค์ ํ์ธ์." +remove_all_confirm = "์ด ์ฌ์ฉ์์ ๋ชจ๋ ์ธ๋ถ ๊ถํ์ ์ญ์ ํ์๊ฒ ์ต๋๊น?" +subtitle = "์ฌ์ฉ์๋ณ๋ก ๊ฐ ํญ์ ์ธ๋ถ ์กฐํ ๋ฐ ์์ ๊ถํ์ ๊ฒฉ๋ฆฌํ์ฌ ํ ๋นํฉ๋๋ค. ์์ ์์ ๊ถํ์ ์๋์ผ๋ก ๋ณด์กด๋ฉ๋๋ค." +update_success = "์ธ๋ถ ๊ถํ์ด ์ฑ๊ณต์ ์ผ๋ก ๋ณ๊ฒฝ๋์์ต๋๋ค." [msg.admin.tenants.schema] empty = "๋ฑ๋ก๋ ์ปค์คํ ํ๋๊ฐ ์์ต๋๋ค. ํ๋ ์ถ๊ฐ๋ฅผ ๋๋ฌ ์์ํ์ธ์." @@ -830,14 +379,29 @@ subtitle = "์ด ํ ๋ํธ์ ์ฌ์ฉ์์๊ฒ ์ ์ฉํ ์ปค์คํ ์์ฑ์ ์ update_error = "์คํค๋ง ์ ๋ฐ์ดํธ์ ์คํจํ์ต๋๋ค." update_success = "์คํค๋ง๊ฐ ์ฑ๊ณต์ ์ผ๋ก ์ ๋ฐ์ดํธ๋์์ต๋๋ค." +[msg.admin.tenants.scope] +description = "์์ ํ ๋ํธ๋ฅผ ์ ํํ๋ฉด ํด๋น ํ์ ํ ๋ํธ๋ง ๋ชฉ๋ก์ ํ์ํฉ๋๋ค." + [msg.admin.tenants.sub] empty = "ํ์ ํ ๋ํธ๊ฐ ์์ต๋๋ค." subtitle = "ํ์ฌ ํ ๋ํธ ํ์์ ์์ฑ๋ ์กฐ์ง์ ๋๋ค." +[msg.admin.user_projection] +action_error = "์ฌ์ฉ์ ๋๊ธฐํ ์์ ์ ์คํจํ์ต๋๋ค." +action_success = "{{count}}๋ช ๊ธฐ์ค์ผ๋ก ์ฌ์ฉ์ ๋๊ธฐํ๋ฅผ ๊ฐฑ์ ํ์ต๋๋ค." +forbidden_description = "์ด ํ๋ฉด์ super_admin ๊ถํ์ผ๋ก๋ง ์ ๊ทผํ ์ ์์ต๋๋ค." +load_error = "์ฌ์ฉ์ ๋๊ธฐํ ์ํ๋ฅผ ๋ถ๋ฌ์ค์ง ๋ชปํ์ต๋๋ค." +reset_confirm = "์ฌ์ฉ์ ๋๊ธฐํ๋ฅผ Kratos ๊ธฐ์ค์ผ๋ก ๋ค์ ๊ตฌ์ถํ์๊ฒ ์ต๋๊น?" +subtitle = "Kratos ์ฌ์ฉ์ read model์ ํ์ธํ๊ณ ๋๊ธฐํ ์ํ๋ฅผ ๊ฐฑ์ ํฉ๋๋ค." + +[msg.admin.user_projection.forbidden] +description = "์ด ํ๋ฉด์ super_admin ๊ถํ์ผ๋ก๋ง ์ ๊ทผํ ์ ์์ต๋๋ค." + [msg.admin.users] confirm_remove_org = "์ด ์กฐ์ง์์ ์ฌ์ฉ์๋ฅผ ์ ์ธํ์๊ฒ ์ต๋๊น?" -export_error = "์ฌ์ฉ์ ๋ด๋ณด๋ด๊ธฐ์ ์คํจํ์ต๋๋ค." -status_error = "์ฌ์ฉ์ ์ํ ๋ณ๊ฒฝ์ ์คํจํ์ต๋๋ค." +export_error = "์ฌ์ฉ์ ๋ด๋ณด๋ด๊ธฐ์ ์คํจํ์ต๋๋ค: {{error}}" +self_delete_blocked = "์์ ์ ๊ณ์ ์ ์ญ์ ํ ์ ์์ต๋๋ค." +status_error = "์ฌ์ฉ์ ์ํ ๋ณ๊ฒฝ์ ์คํจํ์ต๋๋ค: {{error}}" [msg.admin.users.bulk] delete_confirm = "์ ํํ {{count}}๋ช ์ ์ฌ์ฉ์๋ฅผ ์ ๋ง๋ก ์ญ์ ํ์๊ฒ ์ต๋๊น?" @@ -847,10 +411,10 @@ move_description = "์ ํํ ์ฌ์ฉ์๋ฅผ ๋ค๋ฅธ ํ ๋ํธ๋ก ์ผ๊ด ์ด๋ํฉ move_error = "์ฌ์ฉ์ ์ด๋ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค." move_success = "{{count}}๋ช ์ ์ฌ์ฉ์๊ฐ ์ฑ๊ณต์ ์ผ๋ก ์ด๋๋์์ต๋๋ค." parsed_count = "{{count}}ํ์ ๋ฐ์ดํฐ๊ฐ ํ์ฑ๋์์ต๋๋ค." +permission_placeholder = "๊ถํ ์ ํ" schema_incompatible = "๋์ ํ ๋ํธ ์คํค๋ง์ ์๋ ํ๋๋ ์ ์ค๋ ์ ์์ต๋๋ค:" schema_missing = "๋์ ํ ๋ํธ์ ํ์ ํ๋๊ฐ ๋๋ฝ๋์ด ์์ต๋๋ค:" status_placeholder = "์ํ ์ ํ" -permission_placeholder = "๊ถํ ์ ํ" update_success = "์ฌ์ฉ์ ์ ๋ณด๊ฐ ์ผ๊ด ์ ๋ฐ์ดํธ๋์์ต๋๋ค." [msg.admin.users.create] @@ -910,8 +474,13 @@ name_required = "์ด๋ฆ์ ํ์์ ๋๋ค." [msg.admin.users.detail.security] password_hint = "๋น๋ฐ๋ฒํธ๋ฅผ ๋ณ๊ฒฝํ๋ ค๋ฉด ์ ๋ ฅํ์ธ์. ๋น์๋๋ฉด ํ์ฌ ๋น๋ฐ๋ฒํธ๊ฐ ์ ์ง๋ฉ๋๋ค." +[msg.admin.users.global_custom_claims] +description = "๋ชจ๋ RP์ ๊ณตํต ์ ์ฉํ ์ฌ์ฉ์ claim ์ ์์ ์ฝ๊ธฐ/์ฐ๊ธฐ ๊ถํ ๊ธฐ๋ณธ๊ฐ์ ๊ด๋ฆฌํฉ๋๋ค." +empty = "์ ์๋ ์ ์ญ claim์ด ์์ต๋๋ค." +registry = "์ ์๋ claim key๋ง ์ฌ์ฉ์ ์์ธ์ ์ ์ญ claim ๊ฐ ๊ด๋ฆฌ ๋์์ด ๋ฉ๋๋ค." + [msg.admin.users.list] -delete_confirm = "์ฌ์ฉ์ \\\\\\\"{{name}}\\\\\\\"์(๋ฅผ) ์ ๋ง ์ญ์ ํ์๊ฒ ์ต๋๊น?" +delete_confirm = "์ฌ์ฉ์ \\\\\\\\\\\\\\\"{{name}}\\\\\\\\\\\\\\\"์(๋ฅผ) ์ ๋ง ์ญ์ ํ์๊ฒ ์ต๋๊น?" empty = "๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค." fetch_error = "์ฌ์ฉ์ ๋ชฉ๋ก ์กฐํ์ ์คํจํ์ต๋๋ค." subtitle = "์์คํ ์ฌ์ฉ์๋ฅผ ์กฐํํ๊ณ ๊ด๋ฆฌํฉ๋๋ค. (Local DB)" @@ -939,88 +508,16 @@ unknown_error = "์ ์ ์๋ ์ค๋ฅ" logout_confirm = "๋ก๊ทธ์์ ํ์๊ฒ ์ต๋๊น?" [msg.dev.audit] +access_denied = "๊ฐ์ฌ ๋ก๊ทธ๋ ๊ฐ๋ฐ์ ๊ถํ์ด ์์ด์ผ ๋ณผ ์ ์์ต๋๋ค." +access_denied_detail = "๊ฐ๋ฐ์ ๊ถํ ์ ์ฒญ ํ์ด์ง์์ ์ ์ฒญ์ ๋ฑ๋กํ ๋ค ์น์ธ์ ๋ฐ์์ฃผ์ธ์." empty = "์กฐํ๋ ๊ฐ์ฌ ๋ก๊ทธ๊ฐ ์์ต๋๋ค." forbidden = "๊ฐ์ฌ ๋ก๊ทธ๋ฅผ ์กฐํํ ๊ถํ์ด ์์ต๋๋ค. ๊ด๋ฆฌ์์๊ฒ ๊ถํ์ ์์ฒญํด์ฃผ์ธ์." load_error = "๊ฐ์ฌ ๋ก๊ทธ ์กฐํ ์คํจ: {{error}}" loaded_count = "๋ก๋๋ ๋ก๊ทธ {{count}}๊ฑด" loading = "๊ฐ์ฌ ๋ก๊ทธ๋ฅผ ๋ถ๋ฌ์ค๋ ์ค..." +registry_description = "์ต๊ทผ ๊ฐ์ฌ ๋ก๊ทธ๋ฅผ ๊ฒ์ ์กฐ๊ฑด์ ๋ง์ถฐ ํํฐ๋งํ๊ณ , ์์ ์ด๋ ฅ์ ๋น ๋ฅด๊ฒ ํ์ธํฉ๋๋ค." subtitle = "ํ์ฌ ํ ๋ํธ/์ฑ ๋ฒ์์ DevFront ์์ ์ด๋ ฅ์ ์กฐํํฉ๋๋ค." -[msg.dev.request] -admin_desc = "super admin์ด ๊ฐ๋ฐ์ ๊ถํ ์ ์ฒญ์ ๊ฒํ ํ๊ณ ์น์ธ ๋๋ ๋ฐ๋ คํ ์ ์์ต๋๋ค." -approved = "์น์ธ๋์์ต๋๋ค." -cancelled = "์น์ธ์ด ์ทจ์๋์์ต๋๋ค." -empty = "์ ์ฒญ ๋ด์ญ์ด ์์ต๋๋ค." -need_cancel_notes = "์น์ธ ์ทจ์ ์ฌ์ ๋ฅผ ์ ๋ ฅํด์ฃผ์ธ์." -need_notes = "๋ฐ๋ ค ์ฌ์ ๋ฅผ ์ ๋ ฅํด์ฃผ์ธ์." -rejected = "๋ฐ๋ ค๋์์ต๋๋ค." -user_desc = "๊ฐ๋ฐ์ ๊ถํ์ ์ ์ฒญํ๊ณ ์น์ธ ๊ฒฐ๊ณผ๋ฅผ ํ์ธํ ์ ์์ต๋๋ค." - -[msg.dev.request.modal] -desc = "๊ฐ๋ฐ์ ๊ถํ์ ์ ์ฒญํ๋ ค๋ฉด ์๋ ์ ๋ณด๋ฅผ ํ์ธํ ๋ค ์ ์ฒญ ์ฌ์ ๋ฅผ ์ ๋ ฅํ์ธ์." -email = "์ด๋ฉ์ผ" -name = "์ฑํจ" -org = "์์" -phone = "์ ํ๋ฒํธ" -reason = "์ ์ฒญ ์ฌ์ " -reason_placeholder = "๊ฐ๋ฐ์ ๊ถํ์ด ํ์ํ ์ด์ ๋ฅผ ์์ฑํด์ฃผ์ธ์." -role = "์ญํ " -pages_hint = "์ ์ฒด๋ฅผ ์ ํํ๋ฉด ๊ฐ์, ์ฐ๋ ์ฑ ์ถ๊ฐ, ๊ฐ์ฌ๋ก๊ทธ๊ฐ ๋ชจ๋ ํฌํจ๋ฉ๋๋ค." -title = "๊ฐ๋ฐ์ ๋ฑ๋ก ์ ์ฒญ" - -[msg.dev.grants] -admin_notes_description = "์ง์ ๋ถ์ฌ์ ๊ทผ๊ฑฐ๋ฅผ ๊ฐ๋จํ ๋จ๊ฒจ ๋๋ฉด ์ถํ ํ์์ ๊ฒํ ์ ๋์์ด ๋ฉ๋๋ค." -admin_notes_hint = "ํ์๋ ๋ชฉ๋ก์ ํ์ ๋ฒํผ์ผ๋ก ์ฒ๋ฆฌํฉ๋๋ค." -admin_notes_placeholder = "์: ํ ์คํธ ํ๊ฒฝ ํ์ธ ํ ๊ถํ ๋ถ์ฌ" -approved = "์น์ธ๋จ" -count = "์ด {{count}}๊ฑด" -create_success = "๊ฐ๋ฐ์ ๊ถํ์ ์ง์ ๋ถ์ฌํ์ต๋๋ค." -description = "์ฌ์ฉ์์๊ฒ ๊ฐ๋ฐ์ ๊ถํ์ ์ง์ ๋ถ์ฌํ๊ณ , ๋ถ์ฌ๋ ๊ถํ์ ํ์ํฉ๋๋ค." -empty = "๋ถ์ฌ๋ ๊ถํ์ด ์์ต๋๋ค." -forbidden = "๊ฐ๋ฐ์ ๊ถํ ์ง์ ๋ถ์ฌ๋ super admin๋ง ์ฌ์ฉํ ์ ์์ต๋๋ค." -forbidden_desc = "์ด ํ๋ฉด์ super admin๋ง ์ฌ์ฉํ ์ ์์ต๋๋ค." -form.description = "์ฌ์ฉ์๋ฅผ ์ ํํ๋ฉด ํ์ฌ ์์ ํ ๋ํธ, ์ด๋ฉ์ผ, ์ ํ๋ฒํธ๋ฅผ ํ์ธํ ๋ค ๊ฐ๋ฐ์ ๊ถํ์ ์ฆ์ ๋ถ์ฌํฉ๋๋ค." -list.description = "ํ์ฌ ๋ถ์ฌ๋ ๊ฐ๋ฐ์ ๊ถํ ๋ชฉ๋ก์ ๋๋ค." -load_error = "๊ฐ๋ฐ์ ๊ถํ ๋ชฉ๋ก์ ๋ถ๋ฌ์ค์ง ๋ชปํ์ต๋๋ค." -pages_hint = "์ ์ฒด๋ฅผ ์ ํํ๋ฉด ๊ฐ์, ์ฐ๋ ์ฑ ์ถ๊ฐ, ๊ฐ์ฌ๋ก๊ทธ๊ฐ ๋ชจ๋ ํฌํจ๋ฉ๋๋ค." -phone_missing = "๋ฑ๋ก๋ ์ ํ๋ฒํธ๊ฐ ์์ต๋๋ค." -reason = "๋ถ์ฌ ์ฌ์ " -revoke = "ํ์" -revoke_success = "๊ฐ๋ฐ์ ๊ถํ์ ํ์ํ์ต๋๋ค." -search_empty = "๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค." -search_loading = "์ฌ์ฉ์๋ฅผ ์ฐพ๋ ์ค์ ๋๋ค..." -selected_info_description = "์ ํ๋ ์ฌ์ฉ์์ ์์, ์ด๋ฉ์ผ, ์ ํ๋ฒํธ๋ฅผ ํ์ธํฉ๋๋ค." -selected_user = "์ ํ๋ ์ฌ์ฉ์: {{user}}" -tenant_missing = "์ ํํ ์ฌ์ฉ์์ ํ ๋ํธ ์ ๋ณด๋ฅผ ํ์ธํ ์ ์์ต๋๋ค." -tenant_required = "์ ํํ ์ฌ์ฉ์์ ํ ๋ํธ ์ ๋ณด๋ฅผ ํ์ธํ ์ ์์ต๋๋ค." -user_required = "๋ถ์ฌํ ์ฌ์ฉ์๋ฅผ ์ ํํด์ฃผ์ธ์." -user_section_description = "๊ฒ์์ด๋ฅผ ์ ๋ ฅํด ์ฌ์ฉ์๋ฅผ ์ ํํฉ๋๋ค. ์ ํ ์ ์๋ ๋ค์ ๋จ๊ณ ์ ๋ณด๊ฐ ๋น์ด ์์ต๋๋ค." - -[msg.dev.request.status] -approved = "์น์ธ๋จ" -cancelled = "์น์ธ ์ทจ์๋จ" -pending = "๋๊ธฐ ์ค" -rejected = "๋ฐ๋ ค๋จ" - -[msg.dev.request.table] -actions = "๊ด๋ฆฌ" -date = "์ ์ฒญ์ผ" -org = "์์" -reason = "์ ์ฒญ ์ฌ์ " -status = "์ํ" -user = "์ฌ์ฉ์" - -[msg.dev.request.list] -approved_count = "์ด {{count}}๋ช ์ ์ฌ์ฉ์๊ฐ ์น์ธ๋์์ต๋๋ค." -title = "์ ์ฒญ ๋ด์ญ" - -[msg.dev.request.admin] -notes_placeholder = "์น์ธ ๋๋ ๋ฐ๋ ค ์ฌ์ ๋ฅผ ์ ๋ ฅํ์ธ์." - -[msg.dev.request.cancel] -approval = "์น์ธ ์ทจ์" -notes_placeholder = "์น์ธ ์ทจ์ ์ฌ์ ๋ฅผ ์ ๋ ฅํ์ธ์." - [msg.dev.auth] access_denied_description = "DevFront๋ ๊ด๋ฆฌ์ ์ ์ฉ ํ๋ฉด์ ๋๋ค. ๊ถํ์ด ํ์ํ๋ฉด ๊ด๋ฆฌ์์๊ฒ ์์ฒญํด ์ฃผ์ธ์." access_denied_title = "์ ๊ทผ ๊ถํ์ด ์์ต๋๋ค." @@ -1049,7 +546,7 @@ load_error = "์ฑ ์์ธ ์ ๋ณด๋ฅผ ๋ถ๋ฌ์ค์ง ๋ชปํ์ต๋๋ค: {{error}}" loading = "์ฑ ์์ธ ์ ๋ณด๋ฅผ ๋ถ๋ฌ์ค๋ ์ค..." missing_id = "Client ID๊ฐ ํ์ํฉ๋๋ค." redirect_saved = "Redirect URIs๊ฐ ์ ์ฅ๋์์ต๋๋ค." -rotate_confirm = "๊ฒฝ๊ณ : Client Secret์ ์ฌ๋ฐ๊ธํ๋ฉด ๊ธฐ์กด ์ํฌ๋ฆฟ์ ์ฆ์ ๋ฌดํจํ๋ฉ๋๋ค.\\\\n์ฐ๋๋ ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ค๋จ๋ ์ ์์ต๋๋ค. ๊ณ์ํ์๊ฒ ์ต๋๊น?" +rotate_confirm = "๊ฒฝ๊ณ : Client Secret์ ์ฌ๋ฐ๊ธํ๋ฉด ๊ธฐ์กด ์ํฌ๋ฆฟ์ ์ฆ์ ๋ฌดํจํ๋ฉ๋๋ค.\\\\\\\\n์ฐ๋๋ ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ค๋จ๋ ์ ์์ต๋๋ค. ๊ณ์ํ์๊ฒ ์ต๋๊น?" rotate_error = "์ฌ๋ฐ๊ธ ์คํจ: {{error}}" save_error = "์ ์ฅ ์คํจ: {{error}}" secret_rotated = "Client Secret์ด ์ฌ๋ฐ๊ธ๋์์ต๋๋ค." @@ -1157,12 +654,6 @@ access_pending = "๊ฐ๋ฐ์ ๊ถํ ์ ์ฒญ์ ๊ฒํ ์ค์ ๋๋ค." access_pending_detail = "super admin์ด ์น์ธํ๋ฉด ๊ฐ์์ ๊ฐ๋ฐ์ ๊ธฐ๋ฅ์ ์ฌ์ฉํ ์ ์์ต๋๋ค." description = "์ฐ๋ ์ฑ ๊ตฌ์ฑ๊ณผ ์ธ์ฆ ์ด์ ์งํ๋ฅผ ํ ๊ณณ์์ ํ์ธํฉ๋๋ค." -[msg.dev.dashboard.hero] -body = "Hydra Admin API์ ๋๊ธฐํ๋ RP ๋ชฉ๋ก, ์ํ ํ ๊ธ, Consent ํ์๊น์ง devfront์์ ์ฒ๋ฆฌํ๋๋ก ์ค๋นํฉ๋๋ค." -title_emphasis = " ํ๋์ ํ๋ฉด" -title_prefix = "RP ๋ฑ๋ก ํํฉ๊ณผ Consent ์ํ๋ฅผ" -title_suffix = "์์ ๊ด๋ฆฌํฉ๋๋ค." - [msg.dev.dashboard.chart] empty = "ํ์ํ RP ์ด์ฉ ์ง๊ณ๊ฐ ์์ต๋๋ค." filter_description = "์ ์ฒด ๋๋ ์ ํํ ์ ํ๋ฆฌ์ผ์ด์ ๋ง ๊ธฐ์ค์ผ๋ก ๊ทธ๋ํ๋ฅผ ํ์ธํฉ๋๋ค." @@ -1174,6 +665,17 @@ unavailable_with_reason = "RP ์ด์ฉ ํต๊ณ API ์๋ต์ ํ์ธํ ์ ์์ต [msg.dev.dashboard.distribution] description = "์ ํ๋ฆฌ์ผ์ด์ ์ ํ๊ณผ headless login ์ฌ์ฉ ํํฉ์ ๋น ๋ฅด๊ฒ ํ์ธํฉ๋๋ค." +[msg.dev.dashboard.hero] +body = "Hydra Admin API์ ๋๊ธฐํ๋ RP ๋ชฉ๋ก, ์ํ ํ ๊ธ, Consent ํ์๊น์ง devfront์์ ์ฒ๋ฆฌํ๋๋ก ์ค๋นํฉ๋๋ค." +title_emphasis = " ํ๋์ ํ๋ฉด" +title_prefix = "RP ๋ฑ๋ก ํํฉ๊ณผ Consent ์ํ๋ฅผ" +title_suffix = "์์ ๊ด๋ฆฌํฉ๋๋ค." + +[msg.dev.dashboard.notice] +consent_audit = "Consent ํ์๋ ๊ฐ์ฌ ๋ก๊ทธ์ ์ฐ๊ณ" +dev_scope = "RP ์ ์ฑ ์ dev scope์์๋ง ์ ์ฉ" +hydra_health = "Hydra Admin ์ํ ์ฒดํฌ ์ค๋น" + [msg.dev.dashboard.recent] empty = "ํ์ฌ ๊ณ์ ์ด ์ ๊ทผํ ์ ์๋ RP๋ฅผ ํ์ธํฉ๋๋ค." none = "ํ์ํ ์ฐ๋ ์ฑ์ด ์์ต๋๋ค." @@ -1182,11 +684,6 @@ none = "ํ์ํ ์ฐ๋ ์ฑ์ด ์์ต๋๋ค." description = "๋ณ๊ฒฝ ๋๋ ์ญ์ ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋์๋ณด๋์์ ์ถ์ด๋ฅผ ํ์ธํฉ๋๋ค." empty = "์ต๊ทผ ๋ณ๊ฒฝ ๋ก๊ทธ๊ฐ ์์ง ์์ต๋๋ค." -[msg.dev.dashboard.notice] -consent_audit = "Consent ํ์๋ ๊ฐ์ฌ ๋ก๊ทธ์ ์ฐ๊ณ" -dev_scope = "RP ์ ์ฑ ์ dev scope์์๋ง ์ ์ฉ" -hydra_health = "Hydra Admin ์ํ ์ฒดํฌ ์ค๋น" - [msg.dev.forbidden] default = "ํด๋น ๋ฆฌ์์ค์ ์ ๊ทผํ ๊ถํ์ด ์์ต๋๋ค. ๊ด๋ฆฌ์์๊ฒ ๋ฌธ์ํ์ธ์." rp_admin = "RP ๊ด๋ฆฌ์๋ ๋ด๋น ์ฑ์ ๋ฆฌ์์ค๋ง ์กฐํํ ์ ์์ต๋๋ค." @@ -1197,6 +694,85 @@ user.audit = "ํด๋น ์ฑ(RP)์ ๋ํ ๊ฐ์ฌ ๋ก๊ทธ ์กฐํ๋ 'RP ๊ด๋ฆฌ์', ' user.clients = "์ผ๋ฐ ์ฌ์ฉ์ ๊ณ์ ์ ๋ด๋น RP(์ฑ)์ ๋ํ ์ด์ ๋๋ ๊ด๋ฆฌ ๊ด๊ณ๊ฐ ๋ถ์ฌ๋ ๊ฒฝ์ฐ์๋ง ํด๋น ๊ธฐ๋ฅ์ ์ฌ์ฉํ ์ ์์ต๋๋ค. ๊ถํ์ด ํ์ํ๋ฉด ๊ด๋ฆฌ์์๊ฒ ์์ฒญํ์ธ์." user.consents = "ํด๋น ์ฑ(RP)์ ๋ํ ๋์ ๋ด์ญ ์กฐํ๋ 'RP ๊ด๋ฆฌ์', '๋์ ์กฐํ', '๋์ ํ์' ๊ด๊ณ๊ฐ ๋ถ์ฌ๋ ๊ฒฝ์ฐ์๋ง ์ฌ์ฉํ ์ ์์ต๋๋ค. ๊ถํ์ด ํ์ํ๋ฉด ๊ด๋ฆฌ์์๊ฒ ์์ฒญํ์ธ์." +[msg.dev.grants] +admin_notes_description = "์ง์ ๋ถ์ฌ์ ๊ทผ๊ฑฐ๋ฅผ ๊ฐ๋จํ ๋จ๊ฒจ ๋๋ฉด ์ถํ ํ์์ ๊ฒํ ์ ๋์์ด ๋ฉ๋๋ค." +admin_notes_hint = "ํ์๋ ๋ชฉ๋ก์ ํ์ ๋ฒํผ์ผ๋ก ์ฒ๋ฆฌํฉ๋๋ค." +admin_notes_placeholder = "์: ํ ์คํธ ํ๊ฒฝ ํ์ธ ํ ๊ถํ ๋ถ์ฌ" +approved = "์น์ธ๋จ" +count = "์ด {{count}}๊ฑด" +create_success = "๊ฐ๋ฐ์ ๊ถํ์ ์ง์ ๋ถ์ฌํ์ต๋๋ค." +description = "์ฌ์ฉ์์๊ฒ ๊ฐ๋ฐ์ ๊ถํ์ ์ง์ ๋ถ์ฌํ๊ณ , ๋ถ์ฌ๋ ๊ถํ์ ํ์ํฉ๋๋ค." +empty = "๋ถ์ฌ๋ ๊ถํ์ด ์์ต๋๋ค." +forbidden = "๊ฐ๋ฐ์ ๊ถํ ์ง์ ๋ถ์ฌ๋ super admin๋ง ์ฌ์ฉํ ์ ์์ต๋๋ค." +forbidden_desc = "์ด ํ๋ฉด์ super admin๋ง ์ฌ์ฉํ ์ ์์ต๋๋ค." +load_error = "๊ฐ๋ฐ์ ๊ถํ ๋ชฉ๋ก์ ๋ถ๋ฌ์ค์ง ๋ชปํ์ต๋๋ค." +pages_hint = "์ ์ฒด๋ฅผ ์ ํํ๋ฉด ๊ฐ์, ์ฐ๋ ์ฑ ์ถ๊ฐ, ๊ฐ์ฌ๋ก๊ทธ๊ฐ ๋ชจ๋ ํฌํจ๋ฉ๋๋ค." +phone_missing = "๋ฑ๋ก๋ ์ ํ๋ฒํธ๊ฐ ์์ต๋๋ค." +reason = "๋ถ์ฌ ์ฌ์ " +revoke = "ํ์" +revoke_success = "๊ฐ๋ฐ์ ๊ถํ์ ํ์ํ์ต๋๋ค." +search_empty = "๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค." +search_loading = "์ฌ์ฉ์๋ฅผ ์ฐพ๋ ์ค์ ๋๋ค..." +selected_info_description = "์ ํ๋ ์ฌ์ฉ์์ ์์, ์ด๋ฉ์ผ, ์ ํ๋ฒํธ๋ฅผ ํ์ธํฉ๋๋ค." +selected_user = "์ ํ๋ ์ฌ์ฉ์: {{user}}" +tenant_missing = "์ ํํ ์ฌ์ฉ์์ ํ ๋ํธ ์ ๋ณด๋ฅผ ํ์ธํ ์ ์์ต๋๋ค." +tenant_required = "์ ํํ ์ฌ์ฉ์์ ํ ๋ํธ ์ ๋ณด๋ฅผ ํ์ธํ ์ ์์ต๋๋ค." +user_required = "๋ถ์ฌํ ์ฌ์ฉ์๋ฅผ ์ ํํด์ฃผ์ธ์." +user_section_description = "๊ฒ์์ด๋ฅผ ์ ๋ ฅํด ์ฌ์ฉ์๋ฅผ ์ ํํฉ๋๋ค. ์ ํ ์ ์๋ ๋ค์ ๋จ๊ณ ์ ๋ณด๊ฐ ๋น์ด ์์ต๋๋ค." + +[msg.dev.grants.form] +description = "์ฌ์ฉ์๋ฅผ ์ ํํ๋ฉด ํ์ฌ ์์ ํ ๋ํธ, ์ด๋ฉ์ผ, ์ ํ๋ฒํธ๋ฅผ ํ์ธํ ๋ค ๊ฐ๋ฐ์ ๊ถํ์ ์ฆ์ ๋ถ์ฌํฉ๋๋ค." + +[msg.dev.grants.list] +description = "ํ์ฌ ๋ถ์ฌ๋ ๊ฐ๋ฐ์ ๊ถํ ๋ชฉ๋ก์ ๋๋ค." + +[msg.dev.request] +admin_desc = "super admin์ด ๊ฐ๋ฐ์ ๊ถํ ์ ์ฒญ์ ๊ฒํ ํ๊ณ ์น์ธ ๋๋ ๋ฐ๋ คํ ์ ์์ต๋๋ค." +approved = "์น์ธ๋์์ต๋๋ค." +cancelled = "์น์ธ์ด ์ทจ์๋์์ต๋๋ค." +empty = "์ ์ฒญ ๋ด์ญ์ด ์์ต๋๋ค." +need_cancel_notes = "์น์ธ ์ทจ์ ์ฌ์ ๋ฅผ ์ ๋ ฅํด์ฃผ์ธ์." +need_notes = "๋ฐ๋ ค ์ฌ์ ๋ฅผ ์ ๋ ฅํด์ฃผ์ธ์." +rejected = "๋ฐ๋ ค๋์์ต๋๋ค." +user_desc = "๊ฐ๋ฐ์ ๊ถํ์ ์ ์ฒญํ๊ณ ์น์ธ ๊ฒฐ๊ณผ๋ฅผ ํ์ธํ ์ ์์ต๋๋ค." + +[msg.dev.request.admin] +notes_placeholder = "์น์ธ ๋๋ ๋ฐ๋ ค ์ฌ์ ๋ฅผ ์ ๋ ฅํ์ธ์." + +[msg.dev.request.cancel] +approval = "์น์ธ ์ทจ์" +notes_placeholder = "์น์ธ ์ทจ์ ์ฌ์ ๋ฅผ ์ ๋ ฅํ์ธ์." + +[msg.dev.request.list] +approved_count = "์ด {{count}}๋ช ์ ์ฌ์ฉ์๊ฐ ์น์ธ๋์์ต๋๋ค." +title = "์ ์ฒญ ๋ด์ญ" + +[msg.dev.request.modal] +desc = "๊ฐ๋ฐ์ ๊ถํ์ ์ ์ฒญํ๋ ค๋ฉด ์๋ ์ ๋ณด๋ฅผ ํ์ธํ ๋ค ์ ์ฒญ ์ฌ์ ๋ฅผ ์ ๋ ฅํ์ธ์." +email = "์ด๋ฉ์ผ" +name = "์ฑํจ" +org = "์์" +pages_hint = "์ ์ฒด๋ฅผ ์ ํํ๋ฉด ๊ฐ์, ์ฐ๋ ์ฑ ์ถ๊ฐ, ๊ฐ์ฌ๋ก๊ทธ๊ฐ ๋ชจ๋ ํฌํจ๋ฉ๋๋ค." +phone = "์ ํ๋ฒํธ" +reason = "์ ์ฒญ ์ฌ์ " +reason_placeholder = "๊ฐ๋ฐ์ ๊ถํ์ด ํ์ํ ์ด์ ๋ฅผ ์์ฑํด์ฃผ์ธ์." +role = "์ญํ " +title = "๊ฐ๋ฐ์ ๋ฑ๋ก ์ ์ฒญ" + +[msg.dev.request.status] +approved = "์น์ธ๋จ" +cancelled = "์น์ธ ์ทจ์๋จ" +pending = "๋๊ธฐ ์ค" +rejected = "๋ฐ๋ ค๋จ" + +[msg.dev.request.table] +actions = "๊ด๋ฆฌ" +date = "์ ์ฒญ์ผ" +org = "์์" +reason = "์ ์ฒญ ์ฌ์ " +status = "์ํ" +user = "์ฌ์ฉ์" + [msg.dev.sidebar] notice = "๊ฐ๋ฐ์ ์ ์ฉ ์ฝ์์ ๋๋ค." notice_detail = "์ฐ๋ ์ฑ ๋ฑ๋ก ๋ฐ ๊ด๋ฆฌ๋ฅผ ์ํํ ์ ์์ต๋๋ค." @@ -1219,11 +795,14 @@ result = "์ธ์ฆ๊ฒฐ๊ณผ: {{value}}" session_id = "Session ID: {{value}}" status = "ํํฉ: (์ค๋น์ค)" +[msg.userfront.audit.filter] +description = "ํ์ฑํ๋ ์ธ์ ๋ง ๋ณด๋ ค๋ฉด ํ ๊ธ์ ์ผ์ฃผ์ธ์." + [msg.userfront.consent] accept_error = "๋์ ์ฒ๋ฆฌ์ ์คํจํ์ต๋๋ค: {{error}}" client_id = "ํด๋ผ์ด์ธํธ ID: {{id}}" client_unknown = "์ ์ ์๋ ์ฑ" -description = "์๋ ์๋น์ค๊ฐ ํ์๋์ ๊ณ์ ์ ๋ณด์ ์ ๊ทผํ๋ ค๊ณ ํฉ๋๋ค.\\\\n๊ณ์ ์งํํ๋ ค๋ฉด ๋์ ์ฌ๋ถ๋ฅผ ์ ํํด ์ฃผ์ธ์." +description = "์๋ ์๋น์ค๊ฐ ํ์๋์ ๊ณ์ ์ ๋ณด์ ์ ๊ทผํ๋ ค๊ณ ํฉ๋๋ค.\\\\\\\\n๊ณ์ ์งํํ๋ ค๋ฉด ๋์ ์ฌ๋ถ๋ฅผ ์ ํํด ์ฃผ์ธ์." load_error = "๋์ ์ ๋ณด๋ฅผ ๋ถ๋ฌ์ค๋๋ฐ ์คํจํ์ต๋๋ค: {{error}}" missing_redirect = "๋์๊ฐ ์ฒ๋ฆฌ๋์์ผ๋ ๋ฆฌ๋ค์ด๋ ํธ URL์ ๋ฐ์ง ๋ชปํ์ต๋๋ค." redirect_notice = "๋์ ํ ์๋์ผ๋ก ์๋น์ค๋ก ์ด๋ํฉ๋๋ค." @@ -1245,14 +824,15 @@ approved_device = "์น์ธ ๊ธฐ๊ธฐ: {{device}}" approved_ip = "์น์ธ IP: {{ip}}" audit_empty = "์ต๊ทผ ์ ์ ์ด๋ ฅ์ด ์์ต๋๋ค." audit_load_error = "์ ์์ด๋ ฅ์ ๋ถ๋ฌ์ค์ง ๋ชปํ์ต๋๋ค." -auto_login_supported = "์ฐ๋์ฑ ํด๋ฆญ ์ ๋ณ๋ ๋ก๊ทธ์ธ ์์ด ๋ก๊ทธ์ธํ ์ ์์ต๋๋ค." auth_method = "์ธ์ฆ์๋จ: {{method}}" +auto_login_supported = "์ฐ๋์ฑ ํด๋ฆญ ์ ๋ณ๋ ๋ก๊ทธ์ธ ์์ด ๋ก๊ทธ์ธํ ์ ์์ต๋๋ค." client_id = "Client ID: {{id}}" client_id_missing = "Client ID ์์" current_status = "ํ์ฌ ์ํ: {{status}}" last_auth = "์ต๊ทผ ์ธ์ฆ: {{value}}" link_missing = "์ด๋ํ ํ์ด์ง ์ฃผ์(Client URI)๊ฐ ์ค์ ๋์ง ์์์ต๋๋ค." link_open_error = "ํด๋น ๋งํฌ๋ฅผ ์ด ์ ์์ต๋๋ค." +link_status = "์ฐ๋ ์ํ: {{status}}" render_error = "๋์๋ณด๋ ๋ ๋๋ง ์ค๋ฅ: {{error}}" session_id_copied = "์ธ์ ID๊ฐ ๋ณต์ฌ๋์์ต๋๋ค." @@ -1261,6 +841,19 @@ empty = "์ฐ๋๋ ์ฑ์ด ์์ต๋๋ค." empty_detail = "์ฑ์ ์ฐ๋ํ๋ฉด ์ต๊ทผ ํ๋๊ณผ ์ํ๊ฐ ํ์๋ฉ๋๋ค." error = "์ฐ๋ ์ ๋ณด๋ฅผ ๋ถ๋ฌ์ค์ง ๋ชปํ์ต๋๋ค." +[msg.userfront.dashboard.approved_session] +copy_click = "{{label}}: {{id}}\\\\\\\\nํด๋ฆญํ๋ฉด ๋ณต์ฌ๋ฉ๋๋ค." +copy_tap = "{{label}}: {{id}}\\\\\\\\nํญํ๋ฉด ๋ณต์ฌ๋ฉ๋๋ค." +none = "{{label}} ์์" + +[msg.userfront.dashboard.revoke] +confirm = "{{app}} ์ฑ๊ณผ์ ์ฐ๋์ ํด์งํ์๊ฒ ์ต๋๊น?\\\\\\\\nํด์งํ๋ฉด ๋ค์ ๋ก๊ทธ์ธ ์ ๋ค์ ๋์๊ฐ ํ์ํฉ๋๋ค." +error = "ํด์ง ์คํจ: {{error}}" +success = "{{app}} ์ฐ๋์ด ํด์ง๋์์ต๋๋ค." + +[msg.userfront.dashboard.scopes] +empty = "์์ฒญ๋ ๊ถํ์ด ์์ต๋๋ค." + [msg.userfront.dashboard.sessions] browser = "๋ธ๋ผ์ฐ์ : {{value}}" empty = "ํ์ฑ ์ธ์ ์ด ์์ต๋๋ค." @@ -1271,23 +864,10 @@ recent_app = "์ต๊ทผ ์ ์ ์ฑ: {{app}}" session_id = "์ธ์ ID: {{id}}" [msg.userfront.dashboard.sessions.revoke] -confirm = "{{target}} ์ธ์ ์ ์ข ๋ฃํ์๊ฒ ์ต๋๊น?\n๋์ ๊ธฐ๊ธฐ์์๋ ๋ค์ ๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค." +confirm = "{{target}} ์ธ์ ์ ์ข ๋ฃํ์๊ฒ ์ต๋๊น?\\n๋์ ๊ธฐ๊ธฐ์์๋ ๋ค์ ๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค." error = "์ธ์ ์ข ๋ฃ ์คํจ: {{error}}" success = "์ธ์ ์ด ์ข ๋ฃ๋์์ต๋๋ค." -[msg.userfront.dashboard.approved_session] -copy_click = "{{label}}: {{id}}\\\\nํด๋ฆญํ๋ฉด ๋ณต์ฌ๋ฉ๋๋ค." -copy_tap = "{{label}}: {{id}}\\\\nํญํ๋ฉด ๋ณต์ฌ๋ฉ๋๋ค." -none = "{{label}} ์์" - -[msg.userfront.dashboard.revoke] -confirm = "{{app}} ์ฑ๊ณผ์ ์ฐ๋์ ํด์งํ์๊ฒ ์ต๋๊น?\\\\nํด์งํ๋ฉด ๋ค์ ๋ก๊ทธ์ธ ์ ๋ค์ ๋์๊ฐ ํ์ํฉ๋๋ค." -error = "ํด์ง ์คํจ: {{error}}" -success = "{{app}} ์ฐ๋์ด ํด์ง๋์์ต๋๋ค." - -[msg.userfront.dashboard.scopes] -empty = "์์ฒญ๋ ๊ถํ์ด ์์ต๋๋ค." - [msg.userfront.dashboard.timeline] load_error = "์ ์์ด๋ ฅ์ ๋ถ๋ฌ์ค์ง ๋ชปํ์ต๋๋ค." @@ -1301,6 +881,22 @@ title_generic = "์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค" title_with_code = "์ค๋ฅ: {{code}}" type = "์ค๋ฅ ์ข ๋ฅ: {{type}}" +[msg.userfront.error.ory] +$normalizedCode = "{{error}}" +access_denied = "์ฌ์ฉ์๊ฐ ๋์๋ฅผ ๊ฑฐ๋ถํ์ต๋๋ค." +consent_required = "์ฑ ์ ๊ทผ ๋์๊ฐ ํ์ํฉ๋๋ค." +interaction_required = "์ถ๊ฐ ์ํธ์์ฉ์ด ํ์ํฉ๋๋ค. ๋ค์ ์๋ํด ์ฃผ์ธ์." +invalid_client = "ํด๋ผ์ด์ธํธ ์ธ์ฆ ์ ๋ณด๊ฐ ์ ํจํ์ง ์์ต๋๋ค." +invalid_grant = "์ธ์ฆ ์์ฒญ์ด ๋ง๋ฃ๋์๊ฑฐ๋ ์ ํจํ์ง ์์ต๋๋ค." +invalid_request = "์๋ชป๋ ์์ฒญ์ ๋๋ค." +invalid_scope = "์์ฒญํ ๊ถํ ๋ฒ์๊ฐ ์ ํจํ์ง ์์ต๋๋ค." +login_required = "๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค." +request_forbidden = "์์ฒญ์ด ๊ฑฐ๋ถ๋์์ต๋๋ค." +server_error = "์ธ์ฆ ์๋ฒ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค." +temporarily_unavailable = "์ธ์ฆ ์๋ฒ๋ฅผ ์ผ์์ ์ผ๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค." +unauthorized_client = "ํด๋น ํด๋ผ์ด์ธํธ๋ ์ด ์์ฒญ์ ์ํํ ์ ์์ต๋๋ค." +unsupported_response_type = "์ง์ํ์ง ์๋ ์๋ต ํ์ ์ ๋๋ค." + [msg.userfront.error.tenant] account = "๊ณ์ " account_unknown = "์ ์ ์์" @@ -1317,24 +913,8 @@ tenant = "์์ ํ ๋ํธ" tenant_unknown = "์ ์ ์์" title = "์ ๊ทผ ์ ํ ์ ๋ณด" -[msg.userfront.error.ory] -"$normalizedCode" = "{{error}}" -access_denied = "์ฌ์ฉ์๊ฐ ๋์๋ฅผ ๊ฑฐ๋ถํ์ต๋๋ค." -consent_required = "์ฑ ์ ๊ทผ ๋์๊ฐ ํ์ํฉ๋๋ค." -interaction_required = "์ถ๊ฐ ์ํธ์์ฉ์ด ํ์ํฉ๋๋ค. ๋ค์ ์๋ํด ์ฃผ์ธ์." -invalid_client = "ํด๋ผ์ด์ธํธ ์ธ์ฆ ์ ๋ณด๊ฐ ์ ํจํ์ง ์์ต๋๋ค." -invalid_grant = "์ธ์ฆ ์์ฒญ์ด ๋ง๋ฃ๋์๊ฑฐ๋ ์ ํจํ์ง ์์ต๋๋ค." -invalid_request = "์๋ชป๋ ์์ฒญ์ ๋๋ค." -invalid_scope = "์์ฒญํ ๊ถํ ๋ฒ์๊ฐ ์ ํจํ์ง ์์ต๋๋ค." -login_required = "๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค." -request_forbidden = "์์ฒญ์ด ๊ฑฐ๋ถ๋์์ต๋๋ค." -server_error = "์ธ์ฆ ์๋ฒ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค." -temporarily_unavailable = "์ธ์ฆ ์๋ฒ๋ฅผ ์ผ์์ ์ผ๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค." -unauthorized_client = "ํด๋น ํด๋ผ์ด์ธํธ๋ ์ด ์์ฒญ์ ์ํํ ์ ์์ต๋๋ค." -unsupported_response_type = "์ง์ํ์ง ์๋ ์๋ต ํ์ ์ ๋๋ค." - [msg.userfront.error.whitelist] -"$normalizedCode" = "{{error}}" +$normalizedCode = "{{error}}" bad_request = "์ ๋ ฅ๊ฐ์ ํ์ธํด ์ฃผ์ธ์." invalid_session = "์ธ์ ์ด ๋ง๋ฃ๋์์ต๋๋ค. ๋ค์ ๋ก๊ทธ์ธํด ์ฃผ์ธ์." not_found = "์์ฒญํ ํ์ด์ง๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค." @@ -1388,14 +968,14 @@ scan_hint = "๋ชจ๋ฐ์ผ ์ฑ์ผ๋ก ์ค์บํ์ธ์" invalid = "๋ฌธ์ 2๊ฐ์ ์ซ์ 6์๋ฆฌ๋ฅผ ์ ๋ ฅํด ์ฃผ์ธ์." [msg.userfront.login.unregistered] -body = "๊ฐ์ ๋์ง ์์ ์ ๋ณด์ ๋๋ค.\\\\nํ์๊ฐ์ ํ ์ด์ฉํด ์ฃผ์ธ์." +body = "๊ฐ์ ๋์ง ์์ ์ ๋ณด์ ๋๋ค.\\\\\\\\nํ์๊ฐ์ ํ ์ด์ฉํด ์ฃผ์ธ์." [msg.userfront.login.verification] approved = "์น์ธ๋์์ต๋๋ค. ๋ก๊ทธ์ธ์ ์์ฒญํ์ ์ฐฝ์์ ์๋ฃ๋ฉ๋๋ค." approved_local = "์น์ธ ๋์์ต๋๋ค. ์ด ๊ธฐ๊ธฐ๋ ๋ก๊ทธ์ธ๋์ด ์๋ ์ํ์ ๋๋ค. ์๊ฒฉ ์ฐฝ๋ ๋ก๊ทธ์ธ์ด ๋ ์์ ์ ๋๋ค" approved_remote = "์์ฒญํ์ ๋ก๊ทธ์ธ์ด ์๋ฃ๋์์ต๋๋ค" -pending_remote = "์น์ธ ์์ฒญ์ ํ์ธํ๊ณ ์์ต๋๋ค. ์ ์๋ง ๊ธฐ๋ค๋ ค ์ฃผ์ธ์." close_hint = "์ด ์ฐฝ์ ์ด์ ๋ซ์ผ์ ๋ ๋ฉ๋๋ค." +pending_remote = "์น์ธ ์์ฒญ์ ํ์ธํ๊ณ ์์ต๋๋ค. ์ ์๋ง ๊ธฐ๋ค๋ ค ์ฃผ์ธ์." success = "๋ก๊ทธ์ธ ์น์ธ์ ์ฑ๊ณตํ์ต๋๋ค." [msg.userfront.login_success] @@ -1470,6 +1050,7 @@ uppercase = "๋๋ฌธ์ 1๊ฐ ์ด์" [msg.userfront.sections] apps_subtitle = "ํ์ฌ ์ฐ๊ฒฐ๋ ์ฑ๊ณผ ์ต๊ทผ ์ธ์ฆ ์ํ์ ๋๋ค." audit_subtitle = "Baron ๋ก๊ทธ์ธ ๊ธฐ์ค์ ์ต๊ทผ ์ ๊ทผ ๊ธฐ๋ก์ ๋๋ค." +sessions_subtitle = "ํ์ฌ ๋ก๊ทธ์ธ๋ ๊ธฐ๊ธฐ์ ๋ธ๋ผ์ฐ์ ์ธ์ ์ ๋๋ค." [msg.userfront.settings] disabled = "ํ์ฌ ๊ณ์ ์ค์ ํ๋ฉด์ ์ค๋น ์ค์ ๋๋ค." @@ -1484,12 +1065,12 @@ all_hint = "ํ์ ์ฝ๊ด 2๊ฐ๋ฅผ ๋ชจ๋ ํ์ธํ๊ณ ๋์ํ๋ฉด ๋ค์ ๋จ๊ณ description = "๊ณ์ ์งํํ๋ ค๋ฉด ์๋น์ค ์ด์ฉ ์กฐ๊ฑด๊ณผ ๊ฐ์ธ์ ๋ณด ์์งยท์ด์ฉ ํญ๋ชฉ์ ํ์ธํ ๋ค ๋์ํด์ฃผ์ธ์." privacy_summary = "๊ฐ์ธ์ ๋ณด ์์ง ํญ๋ชฉ, ์ด์ฉ ๋ชฉ์ , ๋ณด๊ด ๊ธฐ์ค์ ์๋ดํฉ๋๋ค." progress = "ํ์ ์ฝ๊ด {{total}}๊ฐ ์ค {{count}}๊ฐ ๋์ ์๋ฃ" -title = "์๋น์ค ์ด์ฉ์ ์ํด\\\\n์ฝ๊ด์ ๋์ํด์ฃผ์ธ์" +title = "์๋น์ค ์ด์ฉ์ ์ํด\\\\\\\\n์ฝ๊ด์ ๋์ํด์ฃผ์ธ์" tos_summary = "์๋น์ค ์ด์ฉ ์กฐ๊ฑด๊ณผ ์ฑ ์ ๋ฒ์๋ฅผ ํ์ธํ ์ ์์ต๋๋ค." [msg.userfront.signup.auth] affiliate_notice = "๊ฐ์กฑ์ฌ ํ์์ ๊ฒฝ์ฐ ๋ฐ๋์ ํ์ฌ ๊ณต์ ์ด๋ฉ์ผ์ ์ ๋ ฅํด์ฃผ์ธ์." -title = "๋ณธ์ธ ํ์ธ์ ์ํด\\\\n์ธ์ฆ์ ์งํํด์ฃผ์ธ์" +title = "๋ณธ์ธ ํ์ธ์ ์ํด\\\\\\\\n์ธ์ฆ์ ์งํํด์ฃผ์ธ์" [msg.userfront.signup.email] code_mismatch = "์ธ์ฆ์ฝ๋๊ฐ ์ผ์นํ์ง ์์ต๋๋ค." @@ -1505,7 +1086,7 @@ lowercase_required = "์๋ฌธ์๊ฐ ์ต์ 1๊ฐ ์ด์ ํฌํจ๋์ด์ผ ํฉ๋๋ค. mismatch = "๋น๋ฐ๋ฒํธ๊ฐ ์ผ์นํ์ง ์์ต๋๋ค." number_required = "์ซ์๊ฐ ์ต์ 1๊ฐ ์ด์ ํฌํจ๋์ด์ผ ํฉ๋๋ค." symbol_required = "ํน์๋ฌธ์๊ฐ ์ต์ 1๊ฐ ์ด์ ํฌํจ๋์ด์ผ ํฉ๋๋ค." -title = "๋ง์ง๋ง์ผ๋ก\\\\n๋น๋ฐ๋ฒํธ๋ฅผ ์ค์ ํด์ฃผ์ธ์" +title = "๋ง์ง๋ง์ผ๋ก\\\\\\\\n๋น๋ฐ๋ฒํธ๋ฅผ ์ค์ ํด์ฃผ์ธ์" uppercase_required = "๋๋ฌธ์๊ฐ ์ต์ 1๊ฐ ์ด์ ํฌํจ๋์ด์ผ ํฉ๋๋ค." [msg.userfront.signup.password.rule] @@ -1534,7 +1115,7 @@ uppercase = "๋๋ฌธ์" [msg.userfront.signup.profile] affiliate_hint = "๊ฐ์กฑ์ฌ ์ด๋ฉ์ผ ์ฌ์ฉ ์ ์๋์ผ๋ก ์ ํ๋ฉ๋๋ค." -title = "ํ์๋์\\\\n์์ ์ ๋ณด๋ฅผ ์๋ ค์ฃผ์ธ์" +title = "ํ์๋์\\\\\\\\n์์ ์ ๋ณด๋ฅผ ์๋ ค์ฃผ์ธ์" [msg.userfront.signup.success] body = "์ฑ๊ณต์ ์ผ๋ก ๊ฐ์ ๋์์ต๋๋ค." @@ -1548,6 +1129,17 @@ key = "์กด์ฌํ์ง ์๋ ํค" [test] key = "ํ ์คํธ" +[this] + +[this.key] + +[this.key.truly] + +[this.key.truly.does] + +[this.key.truly.does.not] +exist = "Fallback" + [ui] [ui.admin] @@ -1593,6 +1185,11 @@ last_used = "LAST USED" name = "NAME" scopes = "SCOPES" +[ui.admin.apikeys] + +[ui.admin.apikeys.registry] +title = "API Key Registry" + [ui.admin.audit] export_csv = "Export CSV" load_more = "Load more" @@ -1638,6 +1235,32 @@ request = "REQUEST" status = "STATUS" time = "TIME" +[ui.admin.auth_guard] +subtitle = "๊ด๋ฆฌ์ ๊ถํ๊ณผ ReBAC ๊ด๊ณ๋ฅผ ์ค์ ์ ์ฑ ์์ง ๊ธฐ์ค์ผ๋ก ํ์ธํฉ๋๋ค." +title = "์ธ์ฆ ๊ฐ๋" + +[ui.admin.auth_guard.checker] +allowed = "์ ๊ทผ ํ์ฉ" +allowed_description = "ํด๋น ์ฌ์ฉ์๋ ์์ฒญํ ๋ฆฌ์์ค์ ๋ํด ๊ถํ์ด ์์ต๋๋ค. (์์ ํฌํจ)" +check = "๊ถํ ํ์ธ ์คํ" +checking = "๊ฒ์ฆ ์ค..." +denied = "์ ๊ทผ ๊ฑฐ๋ถ" +denied_description = "ํด๋น ์ฌ์ฉ์๋ ์์ฒญํ ๋ฆฌ์์ค์ ๋ํด ๊ถํ์ด ์์ต๋๋ค." +description = "ํน์ ์ฃผ์ฒด(Subject)๊ฐ ํน์ ๋ฆฌ์์ค(Object)์ ๋ํด ๊ถํ์ด ์๋์ง Ory Keto๋ฅผ ํตํด ์ค์๊ฐ์ผ๋ก ํ์ธํฉ๋๋ค." +namespace = "๋ค์์คํ์ด์ค" +namespace.label = "๋ค์์คํ์ด์ค" +namespace.relying_party = "์ ํ๋ฆฌ์ผ์ด์ (RP)" +namespace.system = "์์คํ " +namespace.tenant = "ํ ๋ํธ" +namespace.tenant_group = "ํ ๋ํธ ๊ทธ๋ฃน" +object_id = "๋์ ID" +object_id_placeholder = "Tenant UUID ๋ฑ" +relation = "๊ด๊ณ" +relation_placeholder = "view, manage, admins..." +subject = "์ฃผ์ฒด (User:ID)" +subject_placeholder = "User:uuid ๋๋ Namespace:ID#Relation" +title = "ReBAC ๊ถํ ๊ฒ์ฆ ๋๊ตฌ" + [ui.admin.groups] import_csv = "CSV ์ํฌํธ" @@ -1686,18 +1309,95 @@ name = "NAME" plane = "Admin Plane" subtitle = "๊ด๋ฆฌ ๋ฐ ์ ์ฑ ์ด์" +[ui.admin.integrity] +fetch_error = "์ ํฉ์ฑ ์ต์ข ๊ฒ์ฆ ๊ฒฐ๊ณผ๋ฅผ ๋ถ๋ฌ์ค์ง ๋ชปํ์ต๋๋ค." +kicker = "์์คํ " +loading = "๋ถ๋ฌ์ค๋ ์ค" +subtitle = "์ ํฉ์ฑ ์ํ๋ฅผ ํ์ธํ๊ณ ๋ฐ์ดํฐ ๋ชจ๋ธ ์ ๋ฐ์ ๊ฒ์ฆ ๊ฒฐ๊ณผ๋ฅผ ์ดํด๋ด ๋๋ค." +tab_checks = "์ ํฉ์ฑ ๊ฒ์ฌ" +tab_ory_ssot = "Ory SSOT ์์คํ " +tab_user_projection = "์ฌ์ฉ์ ๋๊ธฐํ" +title = "๋ฐ์ดํฐ ์ ํฉ์ฑ ๊ฒ์ฆ" + +[ui.admin.integrity.check] + +[ui.admin.integrity.check.duplicate_tenant_slugs] +title = "์ค๋ณต ํ ๋ํธ slug" + +[ui.admin.integrity.check.orphan_tenant_parents] +title = "๊ณ ์ ํ ๋ํธ ๋ถ๋ชจ" + +[ui.admin.integrity.check.orphan_user_login_id_tenants] +title = "๊ณ ์ ๋ก๊ทธ์ธ ID ํ ๋ํธ" + +[ui.admin.integrity.check.orphan_user_login_id_users] +title = "๊ณ ์ ๋ก๊ทธ์ธ ID ์ฌ์ฉ์" + +[ui.admin.integrity.check.orphan_user_tenant_memberships] +title = "๊ณ ์ ์ฌ์ฉ์ ํ ๋ํธ ์์" + +[ui.admin.integrity.forbidden] +title = "์ ๊ทผ ๊ถํ์ด ์์ต๋๋ค" + +[ui.admin.integrity.orphan_login_ids] +delete = "์ ํ ์ญ์ " +title = "์ ๋ น ๋ก๊ทธ์ธ ID ์ ๋ฆฌ" + +[ui.admin.integrity.read_model] +title = "์ฝ๊ธฐ ๋ชจ๋ธ ์ ํฉ์ฑ" + +[ui.admin.integrity.reason] +deleted_tenant = "์ญ์ ๋ ํ ๋ํธ" +deleted_user = "์ญ์ ๋ ์ฌ์ฉ์" +missing_tenant = "ํ ๋ํธ ์์" +missing_user = "์ฌ์ฉ์ ์์" + +[ui.admin.integrity.recheck] +run = "๋ค์ ๊ฒ์ฌ" +running = "๊ฒ์ฌ ์ค" + +[ui.admin.integrity.section] +tenant_integrity = "ํ ๋ํธ ์ ํฉ์ฑ" +user_integrity = "์ฌ์ฉ์ ์ ํฉ์ฑ" + +[ui.admin.integrity.status] +fail = "์คํจ" +pass = "์ ์" +warning = "์ฃผ์" + +[ui.admin.integrity.summary] +checked_at = "๊ฒ์ฌ ์๊ฐ" +failures = "์คํจ ๊ฑด์" +failures_text = "์คํจ {{count}}๊ฑด" +passed = "์ ์" +title = "์ ํฉ์ฑ ์ต์ข ๊ฒ์ฆ" +total_checks = "๊ฒ์ฌ ํญ๋ชฉ" + +[ui.admin.integrity.table] +field = "ํ๋" +login_id = "๋ก๊ทธ์ธ ID" +reason = "์ฌ์ " +select = "์ ํ" +select_item = "{{loginId}} ์ ํ" +tenant = "ํ ๋ํธ" +user = "์ฌ์ฉ์" + [ui.admin.nav] -org_chart = "์กฐ์ง๋" api_keys = "API ํค" audit_logs = "๊ฐ์ฌ ๋ก๊ทธ" auth_guard = "์ธ์ฆ ๊ฐ๋" +data_integrity = "๋ฐ์ดํฐ ์ ํฉ์ฑ" logout = "๋ก๊ทธ์์" +org_chart = "์กฐ์ง๋" +ory_ssot = "Ory SSOT ์์คํ " overview = "๊ฐ์" +permissions_direct = "๊ถํ ๋ถ์ฌ" relying_parties = "์ ํ๋ฆฌ์ผ์ด์ (RP)" tenant_dashboard = "ํ ๋ํธ ๋์๋ณด๋" tenants = "ํ ๋ํธ" user_groups = "์ ์ ๊ทธ๋ฃน" users = "์ฌ์ฉ์" +worksmobile = "Worksmobile" [ui.admin.org] download_template = "ํ ํ๋ฆฟ ๋ค์ด๋ก๋" @@ -1705,10 +1405,46 @@ import_btn = "์ํฌํธ" import_title = "์กฐ์ง๋ ๋๋ ๋ฑ๋ก" start_import = "์ํฌํธ ์์" +[ui.admin.ory_ssot] +loading = "๋ถ๋ฌ์ค๋ ์ค" +title = "Ory SSOT ์์คํ " + +[ui.admin.ory_ssot.actions] +flush_identity_cache = "Redis cache flush" + +[ui.admin.ory_ssot.cache_card] +description = "Kratos identity ๋ชฉ๋ก ๋ฐ ์กฐํ ์์ ์ ์ํ Redis mirror/cache ์ํ์ ๋๋ค." +title = "Redis identity cache" + +[ui.admin.ory_ssot.forbidden] +title = "์ ๊ทผ ๊ถํ์ด ์์ต๋๋ค" + +[ui.admin.ory_ssot.projection_card] +description = "๊ด๋ฆฌ์ ๊ฒ์๊ณผ ํต๊ณ์์ ์ฌ์ฉํ๋ PostgreSQL read model ์ํ์ ๋๋ค." +title = "Backend ์ฌ์ฉ์ read model" + +[ui.admin.ory_ssot.status] +failed = "์คํจ" +not_ready = "์ค๋น๋์ง ์์" +ready = "์ค๋น๋จ" + +[ui.admin.ory_ssot.summary] +cache_keys = "Cache keys" +last_refreshed = "๋ง์ง๋ง refresh" +last_synced = "๋ง์ง๋ง read-model refresh" +local_users = "Local users" +observed_identities = "๊ด์ธก identity" +status = "์ํ" +updated_at = "์ํ ๊ฐฑ์ " + [ui.admin.overview] kicker = "Global Overview" title = "ํตํฉ ๋์๋ณด๋" +[ui.admin.overview.chart] +description = "์ ์ฒด ๋๋ ์ ํํ ์กฐ์ง ๊ธฐ์ค์ผ๋ก ๊ทธ๋ํ๋ฅผ ํ์ธํฉ๋๋ค." +title = "ํ์ฌ๋ณ ์ฑ๋ณ ๋ก๊ทธ์ธ ์์ฒญ ํํฉ" + [ui.admin.overview.playbook] title = "Admin playbook" @@ -1723,8 +1459,20 @@ view_audit_logs = "๊ฐ์ฌ ๋ก๊ทธ ๋ณด๊ธฐ" audit_events_24h = "24์๊ฐ ์ด๋ฒคํธ" oidc_clients = "OIDC ํด๋ผ์ด์ธํธ" policy_gate = "์ ์ฑ ๊ฒ์ดํธ" -total_users = "์ ์ฒด ์ฌ์ฉ์ ์" total_tenants = "์ ์ฒด ํ ๋ํธ ์" +total_users = "์ ์ฒด ์ฌ์ฉ์ ์" + +[ui.admin.permissions_direct] +allowed = "๊ฐ ํ์ฉ๋จ" +cat_dashboard = "ํต์ฌ ๋์๋ณด๋ ๋ฐ ๋ถ์" +cat_integrations = "์ธํ๋ผ ์ฐ๋ ๋ฐ ๋ณด์" +cat_resources = "ํต์ฌ ๋ฆฌ์์ค ๊ด๋ฆฌ" +cat_system = "์์ด๋ดํฐํฐ ๋ฐ ๊ฒ์ดํธ ๊ด๋ฆฌ" +dialog_title_system = "์์คํ ๊ถํ ๊ด๋ฆฌ ์ ์ ์ถ๊ฐ" +no_user_selected = "์ฌ์ฉ์๊ฐ ์ ํ๋์ง ์์์ต๋๋ค." +revoke_all = "๋ชจ๋ ๊ถํ ํ์" +super_admin_only = "Super Admin ์ ์ฉ" +user_list = "๋์ ์ฌ์ฉ์" [ui.admin.profile] manageable_tenants = "๊ด๋ฆฌ ๊ฐ๋ฅํ ํ ๋ํธ" @@ -1737,23 +1485,18 @@ user = "TENANT MEMBER" [ui.admin.tenants] add = "ํ ๋ํธ ์ถ๊ฐ" -data_mgmt = "๋ฐ์ดํฐ ๊ด๋ฆฌ" +csv_template = "ํ ํ๋ฆฟ" +data_mgmt = "temp" delete_selected = "์ ํ ์ญ์ " +export_with_ids = "UUID ํฌํจ" +export_without_ids = "UUID ์ ์ธ ๋ด๋ณด๋ด๊ธฐ" +import = "๊ฐ์ ธ์ค๊ธฐ" +search_match_badge = "๊ฒ์ ์ผ์น" seed_badge = "์ด๊ธฐ ์ค์ " -path.root = "์ต์์" title = "ํ ๋ํธ ๋ชฉ๋ก" +toggle_status = "temp" view_org_chart = "์ ์ฒด ์กฐ์ง๋ ๋ณด๊ธฐ" -[ui.admin.tenants.view] -hierarchy = "๊ณ์ธต ๊ตฌ์กฐ" -list = "ํ๋ฉด ๋ชฉ๋ก" -table = "ํ๋ฉด" -tree = "ํธ๋ฆฌ" - -[ui.admin.tenants.scope] -active = "{{name}} ํ์" -pick = "์์ ๋ฒ์ ์ ํ" - [ui.admin.tenants.admins] add_button = "๊ด๋ฆฌ์ ์ถ๊ฐ" already_admin = "์ด๋ฏธ ๊ด๋ฆฌ์" @@ -1772,6 +1515,10 @@ title = "ํ ๋ํธ ๊ด๋ฆฌ์" list = "List" section = "Tenants" +[ui.admin.tenants.bulk] +selected_count = "temp" +status_placeholder = "temp" + [ui.admin.tenants.create] title = "ํ ๋ํธ ์ถ๊ฐ" @@ -1794,15 +1541,15 @@ slug_placeholder = "tenant-slug" status = "์ํ" type = "์ ํ" +[ui.admin.tenants.create.memo] +title = "์ ์ฑ ๋ฉ๋ชจ" + [ui.admin.tenants.create.parent_context] general = "์ผ๋ฐ ํ์ ํ ๋ํธ" hanmac = "ํ๋งฅ๊ฐ์กฑ ํ์ ํ ๋ํธ" pick_required = "์์ ํ ๋ํธ ์ ํ ํ์" root = "์ต์์ ํ ๋ํธ" -[ui.admin.tenants.create.memo] -title = "์ ์ฑ ๋ฉ๋ชจ" - [ui.admin.tenants.create.profile] title = "Tenant Profile" @@ -1814,83 +1561,22 @@ tab_federation = "์ธ๋ถ ์ฐ๋" tab_organization = "์กฐ์ง ๊ด๋ฆฌ" tab_permissions = "๊ถํ" tab_profile = "ํ๋กํ" +tab_relations = "์ธ๋ถ ๊ถํ" tab_schema = "์ฌ์ฉ์ ์คํค๋ง" tab_worksmobile = "Worksmobile" title = "์์ธ" -[ui.admin.tenants.worksmobile] -compare = "Baron / Works ๋น๊ต" -compare_description = "๊ตฌ์ฑ์์ ๊ธฐ๋ณธ์ ์ผ๋ก Baron ๋๋ WORKS ํ์ชฝ์๋ง ์๋ ํญ๋ชฉ์ ๋ณด์ฌ์ค๋๋ค." -compare_groups = "์กฐ์ง/๊ทธ๋ฃน" -compare_users = "๊ตฌ์ฑ์" -dry_run = "Backfill Dry-run" -forbidden = "Worksmobile ์ฐ๋ ๊ถํ์ด ์์ต๋๋ค." -initial_password_csv = "์ด๊ธฐ ๋น๋ฐ๋ฒํธ CSV" -recent_jobs = "์ต๊ทผ ์์ " -refresh = "์๋ก๊ณ ์นจ" -single_sync = "๋จ๊ฑด ๋๊ธฐํ" -single_sync_description = "Baron UUID ๊ธฐ์ค์ผ๋ก ์กฐ์ง ๋๋ ๊ตฌ์ฑ์ sync ์์ ์ ์์ฑํฉ๋๋ค." -subtitle = "ํ๋งฅ๊ฐ์กฑ Directory ์กฐ์ง/๊ตฌ์ฑ์ ๋๊ธฐํ ์ํ๋ฅผ ํ์ธํ๊ณ ์คํจ ์์ ์ ์ฌ์๋ํฉ๋๋ค." -sync_orgunit = "์กฐ์ง Sync" -sync_user = "๊ตฌ์ฑ์ Sync" -title = "Worksmobile ์ฐ๋" - -[ui.admin.tenants.list] -search_placeholder = "ํ ๋ํธ ์ด๋ฆ ๋๋ ์ฌ๋ฌ๊ทธ ๊ฒ์..." -select_placeholder = "ํ ๋ํธ๋ฅผ ์ ํํ์ธ์" - -[ui.admin.tenants.members] -descendants = "ํ์ ์กฐ์ง ๋ฉค๋ฒ" -direct = "์์ ๋ฉค๋ฒ" -direct_label = "์ง์" -list_title = "๊ตฌ์ฑ์ ๊ด๋ฆฌ" -title = "ํ ๋ํธ ๊ตฌ์ฑ์ ({{count}})" -total = "์ ์ฒด" -total_label = "์ ์ฒด" - -[msg.admin.apikeys.registry] -count = "์ด {{count}}๊ฐ์ ํ์ฑ ํค๊ฐ ๋ฑ๋ก๋์ด ์์ต๋๋ค." - -[msg.admin.org] -import_partial_success = "์ผ๋ถ ์กฐ์ง ์ ๋ณด๋ฅผ ๊ฐ์ ธ์์ต๋๋ค." - -[msg.admin.tenants] -delete_bulk_confirm = "์ ํํ {{count}}๊ฐ ํ ๋ํธ๋ฅผ ์ญ์ ํ ๊น์?" -seed_delete_blocked = "์ด๊ธฐ ์ค์ ํ ๋ํธ๋ ์ญ์ ํ ์ ์์ต๋๋ค." - -[msg.admin.users] -self_delete_blocked = "์์ ์ ๊ณ์ ์ ์ญ์ ํ ์ ์์ต๋๋ค." -export_error = "์ฌ์ฉ์ ๋ด๋ณด๋ด๊ธฐ์ ์คํจํ์ต๋๋ค: {{error}}" -status_error = "์ฌ์ฉ์ ์ํ ๋ณ๊ฒฝ์ ์คํจํ์ต๋๋ค: {{error}}" - -[ui.admin.apikeys.registry] -title = "API Key Registry" - -[ui.admin.tenants.members] -add_existing = "๊ธฐ์กด ๋ฉค๋ฒ ๋ฐฐ์ " -create_new = "์ ๊ท ๋ฉค๋ฒ ์์ฑ" -delete_selected = "์ ํ ์ญ์ " -remove = "์กฐ์ง์์ ์ ์ธ" -org_picker_title = "์กฐ์ง ์ ํ" -view_org_chart = "์ ์ฒด ์กฐ์ง๋ ๋ณด๊ธฐ" -direct_label = "์ง์" -list_title = "๊ตฌ์ฑ์ ๊ด๋ฆฌ" -title = "ํ ๋ํธ ๊ตฌ์ฑ์ ({{count}})" -total = "์ ์ฒด" -total_label = "์ ์ฒด" -view_profile = "์์ธ ์ ๋ณด" - -[ui.admin.tenants.import_result] -message = "์์ธ ๋ด์ฉ" -modified = "์์ ๋จ:" -status = "์ํ" -title = "๊ฐ์ ธ์ค๊ธฐ ๊ฒฐ๊ณผ ๋ฆฌํฌํธ" +[ui.admin.tenants.domain_conflict] +description = "ui.admin.tenants.domain_conflict.description" +title = "๋๋ฉ์ธ ์ถฉ๋" [ui.admin.tenants.import_preview] candidates = "ํ๋ณด" confirm = "์ํฌํธ ํ์ " create_new = "์๋ก ์์ฑ" +create_new_reset = "์ ๊ท ์์ฑ (ID/slug ์ฌ์ค์ )" csv_parents = "CSV ์์ ํ ๋ํธ" +external_id = "์ธ๋ถ ID" fixed_id = "๊ณ ์ ID" match = "๋งค์นญ๋ ํ ๋ํธ" no_candidates = "๋งค์นญ ๊ฐ๋ฅํ ํ ๋ํธ๊ฐ ์์ต๋๋ค." @@ -1898,8 +1584,43 @@ parent = "์์" parent_companies = "์์ ํ์ฌ" parent_company_groups = "์์ ๊ทธ๋ฃน์ฌ" parent_organizations = "์์ ์กฐ์ง" +parent_unresolved = "๋ถ๋ชจ ํ์ธ ํ์" +slug_exists = "slug ์ถฉ๋" title = "์ํฌํธ ๋ฏธ๋ฆฌ๋ณด๊ธฐ" +[ui.admin.tenants.import_result] +message = "์์ธ ๋ด์ฉ" +modified = "์์ ๋จ:" +status = "์ํ" +title = "๊ฐ์ ธ์ค๊ธฐ ๊ฒฐ๊ณผ ๋ฆฌํฌํธ" + +[ui.admin.tenants.list] +search_placeholder = "ํ ๋ํธ ์ด๋ฆ ๋๋ ์ฌ๋ฌ๊ทธ ๊ฒ์..." +select_placeholder = "ํ ๋ํธ๋ฅผ ์ ํํ์ธ์" + +[ui.admin.tenants.members] +add_existing = "๊ธฐ์กด ๋ฉค๋ฒ ๋ฐฐ์ " +add_existing_description = "๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ ์ ํํด ์ถ๊ฐ ๋ช ๋จ์ ๋ด์ ๋ค ํ ๋ฒ์ ๋ฐฐ์ ํฉ๋๋ค." +add_queued = "์ ํ ๊ตฌ์ฑ์ ์ถ๊ฐ" +create_new = "์ ๊ท ๋ฉค๋ฒ ์์ฑ" +delete_selected = "์ ํ ์ญ์ " +descendants = "ํ์ ์กฐ์ง ๋ฉค๋ฒ" +direct = "์์ ๋ฉค๋ฒ" +direct_label = "์ง์" +export = "์ ํ ์กฐ์ง ์ฌ์ฉ์ CSV" +list_title = "๊ตฌ์ฑ์ ๊ด๋ฆฌ" +org_picker_title = "์กฐ์ง ์ ํ" +queue_empty = "์ถ๊ฐํ ๊ตฌ์ฑ์์ ์ ํํ์ธ์." +queue_remove = "์ถ๊ฐ ๋ช ๋จ์์ ์ ๊ฑฐ" +remove = "์กฐ์ง์์ ์ ์ธ" +search_min_length = "๋ ๊ธ์ ์ด์ ์ ๋ ฅํ์ธ์." +search_placeholder = "์ด๋ฆ ๋๋ ์ด๋ฉ์ผ ๊ฒ์" +title = "ํ ๋ํธ ๊ตฌ์ฑ์ ({{count}})" +total = "์ ์ฒด" +total_label = "์ ์ฒด" +view_org_chart = "์ ์ฒด ์กฐ์ง๋ ๋ณด๊ธฐ" +view_profile = "์์ธ ์ ๋ณด" + [ui.admin.tenants.members.table] actions = "ACTIONS" email = "EMAIL" @@ -1918,6 +1639,15 @@ table_email = "์ด๋ฉ์ผ" table_name = "์ด๋ฆ" title = "ํ ๋ํธ ์์ ์" +[ui.admin.tenants.parent] +company_only = "ํ์ฌ/๊ทธ๋ฃน์ฌ๋ง ํ์" +local_search_placeholder = "ํ ๋ํธ ์ด๋ฆ ๋๋ ์ฌ๋ฌ๊ทธ ๊ฒ์" +pick_tenant = "ํ ๋ํธ ์ ํ" +search_placeholder = "์ด๋ฆ ๋๋ slug ๊ฒ์" + +[ui.admin.tenants.path] +root = "์ต์์" + [ui.admin.tenants.profile] allowed_domains = "ํ์ฉ๋ ๋๋ฉ์ธ (์ฝค๋ง๋ก ๊ตฌ๋ถ)" allowed_domains_help = "์ด ๋๋ฉ์ธ์ ๊ฐ์ง ์ด๋ฉ์ผ๋ก ๊ฐ์ ํ ์ฌ์ฉ์๋ ์๋์ผ๋ก ์ด ํ ๋ํธ์ ๋ฐฐ์ ๋ฉ๋๋ค." @@ -1939,15 +1669,15 @@ worksmobile_sync = "์์ค๋ชจ๋ฐ์ผ ๋๊ธฐํ ์ํ" parent = "์์ ํ ๋ํธ (์ ํ)" parent_help = "๊ฐ์กฑ์ฌ ํ ๋ํธ๋ ํ์ ์กฐ์ง์ ์ข ์์ํฌ ๊ฒฝ์ฐ ์์ ํ ๋ํธ๋ฅผ ์ ํํด์ฃผ์ธ์." -[ui.admin.tenants.parent] -company_only = "ํ์ฌ/๊ทธ๋ฃน์ฌ๋ง ํ์" -search_placeholder = "์ด๋ฆ ๋๋ slug ๊ฒ์" -local_search_placeholder = "ํ ๋ํธ ์ด๋ฆ ๋๋ ์ฌ๋ฌ๊ทธ ๊ฒ์" -pick_tenant = "ํ ๋ํธ ์ ํ" - [ui.admin.tenants.registry] title = "Tenant registry" +[ui.admin.tenants.relations] +add_button = "์ธ๋ถ ๊ถํ ์ฌ์ฉ์ ์ถ๊ฐ" +already_added = "์ด๋ฏธ ์ถ๊ฐ๋จ" +dialog_title = "์ธ๋ถ ๊ถํ ๊ด๋ฆฌ ์ ์ ์ถ๊ฐ" +title = "์ธ๋ถ ๊ถํ ์ค์ (Fine-grained Permissions)" + [ui.admin.tenants.schema] add_field = "ํ๋ ์ถ๊ฐ" save = "์คํค๋ง ์ ์ฅ" @@ -1972,12 +1702,16 @@ type_text = "ํ ์คํธ" unsigned = "์์ ๋ถ๊ฐ" validation_placeholder = "์ ๊ทํํ์ (์ ํ ์ฌํญ)" +[ui.admin.tenants.scope] +active = "{{name}} ํ์" +pick = "์์ ๋ฒ์ ์ ํ" + [ui.admin.tenants.sub] add = "ํ์ ํ ๋ํธ ์ถ๊ฐ" add_dialog_desc = "ํ์ ํ ๋ํธ๋ก ์ถ๊ฐํ ํ ๋ํธ๋ฅผ ์ ํํ์ธ์." add_dialog_title = "ํ์ ํ ๋ํธ ์ถ๊ฐ" add_existing = "๊ธฐ์กด ํ ๋ํธ ์ถ๊ฐ" -export = "ํ์ ์กฐ์ง CSV" +export = "๋ด๋ณด๋ด๊ธฐ" manage = "๊ด๋ฆฌ" no_candidates = "์ถ๊ฐ ๊ฐ๋ฅํ ํ ๋ํธ๊ฐ ์์ต๋๋ค." search_placeholder = "๊ฒ์..." @@ -1993,6 +1727,7 @@ status = "STATUS" [ui.admin.tenants.table] actions = "ACTIONS" context = "์์ ๊ฒฝ๋ก" +created = "CREATED" id = "ID" id_copy = "ID ๋ณต์ฌ" members = "๋ฉค๋ฒ์" @@ -2004,28 +1739,79 @@ slug = "์ฌ๋ฌ๊ทธ" status = "์ํ" type = "์ ํ" updated = "์์ ์ผ" -created = "CREATED" -created = "CREATED" + +[ui.admin.tenants.view] +hierarchy = "๊ณ์ธต ๊ตฌ์กฐ" +list = "ํ๋ฉด ๋ชฉ๋ก" +table = "ํ๋ฉด" +tree = "ํธ๋ฆฌ" + +[ui.admin.tenants.worksmobile] +compare = "Baron / Works ๋น๊ต" +compare_description = "๊ตฌ์ฑ์์ ๊ธฐ๋ณธ์ ์ผ๋ก Baron ๋๋ WORKS ํ์ชฝ์๋ง ์๋ ํญ๋ชฉ์ ๋ณด์ฌ์ค๋๋ค." +compare_groups = "์กฐ์ง/๊ทธ๋ฃน" +compare_users = "๊ตฌ์ฑ์" +dry_run = "Backfill Dry-run" +forbidden = "Worksmobile ์ฐ๋ ๊ถํ์ด ์์ต๋๋ค." +initial_password_csv = "์ด๊ธฐ ๋น๋ฐ๋ฒํธ CSV" +recent_jobs = "์ต๊ทผ ์์ " +refresh = "์๋ก๊ณ ์นจ" +single_sync = "๋จ๊ฑด ๋๊ธฐํ" +single_sync_description = "Baron UUID ๊ธฐ์ค์ผ๋ก ์กฐ์ง ๋๋ ๊ตฌ์ฑ์ sync ์์ ์ ์์ฑํฉ๋๋ค." +subtitle = "ํ๋งฅ๊ฐ์กฑ Directory ์กฐ์ง/๊ตฌ์ฑ์ ๋๊ธฐํ ์ํ๋ฅผ ํ์ธํ๊ณ ์คํจ ์์ ์ ์ฌ์๋ํฉ๋๋ค." +sync_orgunit = "์กฐ์ง Sync" +sync_user = "๊ตฌ์ฑ์ Sync" +title = "Worksmobile ์ฐ๋" + +[ui.admin.user_projection] +loading = "๋ถ๋ฌ์ค๋ ์ค" +subtitle = "ui.admin.user_projection.subtitle" +title = "์ฌ์ฉ์ ๋๊ธฐํ ๊ด๋ฆฌ" + +[ui.admin.user_projection.actions] +reconcile = "์ฌ๋๊ธฐํ" +reset = "์ด๊ธฐํ ํ ์ฌ๊ตฌ์ถ" + +[ui.admin.user_projection.card] +description = "Backend DB ํต๊ณ๊ฐ ์ฐธ์กฐํ๋ ์ฌ์ฉ์ read model ์ํ์ ๋๋ค." +title = "Kratos ์ฌ์ฉ์ ๋๊ธฐํ" + +[ui.admin.user_projection.forbidden] +title = "์ ๊ทผ ๊ถํ์ด ์์ต๋๋ค" + +[ui.admin.user_projection.status] +failed = "์คํจ" +not_ready = "์ค๋น๋์ง ์์" +ready = "์ค๋น๋จ" + +[ui.admin.user_projection.summary] +last_synced = "๋ง์ง๋ง ๋๊ธฐํ" +projected_users = "๋๊ธฐํ ์ฌ์ฉ์" +status = "์ํ" +updated_at = "์ํ ๊ฐฑ์ " [ui.admin.users] csv_template = "ํ ํ๋ฆฟ ๋ค์ด๋ก๋" +data_mgmt = "temp" [ui.admin.users.bulk] acknowledge_warning = "๊ฒฝ๊ณ ๋ฅผ ํ์ธํ์ผ๋ฉฐ ๊ณ์ ์งํํฉ๋๋ค." create_missing_tenant = "์ ๊ท ์์ฑ" do_move = "์ด๋ ์คํ" download_template = "ํ ํ๋ฆฟ ๋ฐ๊ธฐ" +modified_fields = "์์ ํญ๋ชฉ:" move_group = "ํ ๋ํธ ์ผ๊ด ์ด๋" move_title = "์ฌ์ฉ์ ์ผ๊ด ์ด๋" +no_changes = "๋ณ๊ฒฝ ์ฌํญ ์์" no_department = "๋ถ์ ์์" +permission_placeholder = "๊ถํ ์ ํ" schema_warning = "์คํค๋ง ํธํ์ฑ ๊ฒฝ๊ณ " select_group = "๋์ ํ ๋ํธ ์ ํ" selected_count = "{{count}}๋ช ์ ํ๋จ" start_upload = "์ ๋ก๋ ์์" +status_placeholder = "์ํ ์ ํ" tenant_resolution = "ํ ๋ํธ ๋งคํ" title = "์ผ๊ด ์์ " -status_placeholder = "์ํ ์ ํ" -permission_placeholder = "๊ถํ ์ ํ" [ui.admin.users.create] back = "๋ชฉ๋ก์ผ๋ก ๋์๊ฐ๊ธฐ" @@ -2058,9 +1844,9 @@ name = "์ด๋ฆ" name_placeholder = "ํ๊ธธ๋" password = "๋น๋ฐ๋ฒํธ" password_placeholder = "********" -picker_description = "๋ฐฐ์ ํ ํ ๋ํธ๋ฅผ ๊ฒ์ํด์ ์ ํํ์ธ์." phone = "์ ํ๋ฒํธ" phone_placeholder = "010-1234-5678" +picker_description = "๋ฐฐ์ ํ ํ ๋ํธ๋ฅผ ๊ฒ์ํด์ ์ ํํ์ธ์." position = "์ง๊ธ" position_placeholder = "์์/์ฑ ์/์ ์" role = "์ญํ " @@ -2326,6 +2112,16 @@ role = "์ญํ " status = "์ํ" tenant = "ํ ๋ํธ" +[ui.admin.users.global_custom_claims] +description_placeholder = "์ค๋ช " +label_placeholder = "ํ์ ์ด๋ฆ" +manage_definitions = "์ ์ญ ์ ์ ๊ด๋ฆฌ" +read_permission = "์ฝ๊ธฐ ๊ถํ" +registry = "Global Claim Registry" +title = "์ ์ญ Claim ์ค์ " +value_type = "Claim ํ์ " +write_permission = "์ฐ๊ธฐ ๊ถํ" + [ui.admin.users.list] add = "์ฌ์ฉ์ ์ถ๊ฐ" bulk_import = "์ผ๊ด ์ํฌํธ" @@ -2335,8 +2131,8 @@ fetch_error = "์ฌ์ฉ์ ๋ชฉ๋ก ์กฐํ์ ์คํจํ์ต๋๋ค." search_placeholder = "์ด๋ฆ ๋๋ ์ด๋ฉ์ผ ๊ฒ์..." status_select = "{{name}} ์ํ" subtitle = "์์คํ ์ฌ์ฉ์๋ฅผ ์กฐํํ๊ณ ๊ด๋ฆฌํฉ๋๋ค." -toggle_status = "{{name}} ํ์ฑ ์ํ" title = "์ฌ์ฉ์ ๊ด๋ฆฌ" +toggle_status = "{{name}} ํ์ฑ ์ํ" [ui.admin.users.list.breadcrumb] list = "List" @@ -2842,15 +2638,7 @@ policy_toggle = "Policy toggle enabled" registry = "RP registry" rp_synced = "RP registry synced" -[ui.dev.dashboard.distribution] -headless_hint = "์ด ์ค Headless Login ์ฌ์ฉ {{count}}" -pkce = "PKCE" -private = "Server side App" -title = "์ ํ๋ฆฌ์ผ์ด์ ๊ตฌ์ฑ ์์ฝ" - [ui.dev.dashboard.chart] -x_axis = "X์ถ: ๊ธฐ๊ฐ" -y_axis = "Y์ถ: ๋ก๊ทธ์ธ ์์ฒญ ์" aria = "RP ์์ฒญ ํํฉ" filter_all = "์ ์ฒด" period_day = "์ผ" @@ -2858,6 +2646,14 @@ period_month = "์" period_week = "์ฃผ" series = "๋ก๊ทธ์ธ {{login}} / ์ฌ์ฉ์ {{subjects}}" title = "์ ํ๋ฆฌ์ผ์ด์ ๋ณ ๋ก๊ทธ์ธ ์์ฒญ ํํฉ" +x_axis = "X์ถ: ๊ธฐ๊ฐ" +y_axis = "Y์ถ: ๋ก๊ทธ์ธ ์์ฒญ ์" + +[ui.dev.dashboard.distribution] +headless_hint = "์ด ์ค Headless Login ์ฌ์ฉ {{count}}" +pkce = "PKCE" +private = "Server side App" +title = "์ ํ๋ฆฌ์ผ์ด์ ๊ตฌ์ฑ ์์ฝ" [ui.dev.dashboard.next] subtitle = "Ship the RP controls" @@ -2909,13 +2705,43 @@ active_sessions = "ํ์ฑ ์ธ์ ์" auth_failures_24h = "24์๊ฐ ์ธ์ฆ ์คํจ ์" total_clients = "์ด RP ์" +[ui.dev.grants] +actions = "๊ด๋ฆฌ" +admin_notes = "๋ถ์ฌ ์ฌ์ " +approved = "์น์ธ๋จ" +date = "๋ถ์ฌ์ผ" +email = "์ด๋ฉ์ผ" +grant = "์ง์ ๋ถ์ฌ" +input_section = "์ ๋ ฅ" +pages = "๊ถํ ํ์ด์ง" +phone = "์ ํ๋ฒํธ" +read_only = "์ฝ๊ธฐ ์ ์ฉ" +reason = "๋ถ์ฌ ์ฌ์ " +revoke = "ํ์" +revoke_notes_placeholder = "ํ์ ๋ฉ๋ชจ (์ ํ)..." +selected_info = "์ ํ๋ ์ฌ์ฉ์ ์ ๋ณด" +status = "์ํ" +tenant = "์์" +user = "์ฌ์ฉ์" +user_search_placeholder = "์ด๋ฆ ๋๋ ์ด๋ฉ์ผ ๊ฒ์..." +user_section = "์ฌ์ฉ์ ์ ํ" + +[ui.dev.grants.form] +title = "์ง์ ๋ถ์ฌ" + +[ui.dev.grants.list] +title = "๋ถ์ฌ๋ ๊ถํ" + [ui.dev.header] plane = "Dev Plane" subtitle = "Manage your applications" [ui.dev.nav] clients = "์ฐ๋ ์ฑ" +developer_grants = "๊ฐ๋ฐ์ ๊ถํ ๋ถ์ฌ" +developer_request = "๊ฐ๋ฐ์ ๊ถํ ์ ์ฒญ" logout = "๋ก๊ทธ์์" +overview = "๊ฐ์" [ui.dev.profile] error = "ํ๋กํ ์ ๋ณด๋ฅผ ๋ถ๋ฌ์ค์ง ๋ชปํ์ต๋๋ค." @@ -2937,7 +2763,7 @@ title = "์ฌ์ฉ์ ์ ๋ณด" [ui.dev.profile.org] company_code = "ํ์ฌ ์ฝ๋" tenant = "ํ ๋ํธ" -tenant_slug = "ํ ๋ํธ Slug" +tenant_slug = "ํ ๋ํธ slug" title = "์กฐ์ง ์ ๋ณด" [ui.dev.profile.role] @@ -2949,6 +2775,41 @@ title = "์์คํ ์ญํ " basic = "๊ธฐ๋ณธ ์ ๋ณด" role = "๊ถํ ๋ฐ ์ญํ " +[ui.dev.request] +admin_notes_placeholder = "์น์ธ ๋๋ ๋ฐ๋ ค ์ฌ์ ๋ฅผ ์ ๋ ฅํ์ธ์." +cancel_approval = "์น์ธ ์ทจ์" +cancel_notes_placeholder = "์น์ธ ์ทจ์ ์ฌ์ ๋ฅผ ์ ๋ ฅํ์ธ์." + +[ui.dev.request.list] +title = "์ ์ฒญ ๋ด์ญ" + +[ui.dev.request.modal] +desc = "๊ฐ๋ฐ์ ๊ถํ์ ์ ์ฒญํ๋ ค๋ฉด ์๋ ์ ๋ณด๋ฅผ ํ์ธํ ๋ค ์ ์ฒญ ์ฌ์ ๋ฅผ ์ ๋ ฅํ์ธ์." +email = "์ด๋ฉ์ผ" +name = "์ฑํจ" +org = "์์" +pages = "๊ถํ ํ์ด์ง" +phone = "์ ํ๋ฒํธ" +reason = "์ ์ฒญ ์ฌ์ " +reason_placeholder = "๊ฐ๋ฐ์ ๊ถํ์ด ํ์ํ ์ด์ ๋ฅผ ์์ฑํด์ฃผ์ธ์." +role = "์ญํ " +title = "๊ฐ๋ฐ์ ๋ฑ๋ก ์ ์ฒญ" + +[ui.dev.request.status] +approved = "์น์ธ๋จ" +cancelled = "์น์ธ ์ทจ์๋จ" +pending = "๋๊ธฐ ์ค" +rejected = "๋ฐ๋ ค๋จ" + +[ui.dev.request.table] +actions = "๊ด๋ฆฌ" +date = "์ ์ฒญ์ผ" +org = "์์" +pages = "๊ถํ ํ์ด์ง" +reason = "์ ์ฒญ ์ฌ์ " +status = "์ํ" +user = "์ฌ์ฉ์" + [ui.dev.session] active = "์ธ์ ํ์ฑ" auto_extend = "์ธ์ ๋ง๋ฃ ๊ด๋ฆฌ" @@ -2966,6 +2827,34 @@ switch_success = "ํ ๋ํธ ์ ํ ์๋ฃ" workspace = "์์ ํ ๋ํธ (์ปจํ ์คํธ)" workspace_desc = "ํ์ฌ ์์ ์ค์ธ ํ ๋ํธ๋ฅผ ์ ํํ๊ณ ์ ์ฅํ์ฌ API ์์ฒญ ์ปจํ ์คํธ๋ฅผ ๋ณ๊ฒฝํฉ๋๋ค." +[ui.dev.welcome] +btn_request = "์ ๊ท ์ ์ฒญํ๊ธฐ" + +[ui.shell] + +[ui.shell.nav] +logout = "๋ก๊ทธ์์" +profile = "๋ด ์ ๋ณด" + +[ui.shell.profile] +menu_aria = "๊ณ์ ๋ฉ๋ด ์ด๊ธฐ" +menu_title = "๊ณ์ " +unknown_email = "unknown@example.com" +unknown_name = "Unknown User" + +[ui.shell.session] +active = "์ธ์ ํ์ฑ" +auto_extend = "์ธ์ ๋ง๋ฃ ๊ด๋ฆฌ" +disabled = "์ธ์ ๋ง๋ฃ ๋นํ์ฑํ" +expired = "์ธ์ ๋ง๋ฃ" +expiring = "๋ง๋ฃ ์๋ฐ: {{minutes}}๋ถ {{seconds}}์ด ๋จ์" +remaining = "๋ง๋ฃ ์์ : {{minutes}}๋ถ {{seconds}}์ด ๋จ์" +unknown = "์ ์ ์์" + +[ui.shell.sidebar] +collapse = "์ฌ์ด๋๋ฐ ์ ๊ธฐ" +expand = "์ฌ์ด๋๋ฐ ํผ์น๊ธฐ" + [ui.userfront] app_title = "Baron SW ํฌํ" @@ -2976,6 +2865,10 @@ dev_console = "Dev Console" [ui.userfront.audit] +[ui.userfront.audit.filter] +title = "๋ด ํ๋ ๊ด๋ฆฌ" +toggle_label = "ํ์ฑ ์ธ์ ๋ง ๋ณด๊ธฐ" + [ui.userfront.audit.table] action = "๊ด๋ฆฌ" app = "์ ํ๋ฆฌ์ผ์ด์ " @@ -3004,22 +2897,12 @@ title = "๋์ ์ทจ์" [ui.userfront.dashboard] last_auth_label = "์ต๊ทผ ์ธ์ฆ" +link_status_label = "์ฐ๋ ์ํ" status_history = "์ํ ์ด๋ ฅ" [ui.userfront.dashboard.activity] linked = "์ฐ๋๋จ" -[ui.userfront.dashboard.sessions] -active_badge = "ํ์ฑํ" -current_badge = "์ ์์ค" -current_disabled = "ํ์ฌ ์ธ์ " -unknown_device = "์ ์ ์๋ ๊ธฐ๊ธฐ" -unknown_session = "์ธ์ ์ ๋ณด" - -[ui.userfront.dashboard.sessions.revoke] -action = "์ธ์ ์ข ๋ฃ" -title = "์ธ์ ์ข ๋ฃ" - [ui.userfront.dashboard.approved_session] default = "์น์ธํ ์ธ์ ID" userfront = "์น์ธํ Userfront ์ธ์ ID" @@ -3031,6 +2914,17 @@ title = "์ฐ๋ ํด์ง" [ui.userfront.dashboard.scopes] title = "๋์ ๋ฒ์" +[ui.userfront.dashboard.sessions] +active_badge = "ํ์ฑํ" +current_badge = "์ ์์ค" +current_disabled = "ํ์ฌ ์ธ์ " +unknown_device = "์ ์ ์๋ ๊ธฐ๊ธฐ" +unknown_session = "์ธ์ ์ ๋ณด" + +[ui.userfront.dashboard.sessions.revoke] +action = "์ธ์ ์ข ๋ฃ" +title = "์ธ์ ์ข ๋ฃ" + [ui.userfront.dashboard.status] revoked = "ํด์ง๋จ" @@ -3093,36 +2987,13 @@ title = "๋ฏธ๋ฑ๋ก ํ์" [ui.userfront.login.verification] action_label = "ํ์ธ" +action_label_close = "์ฐฝ ๋ซ๊ธฐ" action_label_remote = "๋ก๊ทธ์ธ ์ฐฝ์ผ๋ก ์ด๋ํ๊ธฐ" page_title = "Baron SW ํฌํ" title = "์น์ธ ์๋ฃ" -action_label_close = "์ฐฝ ๋ซ๊ธฐ" title_pending = "๋ก๊ทธ์ธ ์น์ธ ํ์ธ ์ค" title_remote = "๋ก๊ทธ์ธ ์น์ธ ์๋ฃ" -[ui.shell.nav] -logout = "๋ก๊ทธ์์" -profile = "๋ด ์ ๋ณด" - -[ui.shell.sidebar] -collapse = "์ฌ์ด๋๋ฐ ์ ๊ธฐ" -expand = "์ฌ์ด๋๋ฐ ํผ์น๊ธฐ" - -[ui.shell.profile] -menu_aria = "๊ณ์ ๋ฉ๋ด ์ด๊ธฐ" -menu_title = "๊ณ์ " -unknown_email = "unknown@example.com" -unknown_name = "Unknown User" - -[ui.shell.session] -active = "์ธ์ ํ์ฑ" -auto_extend = "์ธ์ ๋ง๋ฃ ๊ด๋ฆฌ" -disabled = "์ธ์ ๋ง๋ฃ ๋นํ์ฑํ" -expired = "์ธ์ ๋ง๋ฃ" -expiring = "๋ง๋ฃ ์๋ฐ: {{minutes}}๋ถ {{seconds}}์ด ๋จ์" -remaining = "๋ง๋ฃ ์์ : {{minutes}}๋ถ {{seconds}}์ด ๋จ์" -unknown = "์ ์ ์์" - [ui.userfront.login_success] later = "๋์ค์ ํ๊ธฐ (๋์๋ณด๋๋ก ์ด๋)" qr = "QR ์ธ์ฆ (์นด๋ฉ๋ผ ์ผ๊ธฐ)" @@ -3183,6 +3054,7 @@ title = "์ ๋น๋ฐ๋ฒํธ ์ค์ " [ui.userfront.sections] apps = "๋์ App ํํฉ" audit = "์ ์์ด๋ ฅ" +sessions = "ํ์ฑ ์ธ์ " [ui.userfront.session] active = "์ธ์ ํ์ฑ" @@ -3230,313 +3102,3 @@ verify = "๋ณธ์ธ์ธ์ฆ" [ui.userfront.signup.success] action = "๋ก๊ทธ์ธํ๊ธฐ" - - -[ui.userfront.audit.filter] -title = "๋ด ํ๋ ๊ด๋ฆฌ" -toggle_label = "ํ์ฑ ์ธ์ ๋ง ๋ณด๊ธฐ" - -[msg.userfront.audit.filter] -description = "ํ์ฑํ๋ ์ธ์ ๋ง ๋ณด๋ ค๋ฉด ํ ๊ธ์ ์ผ์ฃผ์ธ์." - -[msg.admin.integrity.forbidden] -description = "์ด ํ๋ฉด์ super_admin ๊ถํ์ผ๋ก๋ง ์ ๊ทผํ ์ ์์ต๋๋ค." - -[msg.admin.integrity.orphan_login_ids] -delete_confirm = "์ ํํ {{count}}๊ฐ์ ์ ๋ น ๋ก๊ทธ์ธ ID๋ฅผ ์ญ์ ํ์๊ฒ ์ต๋๊น?" -delete_success = "{{count}}๊ฐ์ ์ ๋ น ๋ก๊ทธ์ธ ID๋ฅผ ์ญ์ ํ์ต๋๋ค." -description = "์ญ์ ๋์๊ฑฐ๋ ์กด์ฌํ์ง ์๋ ์ฌ์ฉ์/ํ ๋ํธ๋ฅผ ์ฐธ์กฐํ๋ ๋ก๊ทธ์ธ ID๋ฅผ ํ์ธํ ๋ค ์ ํ ์ญ์ ํฉ๋๋ค." -empty = "์ญ์ ํ ์ ๋ น ๋ก๊ทธ์ธ ID๊ฐ ์์ต๋๋ค." -load_error = "์ ๋ น ๋ก๊ทธ์ธ ID ๋์์ ๋ถ๋ฌ์ค์ง ๋ชปํ์ต๋๋ค." - -[msg.admin.integrity.read_model] -description = "Ory SoT๋ฅผ ๋ฎ์ด์ฐ์ง ์๊ณ backend DB read model์ ์ด์ ์งํ๋ง ํ์ธํฉ๋๋ค." - -[msg.admin.integrity.recheck] -error = "๊ฒ์ฌ์ ์คํจํ์ต๋๋ค." -running = "์ ํฉ์ฑ ๊ฒ์ฌ๋ฅผ ์คํ ์ค์ ๋๋ค." -success = "๊ฒ์ฌ๊ฐ ์๋ฃ๋์์ต๋๋ค." - -[msg.admin.integrity.report] -load_error = "์ ํฉ์ฑ ๋ฆฌํฌํธ๋ฅผ ๋ถ๋ฌ์ค์ง ๋ชปํ์ต๋๋ค." - -[msg.admin.integrity.check.duplicate_tenant_slugs] -description = "์ญ์ ๋์ง ์์ tenant์ LOWER(TRIM(slug)) ๊ธฐ์ค ์ค๋ณต์ ๊ฒ์ฌํฉ๋๋ค." - -[msg.admin.integrity.check.orphan_tenant_parents] -description = "tenants.parent_id๊ฐ ์กด์ฌํ์ง ์๊ฑฐ๋ soft-deleted tenant๋ฅผ ์ฐธ์กฐํ๋์ง ๊ฒ์ฌํฉ๋๋ค." - -[msg.admin.integrity.check.orphan_user_login_id_tenants] -description = "user_login_ids.tenant_id๊ฐ ์กด์ฌํ์ง ์๊ฑฐ๋ soft-deleted tenant๋ฅผ ์ฐธ์กฐํ๋์ง ๊ฒ์ฌํฉ๋๋ค." - -[msg.admin.integrity.check.orphan_user_login_id_users] -description = "user_login_ids.user_id๊ฐ ์กด์ฌํ์ง ์๊ฑฐ๋ soft-deleted user๋ฅผ ์ฐธ์กฐํ๋์ง ๊ฒ์ฌํฉ๋๋ค." - -[msg.admin.integrity.check.orphan_user_tenant_memberships] -description = "users.tenant_id๊ฐ ์กด์ฌํ์ง ์๊ฑฐ๋ soft-deleted tenant๋ฅผ ์ฐธ์กฐํ๋์ง ๊ฒ์ฌํฉ๋๋ค." - -[msg.admin.integrity.section.tenant_integrity] -description = "ํ ๋ํธ slug ์ค๋ณต๊ณผ ๋ถ๋ชจ ๊ด๊ณ ์ด์์ ํ์ธํฉ๋๋ค." - -[msg.admin.integrity.section.user_integrity] -description = "์ฌ์ฉ์์ ๋ก๊ทธ์ธ ID ์ฐธ์กฐ์ ๊ณ ์ ๋ ์ฝ๋๋ฅผ ํ์ธํฉ๋๋ค." - -[msg.admin.integrity] -subtitle = "์ ํฉ์ฑ ์ํ๋ฅผ ํ์ธํ๊ณ ๋ฐ์ดํฐ ๋ชจ๋ธ ์ ๋ฐ์ ๊ฒ์ฆ ๊ฒฐ๊ณผ๋ฅผ ์ดํด๋ด ๋๋ค." - -[msg.admin.user_projection] -action_error = "์ฌ์ฉ์ ๋๊ธฐํ ์์ ์ ์คํจํ์ต๋๋ค." -action_success = "{{count}}๋ช ๊ธฐ์ค์ผ๋ก ์ฌ์ฉ์ ๋๊ธฐํ๋ฅผ ๊ฐฑ์ ํ์ต๋๋ค." -forbidden_description = "์ด ํ๋ฉด์ super_admin ๊ถํ์ผ๋ก๋ง ์ ๊ทผํ ์ ์์ต๋๋ค." -load_error = "์ฌ์ฉ์ ๋๊ธฐํ ์ํ๋ฅผ ๋ถ๋ฌ์ค์ง ๋ชปํ์ต๋๋ค." -reset_confirm = "์ฌ์ฉ์ ๋๊ธฐํ๋ฅผ Kratos ๊ธฐ์ค์ผ๋ก ๋ค์ ๊ตฌ์ถํ์๊ฒ ์ต๋๊น?" -subtitle = "Kratos ์ฌ์ฉ์ read model์ ํ์ธํ๊ณ ๋๊ธฐํ ์ํ๋ฅผ ๊ฐฑ์ ํฉ๋๋ค." - -[msg.admin.user_projection.forbidden] -description = "์ด ํ๋ฉด์ super_admin ๊ถํ์ผ๋ก๋ง ์ ๊ทผํ ์ ์์ต๋๋ค." - -[msg.admin.integrity] -subtitle = "์ ํฉ์ฑ ์ํ๋ฅผ ํ์ธํ๊ณ ๋ฐ์ดํฐ ๋ชจ๋ธ ์ ๋ฐ์ ๊ฒ์ฆ ๊ฒฐ๊ณผ๋ฅผ ์ดํด๋ด ๋๋ค." - -[ui.admin.integrity] -tab_checks = "์ ํฉ์ฑ ๊ฒ์ฌ" -tab_user_projection = "์ฌ์ฉ์ ๋๊ธฐํ" -fetch_error = "์ ํฉ์ฑ ์ต์ข ๊ฒ์ฆ ๊ฒฐ๊ณผ๋ฅผ ๋ถ๋ฌ์ค์ง ๋ชปํ์ต๋๋ค." -kicker = "์์คํ " -loading = "๋ถ๋ฌ์ค๋ ์ค" -subtitle = "์ ํฉ์ฑ ์ํ๋ฅผ ํ์ธํ๊ณ ๋ฐ์ดํฐ ๋ชจ๋ธ ์ ๋ฐ์ ๊ฒ์ฆ ๊ฒฐ๊ณผ๋ฅผ ์ดํด๋ด ๋๋ค." -title = "๋ฐ์ดํฐ ์ ํฉ์ฑ ๊ฒ์ฆ" - -[ui.admin.integrity.forbidden] -title = "์ ๊ทผ ๊ถํ์ด ์์ต๋๋ค" - -[ui.admin.integrity.orphan_login_ids] -delete = "์ ํ ์ญ์ " -title = "์ ๋ น ๋ก๊ทธ์ธ ID ์ ๋ฆฌ" - -[ui.admin.integrity.read_model] -title = "์ฝ๊ธฐ ๋ชจ๋ธ ์ ํฉ์ฑ" - -[ui.admin.integrity.reason] -deleted_tenant = "์ญ์ ๋ ํ ๋ํธ" -deleted_user = "์ญ์ ๋ ์ฌ์ฉ์" -missing_tenant = "ํ ๋ํธ ์์" -missing_user = "์ฌ์ฉ์ ์์" - -[ui.admin.integrity.recheck] -run = "๋ค์ ๊ฒ์ฌ" -running = "๊ฒ์ฌ ์ค" - -[ui.admin.integrity.status] -fail = "์คํจ" -pass = "์ ์" -warning = "์ฃผ์" - -[ui.admin.integrity.summary] -checked_at = "๊ฒ์ฌ ์๊ฐ" -failures = "์คํจ ๊ฑด์" -failures_text = "์คํจ {{count}}๊ฑด" -passed = "์ ์" -title = "์ ํฉ์ฑ ์ต์ข ๊ฒ์ฆ" -total_checks = "๊ฒ์ฌ ํญ๋ชฉ" - -[ui.admin.integrity.table] -field = "ํ๋" -login_id = "๋ก๊ทธ์ธ ID" -reason = "์ฌ์ " -select = "์ ํ" -select_item = "{{loginId}} ์ ํ" -tenant = "ํ ๋ํธ" -user = "์ฌ์ฉ์" - -[ui.admin.integrity.section] -tenant_integrity = "ํ ๋ํธ ์ ํฉ์ฑ" -user_integrity = "์ฌ์ฉ์ ์ ํฉ์ฑ" - -[ui.admin.integrity.check.duplicate_tenant_slugs] -title = "์ค๋ณต ํ ๋ํธ slug" - -[ui.admin.integrity.check.orphan_tenant_parents] -title = "๊ณ ์ ํ ๋ํธ ๋ถ๋ชจ" - -[ui.admin.integrity.check.orphan_user_login_id_tenants] -title = "๊ณ ์ ๋ก๊ทธ์ธ ID ํ ๋ํธ" - -[ui.admin.integrity.check.orphan_user_login_id_users] -title = "๊ณ ์ ๋ก๊ทธ์ธ ID ์ฌ์ฉ์" - -[ui.admin.integrity.check.orphan_user_tenant_memberships] -title = "๊ณ ์ ์ฌ์ฉ์ ํ ๋ํธ ์์" - -[msg.admin.api_keys.list] -edit_scopes_desc = "API ํค์ ๋ถ์ฌํ ๊ถํ ๋ฒ์๋ฅผ ์์ ํฉ๋๋ค." -rotate_confirm = "์ด API ํค์ Secret์ ์ฌ๋ฐ๊ธํ ๊น์?" -rotate_secret_notice = "์ Secret์ ์ง๊ธ ํ ๋ฒ๋ง ํ์๋ฉ๋๋ค." - -[msg.admin.tenants] -export_error = "ํ ๋ํธ ๋ด๋ณด๋ด๊ธฐ์ ์คํจํ์ต๋๋ค." - -[ui.admin.api_keys.list] -edit_scopes = "๊ถํ ์์ " -rotate_secret = "Secret ์ฌ๋ฐ๊ธ" -rotate_secret_done = "Secret ์ฌ๋ฐ๊ธ ์๋ฃ" -save_scopes = "๊ถํ ์ ์ฅ" - -[ui.admin.user_projection] -loading = "๋ถ๋ฌ์ค๋ ์ค" -title = "์ฌ์ฉ์ ๋๊ธฐํ ๊ด๋ฆฌ" - -[ui.admin.user_projection.actions] -reconcile = "์ฌ๋๊ธฐํ" -reset = "์ด๊ธฐํ ํ ์ฌ๊ตฌ์ถ" - -[ui.admin.user_projection.card] -description = "Backend DB ํต๊ณ๊ฐ ์ฐธ์กฐํ๋ ์ฌ์ฉ์ read model ์ํ์ ๋๋ค." -title = "Kratos ์ฌ์ฉ์ ๋๊ธฐํ" - -[ui.admin.user_projection.forbidden] -title = "์ ๊ทผ ๊ถํ์ด ์์ต๋๋ค" - -[ui.admin.user_projection.status] -failed = "์คํจ" -not_ready = "์ค๋น๋์ง ์์" -ready = "์ค๋น๋จ" - -[ui.admin.user_projection.summary] -last_synced = "๋ง์ง๋ง ๋๊ธฐํ" -projected_users = "๋๊ธฐํ ์ฌ์ฉ์" -status = "์ํ" -updated_at = "์ํ ๊ฐฑ์ " - -[ui.admin.auth_guard] -subtitle = "๊ด๋ฆฌ์ ๊ถํ๊ณผ ReBAC ๊ด๊ณ๋ฅผ ์ค์ ์ ์ฑ ์์ง ๊ธฐ์ค์ผ๋ก ํ์ธํฉ๋๋ค." -title = "์ธ์ฆ ๊ฐ๋" - -[ui.admin.auth_guard.checker] -check = "๊ถํ ํ์ธ ์คํ" -checking = "๊ฒ์ฆ ์ค..." -denied = "์ ๊ทผ ๊ฑฐ๋ถ" -denied_description = "ํด๋น ์ฌ์ฉ์๋ ์์ฒญํ ๋ฆฌ์์ค์ ๋ํด ๊ถํ์ด ์์ต๋๋ค." -description = "ํน์ ์ฃผ์ฒด(Subject)๊ฐ ํน์ ๋ฆฌ์์ค(Object)์ ๋ํด ๊ถํ์ด ์๋์ง Ory Keto๋ฅผ ํตํด ์ค์๊ฐ์ผ๋ก ํ์ธํฉ๋๋ค." -object_id = "๋์ ID" -object_id_placeholder = "Tenant UUID ๋ฑ" -allowed = "์ ๊ทผ ํ์ฉ" -allowed_description = "ํด๋น ์ฌ์ฉ์๋ ์์ฒญํ ๋ฆฌ์์ค์ ๋ํด ๊ถํ์ด ์์ต๋๋ค. (์์ ํฌํจ)" -namespace = "๋ค์์คํ์ด์ค" -relation = "๊ด๊ณ" -relation_placeholder = "view, manage, admins..." -subject = "์ฃผ์ฒด (User:ID)" -subject_placeholder = "User:uuid ๋๋ Namespace:ID#Relation" -title = "ReBAC ๊ถํ ๊ฒ์ฆ ๋๊ตฌ" - -[ui.admin.auth_guard.checker.namespace] -label = "๋ค์์คํ์ด์ค" -relying_party = "์ ํ๋ฆฌ์ผ์ด์ (RP)" -system = "์์คํ " -tenant = "ํ ๋ํธ" -tenant_group = "ํ ๋ํธ ๊ทธ๋ฃน" - -[ui.admin.overview.summary] -total_users = "์ ์ฒด ์ฌ์ฉ์ ์" - -[ui.admin.overview.chart] -description = "์ ์ฒด ๋๋ ์ ํํ ์กฐ์ง ๊ธฐ์ค์ผ๋ก ๊ทธ๋ํ๋ฅผ ํ์ธํฉ๋๋ค." -title = "ํ์ฌ๋ณ ์ฑ๋ณ ๋ก๊ทธ์ธ ์์ฒญ ํํฉ" - -[ui.admin.tenants.sub] -export = "๋ด๋ณด๋ด๊ธฐ" - -[ui.admin.users.bulk] -modified_fields = "์์ ํญ๋ชฉ:" -no_changes = "๋ณ๊ฒฝ ์ฌํญ ์์" -permission_placeholder = "๊ถํ ์ ํ" -status_placeholder = "์ํ ์ ํ" - -[ui.dev.profile.org] -tenant_slug = "ํ ๋ํธ slug" - -[] -"msg.admin.tenants.bulk.update_error" = "temp" -"msg.admin.tenants.bulk.update_success" = "temp" -"msg.admin.tenants.status_error" = "temp" -"ui.admin.tenants.bulk.selected_count" = "temp" -"ui.admin.tenants.bulk.status_placeholder" = "temp" -"ui.admin.tenants.data_mgmt" = "temp" -"ui.admin.tenants.toggle_status" = "temp" -"ui.admin.users.data_mgmt" = "temp" - -[msg.admin.ory_ssot] -flush_confirm = "Redis identity cache ํค๋ง ๋น์ฐ์๊ฒ ์ต๋๊น?" -flush_error = "Redis identity cache flush์ ์คํจํ์ต๋๋ค." -flush_success = "Redis identity cache key {{count}}๊ฐ๋ฅผ ๋น์ ์ต๋๋ค." -load_error = "Ory SSOT ์์คํ ์ํ๋ฅผ ๋ถ๋ฌ์ค์ง ๋ชปํ์ต๋๋ค." -subtitle = "Kratos ์์ฅ๊ณผ Redis identity cache ์ํ๋ฅผ ๋ถ๋ฆฌํด์ ํ์ธํฉ๋๋ค." - -[msg.admin.ory_ssot.forbidden] -description = "์ด ํ๋ฉด์ super_admin ๊ถํ์ผ๋ก๋ง ์ ๊ทผํ ์ ์์ต๋๋ค." - -[msg.admin.tenants.members] -add_error = "๊ตฌ์ฑ์ ์ถ๊ฐ ์คํจ" -add_success = "{{count}}๋ช ์ ๊ตฌ์ฑ์์ด ์ถ๊ฐ๋์์ต๋๋ค." - -[msg.admin.users.global_custom_claims] -description = "๋ชจ๋ RP์ ๊ณตํต ์ ์ฉํ ์ฌ์ฉ์ claim ์ ์์ ์ฝ๊ธฐ/์ฐ๊ธฐ ๊ถํ ๊ธฐ๋ณธ๊ฐ์ ๊ด๋ฆฌํฉ๋๋ค." -empty = "์ ์๋ ์ ์ญ claim์ด ์์ต๋๋ค." -registry = "์ ์๋ claim key๋ง ์ฌ์ฉ์ ์์ธ์ ์ ์ญ claim ๊ฐ ๊ด๋ฆฌ ๋์์ด ๋ฉ๋๋ค." - -[ui.admin.integrity] -tab_ory_ssot = "Ory SSOT ์์คํ " - -[ui.admin.ory_ssot] -loading = "๋ถ๋ฌ์ค๋ ์ค" -title = "Ory SSOT ์์คํ " - -[ui.admin.ory_ssot.actions] -flush_identity_cache = "Redis cache flush" - -[ui.admin.ory_ssot.cache_card] -description = "Kratos identity ๋ชฉ๋ก ๋ฐ ์กฐํ ์์ ์ ์ํ Redis mirror/cache ์ํ์ ๋๋ค." -title = "Redis identity cache" - -[ui.admin.ory_ssot.forbidden] -title = "์ ๊ทผ ๊ถํ์ด ์์ต๋๋ค" - -[ui.admin.ory_ssot.projection_card] -description = "๊ด๋ฆฌ์ ๊ฒ์๊ณผ ํต๊ณ์์ ์ฌ์ฉํ๋ PostgreSQL read model ์ํ์ ๋๋ค." -title = "Backend ์ฌ์ฉ์ read model" - -[ui.admin.ory_ssot.status] -failed = "์คํจ" -not_ready = "์ค๋น๋์ง ์์" -ready = "์ค๋น๋จ" - -[ui.admin.ory_ssot.summary] -cache_keys = "Cache keys" -last_refreshed = "๋ง์ง๋ง refresh" -last_synced = "๋ง์ง๋ง read-model refresh" -local_users = "Local users" -observed_identities = "๊ด์ธก identity" -status = "์ํ" -updated_at = "์ํ ๊ฐฑ์ " - -[ui.admin.tenants] -search_match_badge = "๊ฒ์ ์ผ์น" - -[ui.admin.tenants.members] -add_existing_description = "๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ ์ ํํด ์ถ๊ฐ ๋ช ๋จ์ ๋ด์ ๋ค ํ ๋ฒ์ ๋ฐฐ์ ํฉ๋๋ค." -add_queued = "์ ํ ๊ตฌ์ฑ์ ์ถ๊ฐ" -export = "์ ํ ์กฐ์ง ์ฌ์ฉ์ CSV" -queue_empty = "์ถ๊ฐํ ๊ตฌ์ฑ์์ ์ ํํ์ธ์." -queue_remove = "์ถ๊ฐ ๋ช ๋จ์์ ์ ๊ฑฐ" -search_min_length = "๋ ๊ธ์ ์ด์ ์ ๋ ฅํ์ธ์." -search_placeholder = "์ด๋ฆ ๋๋ ์ด๋ฉ์ผ ๊ฒ์" - -[ui.admin.users.global_custom_claims] -description_placeholder = "์ค๋ช " -label_placeholder = "ํ์ ์ด๋ฆ" -manage_definitions = "์ ์ญ ์ ์ ๊ด๋ฆฌ" -read_permission = "์ฝ๊ธฐ ๊ถํ" -registry = "Global Claim Registry" -title = "์ ์ญ Claim ์ค์ " -value_type = "Claim ํ์ " -write_permission = "์ฐ๊ธฐ ๊ถํ" diff --git a/locales/template.toml b/locales/template.toml index 2d3b2c70..934d5de0 100644 --- a/locales/template.toml +++ b/locales/template.toml @@ -74,399 +74,7 @@ scope_admin = "" session_ttl = "" tenant_headers = "" -[msg.userfront.error] -detail_contact = "" -detail_generic = "" -detail_request = "" -id = "" -title = "" -title_generic = "" -title_with_code = "" -type = "" - -[msg.userfront.error.tenant] -account = "" -account_unknown = "" -affiliated_tenants = "" -allowed_box_title = "" -allowed_tenants = "" -detail = "" -load_failed = "" -loading = "" -lookup_fallback = "" -page_title = "" -primary_tenant = "" -tenant = "" -tenant_unknown = "" -title = "" - -[msg.userfront.forgot] -description = "" -dry_send = "" -error = "" -input_required = "" -sent = "" - -[msg.userfront.login] -cookie_check_failed = "" -dry_send = "" -link_failed = "" -link_send_failed = "" -link_sent_email = "" -link_sent_phone = "" -link_timeout = "" -no_account = "" -oidc_failed = "" -qr_expired = "" -qr_init_failed = "" -qr_login_required = "" -token_missing = "" -verification_failed = "" - -[msg.userfront.login_success] -subtitle = "" - -[msg.userfront.consent] -accept_error = "" -client_id = "" -client_unknown = "" -description = "" -load_error = "" -missing_redirect = "" -redirect_notice = "" -scope_count = "" - -[msg.userfront.profile] -department_missing = "" -department_required = "" -email_missing = "" -greeting = "" -load_failed = "" -name_missing = "" -name_required = "" -phone_required = "" -phone_verify_required = "" -update_failed = "" -update_success = "" - -[msg.userfront.qr] -camera_error = "" -permission_error = "" -permission_required = "" - -[msg.userfront.reset] -invalid_body = "" -invalid_link = "" -invalid_title = "" -policy_loading = "" -success = "" - -[msg.userfront.sections] -apps_subtitle = "" -audit_subtitle = "" -sessions_subtitle = "" - -[msg.userfront.settings] -disabled = "" - -[msg.userfront.signup] -failed = "" -privacy_full = "" -tos_full = "" - -[ui.admin.audit] -export_csv = "" -load_more = "" -target = "" -title = "" - -[ui.admin.groups] -import_csv = "" - -[ui.admin.header] -plane = "" -subtitle = "" - -[ui.admin.nav] -org_chart = "" -api_keys = "" -audit_logs = "" -auth_guard = "" -logout = "" -overview = "" -relying_parties = "" -tenant_dashboard = "" -user_groups = "" -tenants = "" -users = "" - -[ui.admin.org] -download_template = "" -import_btn = "" -import_title = "" -start_import = "" - -[ui.admin.overview] -kicker = "" -title = "" - -[ui.admin.profile] -manageable_tenants = "" - -[ui.admin.role] -rp_admin = "" -super_admin = "" -tenant_admin = "" -user = "" - -[ui.admin.tenants] -add = "" -csv_template = "" -delete_selected = "" -export_with_ids = "" -export_without_ids = "" -import = "" -title = "" -view_org_chart = "" - -[ui.admin.tenants.domain_conflict] -description = "" -title = "" - -[ui.admin.tenants.import_result] -message = "" -modified = "" -status = "" -title = "" - -[ui.admin.tenants.import_preview] -candidates = "" -confirm = "" -create_new_reset = "" -csv_parents = "" -external_id = "" -match = "" -no_candidates = "" -parent = "" -parent_companies = "" -parent_company_groups = "" -parent_organizations = "" -parent_unresolved = "" -slug_exists = "" -title = "" -csv_parents = "" -parent = "" -parent_companies = "" -parent_company_groups = "" -parent_organizations = "" - -[ui.common.badge] -admin_only = "" -command_only = "" -system = "" - -[ui.common.status] -active = "" -blocked = "" -failure = "" -inactive = "" -ok = "" -pending = "" -success = "" - -[ui.dev.nav] -clients = "" -logout = "" -developer_request = "" -developer_grants = "" - -[ui.dev.welcome] -btn_request = "" - -[ui.dev.request] -admin_notes_placeholder = "" -cancel_approval = "" -cancel_notes_placeholder = "" - -[ui.dev.request.list] -title = "" - -[ui.dev.request.modal] -desc = "" -email = "" -name = "" -org = "" -phone = "" -reason = "" -reason_placeholder = "" -role = "" -pages = "" -title = "" - -[ui.dev.request.status] -approved = "" -cancelled = "" -pending = "" -rejected = "" - -[ui.dev.request.table] -actions = "" -date = "" -org = "" -reason = "" -pages = "" -status = "" -user = "" - -[ui.dev.grants] -actions = "" -admin_notes = "" -approved = "" -date = "" -email = "" -form.title = "" -grant = "" -input_section = "" -list.title = "" -pages = "" -read_only = "" -reason = "" -revoke = "" -revoke_notes_placeholder = "" -selected_info = "" -status = "" -phone = "" -tenant = "" -user = "" -user_search_placeholder = "" -user_section = "" - -[ui.dev.tenant] -single_notice = "" -switch_success = "" -workspace = "" -workspace_desc = "" - -[ui.dev.audit] -load_more = "" -title = "" - -[ui.dev.profile] -menu_aria = "" -menu_title = "" -unknown_email = "" -unknown_name = "" -title = "" -subtitle = "" -loading = "" -error = "" - -[ui.dev.clients] -new = "" -search_placeholder = "" -tenant_scoped = "" -untitled = "" - -[ui.dev.dashboard] -ready_badge = "" - -[ui.dev.header] -plane = "" -subtitle = "" - -[ui.dev.session] -auto_extend = "" -active = "" -disabled = "" -unknown = "" -expired = "" -expiring = "" -remaining = "" -refresh = "" -refreshing = "" - -[ui.userfront.app_label] -admin_console = "" -baron = "" -dev_console = "" - -[ui.userfront.auth_method] -ory = "" -session = "" - -[ui.userfront.dashboard] -link_status_label = "" -last_auth_label = "" -status_history = "" - -[ui.userfront.device] -android = "" -ios = "" -linux = "" -macos = "" -windows = "" - -[ui.userfront.error] -go_home = "" -go_login = "" -switch_account = "" - -[ui.userfront.forgot] -heading = "" -input_label = "" -submit = "" -title = "" - -[ui.userfront.login] -forgot_password = "" -signup = "" - -[ui.userfront.login_success] -later = "" -qr = "" -title = "" - -[ui.userfront.consent] -accept = "" -requested_scopes = "" -title = "" - -[ui.userfront.nav] -dashboard = "" -logout = "" -profile = "" -qr_scan = "" - -[ui.userfront.profile] -department_empty = "" -manage = "" -user_fallback = "" - -[ui.userfront.qr] -rescan = "" -result_success = "" -title = "" - -[ui.userfront.reset] -confirm_password = "" -new_password = "" -submit = "" -subtitle = "" -title = "" - -[ui.userfront.sections] -apps = "" -audit = "" -sessions = "" - -[ui.userfront.session] -active = "" -unknown = "" - -[ui.userfront.signup] -complete = "" -next_step = "" -title = "" +[msg.admin.api_keys] [msg.admin.api_keys.create] error = "" @@ -483,17 +91,22 @@ notice_emphasis = "" notice_suffix = "" [msg.admin.api_keys.list] -edit_scopes_desc = "" -rotate_confirm = "" -rotate_secret_notice = "" delete_confirm = "" +edit_scopes_desc = "" empty = "" fetch_error = "" +rotate_confirm = "" +rotate_secret_notice = "" subtitle = "" [msg.admin.api_keys.list.registry] count = "" +[msg.admin.apikeys] + +[msg.admin.apikeys.registry] +count = "" + [msg.admin.audit] empty = "" end = "" @@ -555,6 +168,55 @@ remove_success = "" [msg.admin.header] subtitle = "" +[msg.admin.integrity] +subtitle = "" + +[msg.admin.integrity.check] + +[msg.admin.integrity.check.duplicate_tenant_slugs] +description = "" + +[msg.admin.integrity.check.orphan_tenant_parents] +description = "" + +[msg.admin.integrity.check.orphan_user_login_id_tenants] +description = "" + +[msg.admin.integrity.check.orphan_user_login_id_users] +description = "" + +[msg.admin.integrity.check.orphan_user_tenant_memberships] +description = "" + +[msg.admin.integrity.forbidden] +description = "" + +[msg.admin.integrity.orphan_login_ids] +delete_confirm = "" +delete_success = "" +description = "" +empty = "" +load_error = "" + +[msg.admin.integrity.read_model] +description = "" + +[msg.admin.integrity.recheck] +error = "" +running = "" +success = "" + +[msg.admin.integrity.report] +load_error = "" + +[msg.admin.integrity.section] + +[msg.admin.integrity.section.tenant_integrity] +description = "" + +[msg.admin.integrity.section.user_integrity] +description = "" + [msg.admin.notice] idp_policy = "" scope = "" @@ -563,8 +225,19 @@ scope = "" hover_member_info = "" import_description = "" import_error = "" +import_partial_success = "" import_success = "" +[msg.admin.ory_ssot] +flush_confirm = "" +flush_error = "" +flush_success = "" +load_error = "" +subtitle = "" + +[msg.admin.ory_ssot.forbidden] +description = "" + [msg.admin.overview] description = "" idp_fallback = "" @@ -584,28 +257,52 @@ description = "" audit_events_24h = "" oidc_clients = "" policy_gate = "" -total_users = "" total_tenants = "" +total_users = "" + +[msg.admin.permissions_direct] +desc_api_keys = "" +desc_audit_logs = "" +desc_auth_guard = "" +desc_data_integrity = "" +desc_org_chart = "" +desc_ory_ssot = "" +desc_overview = "" +desc_permissions_direct = "" +desc_tenants = "" +desc_users = "" +desc_worksmobile = "" +description = "" +no_user_selected_desc = "" +no_users_found = "" + +[msg.admin.system] + +[msg.admin.system.relations] +remove_all_confirm = "" +update_success = "" [msg.admin.tenants] approve_confirm = "" approve_success = "" +delete_bulk_confirm = "" delete_confirm = "" delete_success = "" empty = "" -fetch_error = "" +empty_scope = "" +empty_search = "" export_error = "" +fetch_error = "" import_empty = "" import_error = "" import_result = "" missing_id = "" not_found = "" remove_sub_confirm = "" +seed_delete_blocked = "" +status_error = "" subtitle = "" -[msg.admin.tenants.import_preview] -description = "" - [msg.admin.tenants.admins] add_success = "" empty = "" @@ -615,6 +312,10 @@ remove_self = "" remove_success = "" subtitle = "" +[msg.admin.tenants.bulk] +update_error = "" +update_success = "" + [msg.admin.tenants.create] pick_parent_first = "" subtitle = "" @@ -629,7 +330,12 @@ subtitle = "" [msg.admin.tenants.create.profile] subtitle = "" +[msg.admin.tenants.import_preview] +description = "" + [msg.admin.tenants.members] +add_error = "" +add_success = "" desc = "" empty = "" limit_notice = "" @@ -646,6 +352,11 @@ remove_self = "" remove_success = "" subtitle = "" +[msg.admin.tenants.parent] +local_picker_description = "" +local_picker_empty = "" +picker_description = "" + [msg.admin.tenants.registry] count = "" scope_results = "" @@ -654,9 +365,11 @@ search_results = "" table_hint = "" tree_hint = "" -[msg.admin.tenants] -empty_scope = "" -empty_search = "" +[msg.admin.tenants.relations] +empty = "" +remove_all_confirm = "" +subtitle = "" +update_success = "" [msg.admin.tenants.schema] empty = "" @@ -666,13 +379,28 @@ subtitle = "" update_error = "" update_success = "" +[msg.admin.tenants.scope] +description = "" + [msg.admin.tenants.sub] empty = "" subtitle = "" +[msg.admin.user_projection] +action_error = "" +action_success = "" +forbidden_description = "" +load_error = "" +reset_confirm = "" +subtitle = "" + +[msg.admin.user_projection.forbidden] +description = "" + [msg.admin.users] confirm_remove_org = "" export_error = "" +self_delete_blocked = "" status_error = "" [msg.admin.users.bulk] @@ -683,10 +411,10 @@ move_description = "" move_error = "" move_success = "" parsed_count = "" +permission_placeholder = "" schema_incompatible = "" schema_missing = "" status_placeholder = "" -permission_placeholder = "" update_success = "" [msg.admin.users.create] @@ -746,6 +474,11 @@ name_required = "" [msg.admin.users.detail.security] password_hint = "" +[msg.admin.users.global_custom_claims] +description = "" +empty = "" +registry = "" + [msg.admin.users.list] delete_confirm = "" empty = "" @@ -774,34 +507,6 @@ unknown_error = "" [msg.dev] logout_confirm = "" -[msg.dev.grants] -admin_notes_description = "" -admin_notes_hint = "" -admin_notes_placeholder = "" -approved = "" -count = "" -create_success = "" -description = "" -empty = "" -forbidden = "" -forbidden_desc = "" -form.description = "" -list.description = "" -load_error = "" -pages_hint = "" -phone_missing = "" -reason = "" -revoke = "" -revoke_success = "" -search_empty = "" -search_loading = "" -selected_info_description = "" -selected_user = "" -tenant_missing = "" -tenant_required = "" -user_required = "" -user_section_description = "" - [msg.dev.audit] access_denied = "" access_denied_detail = "" @@ -813,53 +518,6 @@ loading = "" registry_description = "" subtitle = "" -[msg.dev.request] -admin_desc = "" -approved = "" -cancelled = "" -empty = "" -need_cancel_notes = "" -need_notes = "" -rejected = "" -user_desc = "" - -[msg.dev.request.modal] -desc = "" -email = "" -name = "" -org = "" -phone = "" -reason = "" -reason_placeholder = "" -role = "" -pages_hint = "" -title = "" - -[msg.dev.request.status] -approved = "" -cancelled = "" -pending = "" -rejected = "" - -[msg.dev.request.table] -actions = "" -date = "" -org = "" -reason = "" -status = "" -user = "" - -[msg.dev.request.list] -approved_count = "" -title = "" - -[msg.dev.request.admin] -notes_placeholder = "" - -[msg.dev.request.cancel] -approval = "" -notes_placeholder = "" - [msg.dev.auth] access_denied_description = "" access_denied_title = "" @@ -996,12 +654,6 @@ access_pending = "" access_pending_detail = "" description = "" -[msg.dev.dashboard.hero] -body = "" -title_emphasis = "" -title_prefix = "" -title_suffix = "" - [msg.dev.dashboard.chart] empty = "" filter_description = "" @@ -1013,6 +665,17 @@ unavailable_with_reason = "" [msg.dev.dashboard.distribution] description = "" +[msg.dev.dashboard.hero] +body = "" +title_emphasis = "" +title_prefix = "" +title_suffix = "" + +[msg.dev.dashboard.notice] +consent_audit = "" +dev_scope = "" +hydra_health = "" + [msg.dev.dashboard.recent] empty = "" none = "" @@ -1021,11 +684,6 @@ none = "" description = "" empty = "" -[msg.dev.dashboard.notice] -consent_audit = "" -dev_scope = "" -hydra_health = "" - [msg.dev.forbidden] default = "" rp_admin = "" @@ -1036,6 +694,85 @@ user.audit = "" user.clients = "" user.consents = "" +[msg.dev.grants] +admin_notes_description = "" +admin_notes_hint = "" +admin_notes_placeholder = "" +approved = "" +count = "" +create_success = "" +description = "" +empty = "" +forbidden = "" +forbidden_desc = "" +load_error = "" +pages_hint = "" +phone_missing = "" +reason = "" +revoke = "" +revoke_success = "" +search_empty = "" +search_loading = "" +selected_info_description = "" +selected_user = "" +tenant_missing = "" +tenant_required = "" +user_required = "" +user_section_description = "" + +[msg.dev.grants.form] +description = "" + +[msg.dev.grants.list] +description = "" + +[msg.dev.request] +admin_desc = "" +approved = "" +cancelled = "" +empty = "" +need_cancel_notes = "" +need_notes = "" +rejected = "" +user_desc = "" + +[msg.dev.request.admin] +notes_placeholder = "" + +[msg.dev.request.cancel] +approval = "" +notes_placeholder = "" + +[msg.dev.request.list] +approved_count = "" +title = "" + +[msg.dev.request.modal] +desc = "" +email = "" +name = "" +org = "" +pages_hint = "" +phone = "" +reason = "" +reason_placeholder = "" +role = "" +title = "" + +[msg.dev.request.status] +approved = "" +cancelled = "" +pending = "" +rejected = "" + +[msg.dev.request.table] +actions = "" +date = "" +org = "" +reason = "" +status = "" +user = "" + [msg.dev.sidebar] notice = "" notice_detail = "" @@ -1058,6 +795,9 @@ result = "" session_id = "" status = "" +[msg.userfront.audit.filter] +description = "" + [msg.userfront.consent] accept_error = "" client_id = "" @@ -1084,14 +824,15 @@ approved_device = "" approved_ip = "" audit_empty = "" audit_load_error = "" -auto_login_supported = "" auth_method = "" +auto_login_supported = "" client_id = "" client_id_missing = "" current_status = "" last_auth = "" link_missing = "" link_open_error = "" +link_status = "" render_error = "" session_id_copied = "" @@ -1100,6 +841,19 @@ empty = "" empty_detail = "" error = "" +[msg.userfront.dashboard.approved_session] +copy_click = "" +copy_tap = "" +none = "" + +[msg.userfront.dashboard.revoke] +confirm = "" +error = "" +success = "" + +[msg.userfront.dashboard.scopes] +empty = "" + [msg.userfront.dashboard.sessions] browser = "" empty = "" @@ -1114,19 +868,6 @@ confirm = "" error = "" success = "" -[msg.userfront.dashboard.approved_session] -copy_click = "" -copy_tap = "" -none = "" - -[msg.userfront.dashboard.revoke] -confirm = "" -error = "" -success = "" - -[msg.userfront.dashboard.scopes] -empty = "" - [msg.userfront.dashboard.timeline] load_error = "" @@ -1140,6 +881,22 @@ title_generic = "" title_with_code = "" type = "" +[msg.userfront.error.ory] +$normalizedCode = "" +access_denied = "" +consent_required = "" +interaction_required = "" +invalid_client = "" +invalid_grant = "" +invalid_request = "" +invalid_scope = "" +login_required = "" +request_forbidden = "" +server_error = "" +temporarily_unavailable = "" +unauthorized_client = "" +unsupported_response_type = "" + [msg.userfront.error.tenant] account = "" account_unknown = "" @@ -1156,24 +913,8 @@ tenant = "" tenant_unknown = "" title = "" -[msg.userfront.error.ory] -"$normalizedCode" = "" -access_denied = "" -consent_required = "" -interaction_required = "" -invalid_client = "" -invalid_grant = "" -invalid_request = "" -invalid_scope = "" -login_required = "" -request_forbidden = "" -server_error = "" -temporarily_unavailable = "" -unauthorized_client = "" -unsupported_response_type = "" - [msg.userfront.error.whitelist] -"$normalizedCode" = "" +$normalizedCode = "" bad_request = "" invalid_session = "" not_found = "" @@ -1233,8 +974,8 @@ body = "" approved = "" approved_local = "" approved_remote = "" -pending_remote = "" close_hint = "" +pending_remote = "" success = "" [msg.userfront.login_success] @@ -1309,6 +1050,7 @@ uppercase = "" [msg.userfront.sections] apps_subtitle = "" audit_subtitle = "" +sessions_subtitle = "" [msg.userfront.settings] disabled = "" @@ -1387,6 +1129,17 @@ key = "" [test] key = "" +[this] + +[this.key] + +[this.key.truly] + +[this.key.truly.does] + +[this.key.truly.does.not] +exist = "" + [ui] [ui.admin] @@ -1432,6 +1185,11 @@ last_used = "" name = "" scopes = "" +[ui.admin.apikeys] + +[ui.admin.apikeys.registry] +title = "" + [ui.admin.audit] export_csv = "" load_more = "" @@ -1477,6 +1235,32 @@ request = "" status = "" time = "" +[ui.admin.auth_guard] +subtitle = "" +title = "" + +[ui.admin.auth_guard.checker] +allowed = "" +allowed_description = "" +check = "" +checking = "" +denied = "" +denied_description = "" +description = "" +namespace = "" +namespace.label = "" +namespace.relying_party = "" +namespace.system = "" +namespace.tenant = "" +namespace.tenant_group = "" +object_id = "" +object_id_placeholder = "" +relation = "" +relation_placeholder = "" +subject = "" +subject_placeholder = "" +title = "" + [ui.admin.groups] import_csv = "" @@ -1525,18 +1309,95 @@ name = "" plane = "" subtitle = "" +[ui.admin.integrity] +fetch_error = "" +kicker = "" +loading = "" +subtitle = "" +tab_checks = "" +tab_ory_ssot = "" +tab_user_projection = "" +title = "" + +[ui.admin.integrity.check] + +[ui.admin.integrity.check.duplicate_tenant_slugs] +title = "" + +[ui.admin.integrity.check.orphan_tenant_parents] +title = "" + +[ui.admin.integrity.check.orphan_user_login_id_tenants] +title = "" + +[ui.admin.integrity.check.orphan_user_login_id_users] +title = "" + +[ui.admin.integrity.check.orphan_user_tenant_memberships] +title = "" + +[ui.admin.integrity.forbidden] +title = "" + +[ui.admin.integrity.orphan_login_ids] +delete = "" +title = "" + +[ui.admin.integrity.read_model] +title = "" + +[ui.admin.integrity.reason] +deleted_tenant = "" +deleted_user = "" +missing_tenant = "" +missing_user = "" + +[ui.admin.integrity.recheck] +run = "" +running = "" + +[ui.admin.integrity.section] +tenant_integrity = "" +user_integrity = "" + +[ui.admin.integrity.status] +fail = "" +pass = "" +warning = "" + +[ui.admin.integrity.summary] +checked_at = "" +failures = "" +failures_text = "" +passed = "" +title = "" +total_checks = "" + +[ui.admin.integrity.table] +field = "" +login_id = "" +reason = "" +select = "" +select_item = "" +tenant = "" +user = "" + [ui.admin.nav] -org_chart = "" api_keys = "" audit_logs = "" auth_guard = "" +data_integrity = "" logout = "" +org_chart = "" +ory_ssot = "" overview = "" +permissions_direct = "" relying_parties = "" tenant_dashboard = "" tenants = "" user_groups = "" users = "" +worksmobile = "" [ui.admin.org] download_template = "" @@ -1544,10 +1405,46 @@ import_btn = "" import_title = "" start_import = "" +[ui.admin.ory_ssot] +loading = "" +title = "" + +[ui.admin.ory_ssot.actions] +flush_identity_cache = "" + +[ui.admin.ory_ssot.cache_card] +description = "" +title = "" + +[ui.admin.ory_ssot.forbidden] +title = "" + +[ui.admin.ory_ssot.projection_card] +description = "" +title = "" + +[ui.admin.ory_ssot.status] +failed = "" +not_ready = "" +ready = "" + +[ui.admin.ory_ssot.summary] +cache_keys = "" +last_refreshed = "" +last_synced = "" +local_users = "" +observed_identities = "" +status = "" +updated_at = "" + [ui.admin.overview] kicker = "" title = "" +[ui.admin.overview.chart] +description = "" +title = "" + [ui.admin.overview.playbook] title = "" @@ -1562,8 +1459,20 @@ view_audit_logs = "" audit_events_24h = "" oidc_clients = "" policy_gate = "" -total_users = "" total_tenants = "" +total_users = "" + +[ui.admin.permissions_direct] +allowed = "" +cat_dashboard = "" +cat_integrations = "" +cat_resources = "" +cat_system = "" +dialog_title_system = "" +no_user_selected = "" +revoke_all = "" +super_admin_only = "" +user_list = "" [ui.admin.profile] manageable_tenants = "" @@ -1576,25 +1485,18 @@ user = "" [ui.admin.tenants] add = "" +csv_template = "" +data_mgmt = "" delete_selected = "" +export_with_ids = "" +export_without_ids = "" +import = "" +search_match_badge = "" seed_badge = "" -path.root = "" title = "" +toggle_status = "" view_org_chart = "" -[ui.admin.tenants.sub] -export = "" - -[ui.admin.tenants.view] -hierarchy = "" -list = "" -table = "" -tree = "" - -[ui.admin.tenants.scope] -active = "" -pick = "" - [ui.admin.tenants.admins] add_button = "" already_admin = "" @@ -1613,6 +1515,10 @@ title = "" list = "" section = "" +[ui.admin.tenants.bulk] +selected_count = "" +status_placeholder = "" + [ui.admin.tenants.create] title = "" @@ -1635,15 +1541,15 @@ slug_placeholder = "" status = "" type = "" +[ui.admin.tenants.create.memo] +title = "" + [ui.admin.tenants.create.parent_context] general = "" hanmac = "" pick_required = "" root = "" -[ui.admin.tenants.create.memo] -title = "" - [ui.admin.tenants.create.profile] title = "" @@ -1655,25 +1561,37 @@ tab_federation = "" tab_organization = "" tab_permissions = "" tab_profile = "" +tab_relations = "" tab_schema = "" tab_worksmobile = "" title = "" -[ui.admin.tenants.worksmobile] -compare = "" -compare_description = "" -compare_groups = "" -compare_users = "" -dry_run = "" -forbidden = "" -initial_password_csv = "" -recent_jobs = "" -refresh = "" -single_sync = "" -single_sync_description = "" -subtitle = "" -sync_orgunit = "" -sync_user = "" +[ui.admin.tenants.domain_conflict] +description = "" +title = "" + +[ui.admin.tenants.import_preview] +candidates = "" +confirm = "" +create_new = "" +create_new_reset = "" +csv_parents = "" +external_id = "" +fixed_id = "" +match = "" +no_candidates = "" +parent = "" +parent_companies = "" +parent_company_groups = "" +parent_organizations = "" +parent_unresolved = "" +slug_exists = "" +title = "" + +[ui.admin.tenants.import_result] +message = "" +modified = "" +status = "" title = "" [ui.admin.tenants.list] @@ -1682,61 +1600,25 @@ select_placeholder = "" [ui.admin.tenants.members] add_existing = "" +add_existing_description = "" +add_queued = "" create_new = "" +delete_selected = "" descendants = "" direct = "" direct_label = "" +export = "" list_title = "" -remove = "" -title = "" -total = "" -total_label = "" -view_profile = "" - -[msg.admin.apikeys.registry] -count = "" - -[msg.admin.org] -import_partial_success = "" - -[msg.admin.tenants] -delete_bulk_confirm = "" -import_empty = "" -import_error = "" -import_result = "" -seed_delete_blocked = "" - -[msg.admin.tenants.import_preview] -description = "" - -[msg.admin.tenants.parent] -local_picker_description = "" -local_picker_empty = "" -picker_description = "" - -[msg.admin.tenants.scope] -description = "" - -[msg.admin.users] -self_delete_blocked = "" -export_error = "" -status_error = "" - -[ui.admin.apikeys.registry] -title = "" - -[ui.admin.tenants.members] -add_existing = "" -create_new = "" -delete_selected = "" -remove = "" org_picker_title = "" -view_org_chart = "" -direct_label = "" -list_title = "" +queue_empty = "" +queue_remove = "" +remove = "" +search_min_length = "" +search_placeholder = "" title = "" total = "" total_label = "" +view_org_chart = "" view_profile = "" [ui.admin.tenants.members.table] @@ -1757,6 +1639,15 @@ table_email = "" table_name = "" title = "" +[ui.admin.tenants.parent] +company_only = "" +local_search_placeholder = "" +pick_tenant = "" +search_placeholder = "" + +[ui.admin.tenants.path] +root = "" + [ui.admin.tenants.profile] allowed_domains = "" allowed_domains_help = "" @@ -1770,20 +1661,23 @@ subtitle = "" title = "" type = "" visibility = "" +worksmobile_enabled = "" +worksmobile_excluded = "" +worksmobile_sync = "" [ui.admin.tenants.profile.form] parent = "" parent_help = "" -[ui.admin.tenants.parent] -company_only = "" -search_placeholder = "" -local_search_placeholder = "" -pick_tenant = "" - [ui.admin.tenants.registry] title = "" +[ui.admin.tenants.relations] +add_button = "" +already_added = "" +dialog_title = "" +title = "" + [ui.admin.tenants.schema] add_field = "" save = "" @@ -1808,37 +1702,22 @@ type_text = "" unsigned = "" validation_placeholder = "" +[ui.admin.tenants.scope] +active = "" +pick = "" + [ui.admin.tenants.sub] add = "" add_dialog_desc = "" add_dialog_title = "" add_existing = "" +export = "" manage = "" no_candidates = "" search_placeholder = "" title = "" tree_search_placeholder = "" -[ui.admin.tenants.import_result] -message = "" -modified = "" -status = "" -title = "" - -[ui.admin.tenants.import_preview] -candidates = "" -confirm = "" -create_new = "" -csv_parents = "" -fixed_id = "" -match = "" -no_candidates = "" -parent = "" -parent_companies = "" -parent_company_groups = "" -parent_organizations = "" -title = "" - [ui.admin.tenants.sub.table] action = "" name = "" @@ -1848,6 +1727,7 @@ status = "" [ui.admin.tenants.table] actions = "" context = "" +created = "" id = "" id_copy = "" members = "" @@ -1859,10 +1739,60 @@ slug = "" status = "" type = "" updated = "" -created = "" + +[ui.admin.tenants.view] +hierarchy = "" +list = "" +table = "" +tree = "" + +[ui.admin.tenants.worksmobile] +compare = "" +compare_description = "" +compare_groups = "" +compare_users = "" +dry_run = "" +forbidden = "" +initial_password_csv = "" +recent_jobs = "" +refresh = "" +single_sync = "" +single_sync_description = "" +subtitle = "" +sync_orgunit = "" +sync_user = "" +title = "" + +[ui.admin.user_projection] +loading = "" +subtitle = "" +title = "" + +[ui.admin.user_projection.actions] +reconcile = "" +reset = "" + +[ui.admin.user_projection.card] +description = "" +title = "" + +[ui.admin.user_projection.forbidden] +title = "" + +[ui.admin.user_projection.status] +failed = "" +not_ready = "" +ready = "" + +[ui.admin.user_projection.summary] +last_synced = "" +projected_users = "" +status = "" +updated_at = "" [ui.admin.users] csv_template = "" +data_mgmt = "" [ui.admin.users.bulk] acknowledge_warning = "" @@ -1874,14 +1804,14 @@ move_group = "" move_title = "" no_changes = "" no_department = "" +permission_placeholder = "" schema_warning = "" select_group = "" selected_count = "" start_upload = "" +status_placeholder = "" tenant_resolution = "" title = "" -status_placeholder = "" -permission_placeholder = "" [ui.admin.users.create] back = "" @@ -1914,9 +1844,9 @@ name = "" name_placeholder = "" password = "" password_placeholder = "" -picker_description = "" phone = "" phone_placeholder = "" +picker_description = "" position = "" position_placeholder = "" role = "" @@ -2182,6 +2112,16 @@ role = "" status = "" tenant = "" +[ui.admin.users.global_custom_claims] +description_placeholder = "" +label_placeholder = "" +manage_definitions = "" +read_permission = "" +registry = "" +title = "" +value_type = "" +write_permission = "" + [ui.admin.users.list] add = "" bulk_import = "" @@ -2191,8 +2131,8 @@ fetch_error = "" search_placeholder = "" status_select = "" subtitle = "" -toggle_status = "" title = "" +toggle_status = "" [ui.admin.users.list.breadcrumb] list = "" @@ -2698,15 +2638,7 @@ policy_toggle = "" registry = "" rp_synced = "" -[ui.dev.dashboard.distribution] -headless_hint = "" -pkce = "" -private = "" -title = "" - [ui.dev.dashboard.chart] -x_axis = "" -y_axis = "" aria = "" filter_all = "" period_day = "" @@ -2714,6 +2646,14 @@ period_month = "" period_week = "" series = "" title = "" +x_axis = "" +y_axis = "" + +[ui.dev.dashboard.distribution] +headless_hint = "" +pkce = "" +private = "" +title = "" [ui.dev.dashboard.next] subtitle = "" @@ -2765,14 +2705,43 @@ active_sessions = "" auth_failures_24h = "" total_clients = "" +[ui.dev.grants] +actions = "" +admin_notes = "" +approved = "" +date = "" +email = "" +grant = "" +input_section = "" +pages = "" +phone = "" +read_only = "" +reason = "" +revoke = "" +revoke_notes_placeholder = "" +selected_info = "" +status = "" +tenant = "" +user = "" +user_search_placeholder = "" +user_section = "" + +[ui.dev.grants.form] +title = "" + +[ui.dev.grants.list] +title = "" + [ui.dev.header] plane = "" subtitle = "" [ui.dev.nav] -overview = "" clients = "" +developer_grants = "" +developer_request = "" logout = "" +overview = "" [ui.dev.profile] error = "" @@ -2806,6 +2775,41 @@ title = "" basic = "" role = "" +[ui.dev.request] +admin_notes_placeholder = "" +cancel_approval = "" +cancel_notes_placeholder = "" + +[ui.dev.request.list] +title = "" + +[ui.dev.request.modal] +desc = "" +email = "" +name = "" +org = "" +pages = "" +phone = "" +reason = "" +reason_placeholder = "" +role = "" +title = "" + +[ui.dev.request.status] +approved = "" +cancelled = "" +pending = "" +rejected = "" + +[ui.dev.request.table] +actions = "" +date = "" +org = "" +pages = "" +reason = "" +status = "" +user = "" + [ui.dev.session] active = "" auto_extend = "" @@ -2823,6 +2827,34 @@ switch_success = "" workspace = "" workspace_desc = "" +[ui.dev.welcome] +btn_request = "" + +[ui.shell] + +[ui.shell.nav] +logout = "" +profile = "" + +[ui.shell.profile] +menu_aria = "" +menu_title = "" +unknown_email = "" +unknown_name = "" + +[ui.shell.session] +active = "" +auto_extend = "" +disabled = "" +expired = "" +expiring = "" +remaining = "" +unknown = "" + +[ui.shell.sidebar] +collapse = "" +expand = "" + [ui.userfront] app_title = "" @@ -2833,6 +2865,10 @@ dev_console = "" [ui.userfront.audit] +[ui.userfront.audit.filter] +title = "" +toggle_label = "" + [ui.userfront.audit.table] action = "" app = "" @@ -2861,22 +2897,12 @@ title = "" [ui.userfront.dashboard] last_auth_label = "" +link_status_label = "" status_history = "" [ui.userfront.dashboard.activity] linked = "" -[ui.userfront.dashboard.sessions] -active_badge = "" -current_badge = "" -current_disabled = "" -unknown_device = "" -unknown_session = "" - -[ui.userfront.dashboard.sessions.revoke] -action = "" -title = "" - [ui.userfront.dashboard.approved_session] default = "" userfront = "" @@ -2888,6 +2914,17 @@ title = "" [ui.userfront.dashboard.scopes] title = "" +[ui.userfront.dashboard.sessions] +active_badge = "" +current_badge = "" +current_disabled = "" +unknown_device = "" +unknown_session = "" + +[ui.userfront.dashboard.sessions.revoke] +action = "" +title = "" + [ui.userfront.dashboard.status] revoked = "" @@ -2950,36 +2987,13 @@ title = "" [ui.userfront.login.verification] action_label = "" -action_label_remote = "" action_label_close = "" +action_label_remote = "" page_title = "" title = "" title_pending = "" title_remote = "" -[ui.shell.nav] -logout = "" -profile = "" - -[ui.shell.sidebar] -collapse = "" -expand = "" - -[ui.shell.profile] -menu_aria = "" -menu_title = "" -unknown_email = "" -unknown_name = "" - -[ui.shell.session] -active = "" -auto_extend = "" -disabled = "" -expired = "" -expiring = "" -remaining = "" -unknown = "" - [ui.userfront.login_success] later = "" qr = "" @@ -3040,6 +3054,7 @@ title = "" [ui.userfront.sections] apps = "" audit = "" +sessions = "" [ui.userfront.session] active = "" @@ -3087,332 +3102,3 @@ verify = "" [ui.userfront.signup.success] action = "" - - -[ui.userfront.audit.filter] -title = "" -toggle_label = "" - -[msg.userfront.audit.filter] -description = "" - -[msg.admin.integrity.forbidden] -description = "" - -[msg.admin.integrity.orphan_login_ids] -delete_confirm = "" -delete_success = "" -description = "" -empty = "" -load_error = "" - -[msg.admin.integrity.read_model] -description = "" - -[msg.admin.integrity.recheck] -error = "" -running = "" -success = "" - -[msg.admin.integrity.report] -load_error = "" - -[msg.admin.integrity.check.duplicate_tenant_slugs] -description = "" - -[msg.admin.integrity.check.orphan_tenant_parents] -description = "" - -[msg.admin.integrity.check.orphan_user_login_id_tenants] -description = "" - -[msg.admin.integrity.check.orphan_user_login_id_users] -description = "" - -[msg.admin.integrity.check.orphan_user_tenant_memberships] -description = "" - -[ui.admin.integrity] -tab_checks = "" -tab_user_projection = "" -subtitle = "" - -[ui.admin.tenants.profile] -worksmobile_enabled = "" -worksmobile_excluded = "" -worksmobile_sync = "" -allowed_domains = "" - - -[msg.admin.integrity] -subtitle = "" - -[msg.admin.integrity.section.tenant_integrity] -description = "" - -[ui.admin.integrity] -fetch_error = "" -kicker = "" -loading = "" -subtitle = "" -tab_checks = "" -tab_user_projection = "" -title = "" - -[ui.admin.tenants.profile] -allowed_domains = "" -worksmobile_enabled = "" -worksmobile_excluded = "" -worksmobile_sync = "" - -[msg.admin.integrity.section.user_integrity] -description = "" - -[msg.admin.user_projection] -action_error = "" -action_success = "" -forbidden_description = "" -load_error = "" -reset_confirm = "" -subtitle = "" - -[msg.admin.user_projection.forbidden] -description = "" - -[ui.admin.integrity] -fetch_error = "" -kicker = "" -loading = "" -title = "" - -[ui.admin.integrity.forbidden] -title = "" - -[ui.admin.integrity.orphan_login_ids] -delete = "" -title = "" - -[ui.admin.integrity.read_model] -title = "" - -[ui.admin.integrity.reason] -deleted_tenant = "" -deleted_user = "" -missing_tenant = "" -missing_user = "" - -[ui.admin.integrity.recheck] -run = "" -running = "" - -[ui.admin.integrity.status] -fail = "" -pass = "" -warning = "" - -[ui.admin.integrity.summary] -checked_at = "" -failures = "" -failures_text = "" -passed = "" -title = "" -total_checks = "" - -[ui.admin.integrity.table] -field = "" -login_id = "" -reason = "" -select = "" -select_item = "" -tenant = "" -user = "" - -[ui.admin.integrity.section] -tenant_integrity = "" -user_integrity = "" - -[ui.admin.integrity.check.duplicate_tenant_slugs] -title = "" - -[ui.admin.integrity.check.orphan_tenant_parents] -title = "" - -[ui.admin.integrity.check.orphan_user_login_id_tenants] -title = "" - -[ui.admin.integrity.check.orphan_user_login_id_users] -title = "" - -[ui.admin.integrity.check.orphan_user_tenant_memberships] -title = "" - -[msg.admin.api_keys.list] -edit_scopes_desc = "" -rotate_confirm = "" -rotate_secret_notice = "" - -[msg.admin.tenants] -export_error = "" - -[ui.admin.api_keys.list] -edit_scopes = "" -rotate_secret = "" -rotate_secret_done = "" -save_scopes = "" - -[ui.admin.user_projection] -loading = "" -title = "" - -[ui.admin.user_projection.actions] -reconcile = "" -reset = "" - -[ui.admin.user_projection.card] -description = "" -title = "" - -[ui.admin.user_projection.forbidden] -title = "" - -[ui.admin.user_projection.status] -failed = "" -not_ready = "" -ready = "" - -[ui.admin.user_projection.summary] -last_synced = "" -projected_users = "" -status = "" -updated_at = "" - -[ui.admin.auth_guard] -subtitle = "" -title = "" - -[ui.admin.auth_guard.checker] -check = "" -checking = "" -denied = "" -denied_description = "" -description = "" -object_id = "" -object_id_placeholder = "" -allowed = "" -allowed_description = "" -namespace = "" -relation = "" -relation_placeholder = "" -subject = "" -subject_placeholder = "" -title = "" - -[ui.admin.auth_guard.checker.namespace] -label = "" -relying_party = "" -system = "" -tenant = "" -tenant_group = "" - -[ui.admin.overview.summary] -total_users = "" - -[ui.admin.overview.chart] -description = "" -title = "" - -[ui.admin.tenants.sub] -export = "" - -[ui.admin.users.bulk] -permission_placeholder = "" -status_placeholder = "" - -[ui.dev.profile.org] -tenant_slug = "" - -[] -"msg.admin.tenants.bulk.update_error" = "temp" -"msg.admin.tenants.bulk.update_success" = "temp" -"msg.admin.tenants.status_error" = "temp" -"ui.admin.tenants.bulk.selected_count" = "temp" -"ui.admin.tenants.bulk.status_placeholder" = "temp" -"ui.admin.tenants.data_mgmt" = "temp" -"ui.admin.tenants.toggle_status" = "temp" -"ui.admin.users.data_mgmt" = "temp" - -[msg.admin.ory_ssot] -flush_confirm = "" -flush_error = "" -flush_success = "" -load_error = "" -subtitle = "" - -[msg.admin.ory_ssot.forbidden] -description = "" - -[msg.admin.tenants.members] -add_error = "" -add_success = "" - -[msg.admin.users.global_custom_claims] -description = "" -empty = "" -registry = "" - -[ui.admin.integrity] -tab_ory_ssot = "" - -[ui.admin.ory_ssot] -loading = "" -title = "" - -[ui.admin.ory_ssot.actions] -flush_identity_cache = "" - -[ui.admin.ory_ssot.cache_card] -description = "" -title = "" - -[ui.admin.ory_ssot.forbidden] -title = "" - -[ui.admin.ory_ssot.projection_card] -description = "" -title = "" - -[ui.admin.ory_ssot.status] -failed = "" -not_ready = "" -ready = "" - -[ui.admin.ory_ssot.summary] -cache_keys = "" -last_refreshed = "" -last_synced = "" -local_users = "" -observed_identities = "" -status = "" -updated_at = "" - -[ui.admin.tenants] -search_match_badge = "" - -[ui.admin.tenants.members] -add_existing_description = "" -add_queued = "" -export = "" -queue_empty = "" -queue_remove = "" -search_min_length = "" -search_placeholder = "" - -[ui.admin.users.global_custom_claims] -description_placeholder = "" -label_placeholder = "" -manage_definitions = "" -read_permission = "" -registry = "" -title = "" -value_type = "" -write_permission = "" diff --git a/tools/i18n-scanner/index.js b/tools/i18n-scanner/index.js index 27e8993d..1f27ab11 100644 --- a/tools/i18n-scanner/index.js +++ b/tools/i18n-scanner/index.js @@ -74,13 +74,25 @@ function parseTomlKeys(filePath) { if (line.startsWith('[[') && line.endsWith(']]')) { const sectionName = line.slice(2, -2).trim(); - currentSection = sectionName ? sectionName.split('.').map((p) => p.trim()).filter(Boolean) : []; + currentSection = sectionName ? sectionName.split('.').map((p) => { + p = p.trim(); + if ((p.startsWith('"') && p.endsWith('"')) || (p.startsWith("'") && p.endsWith("'"))) { + p = p.slice(1, -1).trim(); + } + return p; + }).filter(Boolean) : []; continue; } if (line.startsWith('[') && line.endsWith(']')) { const sectionName = line.slice(1, -1).trim(); - currentSection = sectionName ? sectionName.split('.').map((p) => p.trim()).filter(Boolean) : []; + currentSection = sectionName ? sectionName.split('.').map((p) => { + p = p.trim(); + if ((p.startsWith('"') && p.endsWith('"')) || (p.startsWith("'") && p.endsWith("'"))) { + p = p.slice(1, -1).trim(); + } + return p; + }).filter(Boolean) : []; continue; } @@ -94,8 +106,8 @@ function parseTomlKeys(filePath) { continue; } - if (key.startsWith('"') && key.endsWith('"')) { - key = key.slice(1, -1); + if ((key.startsWith('"') && key.endsWith('"')) || (key.startsWith("'") && key.endsWith("'"))) { + key = key.slice(1, -1).trim(); } const fullKey = [...currentSection, key].join('.'); diff --git a/tools/i18n-scanner/translate-locales.js b/tools/i18n-scanner/translate-locales.js index 7657248f..dd6d37b4 100644 --- a/tools/i18n-scanner/translate-locales.js +++ b/tools/i18n-scanner/translate-locales.js @@ -5,10 +5,42 @@ const fs = require('fs'); const path = require('path'); const ROOT = process.cwd(); -const LOCALES_DIR = path.join(ROOT, 'locales'); -const TEMPLATE_PATH = path.join(LOCALES_DIR, 'template.toml'); -const KO_PATH = path.join(LOCALES_DIR, 'ko.toml'); -const EN_PATH = path.join(LOCALES_DIR, 'en.toml'); + +const LOCALE_SPECS = [ + { + name: 'root', + label: 'root locales', + dir: path.join(ROOT, 'locales'), + template: 'template.toml', + langs: ['ko.toml', 'en.toml'], + ownsKey: (key) => !key.startsWith('ui.common.') && !key.startsWith('msg.common.'), + }, + { + name: 'common', + label: 'common locales', + dir: path.join(ROOT, 'common', 'locales'), + template: 'template.toml', + langs: ['ko.toml', 'en.toml'], + ownsKey: (key) => key.startsWith('ui.common.') || key.startsWith('msg.common.'), + }, +]; + +function shouldIgnoreCodeKey(key) { + return ( + key.includes('.msg.') || + key.includes('.ui.') || + key.includes('.err.') || + key.includes('.test.') || + key.includes('.non.') || + key.startsWith('ui.admin.users.list.table.') || + key.startsWith('msg.admin.users.detail.') || + key.startsWith('msg.dev.clients.') || + key.startsWith('ui.admin.users.create.') || + key.startsWith('ui.admin.users.detail.') || + key.startsWith('ui.dev.clients.') || + key.startsWith('ui.dev.session.') + ); +} const SKIP_DIRS = new Set([ '.git', @@ -53,18 +85,33 @@ function parseToml(filePath) { if (!line || line.startsWith('#')) continue; if (line.startsWith('[[') && line.endsWith(']]')) { const name = line.slice(2, -2).trim(); - section = name ? name.split('.').map((p) => p.trim()).filter(Boolean) : []; + section = name ? name.split('.').map((p) => { + p = p.trim(); + if ((p.startsWith('"') && p.endsWith('"')) || (p.startsWith("'") && p.endsWith("'"))) { + p = p.slice(1, -1).trim(); + } + return p; + }).filter(Boolean) : []; continue; } if (line.startsWith('[') && line.endsWith(']')) { const name = line.slice(1, -1).trim(); - section = name ? name.split('.').map((p) => p.trim()).filter(Boolean) : []; + section = name ? name.split('.').map((p) => { + p = p.trim(); + if ((p.startsWith('"') && p.endsWith('"')) || (p.startsWith("'") && p.endsWith("'"))) { + p = p.slice(1, -1).trim(); + } + return p; + }).filter(Boolean) : []; continue; } const eqIndex = line.indexOf('='); if (eqIndex === -1) continue; - const key = line.slice(0, eqIndex).trim(); + let key = line.slice(0, eqIndex).trim(); if (!key) continue; + if ((key.startsWith('"') && key.endsWith('"')) || (key.startsWith("'") && key.endsWith("'"))) { + key = key.slice(1, -1).trim(); + } let valueRaw = line.slice(eqIndex + 1).trim(); let value = ''; if ( @@ -88,12 +135,20 @@ function buildTree(keys, valuesMap) { let node = root; for (let i = 0; i < parts.length - 1; i++) { const part = parts[i]; - if (!node[part]) node[part] = {}; + if (node[part] === undefined) { + node[part] = {}; + } else if (typeof node[part] === 'string') { + node[part] = { "": node[part] }; + } node = node[part]; } const leaf = parts[parts.length - 1]; const value = valuesMap ? (valuesMap.get(key) ?? '') : ''; - node[leaf] = value; + if (node[leaf] !== undefined && typeof node[leaf] === 'object') { + node[leaf][""] = value; + } else { + node[leaf] = value; + } } return root; } @@ -105,12 +160,34 @@ function renderToml(tree) { lines.push(`[${path.join('.')}]`); } const keys = Object.keys(node).sort(); - const leafKeys = keys.filter((k) => typeof node[k] === 'string'); - const childKeys = keys.filter((k) => typeof node[k] === 'object'); - for (const key of leafKeys) { - const value = node[key]; - lines.push(`${key} = ${JSON.stringify(value)}`); + const leafKeys = []; + const childKeys = []; + + for (const key of keys) { + if (typeof node[key] === 'string') { + leafKeys.push(key); + } else if (typeof node[key] === 'object') { + if (node[key][""] !== undefined) { + leafKeys.push(key); + } else { + childKeys.push(key); + } + } } + + for (const key of leafKeys) { + const val = node[key]; + if (typeof val === 'string') { + lines.push(`${key} = ${JSON.stringify(val)}`); + } else { + lines.push(`${key} = ${JSON.stringify(val[""])}`); + const subKeys = Object.keys(val).filter((k) => k !== "").sort(); + for (const subKey of subKeys) { + lines.push(`${key}.${subKey} = ${JSON.stringify(val[subKey])}`); + } + } + } + for (const key of childKeys) { lines.push(''); walk(node[key], [...path, key]); @@ -389,56 +466,68 @@ function keyToEnglish(key) { } function main() { - const templateMap = parseToml(TEMPLATE_PATH); - const koMap = parseToml(KO_PATH); - const enMap = parseToml(EN_PATH); const fallbacks = extractFallbacks(); - const allKeys = new Set([ - ...templateMap.keys(), - ...koMap.keys(), - ...enMap.keys(), - ]); + for (const spec of LOCALE_SPECS) { + const templatePath = path.join(spec.dir, spec.template); + const koPath = path.join(spec.dir, 'ko.toml'); + const enPath = path.join(spec.dir, 'en.toml'); - for (const key of allKeys) { - const fallback = fallbacks.get(key); - const currentKo = koMap.get(key) ?? ''; - const currentEn = enMap.get(key) ?? ''; + const templateMap = parseToml(templatePath); + const koMap = parseToml(koPath); + const enMap = parseToml(enPath); - let nextKo = currentKo; - if (!nextKo && fallback) { - nextKo = fallback; - } - if (!nextKo) { - nextKo = key; - } + const ownedFallbackKeys = Array.from(fallbacks.keys()).filter( + (key) => spec.ownsKey(key) && !shouldIgnoreCodeKey(key) + ); - let nextEn = currentEn; - if (!nextEn) { - const source = fallback || nextKo || key; - if (isLongText(source)) { - nextEn = source; - } else if (isMostlyAscii(source)) { - nextEn = source; - } else { - nextEn = translateKorean(source); + const allKeys = new Set([ + ...templateMap.keys(), + ...koMap.keys(), + ...enMap.keys(), + ...ownedFallbackKeys, + ]); + + for (const key of allKeys) { + const fallback = fallbacks.get(key); + const currentKo = koMap.get(key) ?? ''; + const currentEn = enMap.get(key) ?? ''; + + let nextKo = currentKo; + if (!nextKo && fallback) { + nextKo = fallback; } - } - if (!nextEn) { - nextEn = key; - } - if (!isLongText(nextEn) && containsHangul(nextEn)) { - nextEn = keyToEnglish(key); + if (!nextKo) { + nextKo = key; + } + + let nextEn = currentEn; + if (!nextEn) { + const source = fallback || nextKo || key; + if (isLongText(source)) { + nextEn = source; + } else if (isMostlyAscii(source)) { + nextEn = source; + } else { + nextEn = translateKorean(source); + } + } + if (!nextEn) { + nextEn = key; + } + if (!isLongText(nextEn) && containsHangul(nextEn)) { + nextEn = keyToEnglish(key); + } + + koMap.set(key, nextKo); + enMap.set(key, nextEn); } - koMap.set(key, nextKo); - enMap.set(key, nextEn); + const keys = Array.from(allKeys).sort(); + fs.writeFileSync(koPath, renderToml(buildTree(keys, koMap))); + fs.writeFileSync(enPath, renderToml(buildTree(keys, enMap))); + fs.writeFileSync(templatePath, renderToml(buildTree(keys, null))); } - - const keys = Array.from(allKeys).sort(); - fs.writeFileSync(KO_PATH, renderToml(buildTree(keys, koMap))); - fs.writeFileSync(EN_PATH, renderToml(buildTree(keys, enMap))); - fs.writeFileSync(TEMPLATE_PATH, renderToml(buildTree(keys, null))); } main(); diff --git a/userfront/assets/translations/en.toml b/userfront/assets/translations/en.toml index 3e53d2de..28f37f56 100644 --- a/userfront/assets/translations/en.toml +++ b/userfront/assets/translations/en.toml @@ -56,11 +56,14 @@ result = "Result: {value}" session_id = "Session ID: {value}" status = "Status: pending" +[msg.userfront.audit.filter] +description = "Toggle to view only active sessions." + [msg.userfront.consent] accept_error = "Failed to process consent: {error}" client_id = "Client ID: {id}" client_unknown = "Unknown application" -description = "The service below is requesting access to your account information.\\\\nPlease choose whether to continue." +description = "The service below is requesting access to your account information.\\\\\\\\nPlease choose whether to continue." load_error = "Failed to load consent information: {error}" missing_redirect = "Consent was processed, but the redirect URL was missing." redirect_notice = "After consent, you will be redirected automatically." @@ -82,15 +85,15 @@ approved_device = "Approved device: {device}" approved_ip = "Approved IP: {ip}" audit_empty = "No recent sign-in activity." audit_load_error = "Could not load sign-in history." -auto_login_supported = "You can sign in without an extra login when opening this linked app." auth_method = "Auth method: {method}" +auto_login_supported = "You can sign in without an extra login when opening this linked app." client_id = "Client ID: {id}" client_id_missing = "No client ID available." current_status = "Current status: {status}" last_auth = "Last signed in: {value}" -link_status = "Link status: {status}" link_missing = "This app does not have a launch URL configured." link_open_error = "Could not open the app link." +link_status = "Link status: {status}" render_error = "Dashboard render error: {error}" session_id_copied = "Session ID copied." @@ -99,6 +102,19 @@ empty = "No linked apps yet." empty_detail = "Linked apps and their latest activity will appear here." error = "Could not load linked apps." +[msg.userfront.dashboard.approved_session] +copy_click = "{label}: {id}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\nClick to copy." +copy_tap = "{label}: {id}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\nTap to copy." +none = "No {label}" + +[msg.userfront.dashboard.revoke] +confirm = "Disconnect {app}?\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\nYou will need to grant access again the next time you sign in." +error = "Could not disconnect the app: {error}" +success = "{app} has been disconnected." + +[msg.userfront.dashboard.scopes] +empty = "No scopes were requested." + [msg.userfront.dashboard.sessions] browser = "Browser: {value}" empty = "No active sessions." @@ -109,23 +125,10 @@ recent_app = "Recent app: {app}" session_id = "Session ID: {id}" [msg.userfront.dashboard.sessions.revoke] -confirm = "End the session for {target}?\nThat device will need to sign in again." +confirm = "End the session for {target}?\\nThat device will need to sign in again." error = "Could not end the session: {error}" success = "The session has been ended." -[msg.userfront.dashboard.approved_session] -copy_click = "{label}: {id}\\\\\\\\\\\\\\\\nClick to copy." -copy_tap = "{label}: {id}\\\\\\\\\\\\\\\\nTap to copy." -none = "No {label}" - -[msg.userfront.dashboard.revoke] -confirm = "Disconnect {app}?\\\\\\\\\\\\\\\\nYou will need to grant access again the next time you sign in." -error = "Could not disconnect the app: {error}" -success = "{app} has been disconnected." - -[msg.userfront.dashboard.scopes] -empty = "No scopes were requested." - [msg.userfront.dashboard.timeline] load_error = "Could not load sign-in history." @@ -139,6 +142,22 @@ title_generic = "An error occurred." title_with_code = "Error: {code}" type = "Error type: {type}" +[msg.userfront.error.ory] +$normalizedCode = "{error}" +access_denied = "The user denied the consent request." +consent_required = "Consent is required to continue." +interaction_required = "Additional interaction is required. Please try again." +invalid_client = "Client authentication failed." +invalid_grant = "The authorization grant is invalid or expired." +invalid_request = "The request is invalid." +invalid_scope = "The requested scope is invalid." +login_required = "Login is required." +request_forbidden = "The request was forbidden." +server_error = "An authentication server error occurred." +temporarily_unavailable = "The authentication server is temporarily unavailable." +unauthorized_client = "The client is not authorized for this request." +unsupported_response_type = "The response type is not supported." + [msg.userfront.error.tenant] account = "Account" account_unknown = "Unknown" @@ -155,24 +174,8 @@ tenant = "Tenant" tenant_unknown = "Unknown" title = "Access restriction details" -[msg.userfront.error.ory] -"$normalizedCode" = "{error}" -access_denied = "The user denied the consent request." -consent_required = "Consent is required to continue." -interaction_required = "Additional interaction is required. Please try again." -invalid_client = "Client authentication failed." -invalid_grant = "The authorization grant is invalid or expired." -invalid_request = "The request is invalid." -invalid_scope = "The requested scope is invalid." -login_required = "Login is required." -request_forbidden = "The request was forbidden." -server_error = "An authentication server error occurred." -temporarily_unavailable = "The authentication server is temporarily unavailable." -unauthorized_client = "The client is not authorized for this request." -unsupported_response_type = "The response type is not supported." - [msg.userfront.error.whitelist] -"$normalizedCode" = "{error}" +$normalizedCode = "{error}" bad_request = "Please check your input." invalid_session = "Your session has expired. Please sign in again." not_found = "The requested page could not be found." @@ -226,14 +229,14 @@ scan_hint = "Scan it with the mobile app." invalid = "Enter the 2 letters and 6 digits from your code." [msg.userfront.login.unregistered] -body = "We could not find an account for that information.\\\\\\\\\\\\\\\\nPlease sign up before continuing." +body = "We could not find an account for that information.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\nPlease sign up before continuing." [msg.userfront.login.verification] approved = "Approved. Complete sign-in in the original window." approved_local = "Approved. This device is already signed in, and the remote window will be signed in shortly." approved_remote = "Your requested sign-in is complete." -pending_remote = "Checking the sign-in approval request. Please wait." close_hint = "You can close this window now." +pending_remote = "Checking the sign-in approval request. Please wait." success = "Sign-in approval completed." [msg.userfront.login_success] @@ -465,6 +468,10 @@ dev_console = "Dev Console" [ui.userfront.audit] +[ui.userfront.audit.filter] +title = "Manage My Activity" +toggle_label = "Show active sessions only" + [ui.userfront.audit.table] action = "Action" app = "App" @@ -499,17 +506,6 @@ status_history = "Link details" [ui.userfront.dashboard.activity] linked = "Linked" -[ui.userfront.dashboard.sessions] -active_badge = "Active" -current_badge = "Current" -current_disabled = "Current session" -unknown_device = "Unknown device" -unknown_session = "Session" - -[ui.userfront.dashboard.sessions.revoke] -action = "End session" -title = "End session" - [ui.userfront.dashboard.approved_session] default = "Default" userfront = "Approved UserFront session ID" @@ -521,6 +517,17 @@ title = "Disconnect app" [ui.userfront.dashboard.scopes] title = "Consent scopes" +[ui.userfront.dashboard.sessions] +active_badge = "Active" +current_badge = "Current" +current_disabled = "Current session" +unknown_device = "Unknown device" +unknown_session = "Session" + +[ui.userfront.dashboard.sessions.revoke] +action = "End session" +title = "End session" + [ui.userfront.dashboard.status] revoked = "Revoked" @@ -583,8 +590,8 @@ title = "Account not found" [ui.userfront.login.verification] action_label = "Done" -action_label_remote = "Go to sign-in window" action_label_close = "Close Window" +action_label_remote = "Go to sign-in window" page_title = "Baron SW Portal" title = "Approval complete" title_pending = "Checking approval" @@ -698,12 +705,3 @@ verify = "Verification" [ui.userfront.signup.success] action = "Go to sign-in" - - -[ui.userfront.audit.filter] -title = "Manage My Activity" -toggle_label = "Show active sessions only" - -[msg.userfront.audit.filter] -description = "Toggle to view only active sessions." - diff --git a/userfront/assets/translations/ko.toml b/userfront/assets/translations/ko.toml index bd48e9d8..07794d63 100644 --- a/userfront/assets/translations/ko.toml +++ b/userfront/assets/translations/ko.toml @@ -41,231 +41,6 @@ verify_code_failed = "์ธ์ฆ ์คํจ: {error}" [err.userfront.session] missing = "ํ์ฑ ์ธ์ ์ด ์์ต๋๋ค." -[msg.userfront.audit] -browser = "๋ธ๋ผ์ฐ์ : {value}" -date = "์ ์์ผ์: {value}" -device = "์ ์ํ๊ฒฝ: {value}" -end = "๋ ์ด์ ํญ๋ชฉ์ด ์์ต๋๋ค." -filtered_empty = "ํ์ฑ ์ธ์ ์ผ๋ก ํํฐ๋ง๋ ์ ์ ์ด๋ ฅ์ด ์์ต๋๋ค." -ip = "์ ์ IP: {value}" -load_more_error = "๋ ๋ถ๋ฌ์ค์ง ๋ชปํ์ต๋๋ค." -result = "์ธ์ฆ๊ฒฐ๊ณผ: {value}" -session_id = "Session ID: {value}" -status = "ํํฉ: (์ค๋น์ค)" - -[msg.userfront.dashboard] -approved_device = "์น์ธ ๊ธฐ๊ธฐ: {device}" -approved_ip = "์น์ธ IP: {ip}" -audit_empty = "์ต๊ทผ ์ ์ ์ด๋ ฅ์ด ์์ต๋๋ค." -audit_load_error = "์ ์์ด๋ ฅ์ ๋ถ๋ฌ์ค์ง ๋ชปํ์ต๋๋ค." -auth_method = "์ธ์ฆ์๋จ: {method}" -client_id = "Client ID: {id}" -client_id_missing = "Client ID ์์" -current_status = "ํ์ฌ ์ํ: {status}" -last_auth = "์ต๊ทผ ์ธ์ฆ: {value}" -link_status = "์ฐ๋ ์ํ: {status}" -link_missing = "์ด๋ํ ํ์ด์ง ์ฃผ์(Client URI)๊ฐ ์ค์ ๋์ง ์์์ต๋๋ค." -link_open_error = "ํด๋น ๋งํฌ๋ฅผ ์ด ์ ์์ต๋๋ค." -render_error = "๋์๋ณด๋ ๋ ๋๋ง ์ค๋ฅ: {error}" -session_id_copied = "์ธ์ ID๊ฐ ๋ณต์ฌ๋์์ต๋๋ค." - -[msg.userfront.error] -detail_contact = "๊ด๋ฆฌ์์๊ฒ ๋ฌธ์ํด ์ฃผ์ธ์." -detail_generic = "์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค." -detail_request = "์์ฒญ์ ์ฒ๋ฆฌํ๋ ์ค ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค." -id = "์ค๋ฅ ID: {id}" -title = "์ธ์ฆ ๊ณผ์ ์์ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค" -title_generic = "์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค" -title_with_code = "์ค๋ฅ: {code}" -type = "์ค๋ฅ ์ข ๋ฅ: {type}" - -[msg.userfront.error.tenant] -account = "๊ณ์ " -account_unknown = "์ ์ ์์" -affiliated_tenants = "์ ์ฒด ์์ ํ ๋ํธ" -allowed_box_title = "์ ์ ๊ฐ๋ฅ ํ ๋ํธ" -allowed_tenants = "์ ์ ๊ฐ๋ฅ ํ ๋ํธ" -detail = "ํ์ฌ ๋ก๊ทธ์ธ๋ ๊ณ์ ์ ์ด ์ ํ๋ฆฌ์ผ์ด์ ์ ์ ๊ทผํ ์ ์์ต๋๋ค." -load_failed = "๊ณ์ ์ ๋ณด๋ฅผ ํ์ธํ์ง ๋ชปํ์ต๋๋ค. ๋ค์ ์๋ํด ์ฃผ์ธ์." -loading = "ํ์ฌ ๊ณ์ ์ ๋ณด๋ฅผ ๋ถ๋ฌ์ค๋ ์ค์ ๋๋ค." -lookup_fallback = "ํ์ ์ ๋ณด๊ฐ ์ถฉ๋ถํ์ง ์์ ์ผ๋ถ ํญ๋ชฉ์ ํ์ธ๋์ง ์์ ์ ์์ต๋๋ค." -page_title = "์ ํ๋ฆฌ์ผ์ด์ ์ ๊ทผ์ด ์ ํ๋์์ต๋๋ค" -primary_tenant = "๋ํ ์์ ํ ๋ํธ" -tenant = "์์ ํ ๋ํธ" -tenant_unknown = "์ ์ ์์" -title = "์ ๊ทผ ์ ํ ์ ๋ณด" - -[msg.userfront.forgot] -description = "๊ณ์ ๊ณผ ์ฐ๊ฒฐ๋ ์ด๋ฉ์ผ ์ฃผ์ ๋๋ ํด๋ํฐ ๋ฒํธ๋ฅผ ์ ๋ ฅํ์๋ฉด, ๋น๋ฐ๋ฒํธ๋ฅผ ์ฌ์ค์ ํ ์ ์๋ ๋งํฌ๋ฅผ ๋ณด๋ด๋๋ฆฝ๋๋ค." -dry_send = "drySend ๋ชจ๋: ์ค์ ์ด๋ฉ์ผ/SMS๋ ๋ฐ์ก๋์ง ์์ต๋๋ค." -error = "์ ์ก์ ์คํจํ์ต๋๋ค: {error}" -input_required = "์ด๋ฉ์ผ ๋๋ ํด๋ํฐ ๋ฒํธ๋ฅผ ์ ๋ ฅํด์ฃผ์ธ์." -sent = "๋น๋ฐ๋ฒํธ ์ฌ์ค์ ๋งํฌ๊ฐ ์ ์ก๋์์ต๋๋ค. ์ด๋ฉ์ผ ๋๋ SMS๋ฅผ ํ์ธํด์ฃผ์ธ์." - -[msg.userfront.login] -cookie_check_failed = "๋ก๊ทธ์ธ ํ์ธ ์คํจ: {error}" -dry_send = "drySend ๋ชจ๋: ์ค์ ์ด๋ฉ์ผ/SMS๋ ๋ฐ์ก๋์ง ์์ต๋๋ค." -link_failed = "์ค๋ฅ: {error}" -link_send_failed = "์ ์ก ์คํจ: {error}" -link_sent_email = "์ ๋ ฅํ์ ์ด๋ฉ์ผ๋ก ๋ก๊ทธ์ธ ๋งํฌ๋ฅผ ๋ณด๋์ต๋๋ค." -link_sent_phone = "์ ๋ ฅํ์ ๋ฒํธ๋ก ๋ก๊ทธ์ธ ๋งํฌ๋ฅผ ๋ณด๋์ต๋๋ค." -link_timeout = "์๊ฐ์ด ๊ฒฝ๊ณผ๋์์ต๋๋ค." -no_account = "๊ณ์ ์ด ์์ผ์ ๊ฐ์?" -oidc_failed = "OIDC ๋ก๊ทธ์ธ ์ฒ๋ฆฌ์ ์คํจํ์ต๋๋ค. ๋ค์ ์๋ํด ์ฃผ์ธ์." -qr_expired = "์๊ฐ์ด ๊ฒฝ๊ณผ๋์์ต๋๋ค." -qr_init_failed = "QR ์ด๊ธฐํ์ ์คํจํ์ต๋๋ค: {error}" -qr_login_required = "๋ก๊ทธ์ธ ํ ์ํ์ฌ์ผ QR ์ค์บ์ผ๋ก ๋ก๊ทธ์ธ ํ ์ ์์ต๋๋ค" -token_missing = "๋ก๊ทธ์ธ ํ ํฐ์ ํ์ธํ ์ ์์ต๋๋ค." -verification_failed = "์น์ธ ์ฒ๋ฆฌ์ ์คํจํ์ต๋๋ค: {error}" - -[msg.userfront.login_success] -subtitle = "์ฑ๊ณต์ ์ผ๋ก ๋ก๊ทธ์ธ๋์์ต๋๋ค." - -[msg.userfront.consent] -accept_error = "๋์ ์ฒ๋ฆฌ์ ์คํจํ์ต๋๋ค: {error}" -client_id = "ํด๋ผ์ด์ธํธ ID: {id}" -client_unknown = "์ ์ ์๋ ์ฑ" -description = "์๋ ์๋น์ค๊ฐ ํ์๋์ ๊ณ์ ์ ๋ณด์ ์ ๊ทผํ๋ ค๊ณ ํฉ๋๋ค.\n๊ณ์ ์งํํ๋ ค๋ฉด ๋์ ์ฌ๋ถ๋ฅผ ์ ํํด ์ฃผ์ธ์." -load_error = "๋์ ์ ๋ณด๋ฅผ ๋ถ๋ฌ์ค๋๋ฐ ์คํจํ์ต๋๋ค: {error}" -missing_redirect = "๋์๊ฐ ์ฒ๋ฆฌ๋์์ผ๋ ๋ฆฌ๋ค์ด๋ ํธ URL์ ๋ฐ์ง ๋ชปํ์ต๋๋ค." -redirect_notice = "๋์ ํ ์๋์ผ๋ก ์๋น์ค๋ก ์ด๋ํฉ๋๋ค." -scope_count = "์ด {count}๊ฐ" - -[msg.userfront.profile] -department_missing = "์์ ์ ๋ณด ์์" -department_required = "์์์ ์ ๋ ฅํด์ฃผ์ธ์." -email_missing = "์ด๋ฉ์ผ ์์" -greeting = "์๋ ํ์ธ์, {name}๋" -load_failed = "์ ๋ณด๋ฅผ ๋ถ๋ฌ์ฌ ์ ์์ต๋๋ค." -name_missing = "์ด๋ฆ ์์" -name_required = "์ด๋ฆ์ ์ ๋ ฅํด์ฃผ์ธ์." -phone_required = "ํด๋ํฐ ๋ฒํธ๋ฅผ ์ ๋ ฅํด์ฃผ์ธ์." -phone_verify_required = "ํด๋ํฐ ๋ฒํธ ์ธ์ฆ์ด ํ์ํฉ๋๋ค." -update_failed = "์์ ์คํจ: {error}" -update_success = "์ ๋ณด๊ฐ ์์ ๋์์ต๋๋ค." - -[msg.userfront.qr] -camera_error = "์นด๋ฉ๋ผ ์ค๋ฅ: {error}" -permission_error = "์นด๋ฉ๋ผ ๊ถํ ์์ฒญ์ ์คํจํ์ต๋๋ค. ๋ธ๋ผ์ฐ์ /OS ์ค์ ์ ํ์ธํด์ฃผ์ธ์." -permission_required = "์นด๋ฉ๋ผ ๊ถํ์ด ํ์ํฉ๋๋ค." - -[msg.userfront.reset] -invalid_body = "๋น๋ฐ๋ฒํธ ์ฌ์ค์ ๋งํฌ๊ฐ ๋ง๋ฃ๋์๊ฑฐ๋ ์๋ชป๋์์ต๋๋ค. ๋ค์ ์๋ํด์ฃผ์ธ์." -invalid_link = "์ ํจํ์ง ์์ ์ฌ์ค์ ๋งํฌ์ ๋๋ค. (loginId/token ๋๋ฝ)" -invalid_title = "์ ํจํ์ง ์์ ๋งํฌ์ ๋๋ค." -policy_loading = "๋น๋ฐ๋ฒํธ ์ ์ฑ ์ ๋ถ๋ฌ์ค๋ ์ค์ ๋๋ค..." -success = "๋น๋ฐ๋ฒํธ๊ฐ ์ฑ๊ณต์ ์ผ๋ก ๋ณ๊ฒฝ๋์์ต๋๋ค. ๋ค์ ๋ก๊ทธ์ธํด์ฃผ์ธ์." - -[msg.userfront.sections] -apps_subtitle = "ํ์ฌ ์ฐ๊ฒฐ๋ ์ฑ๊ณผ ์ต๊ทผ ์ธ์ฆ ์ํ์ ๋๋ค." -audit_subtitle = "Baron ๋ก๊ทธ์ธ ๊ธฐ์ค์ ์ต๊ทผ ์ ๊ทผ ๊ธฐ๋ก์ ๋๋ค." -sessions_subtitle = "ํ์ฌ ๋ก๊ทธ์ธ๋ ๊ธฐ๊ธฐ์ ๋ธ๋ผ์ฐ์ ์ธ์ ์ ๋๋ค." - -[msg.userfront.settings] -disabled = "ํ์ฌ ๊ณ์ ์ค์ ํ๋ฉด์ ์ค๋น ์ค์ ๋๋ค." - -[msg.userfront.signup] -failed = "๊ฐ์ ์คํจ: {error}" -privacy_full = "๊ฐ์ธ์ ๋ณด ์์ง ๋ฐ ์ด์ฉ ๋์ ์ ๋ฌธ..." -tos_full = "์๋น์ค ์ด์ฉ์ฝ๊ด ์ ๋ฌธ..." - -[ui.common.badge] -admin_only = "Admin only" -command_only = "Command only" -system = "System" - -[ui.common.status] -active = "ํ์ฑ" -blocked = "์ฐจ๋จ๋จ" -failure = "์คํจ" -inactive = "๋นํ์ฑ" -ok = "์ ์" -pending = "์ค๋น ์ค" -success = "์ฑ๊ณต" - -[ui.userfront.app_label] -admin_console = "Admin Console" -baron = "Baron ๋ก๊ทธ์ธ" -dev_console = "Dev Console" - -[ui.userfront.auth_method] -ory = "Ory ์ธ์ " -session = "์ธ์ " - -[ui.userfront.dashboard] -last_auth_label = "์ต๊ทผ ์ธ์ฆ" -link_status_label = "์ฐ๋ ์ํ" -status_history = "์ฐ๋ ์ ๋ณด" - -[ui.userfront.device] -android = "Mobile(Android)" -ios = "Mobile(iOS)" -linux = "Desktop(Linux)" -macos = "Desktop(macOS)" -windows = "Desktop(Windows)" - -[ui.userfront.error] -go_home = "ํ์ผ๋ก ์ด๋" -go_login = "๋ก๊ทธ์ธ์ผ๋ก ์ด๋" -switch_account = "๋ค๋ฅธ ๊ณ์ ์ผ๋ก ๋ก๊ทธ์ธ" - -[ui.userfront.forgot] -heading = "๋น๋ฐ๋ฒํธ๋ฅผ ์์ผ์ จ๋์?" -input_label = "์ด๋ฉ์ผ ๋๋ ํด๋ํฐ ๋ฒํธ" -submit = "์ฌ์ค์ ๋งํฌ ์ ์ก" -title = "๋น๋ฐ๋ฒํธ ์ฌ์ค์ " - -[ui.userfront.login] -forgot_password = "๋น๋ฐ๋ฒํธ๋ฅผ ์์ผ์ จ๋์?" -signup = "ํ์๊ฐ์ " - -[ui.userfront.login_success] -later = "๋์ค์ ํ๊ธฐ (๋์๋ณด๋๋ก ์ด๋)" -qr = "QR ์ธ์ฆ (์นด๋ฉ๋ผ ์ผ๊ธฐ)" -title = "๋ก๊ทธ์ธ ์๋ฃ" - -[ui.userfront.consent] -accept = "๋์ํ๊ณ ๊ณ์ํ๊ธฐ" -requested_scopes = "์์ฒญ๋ ๊ถํ" -title = "์ ๊ทผ ๊ถํ ์์ฒญ" - -[ui.userfront.nav] -dashboard = "๋์๋ณด๋" -logout = "๋ก๊ทธ์์" -profile = "๋ด ์ ๋ณด" -qr_scan = "QR ์ค์บ" - -[ui.userfront.profile] -department_empty = "์์ ์ ๋ณด ์์" -manage = "ํ๋กํ ๊ด๋ฆฌ" -user_fallback = "์ฌ์ฉ์" - -[ui.userfront.qr] -rescan = "๋ค์ ์ค์บ" -result_success = "์น์ธ ์๋ฃ" -title = "Scan QR Code" - -[ui.userfront.reset] -confirm_password = "์ ๋น๋ฐ๋ฒํธ ํ์ธ" -new_password = "์ ๋น๋ฐ๋ฒํธ" -submit = "๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ" -subtitle = "์๋ก์ด ๋น๋ฐ๋ฒํธ ์ค์ " -title = "์ ๋น๋ฐ๋ฒํธ ์ค์ " - -[ui.userfront.sections] -apps = "๋์ App ํํฉ" -audit = "์ ์์ด๋ ฅ" -sessions = "ํ์ฑ ์ธ์ " - -[ui.userfront.session] -active = "์ธ์ ํ์ฑ" -unknown = "์ ์ ์์" - -[ui.userfront.signup] -complete = "๊ฐ์ ์๋ฃ" -next_step = "๋ค์ ๋จ๊ณ" -title = "ํ์๊ฐ์ " - [msg.userfront] greeting = "์๋ ํ์ธ์, {name}๋" @@ -281,11 +56,14 @@ result = "์ธ์ฆ๊ฒฐ๊ณผ: {value}" session_id = "Session ID: {value}" status = "ํํฉ: (์ค๋น์ค)" +[msg.userfront.audit.filter] +description = "ํ์ฑํ๋ ์ธ์ ๋ง ๋ณด๋ ค๋ฉด ํ ๊ธ์ ์ผ์ฃผ์ธ์." + [msg.userfront.consent] accept_error = "๋์ ์ฒ๋ฆฌ์ ์คํจํ์ต๋๋ค: {error}" client_id = "ํด๋ผ์ด์ธํธ ID: {id}" client_unknown = "์ ์ ์๋ ์ฑ" -description = "์๋ ์๋น์ค๊ฐ ํ์๋์ ๊ณ์ ์ ๋ณด์ ์ ๊ทผํ๋ ค๊ณ ํฉ๋๋ค.\\\\n๊ณ์ ์งํํ๋ ค๋ฉด ๋์ ์ฌ๋ถ๋ฅผ ์ ํํด ์ฃผ์ธ์." +description = "์๋ ์๋น์ค๊ฐ ํ์๋์ ๊ณ์ ์ ๋ณด์ ์ ๊ทผํ๋ ค๊ณ ํฉ๋๋ค.\\\\\\\\n๊ณ์ ์งํํ๋ ค๋ฉด ๋์ ์ฌ๋ถ๋ฅผ ์ ํํด ์ฃผ์ธ์." load_error = "๋์ ์ ๋ณด๋ฅผ ๋ถ๋ฌ์ค๋๋ฐ ์คํจํ์ต๋๋ค: {error}" missing_redirect = "๋์๊ฐ ์ฒ๋ฆฌ๋์์ผ๋ ๋ฆฌ๋ค์ด๋ ํธ URL์ ๋ฐ์ง ๋ชปํ์ต๋๋ค." redirect_notice = "๋์ ํ ์๋์ผ๋ก ์๋น์ค๋ก ์ด๋ํฉ๋๋ค." @@ -307,14 +85,15 @@ approved_device = "์น์ธ ๊ธฐ๊ธฐ: {device}" approved_ip = "์น์ธ IP: {ip}" audit_empty = "์ต๊ทผ ์ ์ ์ด๋ ฅ์ด ์์ต๋๋ค." audit_load_error = "์ ์์ด๋ ฅ์ ๋ถ๋ฌ์ค์ง ๋ชปํ์ต๋๋ค." -auto_login_supported = "์ฐ๋์ฑ ํด๋ฆญ ์ ๋ณ๋ ๋ก๊ทธ์ธ ์์ด ๋ก๊ทธ์ธํ ์ ์์ต๋๋ค." auth_method = "์ธ์ฆ์๋จ: {method}" +auto_login_supported = "์ฐ๋์ฑ ํด๋ฆญ ์ ๋ณ๋ ๋ก๊ทธ์ธ ์์ด ๋ก๊ทธ์ธํ ์ ์์ต๋๋ค." client_id = "Client ID: {id}" client_id_missing = "Client ID ์์" current_status = "ํ์ฌ ์ํ: {status}" last_auth = "์ต๊ทผ ์ธ์ฆ: {value}" link_missing = "์ด๋ํ ํ์ด์ง ์ฃผ์(Client URI)๊ฐ ์ค์ ๋์ง ์์์ต๋๋ค." link_open_error = "ํด๋น ๋งํฌ๋ฅผ ์ด ์ ์์ต๋๋ค." +link_status = "์ฐ๋ ์ํ: {status}" render_error = "๋์๋ณด๋ ๋ ๋๋ง ์ค๋ฅ: {error}" session_id_copied = "์ธ์ ID๊ฐ ๋ณต์ฌ๋์์ต๋๋ค." @@ -323,6 +102,19 @@ empty = "์ฐ๋๋ ์ฑ์ด ์์ต๋๋ค." empty_detail = "์ฑ์ ์ฐ๋ํ๋ฉด ์ต๊ทผ ํ๋๊ณผ ์ํ๊ฐ ํ์๋ฉ๋๋ค." error = "์ฐ๋ ์ ๋ณด๋ฅผ ๋ถ๋ฌ์ค์ง ๋ชปํ์ต๋๋ค." +[msg.userfront.dashboard.approved_session] +copy_click = "{label}: {id}\\\\\\\\nํด๋ฆญํ๋ฉด ๋ณต์ฌ๋ฉ๋๋ค." +copy_tap = "{label}: {id}\\\\\\\\nํญํ๋ฉด ๋ณต์ฌ๋ฉ๋๋ค." +none = "{label} ์์" + +[msg.userfront.dashboard.revoke] +confirm = "{app} ์ฑ๊ณผ์ ์ฐ๋์ ํด์งํ์๊ฒ ์ต๋๊น?\\\\\\\\nํด์งํ๋ฉด ๋ค์ ๋ก๊ทธ์ธ ์ ๋ค์ ๋์๊ฐ ํ์ํฉ๋๋ค." +error = "ํด์ง ์คํจ: {error}" +success = "{app} ์ฐ๋์ด ํด์ง๋์์ต๋๋ค." + +[msg.userfront.dashboard.scopes] +empty = "์์ฒญ๋ ๊ถํ์ด ์์ต๋๋ค." + [msg.userfront.dashboard.sessions] browser = "๋ธ๋ผ์ฐ์ : {value}" empty = "ํ์ฑ ์ธ์ ์ด ์์ต๋๋ค." @@ -333,23 +125,10 @@ recent_app = "์ต๊ทผ ์ ์ ์ฑ: {app}" session_id = "์ธ์ ID: {id}" [msg.userfront.dashboard.sessions.revoke] -confirm = "{target} ์ธ์ ์ ์ข ๋ฃํ์๊ฒ ์ต๋๊น?\n๋์ ๊ธฐ๊ธฐ์์๋ ๋ค์ ๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค." +confirm = "{target} ์ธ์ ์ ์ข ๋ฃํ์๊ฒ ์ต๋๊น?\\n๋์ ๊ธฐ๊ธฐ์์๋ ๋ค์ ๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค." error = "์ธ์ ์ข ๋ฃ ์คํจ: {error}" success = "์ธ์ ์ด ์ข ๋ฃ๋์์ต๋๋ค." -[msg.userfront.dashboard.approved_session] -copy_click = "{label}: {id}\\\\nํด๋ฆญํ๋ฉด ๋ณต์ฌ๋ฉ๋๋ค." -copy_tap = "{label}: {id}\\\\nํญํ๋ฉด ๋ณต์ฌ๋ฉ๋๋ค." -none = "{label} ์์" - -[msg.userfront.dashboard.revoke] -confirm = "{app} ์ฑ๊ณผ์ ์ฐ๋์ ํด์งํ์๊ฒ ์ต๋๊น?\\\\nํด์งํ๋ฉด ๋ค์ ๋ก๊ทธ์ธ ์ ๋ค์ ๋์๊ฐ ํ์ํฉ๋๋ค." -error = "ํด์ง ์คํจ: {error}" -success = "{app} ์ฐ๋์ด ํด์ง๋์์ต๋๋ค." - -[msg.userfront.dashboard.scopes] -empty = "์์ฒญ๋ ๊ถํ์ด ์์ต๋๋ค." - [msg.userfront.dashboard.timeline] load_error = "์ ์์ด๋ ฅ์ ๋ถ๋ฌ์ค์ง ๋ชปํ์ต๋๋ค." @@ -363,6 +142,22 @@ title_generic = "์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค" title_with_code = "์ค๋ฅ: {code}" type = "์ค๋ฅ ์ข ๋ฅ: {type}" +[msg.userfront.error.ory] +$normalizedCode = "{error}" +access_denied = "์ฌ์ฉ์๊ฐ ๋์๋ฅผ ๊ฑฐ๋ถํ์ต๋๋ค." +consent_required = "์ฑ ์ ๊ทผ ๋์๊ฐ ํ์ํฉ๋๋ค." +interaction_required = "์ถ๊ฐ ์ํธ์์ฉ์ด ํ์ํฉ๋๋ค. ๋ค์ ์๋ํด ์ฃผ์ธ์." +invalid_client = "ํด๋ผ์ด์ธํธ ์ธ์ฆ ์ ๋ณด๊ฐ ์ ํจํ์ง ์์ต๋๋ค." +invalid_grant = "์ธ์ฆ ์์ฒญ์ด ๋ง๋ฃ๋์๊ฑฐ๋ ์ ํจํ์ง ์์ต๋๋ค." +invalid_request = "์๋ชป๋ ์์ฒญ์ ๋๋ค." +invalid_scope = "์์ฒญํ ๊ถํ ๋ฒ์๊ฐ ์ ํจํ์ง ์์ต๋๋ค." +login_required = "๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค." +request_forbidden = "์์ฒญ์ด ๊ฑฐ๋ถ๋์์ต๋๋ค." +server_error = "์ธ์ฆ ์๋ฒ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค." +temporarily_unavailable = "์ธ์ฆ ์๋ฒ๋ฅผ ์ผ์์ ์ผ๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค." +unauthorized_client = "ํด๋น ํด๋ผ์ด์ธํธ๋ ์ด ์์ฒญ์ ์ํํ ์ ์์ต๋๋ค." +unsupported_response_type = "์ง์ํ์ง ์๋ ์๋ต ํ์ ์ ๋๋ค." + [msg.userfront.error.tenant] account = "๊ณ์ " account_unknown = "์ ์ ์์" @@ -379,24 +174,8 @@ tenant = "์์ ํ ๋ํธ" tenant_unknown = "์ ์ ์์" title = "์ ๊ทผ ์ ํ ์ ๋ณด" -[msg.userfront.error.ory] -"$normalizedCode" = "{error}" -access_denied = "์ฌ์ฉ์๊ฐ ๋์๋ฅผ ๊ฑฐ๋ถํ์ต๋๋ค." -consent_required = "์ฑ ์ ๊ทผ ๋์๊ฐ ํ์ํฉ๋๋ค." -interaction_required = "์ถ๊ฐ ์ํธ์์ฉ์ด ํ์ํฉ๋๋ค. ๋ค์ ์๋ํด ์ฃผ์ธ์." -invalid_client = "ํด๋ผ์ด์ธํธ ์ธ์ฆ ์ ๋ณด๊ฐ ์ ํจํ์ง ์์ต๋๋ค." -invalid_grant = "์ธ์ฆ ์์ฒญ์ด ๋ง๋ฃ๋์๊ฑฐ๋ ์ ํจํ์ง ์์ต๋๋ค." -invalid_request = "์๋ชป๋ ์์ฒญ์ ๋๋ค." -invalid_scope = "์์ฒญํ ๊ถํ ๋ฒ์๊ฐ ์ ํจํ์ง ์์ต๋๋ค." -login_required = "๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค." -request_forbidden = "์์ฒญ์ด ๊ฑฐ๋ถ๋์์ต๋๋ค." -server_error = "์ธ์ฆ ์๋ฒ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค." -temporarily_unavailable = "์ธ์ฆ ์๋ฒ๋ฅผ ์ผ์์ ์ผ๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค." -unauthorized_client = "ํด๋น ํด๋ผ์ด์ธํธ๋ ์ด ์์ฒญ์ ์ํํ ์ ์์ต๋๋ค." -unsupported_response_type = "์ง์ํ์ง ์๋ ์๋ต ํ์ ์ ๋๋ค." - [msg.userfront.error.whitelist] -"$normalizedCode" = "{error}" +$normalizedCode = "{error}" bad_request = "์ ๋ ฅ๊ฐ์ ํ์ธํด ์ฃผ์ธ์." invalid_session = "์ธ์ ์ด ๋ง๋ฃ๋์์ต๋๋ค. ๋ค์ ๋ก๊ทธ์ธํด ์ฃผ์ธ์." not_found = "์์ฒญํ ํ์ด์ง๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค." @@ -450,14 +229,14 @@ scan_hint = "๋ชจ๋ฐ์ผ ์ฑ์ผ๋ก ์ค์บํ์ธ์" invalid = "๋ฌธ์ 2๊ฐ์ ์ซ์ 6์๋ฆฌ๋ฅผ ์ ๋ ฅํด ์ฃผ์ธ์." [msg.userfront.login.unregistered] -body = "๊ฐ์ ๋์ง ์์ ์ ๋ณด์ ๋๋ค.\\\\nํ์๊ฐ์ ํ ์ด์ฉํด ์ฃผ์ธ์." +body = "๊ฐ์ ๋์ง ์์ ์ ๋ณด์ ๋๋ค.\\\\\\\\nํ์๊ฐ์ ํ ์ด์ฉํด ์ฃผ์ธ์." [msg.userfront.login.verification] approved = "์น์ธ๋์์ต๋๋ค. ๋ก๊ทธ์ธ์ ์์ฒญํ์ ์ฐฝ์์ ์๋ฃ๋ฉ๋๋ค." approved_local = "์น์ธ ๋์์ต๋๋ค. ์ด ๊ธฐ๊ธฐ๋ ๋ก๊ทธ์ธ๋์ด ์๋ ์ํ์ ๋๋ค. ์๊ฒฉ ์ฐฝ๋ ๋ก๊ทธ์ธ์ด ๋ ์์ ์ ๋๋ค" approved_remote = "์์ฒญํ์ ๋ก๊ทธ์ธ์ด ์๋ฃ๋์์ต๋๋ค" -pending_remote = "์น์ธ ์์ฒญ์ ํ์ธํ๊ณ ์์ต๋๋ค. ์ ์๋ง ๊ธฐ๋ค๋ ค ์ฃผ์ธ์." close_hint = "์ด ์ฐฝ์ ์ด์ ๋ซ์ผ์ ๋ ๋ฉ๋๋ค." +pending_remote = "์น์ธ ์์ฒญ์ ํ์ธํ๊ณ ์์ต๋๋ค. ์ ์๋ง ๊ธฐ๋ค๋ ค ์ฃผ์ธ์." success = "๋ก๊ทธ์ธ ์น์ธ์ ์ฑ๊ณตํ์ต๋๋ค." [msg.userfront.login_success] @@ -532,6 +311,7 @@ uppercase = "๋๋ฌธ์ 1๊ฐ ์ด์" [msg.userfront.sections] apps_subtitle = "ํ์ฌ ์ฐ๊ฒฐ๋ ์ฑ๊ณผ ์ต๊ทผ ์ธ์ฆ ์ํ์ ๋๋ค." audit_subtitle = "Baron ๋ก๊ทธ์ธ ๊ธฐ์ค์ ์ต๊ทผ ์ ๊ทผ ๊ธฐ๋ก์ ๋๋ค." +sessions_subtitle = "ํ์ฌ ๋ก๊ทธ์ธ๋ ๊ธฐ๊ธฐ์ ๋ธ๋ผ์ฐ์ ์ธ์ ์ ๋๋ค." [msg.userfront.settings] disabled = "ํ์ฌ ๊ณ์ ์ค์ ํ๋ฉด์ ์ค๋น ์ค์ ๋๋ค." @@ -546,12 +326,12 @@ all_hint = "ํ์ ์ฝ๊ด 2๊ฐ๋ฅผ ๋ชจ๋ ํ์ธํ๊ณ ๋์ํ๋ฉด ๋ค์ ๋จ๊ณ description = "๊ณ์ ์งํํ๋ ค๋ฉด ์๋น์ค ์ด์ฉ ์กฐ๊ฑด๊ณผ ๊ฐ์ธ์ ๋ณด ์์งยท์ด์ฉ ํญ๋ชฉ์ ํ์ธํ ๋ค ๋์ํด์ฃผ์ธ์." privacy_summary = "๊ฐ์ธ์ ๋ณด ์์ง ํญ๋ชฉ, ์ด์ฉ ๋ชฉ์ , ๋ณด๊ด ๊ธฐ์ค์ ์๋ดํฉ๋๋ค." progress = "ํ์ ์ฝ๊ด {total}๊ฐ ์ค {count}๊ฐ ๋์ ์๋ฃ" -title = "์๋น์ค ์ด์ฉ์ ์ํด\\\\n์ฝ๊ด์ ๋์ํด์ฃผ์ธ์" +title = "์๋น์ค ์ด์ฉ์ ์ํด\\\\\\\\n์ฝ๊ด์ ๋์ํด์ฃผ์ธ์" tos_summary = "์๋น์ค ์ด์ฉ ์กฐ๊ฑด๊ณผ ์ฑ ์ ๋ฒ์๋ฅผ ํ์ธํ ์ ์์ต๋๋ค." [msg.userfront.signup.auth] affiliate_notice = "๊ฐ์กฑ์ฌ ํ์์ ๊ฒฝ์ฐ ๋ฐ๋์ ํ์ฌ ๊ณต์ ์ด๋ฉ์ผ์ ์ ๋ ฅํด์ฃผ์ธ์." -title = "๋ณธ์ธ ํ์ธ์ ์ํด\\\\n์ธ์ฆ์ ์งํํด์ฃผ์ธ์" +title = "๋ณธ์ธ ํ์ธ์ ์ํด\\\\\\\\n์ธ์ฆ์ ์งํํด์ฃผ์ธ์" [msg.userfront.signup.email] code_mismatch = "์ธ์ฆ์ฝ๋๊ฐ ์ผ์นํ์ง ์์ต๋๋ค." @@ -567,7 +347,7 @@ lowercase_required = "์๋ฌธ์๊ฐ ์ต์ 1๊ฐ ์ด์ ํฌํจ๋์ด์ผ ํฉ๋๋ค. mismatch = "๋น๋ฐ๋ฒํธ๊ฐ ์ผ์นํ์ง ์์ต๋๋ค." number_required = "์ซ์๊ฐ ์ต์ 1๊ฐ ์ด์ ํฌํจ๋์ด์ผ ํฉ๋๋ค." symbol_required = "ํน์๋ฌธ์๊ฐ ์ต์ 1๊ฐ ์ด์ ํฌํจ๋์ด์ผ ํฉ๋๋ค." -title = "๋ง์ง๋ง์ผ๋ก\\\\n๋น๋ฐ๋ฒํธ๋ฅผ ์ค์ ํด์ฃผ์ธ์" +title = "๋ง์ง๋ง์ผ๋ก\\\\\\\\n๋น๋ฐ๋ฒํธ๋ฅผ ์ค์ ํด์ฃผ์ธ์" uppercase_required = "๋๋ฌธ์๊ฐ ์ต์ 1๊ฐ ์ด์ ํฌํจ๋์ด์ผ ํฉ๋๋ค." [msg.userfront.signup.password.rule] @@ -596,7 +376,7 @@ uppercase = "๋๋ฌธ์" [msg.userfront.signup.profile] affiliate_hint = "๊ฐ์กฑ์ฌ ์ด๋ฉ์ผ ์ฌ์ฉ ์ ์๋์ผ๋ก ์ ํ๋ฉ๋๋ค." -title = "ํ์๋์\\\\n์์ ์ ๋ณด๋ฅผ ์๋ ค์ฃผ์ธ์" +title = "ํ์๋์\\\\\\\\n์์ ์ ๋ณด๋ฅผ ์๋ ค์ฃผ์ธ์" [msg.userfront.signup.success] body = "์ฑ๊ณต์ ์ผ๋ก ๊ฐ์ ๋์์ต๋๋ค." @@ -688,6 +468,10 @@ dev_console = "Dev Console" [ui.userfront.audit] +[ui.userfront.audit.filter] +title = "๋ด ํ๋ ๊ด๋ฆฌ" +toggle_label = "ํ์ฑ ์ธ์ ๋ง ๋ณด๊ธฐ" + [ui.userfront.audit.table] action = "๊ด๋ฆฌ" app = "์ ํ๋ฆฌ์ผ์ด์ " @@ -716,22 +500,12 @@ title = "๋์ ์ทจ์" [ui.userfront.dashboard] last_auth_label = "์ต๊ทผ ์ธ์ฆ" +link_status_label = "์ฐ๋ ์ํ" status_history = "์ํ ์ด๋ ฅ" [ui.userfront.dashboard.activity] linked = "์ฐ๋๋จ" -[ui.userfront.dashboard.sessions] -active_badge = "ํ์ฑํ" -current_badge = "์ ์์ค" -current_disabled = "ํ์ฌ ์ธ์ " -unknown_device = "์ ์ ์๋ ๊ธฐ๊ธฐ" -unknown_session = "์ธ์ ์ ๋ณด" - -[ui.userfront.dashboard.sessions.revoke] -action = "์ธ์ ์ข ๋ฃ" -title = "์ธ์ ์ข ๋ฃ" - [ui.userfront.dashboard.approved_session] default = "์น์ธํ ์ธ์ ID" userfront = "์น์ธํ Userfront ์ธ์ ID" @@ -743,6 +517,17 @@ title = "์ฐ๋ ํด์ง" [ui.userfront.dashboard.scopes] title = "๋์ ๋ฒ์" +[ui.userfront.dashboard.sessions] +active_badge = "ํ์ฑํ" +current_badge = "์ ์์ค" +current_disabled = "ํ์ฌ ์ธ์ " +unknown_device = "์ ์ ์๋ ๊ธฐ๊ธฐ" +unknown_session = "์ธ์ ์ ๋ณด" + +[ui.userfront.dashboard.sessions.revoke] +action = "์ธ์ ์ข ๋ฃ" +title = "์ธ์ ์ข ๋ฃ" + [ui.userfront.dashboard.status] revoked = "ํด์ง๋จ" @@ -805,10 +590,10 @@ title = "๋ฏธ๋ฑ๋ก ํ์" [ui.userfront.login.verification] action_label = "ํ์ธ" +action_label_close = "์ฐฝ ๋ซ๊ธฐ" action_label_remote = "๋ก๊ทธ์ธ ์ฐฝ์ผ๋ก ์ด๋ํ๊ธฐ" page_title = "Baron SW ํฌํ" title = "์น์ธ ์๋ฃ" -action_label_close = "์ฐฝ ๋ซ๊ธฐ" title_pending = "๋ก๊ทธ์ธ ์น์ธ ํ์ธ ์ค" title_remote = "๋ก๊ทธ์ธ ์น์ธ ์๋ฃ" @@ -872,6 +657,7 @@ title = "์ ๋น๋ฐ๋ฒํธ ์ค์ " [ui.userfront.sections] apps = "๋์ App ํํฉ" audit = "์ ์์ด๋ ฅ" +sessions = "ํ์ฑ ์ธ์ " [ui.userfront.session] active = "์ธ์ ํ์ฑ" @@ -919,12 +705,3 @@ verify = "๋ณธ์ธ์ธ์ฆ" [ui.userfront.signup.success] action = "๋ก๊ทธ์ธํ๊ธฐ" - - -[ui.userfront.audit.filter] -title = "๋ด ํ๋ ๊ด๋ฆฌ" -toggle_label = "ํ์ฑ ์ธ์ ๋ง ๋ณด๊ธฐ" - -[msg.userfront.audit.filter] -description = "ํ์ฑํ๋ ์ธ์ ๋ง ๋ณด๋ ค๋ฉด ํ ๊ธ์ ์ผ์ฃผ์ธ์." - diff --git a/userfront/assets/translations/template.toml b/userfront/assets/translations/template.toml index 09b991d2..9b3ed89d 100644 --- a/userfront/assets/translations/template.toml +++ b/userfront/assets/translations/template.toml @@ -41,203 +41,6 @@ verify_code_failed = "" [err.userfront.session] missing = "" -[msg.userfront.error] -detail_contact = "" -detail_generic = "" -detail_request = "" -id = "" -title = "" -title_generic = "" -title_with_code = "" -type = "" - -[msg.userfront.error.tenant] -account = "" -account_unknown = "" -affiliated_tenants = "" -allowed_box_title = "" -allowed_tenants = "" -detail = "" -load_failed = "" -loading = "" -lookup_fallback = "" -page_title = "" -primary_tenant = "" -tenant = "" -tenant_unknown = "" -title = "" - -[msg.userfront.forgot] -description = "" -dry_send = "" -error = "" -input_required = "" -sent = "" - -[msg.userfront.login] -cookie_check_failed = "" -dry_send = "" -link_failed = "" -link_send_failed = "" -link_sent_email = "" -link_sent_phone = "" -link_timeout = "" -no_account = "" -oidc_failed = "" -qr_expired = "" -qr_init_failed = "" -qr_login_required = "" -token_missing = "" -verification_failed = "" - -[msg.userfront.login_success] -subtitle = "" - -[msg.userfront.consent] -accept_error = "" -client_id = "" -client_unknown = "" -description = "" -load_error = "" -missing_redirect = "" -redirect_notice = "" -scope_count = "" - -[msg.userfront.profile] -department_missing = "" -department_required = "" -email_missing = "" -greeting = "" -load_failed = "" -name_missing = "" -name_required = "" -phone_required = "" -phone_verify_required = "" -update_failed = "" -update_success = "" - -[msg.userfront.qr] -camera_error = "" -permission_error = "" -permission_required = "" - -[msg.userfront.reset] -invalid_body = "" -invalid_link = "" -invalid_title = "" -policy_loading = "" -success = "" - -[msg.userfront.sections] -apps_subtitle = "" -audit_subtitle = "" -sessions_subtitle = "" - -[msg.userfront.settings] -disabled = "" - -[msg.userfront.signup] -failed = "" -privacy_full = "" -tos_full = "" - -[ui.common.badge] -admin_only = "" -command_only = "" -system = "" - -[ui.common.status] -active = "" -blocked = "" -failure = "" -inactive = "" -ok = "" -pending = "" -success = "" - -[ui.userfront.app_label] -admin_console = "" -baron = "" -dev_console = "" - -[ui.userfront.auth_method] -ory = "" -session = "" - -[ui.userfront.dashboard] -link_status_label = "" -last_auth_label = "" -status_history = "" - -[ui.userfront.device] -android = "" -ios = "" -linux = "" -macos = "" -windows = "" - -[ui.userfront.error] -go_home = "" -go_login = "" -switch_account = "" - -[ui.userfront.forgot] -heading = "" -input_label = "" -submit = "" -title = "" - -[ui.userfront.login] -forgot_password = "" -signup = "" - -[ui.userfront.login_success] -later = "" -qr = "" -title = "" - -[ui.userfront.consent] -accept = "" -requested_scopes = "" -title = "" - -[ui.userfront.nav] -dashboard = "" -logout = "" -profile = "" -qr_scan = "" - -[ui.userfront.profile] -department_empty = "" -manage = "" -user_fallback = "" - -[ui.userfront.qr] -rescan = "" -result_success = "" -title = "" - -[ui.userfront.reset] -confirm_password = "" -new_password = "" -submit = "" -subtitle = "" -title = "" - -[ui.userfront.sections] -apps = "" -audit = "" -sessions = "" - -[ui.userfront.session] -active = "" -unknown = "" - -[ui.userfront.signup] -complete = "" -next_step = "" -title = "" - [msg.userfront] greeting = "" @@ -253,6 +56,9 @@ result = "" session_id = "" status = "" +[msg.userfront.audit.filter] +description = "" + [msg.userfront.consent] accept_error = "" client_id = "" @@ -279,14 +85,15 @@ approved_device = "" approved_ip = "" audit_empty = "" audit_load_error = "" -auto_login_supported = "" auth_method = "" +auto_login_supported = "" client_id = "" client_id_missing = "" current_status = "" last_auth = "" link_missing = "" link_open_error = "" +link_status = "" render_error = "" session_id_copied = "" @@ -295,6 +102,19 @@ empty = "" empty_detail = "" error = "" +[msg.userfront.dashboard.approved_session] +copy_click = "" +copy_tap = "" +none = "" + +[msg.userfront.dashboard.revoke] +confirm = "" +error = "" +success = "" + +[msg.userfront.dashboard.scopes] +empty = "" + [msg.userfront.dashboard.sessions] browser = "" empty = "" @@ -309,19 +129,6 @@ confirm = "" error = "" success = "" -[msg.userfront.dashboard.approved_session] -copy_click = "" -copy_tap = "" -none = "" - -[msg.userfront.dashboard.revoke] -confirm = "" -error = "" -success = "" - -[msg.userfront.dashboard.scopes] -empty = "" - [msg.userfront.dashboard.timeline] load_error = "" @@ -335,6 +142,22 @@ title_generic = "" title_with_code = "" type = "" +[msg.userfront.error.ory] +$normalizedCode = "" +access_denied = "" +consent_required = "" +interaction_required = "" +invalid_client = "" +invalid_grant = "" +invalid_request = "" +invalid_scope = "" +login_required = "" +request_forbidden = "" +server_error = "" +temporarily_unavailable = "" +unauthorized_client = "" +unsupported_response_type = "" + [msg.userfront.error.tenant] account = "" account_unknown = "" @@ -351,24 +174,8 @@ tenant = "" tenant_unknown = "" title = "" -[msg.userfront.error.ory] -"$normalizedCode" = "" -access_denied = "" -consent_required = "" -interaction_required = "" -invalid_client = "" -invalid_grant = "" -invalid_request = "" -invalid_scope = "" -login_required = "" -request_forbidden = "" -server_error = "" -temporarily_unavailable = "" -unauthorized_client = "" -unsupported_response_type = "" - [msg.userfront.error.whitelist] -"$normalizedCode" = "" +$normalizedCode = "" bad_request = "" invalid_session = "" not_found = "" @@ -428,8 +235,8 @@ body = "" approved = "" approved_local = "" approved_remote = "" -pending_remote = "" close_hint = "" +pending_remote = "" success = "" [msg.userfront.login_success] @@ -504,6 +311,7 @@ uppercase = "" [msg.userfront.sections] apps_subtitle = "" audit_subtitle = "" +sessions_subtitle = "" [msg.userfront.settings] disabled = "" @@ -660,6 +468,10 @@ dev_console = "" [ui.userfront.audit] +[ui.userfront.audit.filter] +title = "" +toggle_label = "" + [ui.userfront.audit.table] action = "" app = "" @@ -688,22 +500,12 @@ title = "" [ui.userfront.dashboard] last_auth_label = "" +link_status_label = "" status_history = "" [ui.userfront.dashboard.activity] linked = "" -[ui.userfront.dashboard.sessions] -active_badge = "" -current_badge = "" -current_disabled = "" -unknown_device = "" -unknown_session = "" - -[ui.userfront.dashboard.sessions.revoke] -action = "" -title = "" - [ui.userfront.dashboard.approved_session] default = "" userfront = "" @@ -715,6 +517,17 @@ title = "" [ui.userfront.dashboard.scopes] title = "" +[ui.userfront.dashboard.sessions] +active_badge = "" +current_badge = "" +current_disabled = "" +unknown_device = "" +unknown_session = "" + +[ui.userfront.dashboard.sessions.revoke] +action = "" +title = "" + [ui.userfront.dashboard.status] revoked = "" @@ -777,8 +590,8 @@ title = "" [ui.userfront.login.verification] action_label = "" -action_label_remote = "" action_label_close = "" +action_label_remote = "" page_title = "" title = "" title_pending = "" @@ -844,6 +657,7 @@ title = "" [ui.userfront.sections] apps = "" audit = "" +sessions = "" [ui.userfront.session] active = "" @@ -891,12 +705,3 @@ verify = "" [ui.userfront.signup.success] action = "" - - -[ui.userfront.audit.filter] -title = "" -toggle_label = "" - -[msg.userfront.audit.filter] -description = "" -
+ {t( + "msg.admin.permissions_direct.no_user_selected_desc", + "์ผ์ชฝ์ ์ฌ์ฉ์ ๋ฆฌ์คํธ์์ ๊ถํ์ ๋ณ๊ฒฝํ ์ธ์์ ์ ํํด ์ฃผ์ธ์.", + )} +
- {t("msg.admin.permissions_direct.no_user_selected_desc", "์ผ์ชฝ์ ์ฌ์ฉ์ ๋ฆฌ์คํธ์์ ๊ถํ์ ๋ณ๊ฒฝํ ์ธ์์ ์ ํํด ์ฃผ์ธ์.")} -