updateField(index, { validation: e.target.value })
}
@@ -375,6 +381,7 @@ export function TenantSchemaPage() {
size="icon"
className="text-destructive hover:bg-destructive/10 h-10 w-10"
onClick={() => removeField(index)}
+ disabled={!isWritable}
>
@@ -388,7 +395,9 @@ export function TenantSchemaPage() {
} />
@@ -181,4 +199,15 @@ describe("devfront AppLayout", () => {
expect(authState.signinSilent).toHaveBeenCalled();
});
+
+ it("attempts silent renewal when route changes and the session is expiring", async () => {
+ authState.user.expires_at = Math.floor(Date.now() / 1000) + 60;
+ await renderLayout();
+
+ await act(async () => {
+ (window as TestWindow).__baronNavigate?.("/profile");
+ });
+
+ expect(authState.signinSilent).toHaveBeenCalled();
+ });
});
diff --git a/devfront/src/features/clients/ClientConsentsPage.test.tsx b/devfront/src/features/clients/ClientConsentsPage.test.tsx
index c8d9cede..7a76d587 100644
--- a/devfront/src/features/clients/ClientConsentsPage.test.tsx
+++ b/devfront/src/features/clients/ClientConsentsPage.test.tsx
@@ -201,4 +201,85 @@ describe("ClientConsentsPage RP custom claims", () => {
}),
);
});
+
+ it("keeps date claim inputs and timezone selectors on the same row", async () => {
+ fetchClientMock.mockResolvedValue({
+ ...clientDetail,
+ client: {
+ ...clientDetail.client,
+ metadata: {
+ id_token_claims: [
+ {
+ namespace: "rp_claims",
+ key: "contract_date",
+ value: "",
+ valueType: "date",
+ readPermission: "admin_only",
+ writePermission: "admin_only",
+ },
+ ],
+ },
+ },
+ });
+ fetchConsentsMock.mockResolvedValue({
+ items: [
+ {
+ subject: "user-1",
+ userName: "Consent User",
+ clientId: "client-a",
+ clientName: "Claims App",
+ grantedScopes: ["openid", "profile"],
+ authenticatedAt: "2026-06-11T09:00:00Z",
+ createdAt: "2026-06-10T09:00:00Z",
+ status: "active",
+ tenantId: "tenant-1",
+ tenantName: "Hanmac",
+ rpMetadata: {
+ contract_date: 1781017200,
+ contract_date_permissions: {
+ readPermission: "admin_only",
+ writePermission: "admin_only",
+ },
+ },
+ },
+ ],
+ });
+ fetchRPUserMetadataMock.mockResolvedValue({
+ clientId: "client-a",
+ userId: "user-1",
+ metadata: {
+ contract_date: 1781017200,
+ contract_date_permissions: {
+ readPermission: "admin_only",
+ writePermission: "admin_only",
+ },
+ },
+ });
+
+ const { container } = await renderPage();
+
+ const editButton = Array.from(container.querySelectorAll("button")).find(
+ (button) =>
+ button.textContent?.includes("사용자 Claim 설정") ||
+ button.textContent?.includes("User Claim Settings"),
+ );
+
+ await act(async () => {
+ editButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
+ });
+ await flush();
+
+ const dateInput = container.querySelector(
+ 'input[aria-label="contract_date date"]',
+ );
+ const timeZoneSelect = container.querySelector(
+ 'select[aria-label="contract_date timezone"]',
+ );
+
+ expect(dateInput).not.toBeNull();
+ expect(timeZoneSelect).not.toBeNull();
+ expect(dateInput?.parentElement).toBe(timeZoneSelect?.parentElement);
+ expect(dateInput?.parentElement?.className).toContain("items-center");
+ expect(dateInput?.parentElement?.className).not.toContain("flex-col");
+ });
});
diff --git a/devfront/src/features/clients/ClientConsentsPage.tsx b/devfront/src/features/clients/ClientConsentsPage.tsx
index 1e6f1fa9..9ccdfcf8 100644
--- a/devfront/src/features/clients/ClientConsentsPage.tsx
+++ b/devfront/src/features/clients/ClientConsentsPage.tsx
@@ -1060,7 +1060,14 @@ function ClientConsentsPage() {
aria-label={`${row.key} ${row.valueType}`}
/>
) : (
-
+
{timeZoneOptions.map((timeZone) => (
diff --git a/devfront/src/features/clients/ClientGeneralPage.claims.test.tsx b/devfront/src/features/clients/ClientGeneralPage.claims.test.tsx
index a244ee31..2aa94e53 100644
--- a/devfront/src/features/clients/ClientGeneralPage.claims.test.tsx
+++ b/devfront/src/features/clients/ClientGeneralPage.claims.test.tsx
@@ -409,7 +409,7 @@ describe("ClientGeneralPage RP claims", () => {
);
});
- it("shows supported scopes and custom claims without integrated offline_access from the add scope button", async () => {
+ it("shows supported scopes including offline_access and custom claims from the add scope button", async () => {
const { container } = await renderPage();
const addScopeButton = Array.from(
@@ -422,7 +422,7 @@ describe("ClientGeneralPage RP claims", () => {
});
await flush();
- expect(container.textContent).not.toContain("offline_access");
+ expect(container.textContent).toContain("offline_access");
expect(container.textContent).toContain("old_claim");
const customClaimButton = Array.from(
diff --git a/devfront/src/features/clients/ClientGeneralPage.tsx b/devfront/src/features/clients/ClientGeneralPage.tsx
index af826426..172e9366 100644
--- a/devfront/src/features/clients/ClientGeneralPage.tsx
+++ b/devfront/src/features/clients/ClientGeneralPage.tsx
@@ -759,6 +759,15 @@ function ClientGeneralPage() {
description: tenantScopeDescription,
source: "standard",
},
+ {
+ id: "standard-offline-access",
+ name: "offline_access",
+ description: t(
+ "msg.dev.clients.scopes.offline_access",
+ "refresh token 발급 요청",
+ ),
+ source: "standard",
+ },
],
[tenantScopeDescription],
);
@@ -2789,6 +2798,7 @@ function ClientGeneralPage() {
) : (
{
await expect(claimKeyInput).toHaveValue("new_claim");
});
- test("adds supported scopes and custom claim keys from the scope picker without offline_access", async ({
+ test("adds supported scopes and custom claim keys from the scope picker including offline_access", async ({
page,
}) => {
const state = {
@@ -142,9 +142,9 @@ test.describe("DevFront RP claim cache", () => {
.getByRole("button", { name: /스코프 추가|Scope 추가|Add Scope/i })
.click();
- await expect(page.getByText("offline_access", { exact: true })).toHaveCount(
- 0,
- );
+ await expect(
+ page.getByText("offline_access", { exact: true }),
+ ).toBeVisible();
await expect(
page.getByRole("button", { name: /employee_code/ }),
).toBeVisible();
@@ -328,7 +328,11 @@ test.describe("DevFront RP claim cache", () => {
.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(
@@ -357,13 +361,7 @@ test.describe("DevFront RP claim cache", () => {
const defaultValueInput = page
.getByPlaceholder(/기본값을 입력하세요|Enter the default value/i)
.first();
- await expect(defaultValueInput).toHaveAttribute("inputmode", "numeric");
- await expect(defaultValueInput).toHaveAttribute("pattern", "-?[0-9]*");
await defaultValueInput.fill("3.14");
-
- await expect(
- page.getByText(/Claim 기본값이 타입과 맞지 않습니다|does not match/i),
- ).toBeVisible();
await expect(
page.getByRole("button", { name: /^저장$|^Save$/i }),
).toBeDisabled();
diff --git a/devfront/tests/devfront-login-claims.spec.ts b/devfront/tests/devfront-login-claims.spec.ts
new file mode 100644
index 00000000..9d50e1c3
--- /dev/null
+++ b/devfront/tests/devfront-login-claims.spec.ts
@@ -0,0 +1,239 @@
+import { expect, test } from "@playwright/test";
+import {
+ getPersistedOidcUser,
+ installDevApiMock,
+ seedAuth,
+} from "./helpers/devfront-fixtures";
+import { captureEvidence } from "./helpers/evidence";
+
+type ClaimScenario = {
+ title: string;
+ role: "super_admin" | "user";
+ tenantName: string;
+ userMeTenantId: string;
+ userMeCompanyCode: string;
+ profileClaims: Record
;
+ expectedProfileAssertions: Record;
+ expectTenantsToBeAbsent?: boolean;
+};
+
+const claimScenarios: ClaimScenario[] = [
+ {
+ title: "Server Side App preserves tenant and rp claims",
+ role: "super_admin",
+ tenantName: "Server Side Tenant",
+ userMeTenantId: "tenant-server",
+ userMeCompanyCode: "server-hq",
+ profileClaims: {
+ tenant_id: "tenant-server",
+ companyCode: "server-hq",
+ profile: {
+ names: {
+ name: "서버 앱 사용자",
+ },
+ emails: ["server@example.com"],
+ },
+ joined_tenants: ["tenant-server", "tenant-ops"],
+ tenants: {
+ "tenant-server": {
+ department: "Platform",
+ grade: "Lead",
+ },
+ "tenant-ops": {
+ department: "Operations",
+ grade: "Member",
+ },
+ },
+ rp_claims: {
+ approvalLevel: "A",
+ },
+ metadata: {
+ rp_custom_claims: {
+ "server-app": {
+ approvalLevel: "A",
+ },
+ },
+ },
+ },
+ expectedProfileAssertions: {
+ tenant_id: "tenant-server",
+ companyCode: "server-hq",
+ joined_tenants: ["tenant-server", "tenant-ops"],
+ rp_claims: {
+ approvalLevel: "A",
+ },
+ },
+ },
+ {
+ title: "PKCE preserves nested profile claims without tenant map expansion",
+ role: "user",
+ tenantName: "PKCE Tenant",
+ userMeTenantId: "tenant-pkce",
+ userMeCompanyCode: "pkce-hq",
+ profileClaims: {
+ tenant_id: "tenant-pkce",
+ companyCode: "pkce-hq",
+ profile: {
+ names: {
+ name: "PKCE 사용자",
+ },
+ emails: ["pkce@example.com"],
+ },
+ joined_tenants: ["tenant-pkce"],
+ rp_claims: {
+ features: ["sso", "claims"],
+ },
+ metadata: {
+ rp_custom_claims: {
+ "pkce-app": {
+ features: ["sso", "claims"],
+ },
+ },
+ },
+ },
+ expectedProfileAssertions: {
+ tenant_id: "tenant-pkce",
+ companyCode: "pkce-hq",
+ joined_tenants: ["tenant-pkce"],
+ rp_claims: {
+ features: ["sso", "claims"],
+ },
+ },
+ expectTenantsToBeAbsent: true,
+ },
+ {
+ title: "Headless login keeps session claims together with rp claims",
+ role: "super_admin",
+ tenantName: "Headless Tenant",
+ userMeTenantId: "tenant-headless",
+ userMeCompanyCode: "headless-hq",
+ profileClaims: {
+ tenant_id: "tenant-headless",
+ companyCode: "headless-hq",
+ profile: {
+ names: {
+ name: "헤드리스 사용자",
+ },
+ emails: ["headless@example.com"],
+ },
+ joined_tenants: ["tenant-headless", "tenant-support"],
+ tenants: {
+ "tenant-headless": {
+ department: "Automation",
+ grade: "Manager",
+ },
+ "tenant-support": {
+ department: "Support",
+ grade: "Agent",
+ },
+ },
+ rp_claims: {
+ approvalLevel: "B",
+ loginMode: "headless",
+ },
+ sid: "session-headless-1",
+ session_id: "session-headless-1",
+ metadata: {
+ rp_custom_claims: {
+ "headless-app": {
+ approvalLevel: "B",
+ loginMode: "headless",
+ },
+ },
+ },
+ },
+ expectedProfileAssertions: {
+ tenant_id: "tenant-headless",
+ companyCode: "headless-hq",
+ joined_tenants: ["tenant-headless", "tenant-support"],
+ rp_claims: {
+ approvalLevel: "B",
+ loginMode: "headless",
+ },
+ sid: "session-headless-1",
+ session_id: "session-headless-1",
+ },
+ },
+];
+
+test.describe("DevFront login claims", () => {
+ test.afterEach(async ({ page }, testInfo) => {
+ if (testInfo.status === "passed") {
+ await captureEvidence(page, testInfo, testInfo.title);
+ }
+ });
+
+ for (const scenario of claimScenarios) {
+ test(scenario.title, async ({ page }) => {
+ await seedAuth(page, {
+ role: scenario.role,
+ profile: scenario.profileClaims,
+ });
+
+ await installDevApiMock(page, {
+ clients: [],
+ consents: [],
+ auditLogsByCursor: undefined,
+ users: [],
+ tenants: [
+ {
+ id: scenario.userMeTenantId,
+ name: scenario.tenantName,
+ slug: scenario.userMeCompanyCode,
+ },
+ ],
+ });
+
+ await page.route("**/api/v1/user/me", async (route) => {
+ await route.fulfill({
+ status: 200,
+ contentType: "application/json",
+ body: JSON.stringify({
+ id: "playwright-user",
+ loginId: "playwright@example.com",
+ email: "playwright@example.com",
+ name: "Playwright User",
+ phoneNumber: "",
+ department: "QA",
+ tenantId: "",
+ tenantName: "",
+ role: scenario.role,
+ createdAt: "2026-06-01T00:00:00.000Z",
+ updatedAt: "2026-06-01T00:00:00.000Z",
+ }),
+ });
+ });
+
+ await page.goto("/profile");
+
+ await expect(
+ page.getByRole("heading", { name: "내 정보" }),
+ ).toBeVisible();
+ const storedUser = await getPersistedOidcUser(page);
+ expect(storedUser).not.toBeNull();
+ expect(storedUser?.profile).toMatchObject(
+ scenario.expectedProfileAssertions,
+ );
+ if (scenario.expectTenantsToBeAbsent) {
+ expect(storedUser?.profile).not.toHaveProperty("tenants");
+ } else {
+ expect(storedUser?.profile).toHaveProperty("tenants");
+ }
+ await expect(
+ page.getByText(String(scenario.profileClaims.tenant_id)),
+ ).toBeVisible();
+ await expect(page.getByText(scenario.userMeCompanyCode)).toBeVisible();
+ await page.getByRole("button", { name: "권한 및 역할" }).click();
+ await expect(
+ page.getByRole("heading", { name: "시스템 역할" }),
+ ).toBeVisible();
+ await expect(
+ page.getByText(
+ scenario.role === "super_admin"
+ ? /^(시스템 관리자|Super Admin|SUPER_ADMIN)$/i
+ : /^(일반 사용자|General User|USER)$/i,
+ ),
+ ).toBeVisible();
+ });
+ }
+});
diff --git a/devfront/tests/helpers/devfront-fixtures.ts b/devfront/tests/helpers/devfront-fixtures.ts
index 706e9ab6..2ce66f3c 100644
--- a/devfront/tests/helpers/devfront-fixtures.ts
+++ b/devfront/tests/helpers/devfront-fixtures.ts
@@ -73,6 +73,22 @@ export type DeveloperRequest = {
adminNotes?: string; // 추가
};
+export type SeedAuthOptions = {
+ role?: string;
+ accessToken?: string;
+ idToken?: string;
+ refreshToken?: string;
+ sessionState?: string;
+ expiresInSeconds?: number;
+ state?: Record;
+ profile?: Record;
+ tenantId?: string;
+ companyCode?: string;
+ email?: string;
+ name?: string;
+ phone?: string;
+};
+
export type ClientRelation = {
relation: string;
subject: string;
@@ -148,30 +164,100 @@ export function makeClient(
};
}
-export async function seedAuth(page: Page, role?: string) {
+function resolveSeedAuthOptions(
+ roleOrOptions?: string | SeedAuthOptions,
+): Required> & SeedAuthOptions {
+ if (typeof roleOrOptions === "string") {
+ return { role: roleOrOptions };
+ }
+ return { role: roleOrOptions?.role ?? "super_admin", ...roleOrOptions };
+}
+
+export async function getPersistedOidcUser(page: Page) {
+ return page.evaluate(() => {
+ const storage = window.localStorage;
+ for (let index = 0; index < storage.length; index += 1) {
+ const key = storage.key(index);
+ if (
+ key === null ||
+ !key.startsWith("oidc.user:") ||
+ !key.endsWith(":devfront")
+ ) {
+ continue;
+ }
+
+ const rawValue = storage.getItem(key);
+ if (!rawValue) {
+ continue;
+ }
+
+ try {
+ return JSON.parse(rawValue) as Record;
+ } catch {
+ return null;
+ }
+ }
+
+ return null;
+ });
+}
+
+export async function seedAuth(
+ page: Page,
+ roleOrOptions?: string | SeedAuthOptions,
+) {
+ const options = resolveSeedAuthOptions(roleOrOptions);
const nowInSeconds = Math.floor(Date.now() / 1000);
- seededRoles.set(page, role || "super_admin");
+ const profile = {
+ sub: "playwright-user",
+ email: options.email ?? "playwright@example.com",
+ name: options.name ?? "Playwright User",
+ phone: options.phone ?? "",
+ role: options.profile?.role ?? options.role,
+ tenant_id: options.tenantId ?? "tenant-a",
+ companyCode: options.companyCode ?? "tenant-a",
+ ...options.profile,
+ };
+ seededRoles.set(
+ page,
+ typeof profile.role === "string" ? profile.role : options.role,
+ );
await page.addInitScript(
- ({ issuedAt, injectedRole }) => {
+ ({
+ issuedAt,
+ injectedRole,
+ injectedProfile,
+ injectedState,
+ injectedIdToken,
+ injectedAccessToken,
+ injectedRefreshToken,
+ injectedSessionState,
+ injectedExpiresInSeconds,
+ }) => {
(
window as Window & typeof globalThis & { _IS_TEST_MODE?: boolean }
)._IS_TEST_MODE = true;
const mockOidcUser = {
- id_token: "playwright-id-token",
- session_state: "playwright-session",
- access_token: "playwright-access-token",
- refresh_token: "playwright-refresh-token",
+ id_token: injectedIdToken,
+ session_state: injectedSessionState,
+ access_token: injectedAccessToken,
+ refresh_token: injectedRefreshToken,
token_type: "Bearer",
scope: "openid profile email",
profile: {
sub: "playwright-user",
email: "playwright@example.com",
name: "Playwright User",
- ...(injectedRole ? { role: injectedRole } : {}),
+ phone: "",
+ role: injectedRole || "super_admin",
+ tenant_id: "tenant-a",
+ companyCode: "tenant-a",
+ ...(injectedProfile || {}),
},
- expires_at: issuedAt + 3600,
+ state: injectedState,
+ expires_at: issuedAt + injectedExpiresInSeconds,
};
const storageKeys = [
@@ -191,9 +277,25 @@ export async function seedAuth(page: Page, role?: string) {
}
window.localStorage.setItem("dev_role", injectedRole || "super_admin");
- window.localStorage.setItem("dev_tenant_id", "tenant-a");
+ window.localStorage.setItem(
+ "dev_tenant_id",
+ typeof injectedProfile.tenant_id === "string"
+ ? injectedProfile.tenant_id
+ : "tenant-a",
+ );
+ },
+ {
+ issuedAt: nowInSeconds,
+ injectedRole:
+ typeof profile.role === "string" ? profile.role : options.role,
+ injectedProfile: profile,
+ injectedState: options.state ?? { returnTo: "/clients" },
+ injectedIdToken: options.idToken ?? "playwright-id-token",
+ injectedAccessToken: options.accessToken ?? "playwright-access-token",
+ injectedRefreshToken: options.refreshToken ?? "playwright-refresh-token",
+ injectedSessionState: options.sessionState ?? "playwright-session",
+ injectedExpiresInSeconds: options.expiresInSeconds ?? 3600,
},
- { issuedAt: nowInSeconds, injectedRole: role ?? "" },
);
await page.route("**/oidc/**", async (route) => {
diff --git a/docker/ory/keto/namespaces.ts b/docker/ory/keto/namespaces.ts
index 2d387e27..b39b25fb 100644
--- a/docker/ory/keto/namespaces.ts
+++ b/docker/ory/keto/namespaces.ts
@@ -6,11 +6,126 @@ class System implements Namespace {
related: {
super_admins: User[]
authenticated_users: User[]
+
+ // 🌟 신규 글로벌 메뉴 권한 (Admin Control) 정의 - 조회(Read)
+ overview_viewers: User[]
+ tenants_viewers: User[]
+ org_chart_viewers: User[]
+ worksmobile_viewers: User[]
+ ory_ssot_viewers: User[]
+ data_integrity_viewers: User[]
+ users_viewers: User[]
+ permissions_direct_viewers: User[]
+ auth_guard_viewers: User[]
+ api_keys_viewers: User[]
+ audit_logs_viewers: User[]
+
+ // 🌟 신규 글로벌 메뉴 권한 (Admin Control) 정의 - 수정(Write)
+ overview_managers: User[]
+ tenants_managers: User[]
+ org_chart_managers: User[]
+ worksmobile_managers: User[]
+ ory_ssot_managers: User[]
+ data_integrity_managers: User[]
+ users_managers: User[]
+ permissions_direct_managers: User[]
+ auth_guard_managers: User[]
+ api_keys_managers: User[]
+ audit_logs_managers: User[]
}
permits = {
manage_all: (ctx: Context): boolean =>
- this.related.super_admins.includes(ctx.subject)
+ this.related.super_admins.includes(ctx.subject),
+
+ // 🌟 글로벌 메뉴 허가 규칙 (Permit Rules) - 조회(access_)와 수정(manage_) 완전 분리 이원화
+ access_overview: (ctx: Context): boolean =>
+ this.related.overview_viewers.includes(ctx.subject) ||
+ this.permits.manage_overview(ctx),
+
+ manage_overview: (ctx: Context): boolean =>
+ this.related.overview_managers.includes(ctx.subject) ||
+ this.permits.manage_all(ctx),
+
+ access_tenants: (ctx: Context): boolean =>
+ this.related.tenants_viewers.includes(ctx.subject) ||
+ this.permits.manage_tenants(ctx),
+
+ manage_tenants: (ctx: Context): boolean =>
+ this.related.tenants_managers.includes(ctx.subject) ||
+ this.permits.manage_all(ctx),
+
+ access_org_chart: (ctx: Context): boolean =>
+ this.related.org_chart_viewers.includes(ctx.subject) ||
+ this.permits.manage_org_chart(ctx),
+
+ manage_org_chart: (ctx: Context): boolean =>
+ this.related.org_chart_managers.includes(ctx.subject) ||
+ this.permits.manage_all(ctx),
+
+ access_worksmobile: (ctx: Context): boolean =>
+ this.related.worksmobile_viewers.includes(ctx.subject) ||
+ this.permits.manage_worksmobile(ctx),
+
+ manage_worksmobile: (ctx: Context): boolean =>
+ this.related.worksmobile_managers.includes(ctx.subject) ||
+ this.permits.manage_all(ctx),
+
+ access_ory_ssot: (ctx: Context): boolean =>
+ this.related.ory_ssot_viewers.includes(ctx.subject) ||
+ this.permits.manage_ory_ssot(ctx),
+
+ manage_ory_ssot: (ctx: Context): boolean =>
+ this.related.ory_ssot_managers.includes(ctx.subject) ||
+ this.permits.manage_all(ctx),
+
+ access_data_integrity: (ctx: Context): boolean =>
+ this.related.data_integrity_viewers.includes(ctx.subject) ||
+ this.permits.manage_data_integrity(ctx),
+
+ manage_data_integrity: (ctx: Context): boolean =>
+ this.related.data_integrity_managers.includes(ctx.subject) ||
+ this.permits.manage_all(ctx),
+
+ access_users: (ctx: Context): boolean =>
+ this.related.users_viewers.includes(ctx.subject) ||
+ this.permits.manage_users(ctx),
+
+ manage_users: (ctx: Context): boolean =>
+ this.related.users_managers.includes(ctx.subject) ||
+ this.permits.manage_all(ctx),
+
+ access_permissions_direct: (ctx: Context): boolean =>
+ this.related.permissions_direct_viewers.includes(ctx.subject) ||
+ this.permits.manage_permissions_direct(ctx),
+
+ manage_permissions_direct: (ctx: Context): boolean =>
+ this.related.permissions_direct_managers.includes(ctx.subject) ||
+ this.permits.manage_all(ctx),
+
+ access_auth_guard: (ctx: Context): boolean =>
+ this.related.auth_guard_viewers.includes(ctx.subject) ||
+ this.permits.manage_auth_guard(ctx),
+
+ manage_auth_guard: (ctx: Context): boolean =>
+ this.related.auth_guard_managers.includes(ctx.subject) ||
+ this.permits.manage_all(ctx),
+
+ access_api_keys: (ctx: Context): boolean =>
+ this.related.api_keys_viewers.includes(ctx.subject) ||
+ this.permits.manage_api_keys(ctx),
+
+ manage_api_keys: (ctx: Context): boolean =>
+ this.related.api_keys_managers.includes(ctx.subject) ||
+ this.permits.manage_all(ctx),
+
+ access_audit_logs: (ctx: Context): boolean =>
+ this.related.audit_logs_viewers.includes(ctx.subject) ||
+ this.permits.manage_audit_logs(ctx),
+
+ manage_audit_logs: (ctx: Context): boolean =>
+ this.related.audit_logs_managers.includes(ctx.subject) ||
+ this.permits.manage_all(ctx)
}
}
@@ -22,9 +137,63 @@ class Tenant implements Namespace {
parents: Tenant[]
developer_console_viewer: (User | SubjectSet)[]
developer_console_grant_manager: (User | SubjectSet)[]
+
+ // 🌟 신규 직접 관계 (Direct Relations) 정의
+ profile_viewers: (User | SubjectSet)[]
+ profile_managers: (User | SubjectSet)[]
+
+ permissions_viewers: (User | SubjectSet)[]
+ permissions_managers: (User | SubjectSet)[]
+
+ organization_viewers: (User | SubjectSet)[]
+ organization_managers: (User | SubjectSet)[]
+
+ schema_viewers: (User | SubjectSet)[]
+ schema_managers: (User | SubjectSet)[]
}
permits = {
+ // 1. 프로필 (Profile) 탭 허가 규칙
+ view_profile: (ctx: Context): boolean =>
+ this.related.profile_viewers.includes(ctx.subject) ||
+ this.permits.manage_profile(ctx) ||
+ this.permits.view(ctx), // 멤버/관리자/소유자는 기본 조회 가능
+
+ manage_profile: (ctx: Context): boolean =>
+ this.related.profile_managers.includes(ctx.subject) ||
+ this.permits.manage(ctx), // 관리자/소유자는 기본 수정 가능
+
+ // 2. 권한 관리 (Permissions) 탭 허가 규칙
+ view_permissions: (ctx: Context): boolean =>
+ this.related.permissions_viewers.includes(ctx.subject) ||
+ this.permits.manage_permissions(ctx) ||
+ this.permits.view(ctx),
+
+ manage_permissions: (ctx: Context): boolean =>
+ this.related.permissions_managers.includes(ctx.subject) ||
+ this.permits.manage_admins(ctx), // 소유자는 기본 관리 가능
+
+ // 3. 조직 관리 (Organization) 탭 허가 규칙
+ view_organization: (ctx: Context): boolean =>
+ this.related.organization_viewers.includes(ctx.subject) ||
+ this.permits.manage_organization(ctx) ||
+ this.permits.view(ctx),
+
+ manage_organization: (ctx: Context): boolean =>
+ this.related.organization_managers.includes(ctx.subject) ||
+ this.permits.manage(ctx),
+
+ // 4. 사용자 스키마 (Schema) 탭 허가 규칙
+ view_schema: (ctx: Context): boolean =>
+ this.related.schema_viewers.includes(ctx.subject) ||
+ this.permits.manage_schema(ctx) ||
+ this.permits.view(ctx),
+
+ manage_schema: (ctx: Context): boolean =>
+ this.related.schema_managers.includes(ctx.subject) ||
+ this.permits.manage(ctx),
+
+ // --- 기존 마스터 및 상속 규칙 보존 ---
view: (ctx: Context): boolean =>
this.related.members.includes(ctx.subject) ||
this.related.admins.includes(ctx.subject) ||
diff --git a/docs/adminfront-tab-level-direct-permission-design.md b/docs/adminfront-tab-level-direct-permission-design.md
new file mode 100644
index 00000000..8a7be0bc
--- /dev/null
+++ b/docs/adminfront-tab-level-direct-permission-design.md
@@ -0,0 +1,181 @@
+# [RFC/Design] adminfront: 각 탭별 ReBAC 기반 세부 권한 직접 부여 기능 설계
+
+## 1. 배경 및 목적
+
+현재 `adminfront` 테넌트 상세 페이지는 대략적인 역할 기반 제어(Coarse-grained RBAC/ReBAC) 형태로만 동작합니다.
+운영자는 사용자를 **"소유자(Owner)"** 또는 **"테넌트 관리자(Admin)"**로만 임명할 수 있으며, 이 역할에 의해 테넌트 하위의 4개 탭(프로필, 권한 관리, 조직 관리, 사용자 스키마)의 읽기/쓰기 권한이 통째로 결정됩니다.
+
+하지만 더욱 세밀한 운영 권한 관리가 필요하다는 비즈니스 요구사항에 따라, **"사용자 A에게는 조직 관리 및 스키마 읽기 권한만 부여"**, **"사용자 B에게는 스키마 수정 권한만 부여"**와 같이 탭 레벨에서 세분화된(Fine-grained) 권한을 직접 지정할 수 있는 기능을 신설합니다.
+
+이 설계는 `devfront`에서 이슈 #1029를 통해 구현 완료한 **"RP 세부 관계 직접 부여"** 철학과 완벽히 동일하며, Ory Keto(ReBAC) 및 아웃박스 정합성 엔진을 관통하여 설계됩니다.
+
+---
+
+## 2. 세부 설계 사양
+
+### 2.1 Ory Keto OPL 스키마 변경 (`docker/ory/keto/namespaces.ts`)
+
+`Tenant` 네임스페이스 하위에 각 탭별 읽기(`_viewers`)와 쓰기(`_managers`)를 결정하는 **물리적인 직접 관계(Direct Relations)**를 추가합니다.
+기존 `members`, `admins`, `owners`에 의한 상속 허가 식(Permits)을 유지하여 하위 호환성 및 기존 관리체계의 안정성을 완벽히 보장합니다.
+
+```typescript
+class Tenant implements Namespace {
+ related: {
+ owners: (User | SubjectSet)[]
+ admins: (User | SubjectSet)[]
+ members: (User | SubjectSet | SubjectSet | SubjectSet)[]
+ parents: Tenant[]
+ developer_console_viewer: (User | SubjectSet)[]
+ developer_console_grant_manager: (User | SubjectSet)[]
+
+ // 🌟 신규 직접 관계 (Direct Relations) 정의
+ profile_viewers: (User | SubjectSet)[]
+ profile_managers: (User | SubjectSet)[]
+
+ permissions_viewers: (User | SubjectSet)[]
+ permissions_managers: (User | SubjectSet)[]
+
+ organization_viewers: (User | SubjectSet)[]
+ organization_managers: (User | SubjectSet)[]
+
+ schema_viewers: (User | SubjectSet)[]
+ schema_managers: (User | SubjectSet)[]
+ }
+
+ permits = {
+ // 1. 프로필 (Profile) 탭 허가 규칙
+ view_profile: (ctx: Context): boolean =>
+ this.related.profile_viewers.includes(ctx.subject) ||
+ this.permits.manage_profile(ctx) ||
+ this.permits.view(ctx), // 멤버/관리자/소유자는 기본 조회 가능
+
+ manage_profile: (ctx: Context): boolean =>
+ this.related.profile_managers.includes(ctx.subject) ||
+ this.permits.manage(ctx), // 관리자/소유자는 기본 수정 가능
+
+ // 2. 권한 관리 (Permissions) 탭 허가 규칙
+ view_permissions: (ctx: Context): boolean =>
+ this.related.permissions_viewers.includes(ctx.subject) ||
+ this.permits.manage_permissions(ctx) ||
+ this.permits.view(ctx),
+
+ manage_permissions: (ctx: Context): boolean =>
+ this.related.permissions_managers.includes(ctx.subject) ||
+ this.permits.manage_admins(ctx), // 소유자는 기본 관리 가능
+
+ // 3. 조직 관리 (Organization) 탭 허가 규칙
+ view_organization: (ctx: Context): boolean =>
+ this.related.organization_viewers.includes(ctx.subject) ||
+ this.permits.manage_organization(ctx) ||
+ this.permits.view(ctx),
+
+ manage_organization: (ctx: Context): boolean =>
+ this.related.organization_managers.includes(ctx.subject) ||
+ this.permits.manage(ctx),
+
+ // 4. 사용자 스키마 (Schema) 탭 허가 규칙
+ view_schema: (ctx: Context): boolean =>
+ this.related.schema_viewers.includes(ctx.subject) ||
+ this.permits.manage_schema(ctx) ||
+ this.permits.view(ctx),
+
+ manage_schema: (ctx: Context): boolean =>
+ this.related.schema_managers.includes(ctx.subject) ||
+ this.permits.manage(ctx),
+
+ // --- 기존 마스터 및 상속 규칙 보존 ---
+ view: (ctx: Context): boolean =>
+ this.related.members.includes(ctx.subject) ||
+ this.related.admins.includes(ctx.subject) ||
+ this.related.owners.includes(ctx.subject) ||
+ this.related.parents.traverse((p) => p.permits.view(ctx)),
+
+ manage: (ctx: Context): boolean =>
+ this.related.admins.includes(ctx.subject) ||
+ this.related.owners.includes(ctx.subject) ||
+ this.related.parents.traverse((p) => p.permits.manage(ctx)),
+
+ manage_admins: (ctx: Context): boolean =>
+ this.related.owners.includes(ctx.subject) ||
+ this.related.parents.traverse((p) => p.permits.manage_admins(ctx))
+ }
+}
+```
+
+---
+
+### 2.2 백엔드 API 설계 (`backend/internal/handler/tenant_handler.go`)
+
+세부 권한 부여/회수 API는 해당 테넌트의 최상위 권한 관리자만 수행할 수 있도록 **`Tenant#manage_admins`** 허가 규칙으로 강력하게 인가 보호합니다.
+
+#### A. 세부 권한 관계 전체 조회 API
+* **Endpoint**: `GET /api/v1/admin/tenants/:id/relations`
+* **인가 필터**: `RequireKetoPermission(config, "Tenant", "manage_admins")`
+* **반환 DTO**:
+ ```json
+ {
+ "items": [
+ {
+ "userId": "00000000-0000-0000-0000-000000000010",
+ "name": "홍길동",
+ "email": "kildong@hmac.kr",
+ "relations": ["profile_managers", "schema_viewers"]
+ }
+ ]
+ }
+ ```
+
+#### B. 세부 권한 관계 부여 API
+* **Endpoint**: `POST /api/v1/admin/tenants/:id/relations`
+* **인가 필터**: `RequireKetoPermission(config, "Tenant", "manage_admins")`
+* **Payload**:
+ ```json
+ {
+ "userId": "00000000-0000-0000-0000-000000000010",
+ "relation": "profile_managers"
+ }
+ ```
+* **동작**: 트랜잭셔널 아웃박스에 적재하여 Keto에 `Tenant:#profile_managers@User:` 튜플 반영.
+
+#### C. 세부 권한 관계 회수 API
+* **Endpoint**: `DELETE /api/v1/admin/tenants/:id/relations`
+* **인가 필터**: `RequireKetoPermission(config, "Tenant", "manage_admins")`
+* **Payload**:
+ ```json
+ {
+ "userId": "00000000-0000-0000-0000-000000000010",
+ "relation": "profile_managers"
+ }
+ ```
+* **동작**: 트랜잭셔널 아웃박스에 적재하여 Keto 내 튜플 삭제 반영.
+
+---
+
+### 2.3 프론트엔드 UI 설계
+
+사용자에게 역할(Role) 외에 세부적인 설정을 직관적으로 관리할 수 있도록, 기존 **"권한 관리"** 탭 하단에 **"세부 권한 설정 (Fine-grained Permissions)"** 섹션을 신설합니다.
+
+#### A. 구성 요소
+1. **유저 검색/추가 패널**: 테넌트 소속 사용자를 검색하여 격리 설정 테이블(Matrix)에 추가합니다.
+2. **세부 권한 격리 매트릭스 (Matrix Table)**:
+ * 컬럼: `이름` | `이메일` | `테넌트 프로필` | `권한 관리` | `조직 관리` | `사용자 스키마` | `작업`
+ * 각 탭 컬럼은 드롭다운 셀렉트 박스로 채워집니다:
+ * **`권한 없음 (None)`** / **`조회 가능 (Read)`** / **`수정 가능 (Write)`**
+3. **상태 동기화 연동**:
+ * 셀렉트 박스에서 `조회 가능(Read)` 선택 시: `_viewers` 관계 추가(`POST`) & `_managers` 관계 회수(`DELETE`).
+ * 셀렉트 박스에서 `수정 가능(Write)` 선택 시: `_managers` 관계 추가(`POST`) & `_viewers` 관계 회수(`DELETE`).
+ * 셀렉트 박스에서 `권한 없음(None)` 선택 시: 둘 다 회수(`DELETE`).
+
+---
+
+## 3. 작업 계획 및 테스트 전략
+
+1. **OPL 컴파일 및 빌드 검증**:
+ * namespaces.ts 수정 후 Keto OPL 테스트를 구동하여 컴파일 문법에 문제가 없는지 사전 검증합니다.
+2. **백엔드 구현 및 DB 연동**:
+ * `tenant_handler.go`에 신규 핸들러 추가 후 gg/gorm 아웃박스 통합을 완료합니다.
+3. **프론트엔드 연동 및 Matrix UI 개발**:
+ * `TenantAdminsAndOwnersTab.tsx` 하단부 카드에 매트릭스 테이블 영역을 추가합니다.
+4. **유형 및 단위 테스트**:
+ * 신설된 REST API 명세를 테스트하는 고성능 백엔드 단위 테스트를 작성합니다.
+ * 프론트엔드에서 체크박스 변경 시 올바른 릴레이션이 트리거되는지 검증하는 Vitest 렌더 테스트를 작성합니다.
diff --git a/locales/en.toml b/locales/en.toml
index f6c70996..e83429ec 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,18 +365,23 @@ 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."
@@ -289,6 +389,7 @@ subtitle = "Review and manage child tenants linked under this tenant."
[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 +400,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_partial_error = "Failed to update {{count}} users."
update_success = "User info updated successfully."
@@ -363,6 +464,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."
@@ -402,81 +508,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."
@@ -613,12 +644,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."
@@ -630,6 +655,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."
@@ -638,11 +674,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."
@@ -653,6 +684,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."
@@ -675,11 +785,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."
@@ -701,15 +814,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."
@@ -718,6 +831,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."
@@ -728,23 +854,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."
@@ -758,6 +871,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"
@@ -774,24 +903,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."
@@ -845,14 +958,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]
@@ -1006,6 +1119,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]
@@ -1031,10 +1155,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]
@@ -1051,6 +1175,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"
@@ -1096,6 +1225,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"
@@ -1144,18 +1299,94 @@ 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"
+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"
@@ -1163,10 +1394,39 @@ 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.status]
+failed = "failed"
+not_ready = "not ready"
+ready = "ready"
+
+[ui.admin.ory_ssot.summary]
+cache_keys = "Cache keys"
+last_refreshed = "Last refreshed"
+observed_identities = "Observed identities"
+status = "Status"
+
[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"
@@ -1181,8 +1441,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"
@@ -1196,57 +1468,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"
@@ -1265,6 +1497,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"
@@ -1287,15 +1523,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"
@@ -1307,81 +1543,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."
@@ -1389,8 +1566,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"
@@ -1409,6 +1621,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."
@@ -1430,15 +1651,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"
@@ -1463,12 +1684,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..."
@@ -1484,6 +1709,7 @@ status = "STATUS"
[ui.admin.tenants.table]
actions = "ACTIONS"
context = "Parent Path"
+created = "CREATED"
id = "ID"
id_copy = "Copy ID"
members = "Members"
@@ -1495,27 +1721,52 @@ 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.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"
@@ -1548,9 +1799,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"
@@ -1816,6 +2067,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"
@@ -1825,8 +2086,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"
@@ -2332,15 +2593,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"
@@ -2348,6 +2601,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"
@@ -2399,19 +2660,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."
@@ -2443,66 +2760,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"
@@ -2520,6 +2782,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"
@@ -2530,6 +2820,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"
@@ -2564,17 +2858,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"
@@ -2586,6 +2869,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"
@@ -2648,36 +2942,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"
@@ -2786,272 +3057,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."
-
-[ui.admin.integrity]
-tab_checks = "Integrity Checks"
-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.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 c0235175..02f6d3ec 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,18 @@ subtitle = "이 테넌트의 사용자에게 적용할 커스텀 속성을 정
update_error = "스키마 업데이트에 실패했습니다."
update_success = "스키마가 성공적으로 업데이트되었습니다."
+[msg.admin.tenants.scope]
+description = "상위 테넌트를 선택하면 해당 하위 테넌트만 목록에 표시합니다."
+
[msg.admin.tenants.sub]
empty = "하위 테넌트가 없습니다."
subtitle = "현재 테넌트 하위에 생성된 조직입니다."
[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 +400,10 @@ move_description = "선택한 사용자를 다른 테넌트로 일괄 이동합
move_error = "사용자 이동 중 오류가 발생했습니다."
move_success = "{{count}}명의 사용자가 성공적으로 이동되었습니다."
parsed_count = "{{count}}행의 데이터가 파싱되었습니다."
+permission_placeholder = "권한 선택"
schema_incompatible = "대상 테넌트 스키마에 없는 필드는 유실될 수 있습니다:"
schema_missing = "대상 테넌트의 필수 필드가 누락되어 있습니다:"
status_placeholder = "상태 선택"
-permission_placeholder = "권한 선택"
update_partial_error = "{{count}}명의 사용자 정보 수정에 실패했습니다."
update_success = "사용자 정보가 일괄 업데이트되었습니다."
@@ -911,8 +464,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)"
@@ -940,88 +498,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 = "접근 권한이 없습니다."
@@ -1050,7 +536,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이 재발급되었습니다."
@@ -1158,12 +644,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 = "전체 또는 선택한 애플리케이션만 기준으로 그래프를 확인합니다."
@@ -1175,6 +655,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 = "표시할 연동 앱이 없습니다."
@@ -1183,11 +674,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 관리자는 담당 앱의 리소스만 조회할 수 있습니다."
@@ -1198,6 +684,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 = "연동 앱 등록 및 관리를 수행할 수 있습니다."
@@ -1220,11 +785,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 = "동의 후 자동으로 서비스로 이동합니다."
@@ -1246,14 +814,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가 복사되었습니다."
@@ -1262,6 +831,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 = "활성 세션이 없습니다."
@@ -1272,23 +854,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 = "접속이력을 불러오지 못했습니다."
@@ -1302,6 +871,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 = "알 수 없음"
@@ -1318,24 +903,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 = "요청한 페이지를 찾을 수 없습니다."
@@ -1389,14 +958,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]
@@ -1471,6 +1040,7 @@ uppercase = "대문자 1개 이상"
[msg.userfront.sections]
apps_subtitle = "현재 연결된 앱과 최근 인증 상태입니다."
audit_subtitle = "Baron 로그인 기준의 최근 접근 기록입니다."
+sessions_subtitle = "현재 로그인된 기기와 브라우저 세션입니다."
[msg.userfront.settings]
disabled = "현재 계정 설정 화면은 준비 중입니다."
@@ -1485,12 +1055,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 = "인증코드가 일치하지 않습니다."
@@ -1506,7 +1076,7 @@ lowercase_required = "소문자가 최소 1개 이상 포함되어야 합니다.
mismatch = "비밀번호가 일치하지 않습니다."
number_required = "숫자가 최소 1개 이상 포함되어야 합니다."
symbol_required = "특수문자가 최소 1개 이상 포함되어야 합니다."
-title = "마지막으로\\\\n비밀번호를 설정해주세요"
+title = "마지막으로\\\\\\\\\\\\\\\\n비밀번호를 설정해주세요"
uppercase_required = "대문자가 최소 1개 이상 포함되어야 합니다."
[msg.userfront.signup.password.rule]
@@ -1535,7 +1105,7 @@ uppercase = "대문자"
[msg.userfront.signup.profile]
affiliate_hint = "가족사 이메일 사용 시 자동으로 선택됩니다."
-title = "회원님의\\\\n소속 정보를 알려주세요"
+title = "회원님의\\\\\\\\\\\\\\\\n소속 정보를 알려주세요"
[msg.userfront.signup.success]
body = "성공적으로 가입되었습니다."
@@ -1549,6 +1119,17 @@ key = "존재하지 않는 키"
[test]
key = "테스트"
+[this]
+
+[this.key]
+
+[this.key.truly]
+
+[this.key.truly.does]
+
+[this.key.truly.does.not]
+exist = "Fallback"
+
[ui]
[ui.admin]
@@ -1594,6 +1175,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"
@@ -1639,6 +1225,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 임포트"
@@ -1687,18 +1299,94 @@ name = "NAME"
plane = "Admin Plane"
subtitle = "관리 및 정책 운영"
+[ui.admin.integrity]
+fetch_error = "정합성 최종 검증 결과를 불러오지 못했습니다."
+kicker = "시스템"
+loading = "불러오는 중"
+subtitle = "정합성 상태를 확인하고 데이터 모델 전반의 검증 결과를 살펴봅니다."
+tab_checks = "정합성 검사"
+tab_ory_ssot = "Ory SSOT 시스템"
+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 = "템플릿 다운로드"
@@ -1706,10 +1394,39 @@ 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.status]
+failed = "실패"
+not_ready = "준비되지 않음"
+ready = "준비됨"
+
+[ui.admin.ory_ssot.summary]
+cache_keys = "Cache keys"
+last_refreshed = "마지막 refresh"
+observed_identities = "관측 identity"
+status = "상태"
+
[ui.admin.overview]
kicker = "Global Overview"
title = "통합 대시보드"
+[ui.admin.overview.chart]
+description = "전체 또는 선택한 조직 기준으로 그래프를 확인합니다."
+title = "회사별 앱별 로그인 요청 현황"
+
[ui.admin.overview.playbook]
title = "Admin playbook"
@@ -1724,8 +1441,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 = "관리 가능한 테넌트"
@@ -1738,23 +1467,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 = "이미 관리자"
@@ -1773,6 +1497,10 @@ title = "테넌트 관리자"
list = "List"
section = "Tenants"
+[ui.admin.tenants.bulk]
+selected_count = "temp"
+status_placeholder = "temp"
+
[ui.admin.tenants.create]
title = "테넌트 추가"
@@ -1795,15 +1523,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"
@@ -1815,83 +1543,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 = "매칭 가능한 테넌트가 없습니다."
@@ -1899,8 +1566,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"
@@ -1919,6 +1621,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 = "이 도메인을 가진 이메일로 가입한 사용자는 자동으로 이 테넌트에 배정됩니다."
@@ -1940,15 +1651,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 = "스키마 저장"
@@ -1973,12 +1684,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 = "검색..."
@@ -1994,6 +1709,7 @@ status = "STATUS"
[ui.admin.tenants.table]
actions = "ACTIONS"
context = "상위 경로"
+created = "CREATED"
id = "ID"
id_copy = "ID 복사"
members = "멤버수"
@@ -2005,28 +1721,52 @@ 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.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 = "목록으로 돌아가기"
@@ -2059,9 +1799,9 @@ name = "이름"
name_placeholder = "홍길동"
password = "비밀번호"
password_placeholder = "********"
-picker_description = "배정할 테넌트를 검색해서 선택하세요."
phone = "전화번호"
phone_placeholder = "010-1234-5678"
+picker_description = "배정할 테넌트를 검색해서 선택하세요."
position = "직급"
position_placeholder = "수석/책임/선임"
role = "역할"
@@ -2327,6 +2067,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 = "일괄 임포트"
@@ -2336,8 +2086,8 @@ fetch_error = "사용자 목록 조회에 실패했습니다."
search_placeholder = "이름 또는 이메일 검색..."
status_select = "{{name}} 상태"
subtitle = "시스템 사용자를 조회하고 관리합니다."
-toggle_status = "{{name}} 활성 상태"
title = "사용자 관리"
+toggle_status = "{{name}} 활성 상태"
[ui.admin.users.list.breadcrumb]
list = "List"
@@ -2843,15 +2593,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 = "일"
@@ -2859,6 +2601,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"
@@ -2910,13 +2660,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 = "프로필 정보를 불러오지 못했습니다."
@@ -2938,7 +2718,7 @@ title = "사용자 정보"
[ui.dev.profile.org]
company_code = "회사 코드"
tenant = "테넌트"
-tenant_slug = "테넌트 Slug"
+tenant_slug = "테넌트 slug"
title = "조직 정보"
[ui.dev.profile.role]
@@ -2950,6 +2730,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 = "세션 만료 관리"
@@ -2967,6 +2782,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 포탈"
@@ -2977,6 +2820,10 @@ dev_console = "Dev Console"
[ui.userfront.audit]
+[ui.userfront.audit.filter]
+title = "내 활동 관리"
+toggle_label = "활성 세션만 보기"
+
[ui.userfront.audit.table]
action = "관리"
app = "애플리케이션"
@@ -3005,22 +2852,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"
@@ -3032,6 +2869,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 = "해지됨"
@@ -3094,36 +2942,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 인증 (카메라 켜기)"
@@ -3184,6 +3009,7 @@ title = "새 비밀번호 설정"
[ui.userfront.sections]
apps = "나의 App 현황"
audit = "접속이력"
+sessions = "활성 세션"
[ui.userfront.session]
active = "세션 활성"
@@ -3231,275 +3057,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.integrity]
-subtitle = "정합성 상태를 확인하고 데이터 모델 전반의 검증 결과를 살펴봅니다."
-
-[ui.admin.integrity]
-tab_checks = "정합성 검사"
-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.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 82910d10..1cc26740 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,6 +379,9 @@ subtitle = ""
update_error = ""
update_success = ""
+[msg.admin.tenants.scope]
+description = ""
+
[msg.admin.tenants.sub]
empty = ""
subtitle = ""
@@ -673,6 +389,7 @@ subtitle = ""
[msg.admin.users]
confirm_remove_org = ""
export_error = ""
+self_delete_blocked = ""
status_error = ""
[msg.admin.users.bulk]
@@ -683,10 +400,10 @@ move_description = ""
move_error = ""
move_success = ""
parsed_count = ""
+permission_placeholder = ""
schema_incompatible = ""
schema_missing = ""
status_placeholder = ""
-permission_placeholder = ""
update_partial_error = ""
update_success = ""
@@ -747,6 +464,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 = ""
@@ -775,34 +497,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 = ""
@@ -814,53 +508,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 = ""
@@ -997,12 +644,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 = ""
@@ -1014,6 +655,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 = ""
@@ -1022,11 +674,6 @@ none = ""
description = ""
empty = ""
-[msg.dev.dashboard.notice]
-consent_audit = ""
-dev_scope = ""
-hydra_health = ""
-
[msg.dev.forbidden]
default = ""
rp_admin = ""
@@ -1037,6 +684,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 = ""
@@ -1059,6 +785,9 @@ result = ""
session_id = ""
status = ""
+[msg.userfront.audit.filter]
+description = ""
+
[msg.userfront.consent]
accept_error = ""
client_id = ""
@@ -1085,14 +814,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 = ""
@@ -1101,6 +831,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 = ""
@@ -1115,19 +858,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 = ""
@@ -1141,6 +871,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 = ""
@@ -1157,24 +903,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 = ""
@@ -1234,8 +964,8 @@ body = ""
approved = ""
approved_local = ""
approved_remote = ""
-pending_remote = ""
close_hint = ""
+pending_remote = ""
success = ""
[msg.userfront.login_success]
@@ -1310,6 +1040,7 @@ uppercase = ""
[msg.userfront.sections]
apps_subtitle = ""
audit_subtitle = ""
+sessions_subtitle = ""
[msg.userfront.settings]
disabled = ""
@@ -1388,6 +1119,17 @@ key = ""
[test]
key = ""
+[this]
+
+[this.key]
+
+[this.key.truly]
+
+[this.key.truly.does]
+
+[this.key.truly.does.not]
+exist = ""
+
[ui]
[ui.admin]
@@ -1433,6 +1175,11 @@ last_used = ""
name = ""
scopes = ""
+[ui.admin.apikeys]
+
+[ui.admin.apikeys.registry]
+title = ""
+
[ui.admin.audit]
export_csv = ""
load_more = ""
@@ -1478,6 +1225,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 = ""
@@ -1526,18 +1299,94 @@ name = ""
plane = ""
subtitle = ""
+[ui.admin.integrity]
+fetch_error = ""
+kicker = ""
+loading = ""
+subtitle = ""
+tab_checks = ""
+tab_ory_ssot = ""
+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 = ""
@@ -1545,10 +1394,39 @@ 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.status]
+failed = ""
+not_ready = ""
+ready = ""
+
+[ui.admin.ory_ssot.summary]
+cache_keys = ""
+last_refreshed = ""
+observed_identities = ""
+status = ""
+
[ui.admin.overview]
kicker = ""
title = ""
+[ui.admin.overview.chart]
+description = ""
+title = ""
+
[ui.admin.overview.playbook]
title = ""
@@ -1563,8 +1441,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 = ""
@@ -1577,25 +1467,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 = ""
@@ -1614,6 +1497,10 @@ title = ""
list = ""
section = ""
+[ui.admin.tenants.bulk]
+selected_count = ""
+status_placeholder = ""
+
[ui.admin.tenants.create]
title = ""
@@ -1636,15 +1523,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 = ""
@@ -1656,25 +1543,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]
@@ -1683,61 +1582,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]
@@ -1758,6 +1621,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 = ""
@@ -1771,20 +1643,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 = ""
@@ -1809,37 +1684,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 = ""
@@ -1849,6 +1709,7 @@ status = ""
[ui.admin.tenants.table]
actions = ""
context = ""
+created = ""
id = ""
id_copy = ""
members = ""
@@ -1860,10 +1721,33 @@ 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.users]
csv_template = ""
+data_mgmt = ""
[ui.admin.users.bulk]
acknowledge_warning = ""
@@ -1875,14 +1759,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 = ""
@@ -1915,9 +1799,9 @@ name = ""
name_placeholder = ""
password = ""
password_placeholder = ""
-picker_description = ""
phone = ""
phone_placeholder = ""
+picker_description = ""
position = ""
position_placeholder = ""
role = ""
@@ -2183,6 +2067,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 = ""
@@ -2192,8 +2086,8 @@ fetch_error = ""
search_placeholder = ""
status_select = ""
subtitle = ""
-toggle_status = ""
title = ""
+toggle_status = ""
[ui.admin.users.list.breadcrumb]
list = ""
@@ -2699,15 +2593,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 = ""
@@ -2715,6 +2601,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 = ""
@@ -2766,14 +2660,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 = ""
@@ -2807,6 +2730,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 = ""
@@ -2824,6 +2782,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 = ""
@@ -2834,6 +2820,10 @@ dev_console = ""
[ui.userfront.audit]
+[ui.userfront.audit.filter]
+title = ""
+toggle_label = ""
+
[ui.userfront.audit.table]
action = ""
app = ""
@@ -2862,22 +2852,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 = ""
@@ -2889,6 +2869,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 = ""
@@ -2951,36 +2942,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 = ""
@@ -3041,6 +3009,7 @@ title = ""
[ui.userfront.sections]
apps = ""
audit = ""
+sessions = ""
[ui.userfront.session]
active = ""
@@ -3088,293 +3057,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 = ""
-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 = ""
-title = ""
-
-[ui.admin.tenants.profile]
-allowed_domains = ""
-worksmobile_enabled = ""
-worksmobile_excluded = ""
-worksmobile_sync = ""
-
-[msg.admin.integrity.section.user_integrity]
-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.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..1b608e0b 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,22 +142,6 @@ title_generic = "An error occurred."
title_with_code = "Error: {code}"
type = "Error type: {type}"
-[msg.userfront.error.tenant]
-account = "Account"
-account_unknown = "Unknown"
-affiliated_tenants = "All affiliated tenants"
-allowed_box_title = "Allowed tenants"
-allowed_tenants = "Allowed tenants"
-detail = "The currently signed-in account cannot access this application."
-load_failed = "We could not confirm the account details. Please try again."
-loading = "Loading the current account details."
-lookup_fallback = "Some fields could not be verified because the access context was incomplete."
-page_title = "Access to this application is restricted"
-primary_tenant = "Primary affiliated tenant"
-tenant = "Tenant"
-tenant_unknown = "Unknown"
-title = "Access restriction details"
-
[msg.userfront.error.ory]
"$normalizedCode" = "{error}"
access_denied = "The user denied the consent request."
@@ -171,6 +158,22 @@ 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"
+affiliated_tenants = "All affiliated tenants"
+allowed_box_title = "Allowed tenants"
+allowed_tenants = "Allowed tenants"
+detail = "The currently signed-in account cannot access this application."
+load_failed = "We could not confirm the account details. Please try again."
+loading = "Loading the current account details."
+lookup_fallback = "Some fields could not be verified because the access context was incomplete."
+page_title = "Access to this application is restricted"
+primary_tenant = "Primary affiliated tenant"
+tenant = "Tenant"
+tenant_unknown = "Unknown"
+title = "Access restriction details"
+
[msg.userfront.error.whitelist]
"$normalizedCode" = "{error}"
bad_request = "Please check your input."
@@ -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..76b6952a 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,22 +142,6 @@ 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.error.ory]
"$normalizedCode" = "{error}"
access_denied = "사용자가 동의를 거부했습니다."
@@ -395,6 +158,22 @@ temporarily_unavailable = "인증 서버를 일시적으로 사용할 수 없습
unauthorized_client = "해당 클라이언트는 이 요청을 수행할 수 없습니다."
unsupported_response_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.error.whitelist]
"$normalizedCode" = "{error}"
bad_request = "입력값을 확인해 주세요."
@@ -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..f6776236 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,22 +142,6 @@ 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.error.ory]
"$normalizedCode" = ""
access_denied = ""
@@ -367,6 +158,22 @@ temporarily_unavailable = ""
unauthorized_client = ""
unsupported_response_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.error.whitelist]
"$normalizedCode" = ""
bad_request = ""
@@ -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 = ""
-
diff --git a/userfront/lib/features/dashboard/domain/linked_rp_launch.dart b/userfront/lib/features/dashboard/domain/linked_rp_launch.dart
index ccc3fb1b..6af9511f 100644
--- a/userfront/lib/features/dashboard/domain/linked_rp_launch.dart
+++ b/userfront/lib/features/dashboard/domain/linked_rp_launch.dart
@@ -1,4 +1,4 @@
-import 'providers/linked_rps_provider.dart';
+import 'models.dart';
String? resolveLinkedRpLaunchUrl(LinkedRp rp) {
final normalizedStatus = rp.status.trim().toLowerCase();
diff --git a/userfront/lib/features/dashboard/domain/providers/linked_rps_provider.dart b/userfront/lib/features/dashboard/domain/providers/linked_rps_provider.dart
index c209f2ea..a4616bc6 100644
--- a/userfront/lib/features/dashboard/domain/providers/linked_rps_provider.dart
+++ b/userfront/lib/features/dashboard/domain/providers/linked_rps_provider.dart
@@ -4,57 +4,7 @@ import 'package:userfront/core/services/auth_proxy_service.dart';
import 'package:userfront/core/services/auth_token_store.dart';
import 'package:userfront/core/services/http_client.dart';
import 'package:userfront/core/services/runtime_env.dart';
-
-class LinkedRp {
- final String id;
- final String name;
- final String logo;
- final String url;
- final String initUrl;
- final bool autoLoginSupported;
- final String autoLoginUrl;
- final String status;
- final List scopes;
- final DateTime? lastAuthenticatedAt;
-
- LinkedRp({
- required this.id,
- required this.name,
- required this.logo,
- required this.url,
- required this.initUrl,
- required this.autoLoginSupported,
- required this.autoLoginUrl,
- required this.status,
- required this.scopes,
- required this.lastAuthenticatedAt,
- });
-
- factory LinkedRp.fromJson(Map json) {
- final rawLastAuth = json['lastAuthenticatedAt']?.toString() ?? '';
- DateTime? parsedLastAuth;
- if (rawLastAuth.isNotEmpty) {
- try {
- parsedLastAuth = DateTime.parse(rawLastAuth).toLocal();
- } catch (_) {
- parsedLastAuth = null;
- }
- }
-
- return LinkedRp(
- id: json['id']?.toString() ?? '',
- name: json['name']?.toString() ?? '',
- logo: json['logo']?.toString() ?? '',
- url: json['url']?.toString() ?? '',
- initUrl: json['init_url']?.toString() ?? '',
- autoLoginSupported: json['auto_login_supported'] == true,
- autoLoginUrl: json['auto_login_url']?.toString() ?? '',
- status: json['status']?.toString() ?? 'unknown',
- scopes: (json['scopes'] as List?)?.whereType().toList() ?? [],
- lastAuthenticatedAt: parsedLastAuth,
- );
- }
-}
+import '../models.dart';
class LinkedRpsNotifier extends AsyncNotifier> {
@override
diff --git a/userfront/lib/features/dashboard/presentation/dashboard_screen.dart b/userfront/lib/features/dashboard/presentation/dashboard_screen.dart
index 3d7e9d06..9af72ebb 100644
--- a/userfront/lib/features/dashboard/presentation/dashboard_screen.dart
+++ b/userfront/lib/features/dashboard/presentation/dashboard_screen.dart
@@ -21,7 +21,7 @@ import '../../../../core/ui/layout_breakpoints.dart';
import '../../../../core/ui/toast_service.dart';
import '../../profile/domain/notifiers/profile_notifier.dart';
import '../domain/dashboard_providers.dart';
-import '../domain/models.dart' hide LinkedRp;
+import '../domain/models.dart';
import 'audit_device_utils.dart';
import 'package:userfront/i18n.dart';
diff --git a/userfront/test/linked_rp_launch_test.dart b/userfront/test/linked_rp_launch_test.dart
index c6846a91..f8c7e5d4 100644
--- a/userfront/test/linked_rp_launch_test.dart
+++ b/userfront/test/linked_rp_launch_test.dart
@@ -1,6 +1,6 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:userfront/features/dashboard/domain/linked_rp_launch.dart';
-import 'package:userfront/features/dashboard/domain/providers/linked_rps_provider.dart';
+import 'package:userfront/features/dashboard/domain/models.dart';
LinkedRp _linkedRp({
required String status,