1
0
forked from baron/baron-sso

Merge branch 'dev' into temp-branch

This commit is contained in:
2026-03-27 18:42:59 +09:00
19 changed files with 1478 additions and 1134 deletions

View File

@@ -60,6 +60,12 @@ jobs:
node tools/i18n-scanner/report.js
cat reports/i18n-report.txt
- name: i18n value quality check
run: |
mkdir -p reports
node tools/i18n-scanner/value-check.js
cat reports/i18n-value-report.txt
- name: Setup Go
uses: actions/setup-go@v5
with:

View File

@@ -115,7 +115,7 @@ PLAYWRIGHT_INSTALL_ALL := npx playwright install --with-deps
PLAYWRIGHT_INSTALL_CHROMIUM := npx playwright install --with-deps chromium
endif
.PHONY: code-check code-check-lint code-check-test-jobs code-check-i18n code-check-go-lint code-check-sync-userfront-locales code-check-userfront-install code-check-userfront-lint code-check-front-lint code-check-backend-tests code-check-userfront-tests code-check-adminfront-tests code-check-devfront-tests code-check-userfront-e2e-tests
.PHONY: code-check code-check-lint code-check-test-jobs code-check-i18n code-check-i18n-values code-check-go-lint code-check-sync-userfront-locales code-check-userfront-install code-check-userfront-lint code-check-front-lint code-check-backend-tests code-check-userfront-tests code-check-adminfront-tests code-check-devfront-tests code-check-userfront-e2e-tests
CODE_CHECK_TEST_JOBS ?= 1
PLAYWRIGHT_WORKERS ?= 1
@@ -124,7 +124,7 @@ FLUTTER_TEST_CONCURRENCY ?= 1
code-check: code-check-lint code-check-test-jobs
@echo "code-check complete."
code-check-lint: code-check-i18n code-check-front-lint code-check-go-lint code-check-sync-userfront-locales code-check-userfront-install code-check-userfront-lint
code-check-lint: code-check-i18n code-check-i18n-values code-check-front-lint code-check-go-lint code-check-sync-userfront-locales code-check-userfront-install code-check-userfront-lint
code-check-test-jobs:
@echo "==> run CI-equivalent test jobs (parallel)"
@@ -142,6 +142,12 @@ code-check-i18n:
node tools/i18n-scanner/report.js
@cat reports/i18n-report.txt
code-check-i18n-values:
@echo "==> i18n value quality check"
@mkdir -p reports
node tools/i18n-scanner/value-check.js
@cat reports/i18n-value-report.txt
code-check-go-lint:
@echo "==> go lint/format check"
@if command -v golangci-lint >/dev/null 2>&1; then \

View File

@@ -305,11 +305,19 @@ function ClientGeneralPage() {
<ArrowLeft className="h-4 w-4" />
</Link>
</Button>
<h1 className="text-3xl font-black leading-tight">
{isCreate
? t("ui.dev.clients.general.title_create", "Create Client")
: t("ui.dev.clients.general.title_edit", "Client Settings")}
</h1>
<div>
<h1 className="text-3xl font-black leading-tight">
{isCreate
? t("ui.dev.clients.general.title_create", "Create Client")
: t("ui.dev.clients.general.title_edit", "Client Settings")}
</h1>
<p className="text-muted-foreground">
{t(
"ui.dev.clients.general.subtitle",
"앱 정보, 권한 스코프, 보안 설정을 관리합니다.",
)}
</p>
</div>
</div>
</div>
{!isCreate && (

View File

@@ -337,8 +337,8 @@ delete_confirm = "Are you sure you want to delete this app? This action cannot b
empty = "No consents found."
load_error = "Error loading consents: {{error}}"
loading = "Loading consents..."
showing = "Showing {{from}} to {{to}} of {{total}} users"
subtitle = "Subtitle"
showing = "Showing users {{from}}-{{to}} of {{total}}"
subtitle = "Review and manage user consent grants for this OIDC relying party."
revoke_confirm = "Are you sure you want to revoke this user's permissions? After revocation, the user must consent again on next login."
[msg.dev.clients.details]
@@ -354,14 +354,14 @@ rotate_error = "Rotate Error"
save_error = "Save Error"
secret_rotated = "Secret Rotated"
secret_unavailable = "SECRET_NOT_AVAILABLE"
subtitle = "Subtitle"
subtitle = "Manage OIDC credentials and endpoints."
[msg.dev.clients.details.redirect]
description = "Description"
description = "List the allowed URLs to redirect users to after successful authentication. Separate multiple values with commas (,)."
[msg.dev.clients.details.security]
footer = "Footer"
note = "Note"
footer = "For secret rotation, we recommend checking admin session TTL, rate limits, and notification integrations."
note = "Keep endpoints read-only, and tie secret rotation/copy actions to audit logs."
[msg.dev.clients.general]
load_error = "Error loading client: {{error}}"
@@ -376,15 +376,15 @@ add_subtitle = "Connect an external OIDC provider."
empty = "No IdP configurations found."
[msg.dev.clients.general.identity]
logo_help = "Logo Help"
subtitle = "Subtitle"
logo_help = "PNG or SVG URL shown on the consent and authentication screens."
subtitle = "Set the application name, description, and logo."
[msg.dev.clients.general.redirect]
help = "Enter the redirect URIs. You can modify them in the Federation tab after creation."
[msg.dev.clients.general.scopes]
empty = "Empty"
subtitle = "Subtitle"
empty = "No scopes registered."
subtitle = "Define the permission scopes this application can request."
[msg.dev.clients.general.security]
private_help = "Server side App: For apps that can safely store a client secret, such as Node.js or Java servers."
@@ -987,7 +987,7 @@ type = "Type"
type_boolean = "Boolean"
type_date = "Date"
type_number = "Number"
type_text = "Text"
type_text = "Text Value"
validation_placeholder = "Regex Pattern (Optional)"
[ui.admin.tenants.sub]
@@ -1218,6 +1218,7 @@ env_badge = "Env: dev"
scope_badge = "Scoped to /dev"
[ui.dev.nav]
audit_logs = "Audit Logs"
clients = "Connected Application"
logout = "Logout"
@@ -1329,6 +1330,7 @@ settings = "Settings"
[ui.dev.clients.general]
create = "Create Application"
display_new = "Add Connected Application"
subtitle = "Manage application identity, permission scopes, and security settings."
title_create = "Create Client"
title_edit = "Client Settings"
@@ -1338,7 +1340,7 @@ add_title = "Add Identity Provider"
add_btn = "Add Provider"
[ui.dev.clients.general.identity]
description = "Description"
description = "Application Description"
description_placeholder = "Description Placeholder"
logo = "App Logo URL"
logo_placeholder = "https://example.com/logo.png"
@@ -1358,7 +1360,7 @@ name_placeholder = "e.g. profile"
title = "Scopes"
[ui.dev.clients.general.scopes.table]
description = "Description"
description = "Scope Description"
mandatory = "Mandatory"
name = "Scope Name"
delete = "Delete"

View File

@@ -103,9 +103,9 @@ count = "총 {{count}}개 API 키"
[msg.admin.audit]
empty = "아직 수집된 감사 로그가 없습니다."
end = "End of audit feed"
load_error = "Error loading logs: {{error}}"
loading = "Loading audit logs..."
end = "감사 로그의 마지막입니다."
load_error = "감사 로그를 불러오지 못했습니다: {{error}}"
loading = "감사 로그를 불러오는 중..."
subtitle = "Command 요청 기반 ClickHouse 로그를 조회합니다. 사용자/테넌트는 추후 세션 연동 시 자동 채워집니다."
[msg.admin.audit.filters]
@@ -329,24 +329,24 @@ subtitle = "현재 테넌트/앱 범위의 DevFront 작업 이력을 조회합
deleted = "앱이 삭제되었습니다."
delete_confirm = "정말로 이 앱을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다."
delete_error = "삭제 실패: {{error}}"
load_error = "Error loading clients: {{error}}"
loading = "Loading apps..."
showing = "Showing {{shown}} of {{total}} apps"
load_error = "앱 정보를 불러오지 못했습니다: {{error}}"
loading = "앱 정보를 불러오는 중..."
showing = "전체 {{total}}개 중 {{shown}}개를 표시하는 중입니다."
[msg.dev.clients.consents]
empty = "No consents found."
load_error = "Error loading consents: {{error}}"
loading = "Loading consents..."
empty = "조회된 동의 내역이 없습니다."
load_error = "동의 내역을 불러오지 못했습니다: {{error}}"
loading = "동의 내역을 불러오는 중..."
revoke_confirm = "정말로 이 사용자의 권한을 철회하시겠습니까? 철회 시 사용자는 다음 접속 시 다시 동의해야 합니다."
showing = "Showing {{from}} to {{to}} of {{total}} users"
showing = "총 {{total}}명 중 {{from}}-{{to}} 표시 중"
subtitle = "OIDC Relying Party 사용자 권한을 검토·관리합니다."
[msg.dev.clients.details]
copy_client_id = "Client ID가 복사되었습니다."
copy_client_secret = "Client Secret이 복사되었습니다."
copy_endpoint = "{{label}}가 복사되었습니다."
load_error = "Error loading client: {{error}}"
loading = "Loading client..."
load_error = "클라이언트 정보를 불러오지 못했습니다: {{error}}"
loading = "클라이언트 정보를 불러오는 중..."
missing_id = "Client ID가 필요합니다."
redirect_saved = "Redirect URIs가 저장되었습니다."
rotate_confirm = "경고: Client Secret을 재발급하면 기존 시크릿은 즉시 무효화됩니다.\n연동된 애플리케이션이 중단될 수 있습니다. 계속하시겠습니까?"
@@ -364,8 +364,8 @@ footer = "비밀키 재발행 작업에는 관리자 세션 TTL 확인과 레이
note = "엔드포인트는 읽기 전용으로 유지하고, 비밀키 재발행/복사는 감사 로그와 연계하세요."
[msg.dev.clients.general]
load_error = "Error loading client: {{error}}"
loading = "Loading client..."
load_error = "클라이언트 정보를 불러오지 못했습니다: {{error}}"
loading = "클라이언트 정보를 불러오는 중..."
save_error = "저장 실패: {{error}}"
saved = "설정이 저장되었습니다."
status_changed = "상태가 {{status}}로 변경되었습니다."
@@ -987,7 +987,7 @@ type = "타입"
type_boolean = "Boolean"
type_date = "Date"
type_number = "Number"
type_text = "Text"
type_text = "텍스트"
validation_placeholder = "정규표현식 (선택 사항)"
[ui.admin.tenants.sub]
@@ -1218,6 +1218,7 @@ env_badge = "Env: dev"
scope_badge = "Scoped to /dev"
[ui.dev.nav]
audit_logs = "감사 로그"
clients = "연동 앱"
logout = "로그아웃"
@@ -1262,43 +1263,43 @@ type_all = "모든 유형"
type_label = "유형:"
[ui.dev.clients.consents]
export_csv = "Export CSV"
revoke = "Revoke"
export_csv = "CSV 내보내기"
revoke = "철회"
revoked_at = "철회일: "
scope_label = "권한:"
search_placeholder = "사용자 ID, 이름, 이메일로 검색"
status_label = "Status:"
status_revoked = "Revoked"
subject = "Subject"
title = "User Consent Grants"
status_label = "상태:"
status_revoked = "철회됨"
subject = "사용자"
title = "사용자 동의 내역"
[ui.dev.clients.consents.breadcrumb]
clients = "Clients"
current = "User Consent Grants"
home = "Home"
clients = "연동 앱"
current = "동의 및 사용자"
home = ""
[ui.dev.clients.consents.filters]
advanced = "Advanced Filters"
advanced = "고급 필터"
[ui.dev.clients.consents.stats]
active_grants = "Active Grants"
avg_scopes = "Avg. Scopes per User"
total_scopes = "Total Scopes Issued"
active_grants = "활성 동의"
avg_scopes = "사용자당 평균 스코프"
total_scopes = "총 발급 스코프"
[ui.dev.clients.consents.table]
action = "Action"
first_granted = "First Granted"
last_auth = "Last Authenticated"
scopes = "Granted Scopes"
status = "Status"
tenant = "Tenant"
user = "User"
action = "작업"
first_granted = "최초 동의 일시"
last_auth = "마지막 인증"
scopes = "허용 스코프"
status = "상태"
tenant = "테넌트"
user = "사용자"
[ui.dev.clients.details]
[ui.dev.clients.details.credentials]
client_id = "Client ID"
client_secret = "Client Secret"
client_id = "클라이언트 ID"
client_secret = "클라이언트 시크릿"
title = "앱 자격 증명"
[ui.dev.clients.details.endpoints]
@@ -1307,7 +1308,7 @@ title = "OIDC 엔드포인트"
[ui.dev.clients.details.redirect]
callback_label = "인증 콜백 URL"
label = "Redirect URIs"
label = "리디렉션 URI"
placeholder = "https://your-app.com/callback, http://localhost:3000/auth/callback"
save = "Redirect URIs 저장"
title = "리디렉션 URI 설정"
@@ -1328,6 +1329,7 @@ settings = "설정"
[ui.dev.clients.general]
create = "앱 생성"
display_new = "연동 앱 추가"
subtitle = "앱 정보, 권한 스코프, 보안 설정을 관리합니다."
title_create = "연동 앱 생성"
title_edit = "연동 앱 설정"
@@ -1337,30 +1339,30 @@ add_title = "Add Identity Provider"
add_btn = "Add Provider"
[ui.dev.clients.general.identity]
description = "Description"
description = "설명"
description_placeholder = "앱에 대한 간단한 설명을 입력하세요."
logo = "App Logo URL"
logo = "앱 로고 URL"
logo_placeholder = "https://example.com/logo.png"
logo_preview = "Logo Preview"
logo_preview = "로고 미리보기"
name = "앱 이름"
name_placeholder = "My Awesome Application"
title = "Application Identity"
name_placeholder = "예: 멋진 애플리케이션"
title = "애플리케이션 정보"
[ui.dev.clients.general.redirect]
label = "Redirect URIs"
label = "리디렉션 URI"
placeholder = "https://app.example.com/callback, http://localhost:3000/auth/callback (콤마로 구분)"
[ui.dev.clients.general.scopes]
add = "Scope 추가"
add = "스코프 추가"
description_placeholder = "권한에 대한 설명"
name_placeholder = "e.g. profile"
title = "Scopes"
title = "스코프"
[ui.dev.clients.general.scopes.table]
description = "Description"
mandatory = "Mandatory"
name = "Scope Name"
delete = "Delete"
description = "설명"
mandatory = "필수"
name = "스코프 이름"
delete = "삭제"
[ui.dev.clients.general.security]
private = "Server side App"
@@ -1399,7 +1401,7 @@ profile = "기본 프로필 정보 접근"
[ui.dev.clients.table]
actions = "액션"
application = "애플리케이션"
client_id = "Client ID"
client_id = "클라이언트 ID"
created_at = "생성일"
status = "상태"
type = "유형"
@@ -1569,7 +1571,7 @@ qr_scan = "QR 스캔"
[ui.userfront.profile]
department_empty = "소속 정보 없음"
manage = "프로필 관리"
user_fallback = "User"
user_fallback = "사용자"
[ui.userfront.profile.field]
affiliation = "구분"

View File

@@ -1218,6 +1218,7 @@ env_badge = ""
scope_badge = ""
[ui.dev.nav]
audit_logs = ""
clients = ""
logout = ""
@@ -1328,6 +1329,7 @@ settings = ""
[ui.dev.clients.general]
create = ""
display_new = ""
subtitle = ""
title_create = ""
title_edit = ""

View File

@@ -1,38 +1,23 @@
import { expect, test } from "@playwright/test";
import { seedAuth } from "./helpers/devfront-fixtures";
import {
type Consent,
installDevApiMock,
makeClient,
seedAuth,
} from "./helpers/devfront-fixtures";
test("clients page loads correctly", async ({ page }) => {
await seedAuth(page);
await page.route("**/api/v1/dev/clients**", async (route) => {
if (route.request().method() !== "GET") {
await route.fulfill({
status: 404,
contentType: "application/json",
body: JSON.stringify({ error: "Not found" }),
});
return;
}
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
items: [
{
id: "client-playwright",
name: "Playwright Client",
type: "private",
status: "active",
createdAt: new Date().toISOString(),
redirectUris: ["http://localhost:5174/callback"],
scopes: ["openid", "profile", "email"],
},
],
limit: 50,
offset: 0,
await installDevApiMock(page, {
clients: [
makeClient("client-playwright", {
name: "Playwright Client",
createdAt: new Date().toISOString(),
redirectUris: ["http://localhost:5174/callback"],
}),
});
],
consents: [] as Consent[],
auditLogsByCursor: undefined,
});
await page.goto("/clients");
@@ -46,9 +31,9 @@ test("clients page loads correctly", async ({ page }) => {
// 테이블 헤더 확인
await expect(
page.getByRole("columnheader", { name: "애플리케이션" }),
page.locator("th").filter({ hasText: "애플리케이션" }),
).toBeVisible();
await expect(
page.getByRole("columnheader", { name: "Client ID" }),
page.locator("th").filter({ hasText: /클라이언트 ID|Client ID/i }),
).toBeVisible();
});

View File

@@ -7,6 +7,8 @@ import {
seedAuth,
} from "./helpers/devfront-fixtures";
const appNamePlaceholder = /My Awesome Application|예: 멋진 애플리케이션/i;
test.describe("DevFront audit logs", () => {
test.beforeEach(async ({ page }) => {
page.on("dialog", async (dialog) => {
@@ -87,19 +89,18 @@ test.describe("DevFront audit logs", () => {
await installDevApiMock(page, state);
await page.goto("/clients/new");
await page
.getByPlaceholder("My Awesome Application")
.fill("Realtime New App");
await page.getByPlaceholder(appNamePlaceholder).fill("Realtime New App");
await page
.getByPlaceholder(/https:\/\/app\.example\.com\/callback/i)
.fill("https://realtime.example.com/callback");
await page.getByRole("button", { name: /앱 생성|Create/i }).click();
await expect.poll(() => state.auditLogs.length).toBeGreaterThanOrEqual(1);
await expect.poll(() => state.clients.at(-1)?.id).toMatch(/^client-/);
const createdClientId = state.clients.at(-1)?.id;
expect(createdClientId).toBeTruthy();
await page.goto("/clients/client-realtime/settings");
await page
.getByPlaceholder("My Awesome Application")
.fill("Realtime Updated");
await page.goto(`/clients/${createdClientId}/settings`);
await page.getByPlaceholder(appNamePlaceholder).fill("Realtime Updated");
await page.getByRole("button", { name: /^저장$|^Save$/i }).click();
await expect.poll(() => state.auditLogs.length).toBeGreaterThanOrEqual(2);

View File

@@ -7,6 +7,8 @@ import {
seedAuth,
} from "./helpers/devfront-fixtures";
const appNamePlaceholder = /My Awesome Application|예: 멋진 애플리케이션/i;
test.describe("DevFront clients lifecycle", () => {
test.beforeEach(async ({ page }) => {
page.on("dialog", async (dialog) => {
@@ -36,7 +38,7 @@ test.describe("DevFront clients lifecycle", () => {
await expect(page).toHaveURL(/\/clients\/new$/);
await page
.getByPlaceholder("My Awesome Application")
.getByPlaceholder(appNamePlaceholder)
.fill("Playwright Created App");
await page
.getByPlaceholder(/https:\/\/app\.example\.com\/callback/i)
@@ -45,7 +47,7 @@ test.describe("DevFront clients lifecycle", () => {
.getByRole("button", { name: /앱 생성|클라이언트 생성|Create/i })
.click();
await expect(page).toHaveURL(/\/clients\/client-2\/settings$/);
await expect(page).toHaveURL(/\/clients\/client-\d+\/settings$/);
await expect(
page.getByRole("heading", {
name: /연동 앱 설정|클라이언트 설정|Client Settings/i,
@@ -97,7 +99,7 @@ test.describe("DevFront clients lifecycle", () => {
await installDevApiMock(page, state);
await page.goto("/clients/client-edit/settings");
await page.getByPlaceholder("My Awesome Application").fill("After Name");
await page.getByPlaceholder(appNamePlaceholder).fill("After Name");
await page.getByRole("button", { name: /^저장$|^Save$/i }).click();
await expect.poll(() => state.clients[0]?.name).toBe("After Name");

View File

@@ -39,7 +39,7 @@ test.describe("DevFront consents", () => {
await expect(page.getByText("Alice")).toBeVisible();
await expect(page.getByText("Tenant A")).toBeVisible();
await page.getByRole("button", { name: /권한 철회|Revoke/i }).click();
await page.getByRole("button", { name: /권한 철회|철회|Revoke/i }).click();
await expect(page.getByText(/Revoked|철회/i).first()).toBeVisible();
});
});

View File

@@ -8,6 +8,8 @@ import {
} from "./helpers/devfront-fixtures";
import { captureEvidence } from "./helpers/evidence";
const appNamePlaceholder = /My Awesome Application|예: 멋진 애플리케이션/i;
test.describe("DevFront role report", () => {
test.beforeEach(async ({ page }) => {
page.on("dialog", async (dialog) => {
@@ -91,7 +93,7 @@ test.describe("DevFront role report", () => {
await page.goto("/clients/tenant-a-app-1/settings");
await page
.getByPlaceholder("My Awesome Application")
.getByPlaceholder(appNamePlaceholder)
.fill("Tenant A CRM Updated");
const updatePromise = page.waitForResponse(
@@ -132,7 +134,7 @@ test.describe("DevFront role report", () => {
await page.goto("/clients/new");
await page
.getByPlaceholder("My Awesome Application")
.getByPlaceholder(appNamePlaceholder)
.fill("Super Admin Created App");
await page
.getByPlaceholder(/https:\/\/app\.example\.com\/callback/i)
@@ -145,6 +147,17 @@ test.describe("DevFront role report", () => {
);
await page.getByRole("button", { name: /앱 생성|Create/i }).click();
await createPromise;
await expect
.poll(() =>
state.auditLogs.some((item) => {
try {
return JSON.parse(item.details)?.action === "CREATE_CLIENT";
} catch {
return false;
}
}),
)
.toBe(true);
await page.goto("/audit-logs");
await expect(page.getByText("CREATE_CLIENT")).toBeVisible({

View File

@@ -25,7 +25,11 @@ test.describe("DevFront security and isolation", () => {
await installDevApiMock(page, state);
await page.goto("/clients/tenant-b-client");
await expect(page.getByText(/Error loading client|조회/i)).toBeVisible();
await expect(
page.getByText(
/Error loading (app|client)|앱 정보를 불러오지 못했습니다|클라이언트 정보를 불러오지 못했습니다/i,
),
).toBeVisible();
});
test("RBAC: non-AppManager user should not see private apps", async ({

View File

@@ -44,24 +44,24 @@ slow_down = "Requests are too frequent. Please try again shortly."
[err.userfront]
[err.userfront.auth_proxy]
consent_accept = "Consent Accept"
consent_fetch = "Consent Fetch"
consent_reject = "Consent Reject"
linked_app_revoke = "Linked App Revoke"
login_failed = "Login Failed"
consent_accept = "Failed to accept the consent request."
consent_fetch = "Failed to load consent details."
consent_reject = "Failed to reject the consent request."
linked_app_revoke = "Failed to revoke the linked application."
login_failed = "Login failed."
oidc_accept = "OIDC Accept"
password_reset_complete = "Password Reset Complete"
password_reset_init = "Password Reset Init"
password_reset_complete = "Failed to complete the password reset."
password_reset_init = "Failed to start the password reset."
[err.userfront.profile]
load_failed = "Load Failed"
load_failed = "Failed to load the profile."
password_change_failed = "Password Change Failed"
send_code_failed = "Send Code Failed"
update_failed = "Update Failed"
verify_code_failed = "Verify Code Failed"
send_code_failed = "Failed to send the verification code."
update_failed = "Failed to update the profile."
verify_code_failed = "Verification failed."
[err.userfront.session]
missing = "Missing"
missing = "No active session was found."
[msg]
@@ -78,40 +78,40 @@ forbidden = "You do not have permission to perform this action."
[msg.admin.api_keys]
[msg.admin.api_keys.create]
error = "Error"
name_required = "Name Required"
scope_required = "Scope Required"
scopes_count = "Scopes Count"
scopes_hint = "Scopes Hint"
subtitle = "Subtitle"
error = "Failed to create the API key."
name_required = "Name is required."
scope_required = "Select at least one scope."
scopes_count = "{{count}} scopes will be assigned."
scopes_hint = "Choose the scopes to grant to this API key."
subtitle = "Create and issue an API key for machine-to-machine communication."
[msg.admin.api_keys.create.success]
copy_hint = "Copy Hint"
notice = "Notice"
notice_emphasis = "Notice Emphasis"
notice_suffix = "Notice Suffix"
copy_hint = "Copy the secret now. It will not be shown again."
notice = "The generated secret is displayed only once."
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]
delete_confirm = "Delete Confirm"
empty = "Empty"
fetch_error = "Fetch Error"
subtitle = "Subtitle"
delete_confirm = "Are you sure you want to delete this API key?"
empty = "No API keys have been issued yet."
fetch_error = "Failed to load the API key list."
subtitle = "View and manage the API keys issued for server-to-server communication."
[msg.admin.api_keys.list.registry]
count = "Count"
count = "{{count}} API keys loaded."
[msg.admin.audit]
empty = "Empty"
empty = "No audit logs have been collected yet."
end = "End of audit feed"
load_error = "Error loading logs: {{error}}"
loading = "Loading audit logs..."
subtitle = "Subtitle"
subtitle = "Review command-driven ClickHouse audit logs from the admin workspace."
[msg.admin.audit.filters]
empty = "Empty"
empty = "No filters applied."
[msg.admin.audit.registry]
count = "Count"
count = "{{count}} logs loaded."
[msg.admin.groups]
@@ -120,32 +120,32 @@ description = "Adds a new organization unit such as a department or team."
title = "Create New Organization Unit"
[msg.admin.groups.list]
create_error = "Create Failed"
create_success = "Create Success"
delete_confirm = "Delete Confirm"
delete_error = "Delete Error"
delete_success = "Delete Success"
empty = "Empty"
create_error = "Failed to create the organization unit."
create_success = "Organization unit created successfully."
delete_confirm = "Are you sure you want to delete this organization unit?"
delete_error = "Failed to delete the organization unit."
delete_success = "Organization unit deleted successfully."
empty = "No organization units have been registered yet."
import_error = "Import Error"
import_success = "Import Success"
loading = "Loading..."
subtitle = "Subtitle"
subtitle = "Manage departments and teams under the current tenant."
[msg.admin.groups.members]
add_success = "Add Success"
count = "Count"
empty = "Empty"
remove_confirm = "Remove Confirm"
remove_success = "Remove Success"
title = "Title"
add_success = "Member added successfully."
count = "{{count}} members loaded."
empty = "No members are assigned to this organization unit."
remove_confirm = "Are you sure you want to remove this member?"
remove_success = "Member removed successfully."
title = "Member Management"
[msg.admin.groups.prompt]
user_id = "User Id"
user_id = "Enter the user's UUID to add:"
[msg.admin.groups.roles]
assign_success = "Assign Success"
description = "Description"
empty = "Empty"
description = "Assign or revoke roles for members of this organization unit."
empty = "No roles have been assigned yet."
remove_confirm = "Are you sure you want to revoke this role?"
remove_success = "Role revoked successfully."
@@ -153,8 +153,8 @@ remove_success = "Role revoked successfully."
subtitle = "Tenant isolation & least privilege by default"
[msg.admin.notice]
idp_policy = "IDP Policy"
scope = "Scope"
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."
[msg.admin.org]
hover_member_info = "Hover to see member details."
@@ -163,19 +163,19 @@ import_error = "An error occurred during organization chart import."
import_success = "Organization chart imported successfully."
[msg.admin.overview]
description = "Description"
description = "Review shared metrics and policy status across all tenants in one place."
idp_fallback = "Fallback: Descope"
idp_primary = "IDP: Ory primary"
[msg.admin.overview.playbook]
description = "Description"
idp_body = "IDP Body"
description = "Operational guardrails and architecture decisions for the admin control plane."
idp_body = "All IDP calls are routed through the backend only. Hydra and Kratos admin ports are never exposed publicly."
idp_title = "Backend-only IDP access"
tenant_body = "Tenant Body"
tenant_body = "Tenant headers and audit logging are enabled by default and can later be extended with Keto policies."
tenant_title = "Tenant isolation"
[msg.admin.overview.quick_links]
description = "Description"
description = "Jump to the most frequently used administrative workflows."
[msg.admin.overview.summary]
audit_events_24h = "24h Audit Events"
@@ -184,23 +184,23 @@ policy_gate = "Policy Gate Status"
total_tenants = "Total Tenants"
[msg.admin.tenants]
approve_confirm = "Approve Confirm"
approve_success = "Approve Success"
approve_confirm = "Do you want to approve this tenant?"
approve_success = "Tenant approved successfully."
delete_confirm = "Delete Tenant \"{{name}}\"?"
delete_success = "Tenant deleted."
empty = "Empty"
fetch_error = "Fetch Error"
empty = "No tenants have been registered yet."
fetch_error = "Failed to load the tenant list."
missing_id = "No Tenant ID."
not_found = "Tenant not found."
remove_sub_confirm = 'Remove tenant "{{name}}" from sub-tenants?'
subtitle = "Subtitle"
subtitle = "Review registered tenants and manage their current status."
[msg.admin.tenants.admins]
add_success = "Add Success"
empty = "Empty"
remove_confirm = "Remove Confirm"
remove_success = "Remove Success"
subtitle = "Subtitle"
add_success = "Tenant admin added successfully."
empty = "No tenant admins are assigned yet."
remove_confirm = "Are you sure you want to remove this tenant admin?"
remove_success = "Tenant admin removed successfully."
subtitle = "Manage the administrators assigned to this tenant."
remove_last = "Cannot remove the last admin."
remove_self = "Cannot remove yourself."
@@ -214,17 +214,17 @@ remove_last = "Cannot remove the last owner."
remove_self = "Cannot remove yourself."
[msg.admin.tenants.create]
subtitle = "Subtitle"
subtitle = "Enter the minimum required information to create a tenant."
[msg.admin.tenants.create.form]
domains_help = "Users with these email domains will be automatically assigned to this tenant."
[msg.admin.tenants.create.memo]
body = "Body"
subtitle = "Subtitle"
body = "Leave operational notes or policy reminders for this tenant."
subtitle = "Capture internal policy notes for administrators."
[msg.admin.tenants.create.profile]
subtitle = "Subtitle"
subtitle = "Set the basic tenant profile information."
[msg.admin.tenants.members]
desc = "View the list of users belonging to this organization."
@@ -232,7 +232,7 @@ empty = "No members found."
limit_notice = "Showing members from the first 10 descendant organizations due to size limits."
[msg.admin.tenants.registry]
count = "Count"
count = "{{count}} tenants loaded."
[msg.admin.tenants.schema]
empty = "No custom fields defined. Click \"Add Field\" to begin."
@@ -243,8 +243,8 @@ update_success = "Schema updated successfully"
forbidden_desc = "Only administrators can access user schema settings."
[msg.admin.tenants.sub]
empty = "Empty"
subtitle = "Subtitle"
empty = "No child tenants are connected."
subtitle = "Review and manage child tenants linked under this tenant."
[msg.admin.users]
@@ -266,13 +266,13 @@ password_required = "Password Required"
success = "User created successfully."
[msg.admin.users.create.account]
subtitle = "Subtitle"
subtitle = "Fill in the account details required to create the user."
[msg.admin.users.create.form]
email_required = "Email Required"
field_invalid = "Invalid {{label}} format."
field_required = "{{label}} is required."
name_required = "Name Required"
name_required = "Name is required."
password_auto_help = "Password Auto Help"
password_manual_help = "Password Manual Help"
role_help = "Role Help"
@@ -290,26 +290,26 @@ password_generated = "A secure password has been generated."
[msg.admin.users.detail.form]
field_required = "Required."
name_required = "Name Required"
name_required = "Name is required."
[msg.admin.users.detail.security]
password_hint = "Password Hint"
[msg.admin.users.list]
delete_confirm = "Delete Confirm"
empty = "Empty"
fetch_error = "Fetch Error"
subtitle = "Subtitle"
delete_confirm = "Are you sure you want to delete the selected user?"
empty = "No users match the current filters."
fetch_error = "Failed to load the user list."
subtitle = "Search and manage users registered in the current tenant."
[msg.admin.users.list.columns]
description = "Select columns to display in the table."
no_custom = "No custom fields defined for this tenant."
[msg.admin.users.list.registry]
count = "Count"
count = "{{count}} users loaded."
[msg.common]
error = "Error"
error = "An error occurred."
loading = "Loading..."
no_description = "No Description."
parsing = "Parsing data..."
@@ -354,7 +354,7 @@ empty = "No consents found."
load_error = "Error loading consents: {{error}}"
loading = "Loading consents..."
showing = "Showing {{from}} to {{to}} of {{total}} users"
subtitle = "Subtitle"
subtitle = "Review consent grants and users who have approved this application."
revoke_confirm = "Are you sure you want to revoke this user's permissions? After revocation, the user must consent again on next login."
[msg.dev.clients.details]
@@ -369,15 +369,15 @@ rotate_confirm = "Rotate Confirm"
rotate_error = "Rotate Error"
save_error = "Save Error"
secret_rotated = "Secret Rotated"
secret_unavailable = "SECRET_NOT_AVAILABLE"
subtitle = "Subtitle"
secret_unavailable = "The client secret is not available."
subtitle = "Inspect this application's credentials, endpoints, and security settings."
[msg.dev.clients.details.redirect]
description = "Description"
description = "List the allowed URLs that users can be redirected to after authentication. Separate multiple values with commas."
[msg.dev.clients.details.security]
footer = "Footer"
note = "Note"
footer = "When rotating a secret, confirm the admin session TTL, rate limits, and notification flow."
note = "Keep endpoints read-only and link secret copy or rotation actions to audit logs."
[msg.dev.clients.general]
load_error = "Error loading client: {{error}}"
@@ -393,14 +393,14 @@ empty = "No IdP configurations found."
[msg.dev.clients.general.identity]
logo_help = "Logo Help"
subtitle = "Subtitle"
subtitle = "Manage the OIDC identity, branding, and basic metadata for this application."
[msg.dev.clients.general.redirect]
help = "Enter the redirect URIs. You can modify them in the Federation tab after creation."
[msg.dev.clients.general.scopes]
empty = "Empty"
subtitle = "Subtitle"
empty = "No custom scopes have been added yet."
subtitle = "Define the scopes this application can request."
[msg.dev.clients.general.security]
private_help = "Server side App: For apps that can safely store a client secret, such as Node.js or Java servers."
@@ -422,7 +422,7 @@ profile = "Profile"
[msg.dev.dashboard]
[msg.dev.dashboard.hero]
body = "Body"
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"
@@ -756,16 +756,16 @@ name_placeholder = "Name Placeholder"
section_name = "Section Name"
section_scopes = "Section Scopes"
submit = "Submit"
title = "Title"
title = "Create New API Key"
[ui.admin.api_keys.create.success]
copy_secret = "Copy Secret"
go_list = "Go List"
title = "Title"
title = "API Key Created"
[ui.admin.api_keys.list]
add = "Add"
title = "Title"
title = "API Key Management"
[ui.admin.api_keys.list.breadcrumb]
list = "List"
@@ -785,7 +785,7 @@ scopes = "SCOPES"
export_csv = "Export CSV"
load_more = "Load more"
target = "Target · {{target}}"
title = "Title"
title = "Audit Logs"
[ui.admin.audit.breadcrumb]
logs = "Logs"
@@ -831,7 +831,7 @@ import_csv = "Import Csv"
[ui.admin.groups.create]
description = "Adds a new organization unit such as a department or team."
title = "Title"
title = "Create Organization Unit"
[ui.admin.groups.detail]
breadcrumb_org = "Breadcrumb Org"
@@ -843,7 +843,7 @@ permissions_subtitle = "Permissions Subtitle"
permissions_title = "Permission Manage"
[ui.admin.groups.form]
desc_label = "Description"
desc_label = "Description Label"
desc_placeholder = "Desc Placeholder"
name_label = "Group Name"
name_placeholder = "Name Placeholder"
@@ -899,7 +899,7 @@ title = "Admin playbook"
add_tenant = "Tenant Add"
api_key_management = "API Key Management"
user_management = "User Management"
title = "Title"
title = "Quick Links"
view_audit_logs = "View Audit Logs"
[ui.admin.overview.summary]
@@ -933,7 +933,7 @@ remove_title = "Remove Title"
table_actions = "Table Actions"
table_email = "Email"
table_name = "Name"
title = "Title"
title = "Tenant Admins"
[ui.admin.tenants.owners]
add_button = "Add Owner"
@@ -958,7 +958,7 @@ action = "Create"
section = "Tenants"
[ui.admin.tenants.create.form]
description = "Description"
description = "Tenant Description"
domains_label = "Allowed Domains (Comma separated)"
domains_placeholder = "example.com, example.kr"
name = "Tenant name"
@@ -970,7 +970,7 @@ status = "Status"
type = "Type"
[ui.admin.tenants.create.memo]
title = "Title"
title = "Policy Memo"
[ui.admin.tenants.create.profile]
title = "Tenant Profile"
@@ -978,7 +978,7 @@ title = "Tenant Profile"
[ui.admin.tenants.detail]
breadcrumb_list = "Tenant List"
header_subtitle = "Header Subtitle"
loading = "Loading"
loading = "Loading tenant details..."
tab_federation = "Tab Federation"
tab_organization = "Organization Manage"
tab_permissions = "Permissions"
@@ -1008,7 +1008,7 @@ status = "STATUS"
allowed_domains = "Allowed Domains"
allowed_domains_help = "Users with these email domains will be automatically assigned to this tenant."
approve_button = "Approve Tenant"
description = "Description"
description = "Review and edit the tenant's basic profile information."
name = "Tenant Name"
slug = "Slug"
status = "Status"
@@ -1036,7 +1036,7 @@ type = "Type"
type_boolean = "Boolean"
type_date = "Date"
type_number = "Number"
type_text = "Text"
type_text = "Text Value"
validation_placeholder = "Regex Pattern (Optional)"
type_datetime = "DateTime"
type_float = "Float"
@@ -1090,14 +1090,14 @@ submit = "User Create"
title = "User Add"
[ui.admin.users.create.account]
title = "Title"
title = "Account Information"
[ui.admin.users.create.breadcrumb]
new = "New"
section = "Users"
[ui.admin.users.create.custom_fields]
title = "Title"
title = "Tenant Custom Fields"
[ui.admin.users.create.form]
auto_password = "Auto Password"
@@ -1122,7 +1122,7 @@ tenant = "Tenant"
tenant_global = "Tenant Global"
[ui.admin.users.create.password_generated]
title = "Title"
title = "Initial Password Generated"
[ui.admin.users.detail]
back = "Back"
@@ -1163,10 +1163,10 @@ title = "Affiliation & Organization Info"
[ui.admin.users.list]
add = "User Add"
bulk_import = "Bulk Import"
empty = "Empty"
fetch_error = "Fetch Error"
empty = "No users found."
fetch_error = "Failed to load the user list."
search_placeholder = "Search Placeholder"
subtitle = "Subtitle"
subtitle = "Browse and manage registered users."
title = "User Manage"
[ui.admin.users.list.breadcrumb]
@@ -1180,7 +1180,7 @@ title = "Column Settings"
tenant = "Tenant Filter"
[ui.admin.users.list.registry]
count = "Count"
count = "{{count}} users loaded."
title = "User Registry"
[ui.admin.users.list.table]
@@ -1322,7 +1322,7 @@ role = "Roles & Permissions"
[ui.dev.profile.basic]
title = "User Info"
id = "User ID"
id = "Account ID"
name = "Name"
email = "Email"
phone = "Phone Number"
@@ -1420,6 +1420,7 @@ settings = "Settings"
[ui.dev.clients.general]
create = "Create Application"
display_new = "Add Connected Application"
subtitle = "Manage application settings and security configuration."
title_create = "Create Client"
title_edit = "Client Settings"
@@ -1429,7 +1430,7 @@ add_title = "Add Identity Provider"
add_btn = "Add Provider"
[ui.dev.clients.general.identity]
description = "Description"
description = "Application Description"
description_placeholder = "Description Placeholder"
logo = "App Logo URL"
logo_placeholder = "https://example.com/logo.png"
@@ -1449,7 +1450,7 @@ name_placeholder = "e.g. profile"
title = "Scopes"
[ui.dev.clients.general.scopes.table]
description = "Description"
description = "Scope Description"
mandatory = "Mandatory"
name = "Scope Name"
delete = "Delete"
@@ -1513,7 +1514,7 @@ subtitle = "Ship the RP controls"
title = "Next actions"
[ui.dev.dashboard.ops]
subtitle = "Subtitle"
subtitle = "Operational indicators for the current developer workspace."
title = "Ops board"
[ui.dev.dashboard.ops.card]

View File

@@ -163,9 +163,9 @@ forbidden = "이 작업을 수행할 권한이 없습니다."
[msg.admin.audit]
empty = "아직 수집된 감사 로그가 없습니다."
end = "End of audit feed"
load_error = "Error loading logs: {{error}}"
loading = "Loading audit logs..."
end = "감사 로그의 마지막입니다."
load_error = "감사 로그를 불러오지 못했습니다: {{error}}"
loading = "감사 로그를 불러오는 중..."
subtitle = "Command 요청 기반 ClickHouse 로그를 조회합니다. 사용자/테넌트는 추후 세션 연동 시 자동 채워집니다."
[msg.admin.header]
@@ -221,9 +221,9 @@ subtitle = "현재 테넌트/앱 범위의 DevFront 작업 이력을 조회합
deleted = "앱이 삭제되었습니다."
delete_confirm = "정말로 이 앱을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다."
delete_error = "삭제 실패: {{error}}"
load_error = "Error loading clients: {{error}}"
loading = "Loading apps..."
showing = "Showing {{shown}} of {{total}} apps"
load_error = "앱 정보를 불러오지 못했습니다: {{error}}"
loading = "앱 정보를 불러오는 중..."
showing = "전체 {{total}}개 중 {{shown}}개를 표시하는 중입니다."
[msg.dev.sidebar]
notice = "개발자 전용 콘솔입니다."
@@ -497,7 +497,7 @@ qr_scan = "QR 스캔"
[ui.userfront.profile]
department_empty = "소속 정보 없음"
manage = "프로필 관리"
user_fallback = "User"
user_fallback = "사용자"
[ui.userfront.qr]
rescan = "다시 스캔"
@@ -666,19 +666,19 @@ fetch_error = "사용자 목록 조회에 실패했습니다."
subtitle = "시스템 사용자를 조회하고 관리합니다. (Local DB)"
[msg.dev.clients.consents]
empty = "No consents found."
load_error = "Error loading consents: {{error}}"
loading = "Loading consents..."
empty = "등록된 동의 내역이 없습니다."
load_error = "동의 내역을 불러오지 못했습니다: {{error}}"
loading = "동의 내역을 불러오는 중..."
revoke_confirm = "정말로 이 사용자의 권한을 철회하시겠습니까? 철회 시 사용자는 다음 접속 시 다시 동의해야 합니다."
showing = "Showing {{from}} to {{to}} of {{total}} users"
showing = "전체 {{total}}명 중 {{from}}번째부터 {{to}}번째 사용자를 표시합니다."
subtitle = "OIDC Relying Party 사용자 권한을 검토·관리합니다."
[msg.dev.clients.details]
copy_client_id = "Client ID가 복사되었습니다."
copy_client_secret = "Client Secret이 복사되었습니다."
copy_endpoint = "{{label}}가 복사되었습니다."
load_error = "Error loading client: {{error}}"
loading = "Loading client..."
load_error = "앱 상세 정보를 불러오지 못했습니다: {{error}}"
loading = "앱 상세 정보를 불러오는 중..."
missing_id = "Client ID가 필요합니다."
redirect_saved = "Redirect URIs가 저장되었습니다."
rotate_confirm = "경고: Client Secret을 재발급하면 기존 시크릿은 즉시 무효화됩니다.\n연동된 애플리케이션이 중단될 수 있습니다. 계속하시겠습니까?"
@@ -689,8 +689,8 @@ secret_unavailable = "SECRET_NOT_AVAILABLE"
subtitle = "OIDC 자격 증명과 엔드포인트를 관리합니다."
[msg.dev.clients.general]
load_error = "Error loading client: {{error}}"
loading = "Loading client..."
load_error = "앱 설정을 불러오지 못했습니다: {{error}}"
loading = "앱 설정을 불러오는 중..."
save_error = "저장 실패: {{error}}"
saved = "설정이 저장되었습니다."
status_changed = "상태가 {{status}}로 변경되었습니다."
@@ -1192,6 +1192,7 @@ title = "User Consent Grants"
[ui.dev.clients.general]
create = "앱 생성"
display_new = "연동 앱 추가"
subtitle = "앱 설정과 보안 구성을 관리합니다."
title_create = "연동 앱 생성"
title_edit = "연동 앱 설정"
@@ -1232,7 +1233,7 @@ profile = "기본 프로필 정보 접근"
[ui.dev.clients.table]
actions = "액션"
application = "애플리케이션"
client_id = "Client ID"
client_id = "클라이언트 ID"
created_at = "생성일"
status = "상태"
type = "유형"
@@ -1536,7 +1537,7 @@ type = "타입"
type_boolean = "Boolean"
type_date = "Date"
type_number = "Number"
type_text = "Text"
type_text = "텍스트"
validation_placeholder = "정규표현식 (선택 사항)"
type_datetime = "일시 (DateTime)"
type_float = "실수 (Float)"
@@ -1642,25 +1643,25 @@ current = "User Consent Grants"
home = "Home"
[ui.dev.clients.consents.filters]
advanced = "Advanced Filters"
advanced = "고급 필터"
[ui.dev.clients.consents.stats]
active_grants = "Active Grants"
avg_scopes = "Avg. Scopes per User"
total_scopes = "Total Scopes Issued"
active_grants = "활성 동의 건수"
avg_scopes = "사용자당 평균 스코프"
total_scopes = "총 발급 스코프 수"
[ui.dev.clients.consents.table]
action = "Action"
first_granted = "First Granted"
last_auth = "Last Authenticated"
scopes = "Granted Scopes"
status = "Status"
tenant = "Tenant"
user = "User"
action = "동작"
first_granted = "최초 동의 시각"
last_auth = "마지막 인증 시각"
scopes = "승인된 스코프"
status = "상태"
tenant = "테넌트"
user = "사용자"
[ui.dev.clients.details.credentials]
client_id = "Client ID"
client_secret = "Client Secret"
client_id = "클라이언트 ID"
client_secret = "클라이언트 시크릿"
title = "앱 자격 증명"
[ui.dev.clients.details.endpoints]
@@ -1669,7 +1670,7 @@ title = "OIDC 엔드포인트"
[ui.dev.clients.details.redirect]
callback_label = "인증 콜백 URL"
label = "Redirect URIs"
label = "리디렉션 URI"
placeholder = "https://your-app.com/callback, http://localhost:3000/auth/callback"
save = "Redirect URIs 저장"
title = "리디렉션 URI 설정"
@@ -1688,24 +1689,24 @@ consents = "동의 및 사용자"
settings = "설정"
[ui.dev.clients.general.identity]
description = "Description"
description = "설명"
description_placeholder = "앱에 대한 간단한 설명을 입력하세요."
logo = "App Logo URL"
logo = "앱 로고 URL"
logo_placeholder = "https://example.com/logo.png"
logo_preview = "Logo Preview"
logo_preview = "로고 미리보기"
name = "앱 이름"
name_placeholder = "My Awesome Application"
title = "Application Identity"
name_placeholder = "예: 멋진 애플리케이션"
title = "애플리케이션 정보"
[ui.dev.clients.general.redirect]
label = "Redirect URIs"
label = "리디렉션 URI"
placeholder = "https://app.example.com/callback, http://localhost:3000/auth/callback (콤마로 구분)"
[ui.dev.clients.general.scopes]
add = "Scope 추가"
description_placeholder = "권한에 대한 설명"
name_placeholder = "e.g. profile"
title = "Scopes"
title = "스코프"
[ui.dev.clients.general.security]
private = "Server side App"
@@ -1726,53 +1727,7 @@ label = "이메일 주소"
title = "이메일 인증"
[ui.dev.clients.general.scopes.table]
description = "Description"
mandatory = "Mandatory"
name = "Scope Name"
delete = "Delete"
[msg.admin.users.create.form]
login_id_help = ""
[msg.admin.users.detail]
delete_confirm = ""
delete_error = ""
delete_success = ""
reset_password_confirm = ""
[msg.admin.users.detail.form]
invalid_format = ""
[ui.admin.tenants.schema.field]
is_login_id = ""
[ui.admin.users.create.form]
login_id = ""
login_id_placeholder = ""
[ui.admin.users.detail]
contact_title = ""
created_at = ""
delete = ""
go_list = ""
password_title = ""
reset_password = ""
reset_password_label = ""
save = ""
status_title = ""
[ui.admin.users.detail.form]
job_title = ""
job_title_placeholder = ""
login_id = ""
login_id_placeholder = ""
position = ""
position_placeholder = ""
role_super_admin = ""
role_tenant_admin = ""
role_user = ""
status_active = ""
status_inactive = ""
[ui.admin.users.list.table]
login_id = ""
description = "설명"
mandatory = "필수"
name = "스코프 이름"
delete = "삭제"

View File

@@ -1192,6 +1192,7 @@ title = ""
[ui.dev.clients.general]
create = ""
display_new = ""
subtitle = ""
title_create = ""
title_edit = ""

View File

@@ -0,0 +1,365 @@
#!/usr/bin/env node
'use strict';
const fs = require('fs');
const path = require('path');
const ROOT_DIR = process.cwd();
const LOCALES_DIR = path.join(ROOT_DIR, 'locales');
const TARGET_FILES = ['ko.toml', 'en.toml'];
const PLACEHOLDER_VALUES = new Set([
'title',
'subtitle',
'description',
'body',
'note',
'footer',
'empty',
'error',
'count',
'loading',
'name required',
'scope required',
'scopes count',
'scopes hint',
'delete confirm',
'fetch error',
'create success',
'create failed',
'create error',
'delete success',
'delete error',
'remove confirm',
'remove success',
'add success',
'copy hint',
'notice',
'notice emphasis',
'notice suffix',
'idp policy',
'idp body',
'tenant body',
'approve confirm',
'approve success',
'user id',
'missing',
'consent accept',
'consent fetch',
'consent reject',
'linked app revoke',
'login failed',
'password reset complete',
'password reset init',
'load failed',
'send code failed',
'update failed',
'verify code failed',
'text',
]);
const KO_UNTRANSLATED_VALUES = new Set([
'End of audit feed',
'Error loading logs: {{error}}',
'Loading audit logs...',
'Error loading clients: {{error}}',
'Loading apps...',
'Showing {{shown}} of {{total}} apps',
'No consents found.',
'Error loading consents: {{error}}',
'Loading consents...',
'Showing {{from}} to {{to}} of {{total}} users',
'Error loading client: {{error}}',
'Loading client...',
'Advanced Filters',
'Active Grants',
'Avg. Scopes per User',
'Total Scopes Issued',
'Action',
'First Granted',
'Last Authenticated',
'Granted Scopes',
'Status',
'Tenant',
'User',
'Client ID',
'Client Secret',
'Redirect URIs',
'Application Identity',
'App Logo URL',
'Logo Preview',
'My Awesome Application',
'Scopes',
'Mandatory',
'Scope Name',
'Delete',
'Description',
]);
const SKIP_DIRS = new Set([
'.git',
'node_modules',
'dist',
'build',
'.dart_tool',
'.idea',
'.vscode',
'coverage',
'.next',
'.cache',
'tmp',
'logs',
]);
const CODE_EXTENSIONS = new Set(['.ts', '.tsx', '.dart']);
const CODE_PATTERNS = [
/\b(?:i18n\.)?t\s*\(\s*['"]([^'"]+)['"]/g,
/\btr\s*\(\s*['"]([^'"]+)['"]/g,
/['"]([^'"]+)['"]\s*\.tr\s*\(/g,
];
function readFileRequired(filePath) {
if (!fs.existsSync(filePath)) {
return { ok: false, error: `파일이 없습니다: ${filePath}` };
}
return { ok: true, value: fs.readFileSync(filePath, 'utf8') };
}
function walkDir(dirPath, files) {
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory()) {
if (SKIP_DIRS.has(entry.name)) {
continue;
}
walkDir(path.join(dirPath, entry.name), files);
continue;
}
if (!entry.isFile()) {
continue;
}
const ext = path.extname(entry.name).toLowerCase();
if (!CODE_EXTENSIONS.has(ext)) {
continue;
}
if (entry.name.includes('.test.') || entry.name.includes('.spec.')) {
continue;
}
files.push(path.join(dirPath, entry.name));
}
}
function collectCodeKeys() {
const files = [];
walkDir(ROOT_DIR, files);
const keys = new Set();
for (const filePath of files) {
const content = fs.readFileSync(filePath, 'utf8');
for (const pattern of CODE_PATTERNS) {
let match;
while ((match = pattern.exec(content)) !== null) {
if (match[1]) {
keys.add(match[1]);
}
}
}
}
return keys;
}
function stripInlineComment(value) {
let inSingle = false;
let inDouble = false;
for (let i = 0; i < value.length; i += 1) {
const ch = value[i];
const prev = i > 0 ? value[i - 1] : '';
if (ch === "'" && !inDouble && prev !== '\\') {
inSingle = !inSingle;
continue;
}
if (ch === '"' && !inSingle && prev !== '\\') {
inDouble = !inDouble;
continue;
}
if (ch === '#' && !inSingle && !inDouble) {
return value.slice(0, i).trimEnd();
}
}
return value.trimEnd();
}
function parseTomlStringEntries(filePath) {
const result = readFileRequired(filePath);
if (!result.ok) {
return { ok: false, error: result.error, entries: [] };
}
const entries = new Map();
const lines = result.value.split(/\r?\n/);
let currentSection = [];
for (let index = 0; index < lines.length; index += 1) {
const rawLine = lines[index];
const line = rawLine.trim();
if (!line || line.startsWith('#')) {
continue;
}
if (line.startsWith('[[') && line.endsWith(']]')) {
const sectionName = line.slice(2, -2).trim();
currentSection = sectionName
? sectionName.split('.').map((part) => part.trim()).filter(Boolean)
: [];
continue;
}
if (line.startsWith('[') && line.endsWith(']')) {
const sectionName = line.slice(1, -1).trim();
currentSection = sectionName
? sectionName.split('.').map((part) => part.trim()).filter(Boolean)
: [];
continue;
}
const eqIndex = rawLine.indexOf('=');
if (eqIndex === -1) {
continue;
}
let key = rawLine.slice(0, eqIndex).trim();
if (!key) {
continue;
}
if (key.startsWith('"') && key.endsWith('"')) {
key = key.slice(1, -1);
}
const rawValue = stripInlineComment(rawLine.slice(eqIndex + 1).trim());
if (
(rawValue.startsWith('"') && rawValue.endsWith('"')) ||
(rawValue.startsWith("'") && rawValue.endsWith("'"))
) {
entries.set([...currentSection, key].join('.'), {
line: index + 1,
value: rawValue.slice(1, -1),
});
}
}
return { ok: true, entries };
}
function normalizeValue(value) {
return value.replace(/\{\{[^}]+\}\}/g, ' ').replace(/\s+/g, ' ').trim().toLowerCase();
}
function formatFinding(fileName, line, key, value, reason) {
return `${fileName}:${line} ${key} = "${value}" (${reason})`;
}
function main() {
const errors = [];
const findings = [];
const codeKeys = collectCodeKeys();
const localeMaps = new Map();
for (const fileName of TARGET_FILES) {
const parsed = parseTomlStringEntries(path.join(LOCALES_DIR, fileName));
if (!parsed.ok) {
errors.push(parsed.error);
continue;
}
localeMaps.set(fileName, parsed.entries);
}
if (errors.length > 0) {
console.error('i18n 값 검증 실패');
errors.forEach((error) => console.error(`- ${error}`));
process.exit(1);
}
const koEntries = localeMaps.get('ko.toml');
const enEntries = localeMaps.get('en.toml');
for (const key of codeKeys) {
for (const fileName of TARGET_FILES) {
const entry = localeMaps.get(fileName).get(key);
if (!entry) {
continue;
}
if (PLACEHOLDER_VALUES.has(normalizeValue(entry.value))) {
findings.push(
formatFinding(fileName, entry.line, key, entry.value, 'placeholder value'),
);
}
}
const koEntry = koEntries.get(key);
const enEntry = enEntries.get(key);
if (!koEntry || !enEntry) {
continue;
}
if (koEntry.value === enEntry.value && KO_UNTRANSLATED_VALUES.has(koEntry.value)) {
findings.push(
formatFinding(
'ko.toml',
koEntry.line,
key,
koEntry.value,
'untranslated english value in Korean locale',
),
);
}
}
const reportsDir = path.join(ROOT_DIR, 'reports');
if (!fs.existsSync(reportsDir)) {
fs.mkdirSync(reportsDir, { recursive: true });
}
const generatedAt = new Date().toISOString();
const summaryPath = path.join(reportsDir, 'i18n-value-report.txt');
const jsonPath = path.join(reportsDir, 'i18n-value-report.json');
const summaryLines = [
`generated_at: ${generatedAt}`,
`errors: ${errors.length}`,
`findings: ${findings.length}`,
];
if (findings.length > 0) {
summaryLines.push('details:');
findings.forEach((finding) => summaryLines.push(`- ${finding}`));
}
fs.writeFileSync(summaryPath, `${summaryLines.join('\n')}\n`);
fs.writeFileSync(
jsonPath,
JSON.stringify({ generated_at: generatedAt, errors, findings }, null, 2),
);
if (findings.length > 0) {
console.error('i18n 값 품질 검증 실패');
findings.forEach((finding) => console.error(`- ${finding}`));
process.exit(1);
}
console.log('✅ i18n 값 품질 검증 완료');
}
main();

View File

@@ -21,24 +21,24 @@ user_group = "User Group"
[err.userfront]
[err.userfront.auth_proxy]
consent_accept = "Consent Accept"
consent_fetch = "Consent Fetch"
consent_reject = "Consent Reject"
linked_app_revoke = "Linked App Revoke"
login_failed = "Login Failed"
consent_accept = "Failed to accept the consent request."
consent_fetch = "Failed to load consent details."
consent_reject = "Failed to reject the consent request."
linked_app_revoke = "Failed to revoke the linked application."
login_failed = "Login failed."
oidc_accept = "OIDC Accept"
password_reset_complete = "Password Reset Complete"
password_reset_init = "Password Reset Init"
password_reset_complete = "Failed to complete the password reset."
password_reset_init = "Failed to start the password reset."
[err.userfront.profile]
load_failed = "Load Failed"
load_failed = "Failed to load the profile."
password_change_failed = "Password Change Failed"
send_code_failed = "Send Code Failed"
update_failed = "Update Failed"
verify_code_failed = "Verify Code Failed"
send_code_failed = "Failed to send the verification code."
update_failed = "Failed to update the profile."
verify_code_failed = "Verification failed."
[err.userfront.session]
missing = "Missing"
missing = "No active session was found."
[msg.userfront]
greeting = "Hello, {name}."
@@ -631,3 +631,4 @@ verify = "Verification"
[ui.userfront.signup.success]
action = "Go to sign-in"

View File

@@ -1,5 +1,3 @@
[domain]
[domain.affiliation]
affiliate = "가족사 임직원"
general = "일반 사용자"
@@ -18,325 +16,9 @@ company_group = "COMPANY_GROUP (그룹사/지주사)"
personal = "PERSONAL (개인 워크스페이스)"
user_group = "USER_GROUP (내부 부서/팀)"
[err.userfront]
[err.userfront.auth_proxy]
consent_accept = "동의 처리에 실패했습니다."
consent_fetch = "동의 정보를 가져오지 못했습니다."
consent_reject = "동의 거부에 실패했습니다."
linked_app_revoke = "연동 해지에 실패했습니다."
login_failed = "로그인에 실패했습니다."
oidc_accept = "OIDC 로그인 승인에 실패했습니다."
password_reset_complete = "비밀번호 재설정에 실패했습니다."
password_reset_init = "비밀번호 재설정을 시작하지 못했습니다."
[err.userfront.profile]
load_failed = "프로필을 불러오지 못했습니다: {error}"
password_change_failed = "비밀번호 변경에 실패했습니다: {error}"
send_code_failed = "인증번호 전송 실패: {error}"
update_failed = "프로필 업데이트에 실패했습니다: {error}"
verify_code_failed = "인증 실패: {error}"
[err.userfront.session]
missing = "활성 세션이 없습니다."
[msg.userfront]
greeting = "안녕하세요, {name}님"
[msg.userfront.audit]
date = "접속일자: {value}"
device = "접속환경: {value}"
end = "더 이상 항목이 없습니다."
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_missing = "이동할 페이지 주소(Client URI)가 설정되지 않았습니다."
link_open_error = "해당 링크를 열 수 없습니다."
render_error = "대시보드 렌더링 오류: {error}"
session_id_copied = "세션 ID가 복사되었습니다."
[msg.userfront.dashboard.activities]
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.timeline]
load_error = "접속이력을 불러오지 못했습니다."
[msg.userfront.error]
detail_contact = "관리자에게 문의해 주세요."
detail_generic = "오류가 발생했습니다."
detail_request = "요청을 처리하는 중 문제가 발생했습니다."
id = "오류 ID: {id}"
title = "인증 과정에서 오류가 발생했습니다"
title_generic = "오류가 발생했습니다"
title_with_code = "오류: {code}"
type = "오류 종류: {type}"
[msg.userfront.error.whitelist]
"$normalizedCode" = "{error}"
bad_request = "입력값을 확인해 주세요."
invalid_session = "세션이 만료되었습니다. 다시 로그인해 주세요."
not_found = "요청한 페이지를 찾을 수 없습니다."
password_or_email_mismatch = "이메일 혹은 비밀번호가 일치하지 않습니다."
rate_limited = "요청이 많습니다. 잠시 후 다시 시도해 주세요."
recovery_expired = "재설정 링크가 만료되었습니다. 다시 요청해 주세요."
recovery_invalid = "재설정 링크가 유효하지 않습니다."
settings_disabled = "현재 계정 설정 화면은 준비 중입니다."
verification_required = "추가 인증이 필요합니다. 안내에 따라 진행해 주세요."
[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.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.link]
helper = "입력하신 정보로 로그인 링크를 전송합니다."
missing_login_id = "이메일 또는 휴대폰 번호를 입력해 주세요."
missing_phone = "휴대폰 번호를 입력해 주세요."
resend_wait = "재발송은 {time} 후 가능합니다."
short_code_help = "링크로 받은 값의 뒤 문자 2개와 숫자 6자리를 입력하셔도 로그인 할 수 있습니다."
[msg.userfront.login.password]
failed = "로그인 실패: {error}"
missing_credentials = "이메일(또는 전화번호)와 비밀번호를 모두 입력해주세요."
[msg.userfront.login.qr]
load_failed = "QR 코드를 불러오지 못했습니다."
scan_hint = "모바일 앱으로 스캔하세요"
[msg.userfront.login.short_code]
invalid = "문자 2개와 숫자 6자리를 입력해 주세요."
[msg.userfront.login.unregistered]
body = "가입되지 않은 정보입니다.\n회원가입 후 이용해 주세요."
[msg.userfront.login.verification]
approved = "승인되었습니다. 로그인은 요청하신 창에서 완료됩니다."
approved_local = "승인 되었습니다. 이 기기는 로그인되어 있는 상태입니다. 원격 창도 로그인이 될 예정입니다"
success = "로그인 승인에 성공했습니다."
[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.consent.cancel]
confirm = "권한 동의를 취소하면 해당 서비스를 이용할 수 없습니다. 취소하시겠습니까?"
error = "취소 처리 중 오류가 발생했습니다: {error}"
[msg.userfront.consent.scope]
email = "이메일 주소 (계정 식별 및 알림 용도)"
offline_access = "오프라인 접근 (로그인 유지)"
openid = "OpenID 인증 정보 (로그인 상태 확인)"
phone = "휴대폰 번호 (본인 인증 및 알림)"
profile = "기본 프로필 정보 (이름, 사용자 식별자)"
[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.profile.password]
change_failed = "비밀번호 변경 실패: {error}"
changed = "비밀번호가 변경되었습니다."
current_required = "현재 비밀번호를 입력해 주세요."
mismatch = "새 비밀번호가 일치하지 않습니다."
new_required = "새 비밀번호를 입력해 주세요."
subtitle = "현재 비밀번호 확인 후 새 비밀번호로 변경합니다."
[msg.userfront.profile.phone]
code_sent = "인증번호가 전송되었습니다."
send_failed = "전송 실패: {error}"
verified = "인증되었습니다."
verify_failed = "인증 실패: {error}"
verify_notice = "휴대폰 번호를 변경하려면 SMS 인증이 필요합니다."
[msg.userfront.profile.section]
basic = "계정 기본 정보를 관리합니다."
organization = "소속 및 구분 정보입니다."
security = "비밀번호를 안전하게 관리합니다."
[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.reset.error]
empty_password = "비밀번호를 입력해주세요."
generic = "비밀번호 변경에 실패했습니다: {error}"
lowercase = "최소 1개 이상의 소문자를 포함해야 합니다."
min_length = "비밀번호는 최소 {count}자 이상이어야 합니다."
min_types = "비밀번호는 영문 대/소문자/숫자/특수문자 중 {count}가지 이상 포함해야 합니다."
mismatch = "비밀번호가 일치하지 않습니다."
number = "최소 1개 이상의 숫자를 포함해야 합니다."
symbol = "최소 1개 이상의 특수문자를 포함해야 합니다."
uppercase = "최소 1개 이상의 대문자를 포함해야 합니다."
[msg.userfront.reset.policy]
lowercase = "소문자 1개 이상"
min_length = "최소 {count}자 이상"
min_types = "영문 대/소문자/숫자/특수문자 중 {count}가지 이상"
number = "숫자 1개 이상"
symbol = "특수문자 1개 이상"
uppercase = "대문자 1개 이상"
[msg.userfront.sections]
apps_subtitle = "현재 연결된 앱과 최근 인증 상태입니다."
audit_subtitle = "Baron 로그인 기준의 최근 접근 기록입니다."
[msg.userfront.settings]
disabled = "현재 계정 설정 화면은 준비 중입니다."
[msg.userfront.signup]
failed = "가입 실패: {error}"
privacy_full = "개인정보 수집 및 이용 동의 전문..."
tos_full = "서비스 이용약관 전문..."
[msg.userfront.signup.agreement]
all_hint = "필수 약관 2개를 모두 확인하고 동의하면 다음 단계로 진행할 수 있습니다."
description = "계속 진행하려면 서비스 이용 조건과 개인정보 수집·이용 항목을 확인한 뒤 동의해주세요."
privacy_summary = "개인정보 수집 항목, 이용 목적, 보관 기준을 안내합니다."
progress = "필수 약관 {total}개 중 {count}개 동의 완료"
tos_summary = "서비스 이용 조건과 책임 범위를 확인할 수 있습니다."
title = "서비스 이용을 위해\n약관에 동의해주세요"
[msg.userfront.signup.auth]
affiliate_notice = "가족사 회원의 경우 반드시 회사 공식 이메일을 입력해주세요."
title = "본인 확인을 위해\n인증을 진행해주세요"
[msg.userfront.signup.email]
code_mismatch = "인증코드가 일치하지 않습니다."
duplicate = "이미 가입된 이메일입니다."
invalid = "유효한 이메일 형식이 아닙니다."
send_failed = "발송 실패: {error}"
verified = "✅ 이메일 인증 완료"
verify_failed = "인증 실패: {error}"
[msg.userfront.signup.password]
length_required = "비밀번호는 최소 12자 이상이어야 합니다."
lowercase_required = "소문자가 최소 1개 이상 포함되어야 합니다."
mismatch = "비밀번호가 일치하지 않습니다."
number_required = "숫자가 최소 1개 이상 포함되어야 합니다."
symbol_required = "특수문자가 최소 1개 이상 포함되어야 합니다."
title = "마지막으로\n비밀번호를 설정해주세요"
uppercase_required = "대문자가 최소 1개 이상 포함되어야 합니다."
[msg.userfront.signup.password.rule]
lowercase = "소문자"
min_length = "{count}자 이상"
min_types = "문자 유형 {count}가지 이상"
number = "숫자"
symbol = "특수문자"
uppercase = "대문자"
[msg.userfront.signup.phone]
code_mismatch = "인증코드가 일치하지 않습니다."
send_failed = "발송 실패: {error}"
verified = "✅ 휴대폰 인증 완료"
verify_failed = "인증 실패: {error}"
[msg.userfront.signup.policy]
loading = "비밀번호 정책을 불러오는 중입니다..."
lowercase = "소문자"
min_length = "최소 {count}자 이상"
min_types = "영문 대/소문자/숫자/특수문자 중 {count}가지 이상"
number = "숫자"
summary = "보안 정책: {rules}"
symbol = "특수문자"
uppercase = "대문자"
[msg.userfront.signup.profile]
affiliate_hint = "가족사 이메일 사용 시 자동으로 선택됩니다."
title = "회원님의\n소속 정보를 알려주세요"
[msg.userfront.signup.success]
body = "성공적으로 가입되었습니다."
title = "회원가입 완료"
[ui.common]
add = "추가"
all = "전체"
@@ -391,6 +73,137 @@ theme_toggle = "테마 전환"
unknown = "Unknown"
generate = "생성"
[ui.userfront]
app_title = "Baron SW 포탈"
[err.userfront.auth_proxy]
consent_accept = "동의 처리에 실패했습니다."
consent_fetch = "동의 정보를 가져오지 못했습니다."
consent_reject = "동의 거부에 실패했습니다."
linked_app_revoke = "연동 해지에 실패했습니다."
login_failed = "로그인에 실패했습니다."
oidc_accept = "OIDC 로그인 승인에 실패했습니다."
password_reset_complete = "비밀번호 재설정에 실패했습니다."
password_reset_init = "비밀번호 재설정을 시작하지 못했습니다."
[err.userfront.profile]
load_failed = "프로필을 불러오지 못했습니다: {error}"
password_change_failed = "비밀번호 변경에 실패했습니다: {error}"
send_code_failed = "인증번호 전송 실패: {error}"
update_failed = "프로필 업데이트에 실패했습니다: {error}"
verify_code_failed = "인증 실패: {error}"
[err.userfront.session]
missing = "활성 세션이 없습니다."
[msg.userfront.audit]
date = "접속일자: {value}"
device = "접속환경: {value}"
end = "더 이상 항목이 없습니다."
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_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.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 로그인 기준의 최근 접근 기록입니다."
[msg.userfront.settings]
disabled = "현재 계정 설정 화면은 준비 중입니다."
[msg.userfront.signup]
failed = "가입 실패: {error}"
privacy_full = "개인정보 수집 및 이용 동의 전문..."
tos_full = "서비스 이용약관 전문..."
[ui.common.badge]
admin_only = "Admin only"
command_only = "Command only"
@@ -405,27 +218,11 @@ ok = "정상"
pending = "준비 중"
success = "성공"
[ui.userfront]
app_title = "Baron SW 포탈"
[ui.userfront.app_label]
admin_console = "Admin Console"
baron = "Baron 로그인"
dev_console = "Dev Console"
[ui.userfront.audit]
[ui.userfront.audit.table]
app = "애플리케이션"
auth_method = "인증수단"
date = "접속일자"
device = "접속환경"
ip = "IP"
pending = "(준비중)"
result = "인증결과"
session_id = "Session ID"
status = "현황"
[ui.userfront.auth_method]
ory = "Ory 세션"
session = "세션"
@@ -434,23 +231,6 @@ session = "세션"
last_auth_label = "최근 인증"
status_history = "상태 이력"
[ui.userfront.dashboard.activity]
linked = "연동됨"
[ui.userfront.dashboard.approved_session]
default = "승인한 세션 ID"
userfront = "승인한 Userfront 세션 ID"
[ui.userfront.dashboard.revoke]
confirm_button = "해지하기"
title = "연동 해지"
[ui.userfront.dashboard.scopes]
title = "권한 (Scopes)"
[ui.userfront.dashboard.status]
revoked = "해지됨"
[ui.userfront.device]
android = "Mobile(Android)"
ios = "Mobile(iOS)"
@@ -472,6 +252,258 @@ title = "비밀번호 재설정"
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 = "접속이력"
[ui.userfront.session]
active = "세션 활성"
unknown = "알 수 없음"
[ui.userfront.signup]
complete = "가입 완료"
next_step = "다음 단계"
title = "회원가입"
[msg.userfront.dashboard.activities]
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.timeline]
load_error = "접속이력을 불러오지 못했습니다."
[msg.userfront.error.whitelist]
"$normalizedCode" = "{error}"
bad_request = "입력값을 확인해 주세요."
invalid_session = "세션이 만료되었습니다. 다시 로그인해 주세요."
not_found = "요청한 페이지를 찾을 수 없습니다."
password_or_email_mismatch = "이메일 혹은 비밀번호가 일치하지 않습니다."
rate_limited = "요청이 많습니다. 잠시 후 다시 시도해 주세요."
recovery_expired = "재설정 링크가 만료되었습니다. 다시 요청해 주세요."
recovery_invalid = "재설정 링크가 유효하지 않습니다."
settings_disabled = "현재 계정 설정 화면은 준비 중입니다."
verification_required = "추가 인증이 필요합니다. 안내에 따라 진행해 주세요."
[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.login.link]
helper = "입력하신 정보로 로그인 링크를 전송합니다."
missing_login_id = "이메일 또는 휴대폰 번호를 입력해 주세요."
missing_phone = "휴대폰 번호를 입력해 주세요."
resend_wait = "재발송은 {time} 후 가능합니다."
short_code_help = "링크로 받은 값의 뒤 문자 2개와 숫자 6자리를 입력하셔도 로그인 할 수 있습니다."
[msg.userfront.login.password]
failed = "로그인 실패: {error}"
missing_credentials = "이메일(또는 전화번호)와 비밀번호를 모두 입력해주세요."
[msg.userfront.login.qr]
load_failed = "QR 코드를 불러오지 못했습니다."
scan_hint = "모바일 앱으로 스캔하세요"
[msg.userfront.login.short_code]
invalid = "문자 2개와 숫자 6자리를 입력해 주세요."
[msg.userfront.login.unregistered]
body = "가입되지 않은 정보입니다.\n회원가입 후 이용해 주세요."
[msg.userfront.login.verification]
approved = "승인되었습니다. 로그인은 요청하신 창에서 완료됩니다."
approved_local = "승인 되었습니다. 이 기기는 로그인되어 있는 상태입니다. 원격 창도 로그인이 될 예정입니다"
success = "로그인 승인에 성공했습니다."
[msg.userfront.consent.cancel]
confirm = "권한 동의를 취소하면 해당 서비스를 이용할 수 없습니다. 취소하시겠습니까?"
error = "취소 처리 중 오류가 발생했습니다: {error}"
[msg.userfront.consent.scope]
email = "이메일 주소 (계정 식별 및 알림 용도)"
offline_access = "오프라인 접근 (로그인 유지)"
openid = "OpenID 인증 정보 (로그인 상태 확인)"
phone = "휴대폰 번호 (본인 인증 및 알림)"
profile = "기본 프로필 정보 (이름, 사용자 식별자)"
[msg.userfront.profile.password]
change_failed = "비밀번호 변경 실패: {error}"
changed = "비밀번호가 변경되었습니다."
current_required = "현재 비밀번호를 입력해 주세요."
mismatch = "새 비밀번호가 일치하지 않습니다."
new_required = "새 비밀번호를 입력해 주세요."
subtitle = "현재 비밀번호 확인 후 새 비밀번호로 변경합니다."
[msg.userfront.profile.phone]
code_sent = "인증번호가 전송되었습니다."
send_failed = "전송 실패: {error}"
verified = "인증되었습니다."
verify_failed = "인증 실패: {error}"
verify_notice = "휴대폰 번호를 변경하려면 SMS 인증이 필요합니다."
[msg.userfront.profile.section]
basic = "계정 기본 정보를 관리합니다."
organization = "소속 및 구분 정보입니다."
security = "비밀번호를 안전하게 관리합니다."
[msg.userfront.reset.error]
empty_password = "비밀번호를 입력해주세요."
generic = "비밀번호 변경에 실패했습니다: {error}"
lowercase = "최소 1개 이상의 소문자를 포함해야 합니다."
min_length = "비밀번호는 최소 {count}자 이상이어야 합니다."
min_types = "비밀번호는 영문 대/소문자/숫자/특수문자 중 {count}가지 이상 포함해야 합니다."
mismatch = "비밀번호가 일치하지 않습니다."
number = "최소 1개 이상의 숫자를 포함해야 합니다."
symbol = "최소 1개 이상의 특수문자를 포함해야 합니다."
uppercase = "최소 1개 이상의 대문자를 포함해야 합니다."
[msg.userfront.reset.policy]
lowercase = "소문자 1개 이상"
min_length = "최소 {count}자 이상"
min_types = "영문 대/소문자/숫자/특수문자 중 {count}가지 이상"
number = "숫자 1개 이상"
symbol = "특수문자 1개 이상"
uppercase = "대문자 1개 이상"
[msg.userfront.signup.agreement]
all_hint = "필수 약관 2개를 모두 확인하고 동의하면 다음 단계로 진행할 수 있습니다."
description = "계속 진행하려면 서비스 이용 조건과 개인정보 수집·이용 항목을 확인한 뒤 동의해주세요."
privacy_summary = "개인정보 수집 항목, 이용 목적, 보관 기준을 안내합니다."
progress = "필수 약관 {total}개 중 {count}개 동의 완료"
tos_summary = "서비스 이용 조건과 책임 범위를 확인할 수 있습니다."
title = "서비스 이용을 위해\n약관에 동의해주세요"
[msg.userfront.signup.auth]
affiliate_notice = "가족사 회원의 경우 반드시 회사 공식 이메일을 입력해주세요."
title = "본인 확인을 위해\n인증을 진행해주세요"
[msg.userfront.signup.email]
code_mismatch = "인증코드가 일치하지 않습니다."
duplicate = "이미 가입된 이메일입니다."
invalid = "유효한 이메일 형식이 아닙니다."
send_failed = "발송 실패: {error}"
verified = "✅ 이메일 인증 완료"
verify_failed = "인증 실패: {error}"
[msg.userfront.signup.password]
length_required = "비밀번호는 최소 12자 이상이어야 합니다."
lowercase_required = "소문자가 최소 1개 이상 포함되어야 합니다."
mismatch = "비밀번호가 일치하지 않습니다."
number_required = "숫자가 최소 1개 이상 포함되어야 합니다."
symbol_required = "특수문자가 최소 1개 이상 포함되어야 합니다."
title = "마지막으로\n비밀번호를 설정해주세요"
uppercase_required = "대문자가 최소 1개 이상 포함되어야 합니다."
[msg.userfront.signup.phone]
code_mismatch = "인증코드가 일치하지 않습니다."
send_failed = "발송 실패: {error}"
verified = "✅ 휴대폰 인증 완료"
verify_failed = "인증 실패: {error}"
[msg.userfront.signup.policy]
loading = "비밀번호 정책을 불러오는 중입니다..."
lowercase = "소문자"
min_length = "최소 {count}자 이상"
min_types = "영문 대/소문자/숫자/특수문자 중 {count}가지 이상"
number = "숫자"
summary = "보안 정책: {rules}"
symbol = "특수문자"
uppercase = "대문자"
[msg.userfront.signup.profile]
affiliate_hint = "가족사 이메일 사용 시 자동으로 선택됩니다."
title = "회원님의\n소속 정보를 알려주세요"
[msg.userfront.signup.success]
body = "성공적으로 가입되었습니다."
title = "회원가입 완료"
[ui.userfront.audit.table]
app = "애플리케이션"
auth_method = "인증수단"
date = "접속일자"
device = "접속환경"
ip = "IP"
pending = "(준비중)"
result = "인증결과"
session_id = "Session ID"
status = "현황"
[ui.userfront.dashboard.activity]
linked = "연동됨"
[ui.userfront.dashboard.approved_session]
default = "승인한 세션 ID"
userfront = "승인한 Userfront 세션 ID"
[ui.userfront.dashboard.revoke]
confirm_button = "해지하기"
title = "연동 해지"
[ui.userfront.dashboard.scopes]
title = "권한 (Scopes)"
[ui.userfront.dashboard.status]
revoked = "해지됨"
[ui.userfront.login.action]
submit = "로그인"
@@ -509,31 +541,10 @@ action_label = "확인"
page_title = "로그인 승인"
title = "승인 완료"
[ui.userfront.login_success]
later = "나중에 하기 (대시보드로 이동)"
qr = "QR 인증 (카메라 켜기)"
title = "로그인 완료"
[ui.userfront.consent]
accept = "동의하고 계속하기"
requested_scopes = "요청된 권한"
title = "접근 권한 요청"
[ui.userfront.consent.cancel]
confirm_button = "예, 취소합니다"
title = "동의 취소"
[ui.userfront.nav]
dashboard = "대시보드"
logout = "로그아웃"
profile = "내 정보"
qr_scan = "QR 스캔"
[ui.userfront.profile]
department_empty = "소속 정보 없음"
manage = "프로필 관리"
user_fallback = "User"
[ui.userfront.profile.field]
affiliation = "구분"
company_code = "회사코드"
@@ -560,31 +571,6 @@ basic = "기본 정보"
organization = "조직 정보"
security = "보안"
[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 = "접속이력"
[ui.userfront.session]
active = "세션 활성"
unknown = "알 수 없음"
[ui.userfront.signup]
complete = "가입 완료"
next_step = "다음 단계"
title = "회원가입"
[ui.userfront.signup.agreement]
all = "모두 동의합니다"
privacy_title = "개인정보 수집 및 이용 동의 (필수)"
@@ -595,10 +581,6 @@ tos_title = "바론 소프트웨어 이용약관 (필수)"
code_label = "인증코드 6자리"
request_code = "인증요청"
[ui.userfront.signup.auth.email]
label = "이메일 주소"
title = "이메일 인증"
[ui.userfront.signup.password]
confirm_label = "비밀번호 확인"
label = "비밀번호"
@@ -622,3 +604,16 @@ verify = "본인인증"
[ui.userfront.signup.success]
action = "로그인하기"
[msg.userfront.signup.password.rule]
lowercase = "소문자"
min_length = "{count}자 이상"
min_types = "문자 유형 {count}가지 이상"
number = "숫자"
symbol = "특수문자"
uppercase = "대문자"
[ui.userfront.signup.auth.email]
label = "이메일 주소"
title = "이메일 인증"

View File

@@ -1,5 +1,3 @@
[domain]
[domain.affiliation]
affiliate = ""
general = ""
@@ -18,325 +16,9 @@ company_group = ""
personal = ""
user_group = ""
[err.userfront]
[err.userfront.auth_proxy]
consent_accept = ""
consent_fetch = ""
consent_reject = ""
linked_app_revoke = ""
login_failed = ""
oidc_accept = ""
password_reset_complete = ""
password_reset_init = ""
[err.userfront.profile]
load_failed = ""
password_change_failed = ""
send_code_failed = ""
update_failed = ""
verify_code_failed = ""
[err.userfront.session]
missing = ""
[msg.userfront]
greeting = ""
[msg.userfront.audit]
date = ""
device = ""
end = ""
ip = ""
load_more_error = ""
result = ""
session_id = ""
status = ""
[msg.userfront.dashboard]
approved_device = ""
approved_ip = ""
audit_empty = ""
audit_load_error = ""
render_error = ""
auth_method = ""
client_id = ""
client_id_missing = ""
current_status = ""
last_auth = ""
link_missing = ""
link_open_error = ""
session_id_copied = ""
[msg.userfront.dashboard.activities]
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.timeline]
load_error = ""
[msg.userfront.error]
detail_contact = ""
detail_generic = ""
detail_request = ""
id = ""
title = ""
title_generic = ""
title_with_code = ""
type = ""
[msg.userfront.error.whitelist]
"$normalizedCode" = ""
settings_disabled = ""
invalid_session = ""
verification_required = ""
recovery_expired = ""
recovery_invalid = ""
rate_limited = ""
not_found = ""
bad_request = ""
password_or_email_mismatch = ""
[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.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.link]
helper = ""
missing_login_id = ""
missing_phone = ""
resend_wait = ""
short_code_help = ""
[msg.userfront.login.password]
failed = ""
missing_credentials = ""
[msg.userfront.login.qr]
load_failed = ""
scan_hint = ""
[msg.userfront.login.short_code]
invalid = ""
[msg.userfront.login.unregistered]
body = ""
[msg.userfront.login.verification]
approved = ""
approved_local = ""
success = ""
[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.consent.cancel]
confirm = ""
error = ""
[msg.userfront.consent.scope]
email = ""
offline_access = ""
openid = ""
phone = ""
profile = ""
[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.profile.password]
change_failed = ""
changed = ""
current_required = ""
mismatch = ""
new_required = ""
subtitle = ""
[msg.userfront.profile.phone]
code_sent = ""
send_failed = ""
verified = ""
verify_failed = ""
verify_notice = ""
[msg.userfront.profile.section]
basic = ""
organization = ""
security = ""
[msg.userfront.qr]
camera_error = ""
permission_error = ""
permission_required = ""
[msg.userfront.reset]
invalid_body = ""
invalid_link = ""
invalid_title = ""
policy_loading = ""
success = ""
[msg.userfront.reset.error]
empty_password = ""
generic = ""
lowercase = ""
min_length = ""
min_types = ""
mismatch = ""
number = ""
symbol = ""
uppercase = ""
[msg.userfront.reset.policy]
lowercase = ""
min_length = ""
min_types = ""
number = ""
symbol = ""
uppercase = ""
[msg.userfront.sections]
apps_subtitle = ""
audit_subtitle = ""
[msg.userfront.settings]
disabled = ""
[msg.userfront.signup]
failed = ""
privacy_full = ""
tos_full = ""
[msg.userfront.signup.agreement]
all_hint = ""
description = ""
privacy_summary = ""
progress = ""
tos_summary = ""
title = ""
[msg.userfront.signup.auth]
affiliate_notice = ""
title = ""
[msg.userfront.signup.email]
code_mismatch = ""
duplicate = ""
invalid = ""
send_failed = ""
verified = ""
verify_failed = ""
[msg.userfront.signup.password]
length_required = ""
lowercase_required = ""
mismatch = ""
number_required = ""
symbol_required = ""
title = ""
uppercase_required = ""
[msg.userfront.signup.password.rule]
lowercase = ""
min_length = ""
min_types = ""
number = ""
symbol = ""
uppercase = ""
[msg.userfront.signup.phone]
code_mismatch = ""
send_failed = ""
verified = ""
verify_failed = ""
[msg.userfront.signup.policy]
loading = ""
lowercase = ""
min_length = ""
min_types = ""
number = ""
summary = ""
symbol = ""
uppercase = ""
[msg.userfront.signup.profile]
affiliate_hint = ""
title = ""
[msg.userfront.signup.success]
body = ""
title = ""
[ui.common]
add = ""
all = ""
@@ -391,6 +73,137 @@ theme_toggle = ""
unknown = ""
generate = ""
[ui.userfront]
app_title = ""
[err.userfront.auth_proxy]
consent_accept = ""
consent_fetch = ""
consent_reject = ""
linked_app_revoke = ""
login_failed = ""
oidc_accept = ""
password_reset_complete = ""
password_reset_init = ""
[err.userfront.profile]
load_failed = ""
password_change_failed = ""
send_code_failed = ""
update_failed = ""
verify_code_failed = ""
[err.userfront.session]
missing = ""
[msg.userfront.audit]
date = ""
device = ""
end = ""
ip = ""
load_more_error = ""
result = ""
session_id = ""
status = ""
[msg.userfront.dashboard]
approved_device = ""
approved_ip = ""
audit_empty = ""
audit_load_error = ""
render_error = ""
auth_method = ""
client_id = ""
client_id_missing = ""
current_status = ""
last_auth = ""
link_missing = ""
link_open_error = ""
session_id_copied = ""
[msg.userfront.error]
detail_contact = ""
detail_generic = ""
detail_request = ""
id = ""
title = ""
title_generic = ""
title_with_code = ""
type = ""
[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 = ""
[msg.userfront.settings]
disabled = ""
[msg.userfront.signup]
failed = ""
privacy_full = ""
tos_full = ""
[ui.common.badge]
admin_only = ""
command_only = ""
@@ -405,27 +218,11 @@ ok = ""
pending = ""
success = ""
[ui.userfront]
app_title = ""
[ui.userfront.app_label]
admin_console = ""
baron = ""
dev_console = ""
[ui.userfront.audit]
[ui.userfront.audit.table]
app = ""
auth_method = ""
date = ""
device = ""
ip = ""
pending = ""
result = ""
session_id = ""
status = ""
[ui.userfront.auth_method]
ory = ""
session = ""
@@ -434,23 +231,6 @@ session = ""
last_auth_label = ""
status_history = ""
[ui.userfront.dashboard.activity]
linked = ""
[ui.userfront.dashboard.approved_session]
default = ""
userfront = ""
[ui.userfront.dashboard.revoke]
confirm_button = ""
title = ""
[ui.userfront.dashboard.scopes]
title = ""
[ui.userfront.dashboard.status]
revoked = ""
[ui.userfront.device]
android = ""
ios = ""
@@ -472,6 +252,258 @@ title = ""
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 = ""
[ui.userfront.session]
active = ""
unknown = ""
[ui.userfront.signup]
complete = ""
next_step = ""
title = ""
[msg.userfront.dashboard.activities]
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.timeline]
load_error = ""
[msg.userfront.error.whitelist]
"$normalizedCode" = ""
settings_disabled = ""
invalid_session = ""
verification_required = ""
recovery_expired = ""
recovery_invalid = ""
rate_limited = ""
not_found = ""
bad_request = ""
password_or_email_mismatch = ""
[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.login.link]
helper = ""
missing_login_id = ""
missing_phone = ""
resend_wait = ""
short_code_help = ""
[msg.userfront.login.password]
failed = ""
missing_credentials = ""
[msg.userfront.login.qr]
load_failed = ""
scan_hint = ""
[msg.userfront.login.short_code]
invalid = ""
[msg.userfront.login.unregistered]
body = ""
[msg.userfront.login.verification]
approved = ""
approved_local = ""
success = ""
[msg.userfront.consent.cancel]
confirm = ""
error = ""
[msg.userfront.consent.scope]
email = ""
offline_access = ""
openid = ""
phone = ""
profile = ""
[msg.userfront.profile.password]
change_failed = ""
changed = ""
current_required = ""
mismatch = ""
new_required = ""
subtitle = ""
[msg.userfront.profile.phone]
code_sent = ""
send_failed = ""
verified = ""
verify_failed = ""
verify_notice = ""
[msg.userfront.profile.section]
basic = ""
organization = ""
security = ""
[msg.userfront.reset.error]
empty_password = ""
generic = ""
lowercase = ""
min_length = ""
min_types = ""
mismatch = ""
number = ""
symbol = ""
uppercase = ""
[msg.userfront.reset.policy]
lowercase = ""
min_length = ""
min_types = ""
number = ""
symbol = ""
uppercase = ""
[msg.userfront.signup.agreement]
all_hint = ""
description = ""
privacy_summary = ""
progress = ""
tos_summary = ""
title = ""
[msg.userfront.signup.auth]
affiliate_notice = ""
title = ""
[msg.userfront.signup.email]
code_mismatch = ""
duplicate = ""
invalid = ""
send_failed = ""
verified = ""
verify_failed = ""
[msg.userfront.signup.password]
length_required = ""
lowercase_required = ""
mismatch = ""
number_required = ""
symbol_required = ""
title = ""
uppercase_required = ""
[msg.userfront.signup.phone]
code_mismatch = ""
send_failed = ""
verified = ""
verify_failed = ""
[msg.userfront.signup.policy]
loading = ""
lowercase = ""
min_length = ""
min_types = ""
number = ""
summary = ""
symbol = ""
uppercase = ""
[msg.userfront.signup.profile]
affiliate_hint = ""
title = ""
[msg.userfront.signup.success]
body = ""
title = ""
[ui.userfront.audit.table]
app = ""
auth_method = ""
date = ""
device = ""
ip = ""
pending = ""
result = ""
session_id = ""
status = ""
[ui.userfront.dashboard.activity]
linked = ""
[ui.userfront.dashboard.approved_session]
default = ""
userfront = ""
[ui.userfront.dashboard.revoke]
confirm_button = ""
title = ""
[ui.userfront.dashboard.scopes]
title = ""
[ui.userfront.dashboard.status]
revoked = ""
[ui.userfront.login.action]
submit = ""
@@ -509,31 +541,10 @@ action_label = ""
page_title = ""
title = ""
[ui.userfront.login_success]
later = ""
qr = ""
title = ""
[ui.userfront.consent]
accept = ""
requested_scopes = ""
title = ""
[ui.userfront.consent.cancel]
confirm_button = ""
title = ""
[ui.userfront.nav]
dashboard = ""
logout = ""
profile = ""
qr_scan = ""
[ui.userfront.profile]
department_empty = ""
manage = ""
user_fallback = ""
[ui.userfront.profile.field]
affiliation = ""
company_code = ""
@@ -560,31 +571,6 @@ basic = ""
organization = ""
security = ""
[ui.userfront.qr]
rescan = ""
result_success = ""
title = ""
[ui.userfront.reset]
confirm_password = ""
new_password = ""
submit = ""
subtitle = ""
title = ""
[ui.userfront.sections]
apps = ""
audit = ""
[ui.userfront.session]
active = ""
unknown = ""
[ui.userfront.signup]
complete = ""
next_step = ""
title = ""
[ui.userfront.signup.agreement]
all = ""
privacy_title = ""
@@ -595,10 +581,6 @@ tos_title = ""
code_label = ""
request_code = ""
[ui.userfront.signup.auth.email]
label = ""
title = ""
[ui.userfront.signup.password]
confirm_label = ""
label = ""
@@ -622,3 +604,16 @@ verify = ""
[ui.userfront.signup.success]
action = ""
[msg.userfront.signup.password.rule]
lowercase = ""
min_length = ""
min_types = ""
number = ""
symbol = ""
uppercase = ""
[ui.userfront.signup.auth.email]
label = ""
title = ""