diff --git a/.gitea/workflows/code_check.yml b/.gitea/workflows/code_check.yml index 77d23ab5..b42827f9 100644 --- a/.gitea/workflows/code_check.yml +++ b/.gitea/workflows/code_check.yml @@ -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: diff --git a/Makefile b/Makefile index 8466a2b2..08b09822 100644 --- a/Makefile +++ b/Makefile @@ -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 \ diff --git a/devfront/src/features/clients/ClientGeneralPage.tsx b/devfront/src/features/clients/ClientGeneralPage.tsx index 964d4d55..6aeedfc0 100644 --- a/devfront/src/features/clients/ClientGeneralPage.tsx +++ b/devfront/src/features/clients/ClientGeneralPage.tsx @@ -305,11 +305,19 @@ function ClientGeneralPage() { -

- {isCreate - ? t("ui.dev.clients.general.title_create", "Create Client") - : t("ui.dev.clients.general.title_edit", "Client Settings")} -

+
+

+ {isCreate + ? t("ui.dev.clients.general.title_create", "Create Client") + : t("ui.dev.clients.general.title_edit", "Client Settings")} +

+

+ {t( + "ui.dev.clients.general.subtitle", + "앱 정보, 권한 스코프, 보안 설정을 관리합니다.", + )} +

+
{!isCreate && ( diff --git a/devfront/src/locales/en.toml b/devfront/src/locales/en.toml index f6dc1fac..6b2696f2 100644 --- a/devfront/src/locales/en.toml +++ b/devfront/src/locales/en.toml @@ -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" diff --git a/devfront/src/locales/ko.toml b/devfront/src/locales/ko.toml index 64580034..1c1292e8 100644 --- a/devfront/src/locales/ko.toml +++ b/devfront/src/locales/ko.toml @@ -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 = "구분" diff --git a/devfront/src/locales/template.toml b/devfront/src/locales/template.toml index 00d6daa1..aa09aed6 100644 --- a/devfront/src/locales/template.toml +++ b/devfront/src/locales/template.toml @@ -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 = "" diff --git a/devfront/tests/clients.spec.ts b/devfront/tests/clients.spec.ts index c1b2dc43..b304583e 100644 --- a/devfront/tests/clients.spec.ts +++ b/devfront/tests/clients.spec.ts @@ -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(); }); diff --git a/devfront/tests/devfront-audit.spec.ts b/devfront/tests/devfront-audit.spec.ts index efc31c75..8a0890d2 100644 --- a/devfront/tests/devfront-audit.spec.ts +++ b/devfront/tests/devfront-audit.spec.ts @@ -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); diff --git a/devfront/tests/devfront-clients-lifecycle.spec.ts b/devfront/tests/devfront-clients-lifecycle.spec.ts index 7a662395..b88c4e29 100644 --- a/devfront/tests/devfront-clients-lifecycle.spec.ts +++ b/devfront/tests/devfront-clients-lifecycle.spec.ts @@ -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"); diff --git a/devfront/tests/devfront-consents.spec.ts b/devfront/tests/devfront-consents.spec.ts index 593cab76..27518c3a 100644 --- a/devfront/tests/devfront-consents.spec.ts +++ b/devfront/tests/devfront-consents.spec.ts @@ -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(); }); }); diff --git a/devfront/tests/devfront-role-switch-report.spec.ts b/devfront/tests/devfront-role-switch-report.spec.ts index 6faedf92..e6b2af07 100644 --- a/devfront/tests/devfront-role-switch-report.spec.ts +++ b/devfront/tests/devfront-role-switch-report.spec.ts @@ -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({ diff --git a/devfront/tests/devfront-security.spec.ts b/devfront/tests/devfront-security.spec.ts index 7c75e36f..36e526aa 100644 --- a/devfront/tests/devfront-security.spec.ts +++ b/devfront/tests/devfront-security.spec.ts @@ -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 ({ diff --git a/locales/en.toml b/locales/en.toml index a0a62873..2b873498 100644 --- a/locales/en.toml +++ b/locales/en.toml @@ -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] diff --git a/locales/ko.toml b/locales/ko.toml index f8a79f60..be21c7e2 100644 --- a/locales/ko.toml +++ b/locales/ko.toml @@ -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 = "삭제" diff --git a/locales/template.toml b/locales/template.toml index 12aac3b9..de802eb1 100644 --- a/locales/template.toml +++ b/locales/template.toml @@ -1192,6 +1192,7 @@ title = "" [ui.dev.clients.general] create = "" display_new = "" +subtitle = "" title_create = "" title_edit = "" diff --git a/tools/i18n-scanner/value-check.js b/tools/i18n-scanner/value-check.js new file mode 100644 index 00000000..9da4acab --- /dev/null +++ b/tools/i18n-scanner/value-check.js @@ -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(); diff --git a/userfront/assets/translations/en.toml b/userfront/assets/translations/en.toml index 538eeb5b..79d64f17 100644 --- a/userfront/assets/translations/en.toml +++ b/userfront/assets/translations/en.toml @@ -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" + diff --git a/userfront/assets/translations/ko.toml b/userfront/assets/translations/ko.toml index 18d2b303..e87f37ea 100644 --- a/userfront/assets/translations/ko.toml +++ b/userfront/assets/translations/ko.toml @@ -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 = "이메일 인증" + diff --git a/userfront/assets/translations/template.toml b/userfront/assets/translations/template.toml index 44c85800..d954d383 100644 --- a/userfront/assets/translations/template.toml +++ b/userfront/assets/translations/template.toml @@ -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 = "" +