Files
crank/apps/ui/js/i18n.js
T
github-ops db97d37868
Deploy / deploy (push) Successful in 2m42s
CI / Rust Checks (push) Successful in 6m5s
CI / UI Checks (push) Successful in 6s
CI / Deployment Manifests (push) Successful in 3s
CI / Frontend E2E (push) Successful in 8m3s
Remove auth profile references from secrets page
2026-06-19 18:42:17 +00:00

1924 lines
137 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ═══════════════════════════════════════════════
Crank — i18n
Reads `crank_lang` from localStorage ('en'|'ru')
═══════════════════════════════════════════════ */
var TRANSLATIONS = {
en: {
// Nav
'nav.operations': 'Operations',
'nav.agents': 'Agents',
'nav.apikeys': 'API Keys',
'nav.secrets': 'Secrets',
'nav.logs': 'Logs',
'nav.usage': 'Usage',
'nav.settings': 'Settings',
'nav.logout': 'Log out',
'nav.workspaces': 'Your workspaces',
'nav.notifications': 'Notifications',
'nav.menu': 'Menu',
'nav.account': 'Account',
'nav.user_fallback': 'Crank',
'nav.workspace_fallback': 'workspace',
// Operations page
'ops.title': 'Operations',
'ops.subtitle': 'Tool contracts that expose your upstream APIs as MCP tools',
'ops.new': 'New operation',
'ops.loading': 'Loading operations…',
'ops.empty.title': 'No operations found',
'ops.empty.sub': 'Try adjusting your search or filters.',
'ops.empty.initial.title': 'No operations yet',
'ops.empty.initial.sub': 'Create the first operation in this workspace to expose a live MCP tool.',
'ops.error.title': 'Backend connection failed',
'ops.delete.confirm': 'Delete this operation? This cannot be undone.',
'ops.delete.success.title': 'Operation deleted',
'ops.delete.success.message': '{name} was deleted.',
'ops.delete.error.title': 'Delete failed',
'ops.delete.error.message': 'Failed to delete operation',
'ops.action.edit': 'Edit operation',
'ops.action.delete':'Delete operation',
'ops.stats.synced': 'Catalog synced with backend',
'ops.stats.none': 'No operations yet',
'ops.stats.share': '{value}% of total',
'ops.stats.no_published': 'No published tools yet',
'ops.stats.requests_rollup': 'Summed from operation usage',
'ops.stats.no_traffic': 'No traffic recorded today',
'ops.stats.latency_weighted': "Weighted by today's calls",
'ops.stats.latency_waiting': 'Waiting for runtime samples',
'ops.results': 'Showing {shown} of {total}',
'ops.range': 'Showing {from}{to} of {total} operations',
'ops.filter.protocol': 'Protocol: {value}',
'ops.filter.category': 'Category: {value}',
'ops.filter.agent': 'Agent: {value}',
'ops.no_active_agents': 'No active agents',
// Stats
'stats.total': 'Total operations',
'stats.active': 'Active',
'stats.requests': 'Requests today',
'stats.latency': 'Avg. latency',
// Tabs
'tab.all': 'All',
'tab.active': 'Active',
'tab.draft': 'Draft',
'tab.error': 'Error',
'tab.inactive': 'Inactive',
// Filters & sort
'filter.search': 'Search operations...',
'filter.protocol': 'Protocol',
'filter.status': 'Status',
'filter.category': 'Category',
'filter.agent': 'Agent',
'filter.showing': 'Showing',
'filter.of': 'of',
'filter.clear': 'Clear filter',
'filter.clear_all': 'Clear all',
'filter.remove': 'Remove filter',
'sort.created_desc':'Sort: Last created',
'sort.created_asc': 'Sort: Oldest first',
'sort.name_asc': 'Sort: Name AZ',
'sort.name_desc': 'Sort: Name ZA',
// Table headers
'th.name': 'NAME',
'th.protocol': 'PROTOCOL',
'th.status': 'STATUS',
'th.created': 'CREATED',
'th.url': 'TARGET URL',
// Pagination
'page.showing': 'Showing',
'page.of': 'of',
'page.ops': 'operations',
// API Keys page
'apikeys.title': 'Agent Keys',
'apikeys.subtitle': 'Keys authenticate external MCP clients against a specific AI agent endpoint.',
'apikeys.new': 'Create key',
'apikeys.agent.title': 'Agent access',
'apikeys.agent.subtitle': 'Select the AI agent whose MCP endpoint should accept this key.',
'apikeys.agent.label': 'AI agent',
'apikeys.agent.empty': 'No agents available',
'apikeys.agent.empty_hint': 'Create an AI agent first, then issue machine access keys for its MCP endpoint.',
'apikeys.agent.endpoint': 'MCP endpoint: {endpoint}',
'apikeys.agent.endpoint_missing': 'The selected agent does not have a published MCP endpoint yet.',
'apikeys.machine_access.title': 'Machine access modes',
'apikeys.machine_access.title_community': 'Community machine access',
'apikeys.machine_access.subtitle': 'This build exposes the stable machine-access contract and shows which modes are available now.',
'apikeys.machine_access.subtitle_community': 'Community exposes the stable MCP machine-access contract, but only static AI-agent keys are active in this build.',
'apikeys.machine_access.summary_default': 'Community currently supports static AI-agent keys.',
'apikeys.machine_access.summary': 'This build supports {access_modes} for operations marked with {security_levels}. Short-lived and one-time token issuance keeps the same public HTTP surface when a commercial edition is enabled.',
'apikeys.machine_access.note': 'Short-lived and one-time token issuance uses the same public HTTP surface, but requires a commercial edition.',
'apikeys.machine_access.note_community': 'Use static AI-agent keys in Community. Short-lived tokens and one-time tokens require a commercial edition.',
'apikeys.th.name': 'NAME',
'apikeys.th.scope': 'SCOPE',
'apikeys.th.created':'CREATED',
'apikeys.th.last': 'LAST USED',
'apikeys.callout.title': 'Keys are only shown once.',
'apikeys.callout.body': 'Copy and store it securely right after creation — Crank stores only a hash. Last used updates after a successful MCP call.',
'apikeys.active.title': 'Active keys',
'apikeys.active.subtitle': '{active} active · {revoked} revoked',
'apikeys.search': 'Filter keys…',
'apikeys.th.prefix': 'KEY PREFIX',
'apikeys.th.scopes': 'SCOPES',
'apikeys.th.status': 'STATUS',
'apikeys.scope_ref': 'Scope reference',
'apikeys.scope.read': 'Initialize MCP sessions, ping the server, and list tools for the selected AI agent.',
'apikeys.scope.write': 'Execute `tools/call` requests against published agent toolsets.',
'apikeys.scope.deploy': 'Reserved for deploy-scoped automation. Today it also permits MCP read/write flows.',
'apikeys.modal.title': 'Create agent key',
'apikeys.modal.name': 'Key name',
'apikeys.modal.name_hint': 'A descriptive label to identify the key. Only visible to admins.',
'apikeys.modal.name_placeholder': 'e.g. Production, CI pipeline',
'apikeys.modal.scopes': 'Scopes',
'apikeys.modal.reveal_title': 'Copy this key now.',
'apikeys.modal.reveal_body': "It won't be shown again.",
'apikeys.modal.copy': 'Copy to clipboard',
'apikeys.modal.cancel': 'Cancel',
'apikeys.modal.create': 'Create key',
'apikeys.modal.done': "Done — I've copied the key",
'apikeys.status.active': 'Active',
'apikeys.status.revoked': 'Revoked',
'apikeys.last_used.never': 'Never',
'apikeys.empty.none': 'No API keys yet',
'apikeys.empty.search': 'No keys match your search',
'apikeys.loading': 'Loading…',
'apikeys.error.api': 'Workspace or API is unavailable',
'apikeys.error.load': 'Failed to load API keys',
'apikeys.confirm.revoke': 'Revoke this key? All requests using it will fail immediately.',
'apikeys.confirm.delete': 'Delete this key? This cannot be undone.',
'apikeys.toast.revoke_title': 'Key revoked',
'apikeys.toast.revoke_message': '{name} was revoked.',
'apikeys.toast.revoke_error_title': 'Revoke failed',
'apikeys.toast.revoke_error_message': 'Failed to revoke key',
'apikeys.toast.delete_title': 'Key deleted',
'apikeys.toast.delete_message': '{name} was deleted.',
'apikeys.toast.delete_error_title': 'Delete failed',
'apikeys.toast.delete_error_message': 'Failed to delete key',
'apikeys.toast.scope_missing_title': 'Missing scope',
'apikeys.toast.scope_missing_message': 'Select at least one scope before creating a key.',
'apikeys.toast.create_title': 'Agent key created',
'apikeys.toast.create_message': 'Copy the key now. It will not be shown again.',
'apikeys.toast.create_error_title': 'Key creation failed',
'apikeys.toast.create_error_message': 'Failed to create key',
'apikeys.toast.copy_title': 'Agent key copied',
'apikeys.toast.copy_message': 'Store the raw key securely. It cannot be revealed again.',
'apikeys.toast.prefix_title': 'Key prefix copied',
'apikeys.action.copy_prefix': 'Copy key prefix',
'apikeys.action.revoke': 'Revoke key',
'apikeys.action.delete': 'Delete',
'apikeys.creating': 'Creating…',
// Secrets page
'secrets.title': 'Secrets',
'secrets.subtitle': 'Encrypted credentials for upstream REST APIs.',
'secrets.new': 'Create secret',
'secrets.callout.title': 'Secret plaintext is write-only.',
'secrets.callout.body': 'Crank encrypts secret values with the workspace master key. After creation or rotation, the API returns only metadata, so this page focuses on secret management and where each secret is used.',
'secrets.active.title': 'Workspace secrets',
'secrets.active.subtitle': '{active} active · {total} total',
'secrets.search': 'Filter secrets…',
'secrets.th.name': 'NAME',
'secrets.th.kind': 'KIND',
'secrets.th.version': 'VERSION',
'secrets.th.last_used': 'LAST USED',
'secrets.th.used_by': 'USED BY',
'secrets.th.status': 'STATUS',
'secrets.kind.token': 'Token',
'secrets.kind.username_password': 'Username/password',
'secrets.kind.header': 'Header value',
'secrets.kind.generic': 'Generic JSON',
'secrets.status.active': 'Active',
'secrets.status.disabled': 'Disabled',
'secrets.last_used.never': 'Never',
'secrets.used_by_count.one': '{count} binding',
'secrets.used_by_count.other': '{count} bindings',
'secrets.loading': 'Loading…',
'secrets.empty.none': 'No secrets yet',
'secrets.empty.search': 'No secrets match your search',
'secrets.error.workspace': 'Workspace is not selected',
'secrets.error.load': 'Failed to load secrets',
'secrets.action.rotate': 'Rotate',
'secrets.action.delete': 'Delete',
'secrets.confirm.delete': 'Delete secret {name}? This cannot be undone while the secret is still used by an operation.',
'secrets.toast.create_title': 'Secret created',
'secrets.toast.create_message': 'The secret was stored encrypted. Plaintext cannot be viewed again through the API.',
'secrets.toast.create_error_title': 'Secret creation failed',
'secrets.toast.create_error_message': 'Failed to create secret',
'secrets.toast.rotate_title': 'Secret rotated',
'secrets.toast.rotate_message': 'A new secret version is now active.',
'secrets.toast.rotate_error_title': 'Secret rotation failed',
'secrets.toast.rotate_error_message': 'Failed to rotate secret',
'secrets.toast.delete_title': 'Secret deleted',
'secrets.toast.delete_message': '{name} was deleted.',
'secrets.toast.delete_error_title': 'Secret deletion failed',
'secrets.toast.delete_error_message': 'Failed to delete secret',
'secrets.modal.create_title': 'Create secret',
'secrets.modal.rotate_title': 'Rotate secret',
'secrets.modal.name': 'Secret name',
'secrets.modal.name_hint': 'Use a stable operator-facing label. Plaintext values are never returned after storage.',
'secrets.modal.kind': 'Secret kind',
'secrets.modal.value': 'Secret value',
'secrets.modal.header_value': 'Header value',
'secrets.modal.username': 'Username',
'secrets.modal.password': 'Password',
'secrets.modal.json_payload': 'JSON payload',
'secrets.modal.json_hint': 'Useful for generic secret payloads. The value must be valid JSON.',
'secrets.modal.create_note': 'Creation stores encrypted plaintext and returns metadata only.',
'secrets.modal.rotate_note': 'Rotation creates a new encrypted version and keeps the same secret id.',
'secrets.modal.cancel': 'Cancel',
'secrets.modal.create_action': 'Create secret',
'secrets.modal.rotate_action': 'Rotate secret',
'secrets.modal.creating': 'Creating…',
'secrets.modal.rotating': 'Rotating…',
'secrets.hint.token': 'Best for bearer tokens and simple API key values.',
'secrets.hint.username_password': 'Stores username and password as one encrypted payload.',
'secrets.hint.header': 'Stores a raw header value. Header name is configured in the operation wizard.',
'secrets.hint.generic': 'Stores arbitrary JSON for custom integrations that need a structured secret payload.',
'secrets.validation.name_required': 'Secret name is required.',
'secrets.validation.value_required': 'Secret value is required.',
'secrets.validation.username_password_required': 'Username and password are both required.',
'secrets.validation.json_invalid': 'Generic secret payload must be valid JSON.',
// Logs page
'logs.title': 'Logs',
'logs.subtitle': 'Real-time invocation log for all operations in this workspace.',
'logs.search': 'Search logs...',
'logs.level.all': 'All',
'logs.level.info': 'Info',
'logs.level.warn': 'Warn',
'logs.level.error': 'Error',
'logs.level.debug': 'Debug',
'logs.live': 'Live',
'logs.paused': 'Paused',
'logs.refresh': 'Refresh',
'logs.range.30m': 'Last 30 min',
'logs.range.1h': 'Last hour',
'logs.range.6h': 'Last 6 hours',
'logs.range.24h': 'Last 24 hours',
'logs.range.7d': 'Last 7 days',
'logs.search_messages': 'Search messages…',
'logs.detail.agent': 'Agent',
'logs.detail.request': 'Request preview',
'logs.detail.response': 'Response preview',
'logs.detail.meta': 'Metadata',
'logs.loading.title': 'Loading logs…',
'logs.loading.sub': 'Fetching invocation history for the current workspace.',
'logs.error.title': 'Unable to load logs',
'logs.error.api': 'Admin API is not available',
'logs.error.workspace': 'Workspace is not selected',
'logs.error.load': 'Failed to load logs',
'logs.empty.title': 'No log entries found',
'logs.empty.filtered': 'Try widening the period or clearing the current search and level filters.',
'logs.empty.initial': 'Run tests or published MCP tools to start collecting invocation history.',
'logs.live.on.title': 'Live mode enabled',
'logs.live.on.body': 'Polling logs every 4 seconds.',
'logs.live.off.title': 'Live mode paused',
'logs.live.off.body': 'Automatic polling is paused.',
'logs.refresh.title': 'Logs refreshed',
'logs.refresh.body': 'The latest invocation records were loaded for the current workspace.',
// Usage page
'usage.title': 'Usage',
'usage.subtitle': 'Invocation metrics for all operations in your workspace.',
'usage.export': 'Export CSV',
'usage.period.7d': 'Last 7 days',
'usage.period.30d': 'Last 30 days',
'usage.period.90d': 'Last 90 days',
'usage.period.this_month': 'This month',
'usage.period.label.7d': 'last 7 days',
'usage.period.label.30d': 'last 30 days',
'usage.period.label.90d': 'last 90 days',
'usage.period.label.this_month': 'this month',
'usage.stats.total': 'Total invocations',
'usage.stats.success': 'Success rate',
'usage.stats.p50': 'Median latency (p50)',
'usage.stats.p99': 'p99 latency',
'usage.stats.across': 'Across {period}',
'usage.stats.success_calls': '{count} successful calls',
'usage.stats.median': 'Median latency for selected period',
'usage.stats.error_calls': '{count} error calls',
'usage.chart.title': 'Invocations over time',
'usage.chart.success': 'Success',
'usage.chart.error': 'Error',
'usage.chart.empty.title': 'No usage data yet',
'usage.chart.empty.sub': 'Invocation metrics will appear here after tests or published tool calls in the selected period.',
'usage.table.title': 'By operation',
'usage.table.subtitle': 'Breakdown for {period}',
'usage.table.th.operation': 'Operation',
'usage.table.th.protocol': 'Protocol',
'usage.table.th.calls': 'Calls',
'usage.table.th.errors': 'Errors',
'usage.table.th.error_rate': 'Error rate',
'usage.table.th.latency': 'Latency (p50 / p95 / p99)',
'usage.table.th.share': 'Traffic share',
'usage.table.empty.title': 'No operation breakdown yet',
'usage.table.empty.sub': 'The selected period has no invocation samples to break down by operation.',
'usage.table.share': '{value}% of workspace traffic',
'usage.loading.title': 'Loading usage…',
'usage.loading.sub': 'Aggregating invocation metrics for the selected workspace.',
'usage.error.title': 'Unable to load usage',
'usage.error.api': 'Admin API is not available',
'usage.error.workspace': 'Workspace is not selected',
'usage.error.load': 'Failed to load usage',
'usage.export.empty.title': 'No usage data loaded',
'usage.export.empty.body': 'Load usage data before exporting the CSV snapshot.',
'usage.export.done.title': 'Usage exported',
'usage.export.done.body': 'The current usage snapshot was exported as CSV.',
'usage.chart.ok': '{count} ok',
'usage.chart.errors': '{count} errors',
'usage.chart.week': 'Wk {index}',
'usage.csv.header': 'Operation,Protocol,Calls,Errors,Error Rate (%),p50 (ms),p95 (ms),p99 (ms)',
// Settings sidebar
'settings.title': 'Settings',
'settings.nav.workspace': 'Workspace',
'settings.nav.profile': 'Profile',
'settings.nav.security': 'Security',
'settings.nav.notif': 'Notifications',
'settings.nav.danger': 'Danger zone',
// Settings — workspace section
'settings.ws.title': 'Workspace settings',
'settings.ws.subtitle': 'General information about your workspace',
'settings.ws.name': 'Workspace name',
'settings.ws.display': 'Display name',
'settings.ws.desc': 'Description',
// Settings — language
'settings.lang.title': 'Language',
'settings.lang.subtitle': 'Interface display language',
'settings.lang.en': 'English',
'settings.lang.ru': 'Русский',
// Settings — profile section
'settings.profile.title': 'Profile',
'settings.profile.name': 'Full name',
'settings.profile.email': 'Email',
'settings.profile.role': 'Role',
'settings.page.title': 'Account settings',
'settings.page.subtitle': 'Manage your profile, password, workspace settings, and the capability limits of this build.',
'settings.ws.slug_hint': 'Used in API endpoints and across the console. Only lowercase letters, numbers and hyphens.',
'settings.ws.description_optional': 'Description (optional)',
'settings.ws.description_placeholder': 'What does this workspace do?',
'settings.profile.avatar_hint': 'Your avatar is generated from your display name and email.',
'settings.profile.backend_hint': 'Your name and email are stored on the backend and used across the console and session identity.',
'settings.profile.first_name': 'First name',
'settings.profile.last_name': 'Last name',
'settings.profile.last_name_placeholder': 'Optional',
'settings.profile.email_label': 'Email address',
'settings.profile.email_hint': 'Changing email updates the login identity for the current account.',
'settings.profile.save': 'Save profile',
'settings.profile.saving': 'Saving…',
'settings.profile.saved': 'Saved ✓',
'settings.profile.saved_status': 'Profile saved.',
'settings.profile.save_error': 'Failed to save profile',
'settings.security.title': 'Security',
'settings.security.current_password': 'Current password',
'settings.security.new_password': 'New password',
'settings.security.new_password_placeholder': 'min. 12 characters',
'settings.security.confirm_password': 'Confirm new password',
'settings.security.change_password': 'Change password',
'settings.security.mismatch': 'New password and confirmation do not match.',
'settings.security.saved': 'Password updated.',
'settings.security.save_error': 'Failed to change password',
'settings.security.capabilities_title': 'Build capability summary',
'settings.security.capabilities_body': 'This section shows what the current build supports. Password sign-in with one HttpOnly browser session is available now; short-lived tokens, one-time tokens, and advanced governance depend on the edition.',
'settings.security.not_available': 'Build capabilities',
'settings.security.capability_title_community': 'Community build',
'settings.security.capability_title_enterprise': 'Enterprise build',
'settings.security.capability_title_cloud': 'Cloud build',
'settings.security.capability_summary': 'This build supports {protocols}, {access_modes}, and {security_levels} operation security. Limits: {max_workspaces} workspace, {max_users} user, {max_agents} AI agent. Short-lived tokens, one-time tokens, and advanced governance require a commercial edition.',
'settings.capability.rest': 'REST / HTTP',
'settings.capability.graphql': 'GraphQL',
'settings.capability.grpc': 'gRPC unary',
'settings.capability.websocket': 'WebSocket',
'settings.capability.soap': 'SOAP',
'settings.capability.standard': 'standard',
'settings.capability.elevated': 'elevated',
'settings.capability.strict': 'strict',
'settings.capability.static_agent_key': 'static agent keys',
'settings.capability.short_lived_token': 'short-lived tokens',
'settings.capability.one_time_token': 'one-time tokens',
'settings.capability.unlimited': 'unlimited',
'settings.capability.none': 'none',
'settings.session.title': 'Current session',
'settings.session.browser': 'Browser session',
'settings.session.current': 'current',
'settings.session.loading': 'Loading current session details…',
'settings.session.unavailable': 'Session details are unavailable. Use Log out to revoke the current browser session.',
'settings.session.current_workspace': 'current workspace',
'settings.session.summary': '{email} · {role} in {workspace}. Use Log out to revoke the current browser session.',
'settings.notifications.title': 'Notifications',
'settings.notifications.not_ready_title': 'Not included in this build.',
'settings.notifications.not_ready_body': 'Notification routing and per-user preferences are outside the current Community surface.',
'settings.notifications.capability_title_community': 'Community build boundary',
'settings.notifications.capability_body_community': 'Per-user notification routing and delivery channels are outside the current Community surface. Use logs and usage views for operational follow-up in this build.',
'settings.notifications.capability_title_enterprise': 'Enterprise delivery controls',
'settings.notifications.capability_body_enterprise': 'Notification routing depends on enterprise delivery controls and is wired separately from the Community console surface.',
'settings.notifications.capability_title_cloud': 'Cloud delivery controls',
'settings.notifications.capability_body_cloud': 'Notification routing depends on hosted delivery controls and is wired separately from the Community console surface.',
'settings.planned_badge': 'Planned',
'settings.notifications.spike_title': 'Operation error spike',
'settings.notifications.spike_body': 'Alert when error rate exceeds a rolling threshold for a published tool.',
'settings.notifications.latency_title': 'Upstream latency degradation',
'settings.notifications.latency_body': 'Notify operators when p99 latency rises sharply above the baseline.',
'settings.notifications.digest_title': 'Quota and usage digests',
'settings.notifications.digest_body': 'Deliver periodic summaries for quota consumption, errors and workspace activity.',
'settings.workspace.saving': 'Saving…',
'settings.workspace.saved': 'Saved ✓',
'settings.workspace.save_error': 'Failed to save workspace settings',
'settings.workspace.save_error_title': 'Workspace save failed',
// Workspace setup
'workspace_setup.title': 'Workspace settings',
'workspace_setup.page_subtitle': 'Configure the identity of your self-hosted workspace.',
'workspace_setup.back': 'Back',
'workspace_setup.identity.title': 'Workspace identity',
'workspace_setup.identity.avatar_hint': 'Avatar is derived from your workspace name.',
'workspace_setup.name': 'Workspace name',
'workspace_setup.slug': 'Slug',
'workspace_setup.required': 'required',
'workspace_setup.optional': '(optional)',
'workspace_setup.slug_hint': 'Only lowercase letters, numbers, and hyphens. Used in MCP endpoint URLs.',
'workspace_setup.description': 'Description',
'workspace_setup.description_placeholder': 'What does this workspace do?',
'workspace_setup.actions.save': 'Save changes',
'workspace_setup.create.title': 'Create a new workspace',
'workspace_setup.create.subtitle': 'This Community installation uses one workspace for MCP operations and agents.',
'workspace_setup.create.footer': 'This Community installation uses one workspace.',
'workspace_setup.danger.title': 'Danger zone',
'workspace_setup.danger.export_title': 'Export all data',
'workspace_setup.danger.export_body': 'Download a JSON snapshot of workspace settings, operations, agents, secrets, usage data and agent access keys.',
'workspace_setup.export': 'Export',
'workspace_setup.role.owner': 'Owner',
'workspace_setup.role.admin': 'Admin',
'workspace_setup.role.operator': 'Developer',
'workspace_setup.role.viewer': 'Viewer',
'workspace_setup.saving_create': 'Creating…',
'workspace_setup.saving_update': 'Saving…',
'workspace_setup.saved': 'Saved ✓',
'workspace_setup.save_error': 'Failed to save workspace',
'workspace_setup.save_error_title': 'Save failed',
'workspace_setup.export_error': 'Failed to export workspace',
'workspace_setup.export_error_title': 'Export failed',
'workspace.switch_error': 'Failed to switch workspace',
'workspace.switch_error_title': 'Workspace switch failed',
'workspace_setup.custom_protocol.rest': 'REST',
'workspace_setup.custom_protocol.graphql': 'GraphQL',
'workspace_setup.custom_protocol.grpc': 'gRPC',
'workspace_setup.custom_protocol.websocket': 'WebSocket',
'workspace_setup.custom_protocol.soap': 'SOAP',
// Wizard
'wizard.back_catalog': 'Operations',
'wizard.progress.create': 'Create operation',
'wizard.progress.edit': 'Edit operation',
'wizard.exit': 'Exit wizard',
'wizard.step_of': 'Step <strong>{step}</strong> of {total}',
'wizard.step_short': 'Step {step}',
'wizard.status.completed': 'Completed',
'wizard.status.in_progress': 'In progress',
'wizard.status.not_started': 'Not started',
'wizard.help.title': 'Need help?',
'wizard.help.body': 'Unsure which protocol to choose? Read the',
'wizard.help.link': 'protocol comparison guide →',
'wizard.progress.section': 'Progress',
'wizard.step_name.protocol': 'Protocol',
'wizard.step_name.upstream': 'Upstream target',
'wizard.step_name.tool': 'Tool config',
'wizard.step_name.mapping': 'Mapping',
'wizard.button.back': 'Back',
'wizard.button.continue': 'Continue',
'wizard.button.save_draft': 'Save draft',
'wizard.button.create': 'Create operation',
'wizard.button.save_changes': 'Save changes',
'wizard.sidebar.brand.create': 'Create <span>operation</span>',
'wizard.sidebar.brand.edit': 'Edit <span>operation</span>',
'wizard.step1.title': 'Choose a protocol',
'wizard.step1.subtitle': 'Select the transport protocol your upstream service uses. This determines how Crank will communicate with your API and which configuration options are available in subsequent steps.',
'wizard.step1.rest_name': 'REST / HTTP',
'wizard.step1.rest_tagline': 'Standard HTTP verbs over a RESTful endpoint. The most broadly supported option.',
'wizard.step1.graphql_name': 'GraphQL',
'wizard.step1.graphql_tagline': 'Query and mutate a GraphQL schema. Supports queries and mutations over one fixed operation.',
'wizard.step1.grpc_name': 'gRPC',
'wizard.step1.grpc_tagline': 'High-performance binary RPC over HTTP/2 for unary calls to low-latency internal services.',
'wizard.step1.websocket_name': 'WebSocket',
'wizard.step1.websocket_tagline': 'Stateful event streams and push-oriented integrations normalized into bounded MCP streaming tools.',
'wizard.step1.soap_name': 'SOAP',
'wizard.step1.soap_tagline': 'Enterprise WSDL/XSD service contracts normalized into MCP tools with structured XML mapping.',
'wizard.step1.query': 'Query',
'wizard.step1.mutation': 'Mutation',
'wizard.step1.unary': 'Unary',
'wizard.step1.tip_title': 'You can change this later',
'wizard.step1.tip_body': 'Switching protocol after configuring subsequent steps will clear schema and mapping data. Save your operation as a draft before experimenting. See protocol migration guide for details.',
'wizard.step1.community_protocol_title': 'Community protocol scope',
'wizard.step1.community_protocol_note': 'This Community build hides {protocols}. Commercial editions unlock those protocol surfaces.',
'wizard.step5.streaming_title': 'Streaming execution in Community',
'wizard.step5.community_streaming_note': 'This Community build keeps operations in unary mode. Window, session and async job tool families require a commercial edition.',
'wizard.step2.title': 'Select upstream & endpoint',
'wizard.step2.subtitle': 'Choose an existing upstream or register a new one. Then define the specific endpoint path this operation will call.',
'wizard.required': 'Required',
'wizard.optional': 'Optional',
'wizard.step2.upstream_title': 'Upstream',
'wizard.step2.upstream_subtitle': 'Shared host, base URL, and authentication',
'wizard.step2.upstream_placeholder': 'Select an upstream…',
'wizard.step2.search_placeholder': 'Search by name or URL…',
'wizard.step2.change': 'Change',
'wizard.step2.auth_none': 'No auth',
'wizard.step2.auth_bearer': 'Bearer token',
'wizard.step2.auth_basic': 'Basic auth',
'wizard.step2.auth_apikey': 'API key',
'wizard.step2.register_new': 'Register new upstream',
'wizard.step2.name': 'Name',
'wizard.step2.base_url': 'Base URL',
'wizard.step2.unique_hint': 'Unique identifier for this upstream.',
'wizard.step2.base_url_hint': 'Root URL without a trailing slash. Auth is configured separately below.',
'wizard.step2.auth_selector': 'Upstream auth',
'wizard.step2.auth_selector_hint': 'Use secrets-backed auth profiles instead of embedding credential headers in the upstream definition.',
'wizard.step2.auth_mode.none': 'No auth',
'wizard.step2.auth_mode.existing': 'Use existing auth profile',
'wizard.step2.auth_mode.create': 'Create auth profile now',
'wizard.step2.auth_profile': 'Auth profile',
'wizard.step2.auth_profile_hint': 'The selected auth profile is applied automatically when the tool runs.',
'wizard.step2.auth_profile_empty': 'No auth profiles yet. Create one below.',
'wizard.step2.create_profile_title': 'Create auth profile',
'wizard.step2.create_profile_subtitle': 'Create a reusable profile backed by encrypted workspace secrets.',
'wizard.step2.profile_name': 'Profile name',
'wizard.step2.profile_kind': 'Auth kind',
'wizard.step2.auth_kind.bearer': 'Bearer token',
'wizard.step2.auth_kind.basic': 'Basic auth',
'wizard.step2.auth_kind.api_key_header': 'API key header',
'wizard.step2.auth_kind.api_key_query': 'API key query',
'wizard.step2.header_name': 'Header name',
'wizard.step2.query_param': 'Query param',
'wizard.step2.username_secret': 'Username secret',
'wizard.step2.password_secret': 'Password secret',
'wizard.step2.secret_value': 'Secret',
'wizard.step2.quick_secret': 'Quick create secret',
'wizard.step2.manage_secrets': 'Open secrets page',
'wizard.step2.static_headers': 'Static headers (optional)',
'wizard.step2.static_headers_hint': 'Optional non-secret headers sent on every request to this upstream. Use auth profiles for credentials.',
'wizard.step2.quick_secret_title': 'Quick create secret',
'wizard.step2.quick_secret_name': 'Secret name',
'wizard.step2.quick_secret_kind': 'Secret kind',
'wizard.step2.quick_secret_value': 'Secret value',
'wizard.step2.quick_secret_hint': 'The plaintext is encrypted immediately and will not be returned again.',
'wizard.step2.quick_secret_submit': 'Create secret',
'wizard.step2.save_upstream': 'Save upstream',
'wizard.step2.endpoint_title': 'Endpoint',
'wizard.step2.endpoint_subtitle': 'Path and HTTP method for this specific operation',
'wizard.step2.path_template': 'Path template',
'wizard.step2.path_hint': 'Appended to the upstream base URL. Use {param} for path variables.',
'wizard.step3.rest.label': 'Request config',
'wizard.step3.graphql.label': 'GQL operation',
'wizard.step3.rest.title': 'HTTP method & format',
'wizard.step3.rest.subtitle': 'Choose the HTTP verb this operation sends and configure content negotiation headers. Crank will serialize MCP tool arguments into the appropriate request body format.',
'wizard.step3.rest.method_title': 'HTTP method',
'wizard.step3.rest.method_subtitle': 'Verb sent to the upstream on every tool invocation',
'wizard.step3.rest.read': 'Read',
'wizard.step3.rest.create': 'Create',
'wizard.step3.rest.replace': 'Replace',
'wizard.step3.rest.update': 'Update',
'wizard.step3.rest.remove': 'Remove',
'wizard.step3.rest.format_title': 'Request format',
'wizard.step3.rest.format_subtitle': 'Content negotiation and serialisation',
'wizard.step3.rest.content_type': 'Content-Type',
'wizard.step3.rest.content_type_hint': 'How the request body is encoded when sent to the upstream.',
'wizard.step3.rest.accept': 'Accept',
'wizard.step3.rest.accept_hint': 'Accepted response content types from the upstream server.',
'wizard.step3.graphql.title': 'GraphQL operation',
'wizard.step3.graphql.subtitle': 'Define the GraphQL operation this tool will execute. All requests are sent as HTTP POST to the endpoint configured in the previous step. Variables are populated from MCP tool arguments at runtime.',
'wizard.step3.graphql.type_title': 'Operation type',
'wizard.step3.graphql.type_subtitle': 'GraphQL operation kind — subscriptions are not supported',
'wizard.step3.graphql.query_desc': 'Fetch data without side-effects',
'wizard.step3.graphql.mutation_desc': 'Create, update or delete data',
'wizard.step3.graphql.doc_title': 'Query document',
'wizard.step3.graphql.doc_subtitle': 'GraphQL document with typed variable declarations',
'wizard.step3.graphql.variables_title': 'Variables are mapped in step 5',
'wizard.step3.graphql.variables_body': 'Declare all variables in the query document using the $variable: Type syntax. In step 5 you will map MCP input fields to these variables. Crank serializes the final {"query","variables"} payload automatically.',
'wizard.step4.title': 'Tool config',
'wizard.step4.subtitle': 'Name your tool, write the LLM-facing description, and define the input/output schemas. The description is the primary signal the model uses when deciding whether to call this tool.',
'wizard.step4.identity_title': 'Tool identity',
'wizard.step4.identity_subtitle': 'Machine-readable name and LLM-facing description',
'wizard.step4.tool_name': 'Tool name',
'wizard.step4.tool_name_hint': 'Machine-readable identifier used in MCP tool calls. Only lowercase letters, numbers and underscores. Cannot be changed after publishing.',
'wizard.step4.display_name': 'Display name',
'wizard.step4.display_name_hint': 'Human-readable name shown in the console and audit log.',
'wizard.step4.tool_title': 'Tool title',
'wizard.step4.tool_title_hint': 'Short imperative sentence shown in the MCP tool manifest.',
'wizard.step4.description': 'Description',
'wizard.step4.description_hint': 'LLM-facing description. Be precise — this is the primary signal the model uses to decide whether to invoke this tool.',
'wizard.step4.description_title': 'Writing effective descriptions',
'wizard.step4.description_body': 'Start with a verb ("Creates", "Fetches", "Updates"). Mention key input requirements. Describe what a successful response looks like. Avoid vague phrasing like "handles" or "manages". See description best practices →',
'wizard.step4.input_schema_title': 'Input schema',
'wizard.step4.input_schema_subtitle': 'Parameters the LLM passes when calling this tool',
'wizard.step4.output_schema_title': 'Output schema',
'wizard.step4.output_schema_subtitle': 'Shape of the data returned to the LLM after a successful call',
'wizard.step4.schema_draft': 'JSON Schema draft-07',
'wizard.step4.protocol_agnostic_title': 'Schemas are protocol-agnostic',
'wizard.step4.protocol_agnostic_body': 'These schemas describe the MCP contract, not the upstream wire format. Field mapping in Step 5 translates between the two. You can upload a sample JSON response to auto-generate the output schema.',
'wizard.step5.title': 'Mapping and execution',
'wizard.step5.subtitle': 'Define how MCP tool arguments map to upstream request fields, and how the upstream response maps back to MCP output. Then set execution parameters — auth is configured per-upstream in step 2.',
'wizard.step5.input_request': 'Input → Request',
'wizard.step5.output_response': 'Response → Output',
'wizard.step5.execution': 'Execution',
'wizard.step5.exec_title': 'Execution config',
'wizard.step5.exec_subtitle': 'Timeouts, retry policy and auth profile reference',
'wizard.step5.security_level_title': 'Operation security in Community',
'wizard.step5.community_security_note': 'Community supports only the standard operation security level with static AI-agent keys. Short-lived and one-time token flows require a commercial edition.',
'wizard.step5.live_title': 'Live validation and publishing',
'wizard.step5.draft_actions': 'Draft actions',
'wizard.step5.live_save_title': 'Live actions save the draft first',
'wizard.step5.live_save_body': 'Sample uploads, test runs, YAML actions and publish all persist the current draft before calling the backend.',
'wizard.step5.samples_title': 'Samples and draft generation',
'wizard.step5.samples_subtitle': 'Persist input/output samples, then generate schemas and mappings from real payloads.',
'wizard.step5.input_sample': 'Input sample',
'wizard.step5.output_sample': 'Output sample',
'wizard.step5.save_input_sample': 'Save input sample',
'wizard.step5.save_output_sample': 'Save output sample',
'wizard.step5.generate_draft': 'Generate draft from samples',
'wizard.step5.test_title': 'Test run',
'wizard.step5.test_subtitle': 'Run the current draft against the upstream before publishing it.',
'wizard.step5.test_input_payload': 'Test input payload',
'wizard.step5.run_test': 'Run test',
'wizard.step5.use_response_output': 'Use response as output sample',
'wizard.step5.request_preview': 'Request preview',
'wizard.step5.response_preview': 'Response preview',
'wizard.step5.errors': 'Errors',
'wizard.step5.choose_descriptor': 'Choose descriptor set',
'wizard.step5.upload_discover': 'Upload and discover services',
'wizard.step5.no_descriptor': 'No descriptor set selected',
'wizard.step5.yaml_title': 'YAML import and export',
'wizard.step5.yaml_subtitle': 'Move operation drafts between environments without leaving the wizard.',
'wizard.step5.export_yaml': 'Export YAML',
'wizard.step5.load_yaml_file': 'Load YAML file',
'wizard.step5.import_yaml': 'Import YAML',
'wizard.step5.yaml_document': 'YAML document',
'wizard.step5.yaml_placeholder': 'Paste exported YAML here to create or upsert an operation.',
'wizard.step5.publish_title': 'Publish',
'wizard.step5.publish_subtitle': 'Save the current draft and expose it to published agents.',
'wizard.step5.publish_hint': 'Publishing uses the current draft version from this wizard session.',
'wizard.step5.publish_action': 'Publish operation',
'wizard.step5.editable_title': 'Operation stays editable in this wizard',
'wizard.step5.editable_body': 'Saving creates or updates the current draft in place. Use the live actions above to upload samples, test the draft, export or import YAML, and publish when the upstream contract is ready.',
'wizard.live.failed_title': 'Live action failed',
'wizard.live.failed_body': 'The backend request failed. Review the draft and try again.',
'wizard.error.no_workspace': 'No workspace selected',
'wizard.error.tool_name': 'Tool name is required',
'wizard.error.parser_unavailable': 'YAML parser is not available',
'wizard.error.select_upstream': 'Select or register an upstream first',
'wizard.error.auth_profile_required': 'Select an auth profile or switch upstream auth to No auth.',
'wizard.error.auth_profile_name': 'Auth profile name is required.',
'wizard.error.secret_required': 'Select the secret required by this auth profile.',
'wizard.error.basic_secrets_required': 'Select both username and password secrets.',
'wizard.error.header_name_required': 'Header name is required for this auth profile.',
'wizard.error.query_param_required': 'Query parameter name is required for this auth profile.',
'wizard.error.secret_name_required': 'Secret name is required.',
'wizard.error.secret_value_required': 'Secret value is required.',
'wizard.error.save_upstream_first': 'Save the upstream after creating a new auth profile before continuing.',
'wizard.toast.auth_profile_created_title': 'Auth profile created',
'wizard.toast.auth_profile_created_body': 'New upstream auth profile is ready and selected.',
'wizard.toast.auth_profile_error_title': 'Auth profile creation failed',
'wizard.toast.quick_secret_created_title': 'Secret created',
'wizard.toast.quick_secret_created_body': 'The new secret is now available in the auth selector.',
'wizard.toast.quick_secret_error_title': 'Secret creation failed',
'wizard.error.save': 'Failed to save operation',
'wizard.error.save_title': 'Save failed',
'wizard.save.created': 'Draft created',
'wizard.save.updated': 'Draft updated',
'wizard.save.body': 'The current draft is saved on the backend and ready for live actions in this wizard.',
'wizard.save.saved': 'Saved ✓',
'wizard.busy.input_sample': 'Saving input sample…',
'wizard.busy.output_sample': 'Saving output sample…',
'wizard.busy.generate': 'Generating draft…',
'wizard.busy.test': 'Running test…',
'wizard.busy.export_yaml': 'Exporting YAML…',
'wizard.busy.import_yaml': 'Importing YAML…',
'wizard.busy.publish': 'Publishing operation…',
'wizard.busy.descriptor': 'Uploading descriptor set…',
'wizard.busy.wsdl': 'Inspecting WSDL…',
'wizard.sample.input_saved': 'Input sample saved',
'wizard.sample.input_saved_body': 'The input sample is now stored against the current draft version.',
'wizard.sample.output_saved': 'Output sample saved',
'wizard.sample.output_saved_body': 'The output sample is now stored against the current draft version.',
'wizard.sample.generated': 'Draft generated',
'wizard.sample.generated_body': 'Schemas and mappings were regenerated from the saved samples.',
'wizard.test.completed': 'Test run completed',
'wizard.test.failed': 'Test run returned errors',
'wizard.test.completed_body': 'The current draft executed successfully against the upstream.',
'wizard.test.failed_body': 'The draft executed, but the backend returned validation or runtime errors.',
'wizard.test.window_completed': 'Window test completed',
'wizard.test.window_completed_body': 'Collected a bounded result window.',
'wizard.test.window_truncated_note': 'Results were truncated.',
'wizard.test.window_more_note': 'More data is available if you continue from the streaming views.',
'wizard.test.window_incomplete_note': 'The upstream stream ended before the configured window was fully collected.',
'wizard.test.websocket_window_completed': 'WebSocket window test completed',
'wizard.test.websocket_window_completed_body': 'Collected {items} WebSocket event(s) inside the bounded test window.',
'wizard.test.websocket_window_truncated_note': 'The bounded window stopped after reaching the configured item or byte limit.',
'wizard.test.websocket_window_more_note': 'The socket produced more events than this test window retained. Increase limits or switch to session mode if you need a longer stream.',
'wizard.test.websocket_window_incomplete_note': 'The upstream socket closed before the bounded window finished collecting all configured events.',
'wizard.test.websocket_session_started': 'WebSocket session test started',
'wizard.test.websocket_session_started_body': 'WebSocket session {session_id} was created. Continue from Stream sessions after a short delay.',
'wizard.test.websocket_async_job_started': 'WebSocket async job test started',
'wizard.test.websocket_async_job_started_body': 'WebSocket async job {job_id} was created. Continue from the async jobs view for status and result.',
'wizard.test.websocket_failed': 'WebSocket test returned errors',
'wizard.test.websocket_failed_body': 'The socket test failed before Crank could collect a bounded result window.',
'wizard.test.websocket_failed_reconnect_body': 'The upstream socket closed before enough events were collected, and the configured reconnect policy was exhausted.',
'wizard.test.websocket_failed_closed_body': 'The upstream socket closed before the bounded test window completed.',
'wizard.test.session_started': 'Session test started',
'wizard.test.session_started_body': 'Session {session_id} was created. Check Stream sessions in a few seconds.',
'wizard.test.async_job_started': 'Async job test started',
'wizard.test.async_job_started_body': 'Async job {job_id} was created. Track status and result from the async jobs view.',
'wizard.test.no_response': 'No test response yet',
'wizard.test.no_response_body': 'Run a test first, then copy the response preview into the output sample editor.',
'wizard.test.response_copied': 'Response copied',
'wizard.test.response_copied_body': 'The latest test response was copied into the output sample editor.',
'wizard.boolean.yes': 'yes',
'wizard.boolean.no': 'no',
'wizard.yaml.exported': 'YAML exported',
'wizard.yaml.exported_body': 'The current draft YAML was downloaded from the backend.',
'wizard.yaml.none': 'No YAML provided',
'wizard.yaml.none_body': 'Paste a YAML document or load it from a file before importing.',
'wizard.yaml.imported': 'YAML imported',
'wizard.yaml.imported_body': 'The imported operation draft was stored. The wizard will reopen it now.',
'wizard.yaml.loaded': 'YAML loaded',
'wizard.yaml.loaded_body': 'The YAML document was loaded into the import editor.',
'wizard.publish.done': 'Operation published',
'wizard.publish.done_body': 'Version {version} is now published and can be bound into agents.',
'wizard.grpc.discovery_title': 'gRPC discovery moved to live descriptor flow',
'wizard.grpc.discovery_body': 'Server reflection is not wired in this deployment. Save the draft, then use the descriptor-set tools in Step 5 to upload a real descriptor set and discover unary methods from the backend.',
'wizard.grpc.no_methods': 'No unary RPC methods found.',
'wizard.grpc.fields_unresolved': 'Fields not resolved.',
'wizard.grpc.fields_unresolved_body': 'Import or external types are not expanded client-side.',
'wizard.grpc.services_found': 'Found {services_label} and {methods_label}.',
'wizard.grpc.services_label.one': '{count} service',
'wizard.grpc.services_label.other': '{count} services',
'wizard.grpc.methods_label.one': '{count} unary method',
'wizard.grpc.methods_label.other': '{count} unary methods',
'wizard.grpc.services_discovered': '{services} services · {methods} unary methods discovered',
'wizard.grpc.services_none': 'No unary methods discovered yet',
// Common buttons
'btn.save': 'Save changes',
'btn.cancel': 'Cancel',
'btn.create': 'Create',
'btn.delete': 'Delete',
'btn.revoke': 'Revoke',
'btn.copy': 'Copy',
// Agents page
'agents.title': 'Agents',
'agents.subtitle': 'Named MCP endpoints that expose a curated subset of operations to an LLM',
'agents.subtitle.community': 'Dedicated MCP endpoints for AI agents. Community keeps machine access on static agent keys.',
'agents.new': 'New agent',
'agents.limit.title': 'Agent limit reached',
'agents.limit.message': 'This edition allows up to {limit} AI agent. Remove an existing agent to add another one.',
'agents.stats.total': 'Total agents',
'agents.stats.total_sub': 'across this workspace',
'agents.stats.published': 'Published',
'agents.stats.published_sub': '{value}% of total',
'agents.stats.tools': 'Operations exposed',
'agents.stats.tools_sub': 'across {count} agents',
'agents.stats.calls': 'Calls today',
'agents.stats.calls_sub': 'all agents combined',
'agents.search': 'Search agents...',
'agents.results': '{shown} of {total} agents',
'agents.callout.default': 'Each agent gets its own MCP endpoint. Connect your LLM client to a specific agent so it only sees the tools it needs — not all {count} operations in this workspace.',
'agents.callout.community': 'Each agent gets its own MCP endpoint and static agent keys in Community. Connect your LLM client to a specific agent so it only sees the tools it needs — not all {count} operations in this workspace.',
'agents.loading': 'Loading agents…',
'agents.error.title': 'Failed to load agents',
'agents.error.api': 'Workspace or API is unavailable',
'agents.error.load': 'Failed to load agents',
'agents.empty.initial.title': 'No agents yet',
'agents.empty.initial.sub': 'Create your first agent to get a dedicated MCP endpoint with a curated set of tools.',
'agents.empty.initial.sub_community': 'Create your first agent to get one dedicated MCP endpoint and its static machine-access keys for a focused toolset.',
'agents.empty.initial.next_step': 'After creation, issue separate machine-access keys for this endpoint on the API Keys page.',
'agents.empty.search.title': 'No agents match "{query}"',
'agents.empty.search.sub': 'Try a different search term.',
'agents.card.tools': 'tools',
'agents.card.keys': 'keys',
'agents.card.calls_today': 'calls today',
'agents.card.created': 'Created {date}',
'agents.card.copy_endpoint': 'Copy endpoint',
'agents.card.endpoint_help': 'Use this MCP endpoint in your client and manage its machine-access keys on the API Keys page.',
'agents.card.endpoint_help_community': 'Use this MCP endpoint in your client. Community keeps machine access on static agent keys managed on the API Keys page.',
'agents.action.edit': 'Edit',
'agents.action.archive': 'Archive',
'agents.action.delete': 'Delete',
'agents.lifecycle.publish': 'Publish',
'agents.lifecycle.unpublish': 'Unpublish',
'agents.lifecycle.restore_draft': 'Restore draft',
'agents.lifecycle.published': 'Published',
'agents.lifecycle.archived': 'Archived',
'agents.lifecycle.draft': 'Draft',
'agents.drawer.new_title': 'New agent',
'agents.drawer.edit_title': 'Edit agent',
'agents.drawer.new_sub': 'Configure a named MCP endpoint for your LLM',
'agents.drawer.new_sub_community': 'Configure one MCP endpoint and its static agent-key boundary for a focused LLM use case',
'agents.drawer.edit_sub': 'Update agent settings and tool selection',
'agents.drawer.identity': 'Identity',
'agents.drawer.display_name': 'Display name',
'agents.drawer.slug': 'Slug',
'agents.drawer.description': 'Description',
'agents.drawer.optional': '(optional)',
'agents.drawer.required': 'required',
'agents.drawer.status': 'Status',
'agents.drawer.endpoint': 'MCP endpoint',
'agents.drawer.slug_hint': 'Keep the slug stable after clients start using this endpoint. Changing it changes the MCP path.',
'agents.drawer.placeholder.name': 'Customer Support',
'agents.drawer.placeholder.slug': 'customer-support',
'agents.drawer.placeholder.description': 'What does this agent do? What LLM or use-case is it for?',
'agents.drawer.operations': 'Operations',
'agents.drawer.selected': '{count} selected',
'agents.drawer.operations_sub': 'Select which operations this agent exposes. LLMs connecting to this agent will only see these tools.',
'agents.drawer.operations_sub_community': 'Select which operations this agent exposes. Any keys issued for this agent only work against this MCP endpoint.',
'agents.drawer.filter_ops': 'Filter operations…',
'agents.drawer.ops_no_match': 'No operations match "{query}"',
'agents.drawer.ops_selected': '{count} operations selected',
'agents.drawer.clear_all': 'Clear all',
'agents.drawer.recommendation': "You've selected {count} tools. LLMs usually work best when an agent has fewer than 15 tools. Consider splitting this into separate agents by use case.",
'agents.drawer.cancel': 'Cancel',
'agents.drawer.create': 'Create agent',
'agents.drawer.save': 'Save changes',
'agents.toast.saved_title_create': 'Agent created',
'agents.toast.saved_title_update': 'Agent updated',
'agents.toast.saved_message': '{name} was saved with {count} tool bindings.',
'agents.toast.save_error_title': 'Agent update failed',
'agents.toast.save_error_message': 'Failed to save agent',
'agents.toast.delete_confirm': 'Delete this agent? This cannot be undone.',
'agents.toast.delete_title': 'Agent deleted',
'agents.toast.delete_message': '{name} was deleted.',
'agents.toast.delete_error_title': 'Delete failed',
'agents.toast.delete_error_message': 'Failed to delete agent',
'agents.toast.lifecycle_title': 'Agent lifecycle updated',
'agents.toast.lifecycle_publish': '{name} was published.',
'agents.toast.lifecycle_unpublish': '{name} was returned to draft.',
'agents.toast.lifecycle_archive': '{name} was archived.',
'agents.toast.lifecycle_error_title': 'Lifecycle update failed',
'agents.toast.lifecycle_error_message': 'Failed to update agent lifecycle',
'agents.toast.endpoint_title': 'MCP endpoint copied',
// Demo content
'demo.agent.revops-copilot.display_name': 'RevOps Copilot',
'demo.agent.revops-copilot.description': 'Revenue operations assistant with CRM and billing tools.',
'demo.agent.support-triage.display_name': 'Support Triage',
'demo.agent.support-triage.description': 'Draft support agent focused on unary gRPC workflows.',
'demo.operation.crm_create_lead.display_name': 'Create CRM Lead',
'demo.operation.crm_create_lead.description': 'Create a lead record in the CRM system.',
'demo.operation.billing_get_invoice.display_name': 'Get Invoice Status',
'demo.operation.billing_get_invoice.description': 'Fetch invoice state and amount from billing.',
'demo.operation.support_lookup_ticket.display_name': 'Lookup Support Ticket',
'demo.operation.support_lookup_ticket.description': 'Draft unary gRPC support lookup using a descriptor set.',
'demo.operation.marketing_archive_contact.display_name': 'Archive Marketing Contact',
'demo.operation.marketing_archive_contact.description': 'Legacy archived flow kept for audit only.',
// Login
'login.title': 'Sign in',
'login.subtitle': 'Welcome back to your workspace',
'login.email': 'Email',
'login.password': 'Password',
'login.submit': 'Sign in',
'login.email_label': 'Email address',
'login.email_placeholder': 'you@acme.com',
'login.password_only': 'Sign in with email and password.',
'login.error.required': 'Please enter your email and password.',
'login.error.invalid': 'Invalid email or password. Please try again.',
'login.error.generic': 'Unable to sign in right now. Please try again.',
},
ru: {
// Nav
'nav.operations': 'Операции',
'nav.agents': 'Агенты',
'nav.apikeys': 'API Ключи',
'nav.secrets': 'Секреты',
'nav.logs': 'Логи',
'nav.usage': 'Использование',
'nav.settings': 'Настройки',
'nav.logout': 'Выйти',
'nav.workspaces': 'Ваши воркспейсы',
'nav.notifications': 'Уведомления',
'nav.menu': 'Меню',
'nav.account': 'Аккаунт',
'nav.user_fallback': 'Crank',
'nav.workspace_fallback': 'workspace',
// Operations page
'ops.title': 'Операции',
'ops.subtitle': 'Контракты инструментов, открывающие ваши API как MCP-инструменты',
'ops.new': 'Новая операция',
'ops.loading': 'Загрузка операций…',
'ops.empty.title': 'Операции не найдены',
'ops.empty.sub': 'Попробуйте изменить поисковый запрос или фильтры.',
'ops.empty.initial.title': 'Операций пока нет',
'ops.empty.initial.sub': 'Создайте первую операцию в этом воркспейсе, чтобы открыть живой MCP-инструмент.',
'ops.error.title': 'Не удалось подключиться к backend',
'ops.delete.confirm': 'Удалить эту операцию? Действие нельзя отменить.',
'ops.delete.success.title': 'Операция удалена',
'ops.delete.success.message': 'Операция {name} удалена.',
'ops.delete.error.title': 'Не удалось удалить',
'ops.delete.error.message': 'Не удалось удалить операцию',
'ops.action.edit': 'Редактировать операцию',
'ops.action.delete':'Удалить операцию',
'ops.stats.synced': 'Каталог синхронизирован с backend',
'ops.stats.none': 'Операций пока нет',
'ops.stats.share': '{value}% от общего числа',
'ops.stats.no_published': 'Опубликованных инструментов пока нет',
'ops.stats.requests_rollup': 'Сумма по статистике операций',
'ops.stats.no_traffic': 'Трафик сегодня не зафиксирован',
'ops.stats.latency_weighted': 'Взвешено по сегодняшним вызовам',
'ops.stats.latency_waiting': 'Ожидание runtime-метрик',
'ops.results': 'Показано {shown} из {total}',
'ops.range': 'Показано {from}{to} из {total} операций',
'ops.filter.protocol': 'Протокол: {value}',
'ops.filter.category': 'Категория: {value}',
'ops.filter.agent': 'Агент: {value}',
'ops.no_active_agents': 'Нет активных агентов',
// Stats
'stats.total': 'Всего операций',
'stats.active': 'Активные',
'stats.requests': 'Запросов сегодня',
'stats.latency': 'Ср. задержка',
// Tabs
'tab.all': 'Все',
'tab.active': 'Активные',
'tab.draft': 'Черновик',
'tab.error': 'Ошибка',
'tab.inactive': 'Неактивные',
// Filters & sort
'filter.search': 'Поиск операций...',
'filter.protocol': 'Протокол',
'filter.status': 'Статус',
'filter.category': 'Категория',
'filter.agent': 'Агент',
'filter.showing': 'Показано',
'filter.of': 'из',
'filter.clear': 'Сбросить',
'filter.clear_all': 'Сбросить все',
'filter.remove': 'Убрать фильтр',
'sort.created_desc':'Сначала новые',
'sort.created_asc': 'Сначала старые',
'sort.name_asc': 'Название А–Я',
'sort.name_desc': 'Название Я–А',
// Table headers
'th.name': 'НАЗВАНИЕ',
'th.protocol': 'ПРОТОКОЛ',
'th.status': 'СТАТУС',
'th.created': 'СОЗДАН',
'th.url': 'ЦЕЛЕВОЙ URL',
// Pagination
'page.showing': 'Показано',
'page.of': 'из',
'page.ops': 'операций',
// API Keys page
'apikeys.title': 'Ключи агентов',
'apikeys.subtitle': 'Ключи аутентифицируют внешние MCP-клиенты для конкретного endpoint AI-агента.',
'apikeys.new': 'Создать ключ',
'apikeys.agent.title': 'Доступ агента',
'apikeys.agent.subtitle': 'Выберите AI-агента, чей MCP endpoint должен принимать этот ключ.',
'apikeys.agent.label': 'AI-агент',
'apikeys.agent.empty': 'Агенты отсутствуют',
'apikeys.agent.empty_hint': 'Сначала создайте AI-агента, затем выпустите машинный ключ для его MCP endpoint.',
'apikeys.agent.endpoint': 'MCP endpoint: {endpoint}',
'apikeys.agent.endpoint_missing': 'У выбранного агента пока нет опубликованного MCP endpoint.',
'apikeys.machine_access.title': 'Режимы машинного доступа',
'apikeys.machine_access.title_community': 'Машинный доступ Community',
'apikeys.machine_access.subtitle': 'Эта сборка показывает стабильный контракт машинного доступа и сразу отражает доступные режимы.',
'apikeys.machine_access.subtitle_community': 'Community публикует стабильный MCP-контракт машинного доступа, но в этой сборке активны только статические ключи AI-агентов.',
'apikeys.machine_access.summary_default': 'Community сейчас поддерживает только статические ключи AI-агентов.',
'apikeys.machine_access.summary': 'Эта сборка поддерживает {access_modes} для операций с уровнями защиты {security_levels}. При включении коммерческой редакции short-lived и one-time token issuance используют тот же public HTTP surface.',
'apikeys.machine_access.note': 'Выдача short-lived и one-time token использует тот же public HTTP surface, но требует коммерческую редакцию.',
'apikeys.machine_access.note_community': 'В Community используйте статические ключи AI-агентов. Short-lived и one-time token требуют коммерческую редакцию.',
'apikeys.th.name': 'НАЗВАНИЕ',
'apikeys.th.scope': 'ОБЛАСТЬ',
'apikeys.th.created':'СОЗДАН',
'apikeys.th.last': 'ПОСЛЕДНЕЕ ИСПОЛЬЗОВАНИЕ',
'apikeys.callout.title': 'Ключ показывается только один раз.',
'apikeys.callout.body': 'Скопируйте и сохраните ключ сразу после создания — Crank хранит только хэш. Поле «Последнее использование» обновляется после успешного MCP-вызова.',
'apikeys.active.title': 'Активные ключи',
'apikeys.active.subtitle': '{active} активных · {revoked} отозванных',
'apikeys.search': 'Фильтр ключей…',
'apikeys.th.prefix': 'ПРЕФИКС КЛЮЧА',
'apikeys.th.scopes': 'ОБЛАСТИ',
'apikeys.th.status': 'СТАТУС',
'apikeys.scope_ref': 'Справка по областям',
'apikeys.scope.read': 'Инициализация MCP-сессий, ping сервера и получение списка инструментов для выбранного AI-агента.',
'apikeys.scope.write': 'Выполнение `tools/call` для опубликованных наборов инструментов агента.',
'apikeys.scope.deploy': 'Зарезервировано для deploy-автоматизации. Сейчас также разрешает MCP read/write сценарии.',
'apikeys.modal.title': 'Создать ключ агента',
'apikeys.modal.name': 'Имя ключа',
'apikeys.modal.name_hint': 'Понятная метка для идентификации ключа. Видна только администраторам.',
'apikeys.modal.name_placeholder': 'например, Production, CI pipeline',
'apikeys.modal.scopes': 'Области',
'apikeys.modal.reveal_title': 'Скопируйте ключ сейчас.',
'apikeys.modal.reveal_body': 'Повторно он не будет показан.',
'apikeys.modal.copy': 'Скопировать',
'apikeys.modal.cancel': 'Отмена',
'apikeys.modal.create': 'Создать ключ',
'apikeys.modal.done': 'Готово — ключ скопирован',
'apikeys.status.active': 'Активен',
'apikeys.status.revoked': 'Отозван',
'apikeys.last_used.never': 'Никогда',
'apikeys.empty.none': 'API-ключей пока нет',
'apikeys.empty.search': 'Нет ключей по текущему поиску',
'apikeys.loading': 'Загрузка…',
'apikeys.error.api': 'Воркспейс или API недоступен',
'apikeys.error.load': 'Не удалось загрузить API-ключи',
'apikeys.confirm.revoke': 'Отозвать этот ключ? Все запросы с ним сразу начнут падать.',
'apikeys.confirm.delete': 'Удалить этот ключ? Действие нельзя отменить.',
'apikeys.toast.revoke_title': 'Ключ отозван',
'apikeys.toast.revoke_message': 'Ключ {name} отозван.',
'apikeys.toast.revoke_error_title': 'Не удалось отозвать',
'apikeys.toast.revoke_error_message': 'Не удалось отозвать ключ',
'apikeys.toast.delete_title': 'Ключ удален',
'apikeys.toast.delete_message': 'Ключ {name} удален.',
'apikeys.toast.delete_error_title': 'Не удалось удалить',
'apikeys.toast.delete_error_message': 'Не удалось удалить ключ',
'apikeys.toast.scope_missing_title': 'Не выбрана область',
'apikeys.toast.scope_missing_message': 'Выберите хотя бы одну область перед созданием ключа.',
'apikeys.toast.create_title': 'Ключ агента создан',
'apikeys.toast.create_message': 'Скопируйте ключ сейчас. Повторно он не будет показан.',
'apikeys.toast.create_error_title': 'Не удалось создать ключ',
'apikeys.toast.create_error_message': 'Не удалось создать ключ',
'apikeys.toast.copy_title': 'Ключ агента скопирован',
'apikeys.toast.copy_message': 'Сохраните исходный ключ в надежном месте. Повторно показать его нельзя.',
'apikeys.toast.prefix_title': 'Префикс ключа скопирован',
'apikeys.action.copy_prefix': 'Скопировать префикс ключа',
'apikeys.action.revoke': 'Отозвать ключ',
'apikeys.action.delete': 'Удалить',
'apikeys.creating': 'Создание…',
// Secrets page
'secrets.title': 'Секреты',
'secrets.subtitle': 'Зашифрованные учетные данные для upstream REST API.',
'secrets.new': 'Создать секрет',
'secrets.callout.title': 'Plaintext секрета доступен только на запись.',
'secrets.callout.body': 'Crank шифрует значения секретов мастер-ключом воркспейса. После создания или ротации API возвращает только метаданные, поэтому на этой странице доступны управление секретами и просмотр их использования.',
'secrets.active.title': 'Секреты воркспейса',
'secrets.active.subtitle': '{active} активных · {total} всего',
'secrets.search': 'Фильтр секретов…',
'secrets.th.name': 'НАЗВАНИЕ',
'secrets.th.kind': 'ТИП',
'secrets.th.version': 'ВЕРСИЯ',
'secrets.th.last_used': 'ПОСЛЕДНЕЕ ИСПОЛЬЗОВАНИЕ',
'secrets.th.used_by': 'ИСПОЛЬЗУЕТСЯ В',
'secrets.th.status': 'СТАТУС',
'secrets.kind.token': 'Токен',
'secrets.kind.username_password': 'Логин/пароль',
'secrets.kind.header': 'Значение заголовка',
'secrets.kind.generic': 'Общий JSON',
'secrets.status.active': 'Активен',
'secrets.status.disabled': 'Отключен',
'secrets.last_used.never': 'Никогда',
'secrets.used_by_count.one': '{count} привязка',
'secrets.used_by_count.few': '{count} привязки',
'secrets.used_by_count.many': '{count} привязок',
'secrets.used_by_count.other': '{count} привязки',
'secrets.loading': 'Загрузка…',
'secrets.empty.none': 'Секретов пока нет',
'secrets.empty.search': 'Нет секретов по текущему поиску',
'secrets.error.workspace': 'Воркспейс не выбран',
'secrets.error.load': 'Не удалось загрузить секреты',
'secrets.action.rotate': 'Ротировать',
'secrets.action.delete': 'Удалить',
'secrets.confirm.delete': 'Удалить секрет {name}? Это нельзя сделать, пока секрет используется в операции.',
'secrets.toast.create_title': 'Секрет создан',
'secrets.toast.create_message': 'Секрет сохранен в зашифрованном виде. Повторно посмотреть plaintext через API нельзя.',
'secrets.toast.create_error_title': 'Не удалось создать секрет',
'secrets.toast.create_error_message': 'Не удалось создать секрет',
'secrets.toast.rotate_title': 'Секрет ротирован',
'secrets.toast.rotate_message': 'Новая версия секрета теперь активна.',
'secrets.toast.rotate_error_title': 'Не удалось ротировать секрет',
'secrets.toast.rotate_error_message': 'Не удалось ротировать секрет',
'secrets.toast.delete_title': 'Секрет удален',
'secrets.toast.delete_message': 'Секрет {name} удален.',
'secrets.toast.delete_error_title': 'Не удалось удалить секрет',
'secrets.toast.delete_error_message': 'Не удалось удалить секрет',
'secrets.modal.create_title': 'Создать секрет',
'secrets.modal.rotate_title': 'Ротировать секрет',
'secrets.modal.name': 'Имя секрета',
'secrets.modal.name_hint': 'Используйте стабильную операторскую метку. Plaintext после сохранения не возвращается.',
'secrets.modal.kind': 'Тип секрета',
'secrets.modal.value': 'Значение секрета',
'secrets.modal.header_value': 'Значение заголовка',
'secrets.modal.username': 'Логин',
'secrets.modal.password': 'Пароль',
'secrets.modal.json_payload': 'JSON payload',
'secrets.modal.json_hint': 'Подходит для общих secret payloads. Значение должно быть валидным JSON.',
'secrets.modal.create_note': 'Создание сохраняет зашифрованный plaintext и возвращает только metadata.',
'secrets.modal.rotate_note': 'Ротация создает новую зашифрованную версию и сохраняет тот же secret id.',
'secrets.modal.cancel': 'Отмена',
'secrets.modal.create_action': 'Создать секрет',
'secrets.modal.rotate_action': 'Ротировать секрет',
'secrets.modal.creating': 'Создание…',
'secrets.modal.rotating': 'Ротация…',
'secrets.hint.token': 'Лучший вариант для bearer tokens и простых API key values.',
'secrets.hint.username_password': 'Хранит логин и пароль как один зашифрованный payload.',
'secrets.hint.header': 'Хранит сырое значение заголовка. Имя заголовка задается в мастере операции.',
'secrets.hint.generic': 'Хранит произвольный JSON для нестандартных интеграций, которым нужен структурированный секрет.',
'secrets.validation.name_required': 'Нужно указать имя секрета.',
'secrets.validation.value_required': 'Нужно указать значение секрета.',
'secrets.validation.username_password_required': 'Нужно указать и логин, и пароль.',
'secrets.validation.json_invalid': 'Generic secret payload должен быть валидным JSON.',
// Logs page
'logs.title': 'Логи',
'logs.subtitle': 'Живой журнал вызовов по всем операциям этого воркспейса.',
'logs.search': 'Поиск в логах...',
'logs.level.all': 'Все',
'logs.level.info': 'Info',
'logs.level.warn': 'Warn',
'logs.level.error': 'Error',
'logs.level.debug': 'Debug',
'logs.live': 'Live',
'logs.paused': 'Пауза',
'logs.refresh': 'Обновить',
'logs.range.30m': 'Последние 30 мин',
'logs.range.1h': 'Последний час',
'logs.range.6h': 'Последние 6 часов',
'logs.range.24h': 'Последние 24 часа',
'logs.range.7d': 'Последние 7 дней',
'logs.search_messages': 'Поиск по сообщениям…',
'logs.detail.agent': 'Агент',
'logs.detail.request': 'Предпросмотр запроса',
'logs.detail.response': 'Предпросмотр ответа',
'logs.detail.meta': 'Метаданные',
'logs.loading.title': 'Загрузка логов…',
'logs.loading.sub': 'Получаем историю вызовов для текущего воркспейса.',
'logs.error.title': 'Не удалось загрузить логи',
'logs.error.api': 'Admin API недоступен',
'logs.error.workspace': 'Воркспейс не выбран',
'logs.error.load': 'Не удалось загрузить логи',
'logs.empty.title': 'Записей логов не найдено',
'logs.empty.filtered': 'Попробуйте расширить период или сбросить текущий поиск и фильтры уровня.',
'logs.empty.initial': 'Запустите тесты или опубликованные MCP-инструменты, чтобы начать собирать историю вызовов.',
'logs.live.on.title': 'Live режим включен',
'logs.live.on.body': 'Логи опрашиваются каждые 4 секунды.',
'logs.live.off.title': 'Live режим на паузе',
'logs.live.off.body': 'Автоматический опрос остановлен.',
'logs.refresh.title': 'Логи обновлены',
'logs.refresh.body': 'Получены последние записи вызовов для текущего воркспейса.',
// Usage page
'usage.title': 'Использование',
'usage.subtitle': 'Метрики вызовов по всем операциям этого воркспейса.',
'usage.export': 'Экспорт CSV',
'usage.period.7d': 'Последние 7 дней',
'usage.period.30d': 'Последние 30 дней',
'usage.period.90d': 'Последние 90 дней',
'usage.period.this_month': 'Этот месяц',
'usage.period.label.7d': 'последние 7 дней',
'usage.period.label.30d': 'последние 30 дней',
'usage.period.label.90d': 'последние 90 дней',
'usage.period.label.this_month': 'этот месяц',
'usage.stats.total': 'Всего вызовов',
'usage.stats.success': 'Доля успешных',
'usage.stats.p50': 'Медианная задержка (p50)',
'usage.stats.p99': 'Задержка p99',
'usage.stats.across': 'За период: {period}',
'usage.stats.success_calls': 'Успешных вызовов: {count}',
'usage.stats.median': 'Медианная задержка за выбранный период',
'usage.stats.error_calls': 'Ошибочных вызовов: {count}',
'usage.chart.title': 'Вызовы во времени',
'usage.chart.success': 'Успех',
'usage.chart.error': 'Ошибка',
'usage.chart.empty.title': 'Данных по использованию пока нет',
'usage.chart.empty.sub': 'Метрики появятся здесь после тестов или вызовов опубликованных инструментов за выбранный период.',
'usage.table.title': 'По операциям',
'usage.table.subtitle': 'Разбивка за период: {period}',
'usage.table.th.operation': 'Операция',
'usage.table.th.protocol': 'Протокол',
'usage.table.th.calls': 'Вызовы',
'usage.table.th.errors': 'Ошибки',
'usage.table.th.error_rate': 'Доля ошибок',
'usage.table.th.latency': 'Задержка (p50 / p95 / p99)',
'usage.table.th.share': 'Доля трафика',
'usage.table.empty.title': 'Разбивки по операциям пока нет',
'usage.table.empty.sub': 'За выбранный период нет вызовов, которые можно разложить по операциям.',
'usage.table.share': '{value}% трафика воркспейса',
'usage.loading.title': 'Загрузка использования…',
'usage.loading.sub': 'Собираем метрики вызовов для выбранного воркспейса.',
'usage.error.title': 'Не удалось загрузить использование',
'usage.error.api': 'Admin API недоступен',
'usage.error.workspace': 'Воркспейс не выбран',
'usage.error.load': 'Не удалось загрузить метрики использования',
'usage.export.empty.title': 'Данные по использованию не загружены',
'usage.export.empty.body': 'Сначала загрузите данные использования, а потом экспортируйте снимок CSV.',
'usage.export.done.title': 'Использование экспортировано',
'usage.export.done.body': 'Текущий снимок использования экспортирован в CSV.',
'usage.chart.ok': 'Успешных: {count}',
'usage.chart.errors': 'Ошибок: {count}',
'usage.chart.week': 'Нед. {index}',
'usage.csv.header': 'Операция,Протокол,Вызовы,Ошибки,Доля ошибок (%),p50 (мс),p95 (мс),p99 (мс)',
// Settings sidebar
'settings.title': 'Настройки',
'settings.nav.workspace': 'Воркспейс',
'settings.nav.profile': 'Профиль',
'settings.nav.security': 'Безопасность',
'settings.nav.notif': 'Уведомления',
'settings.nav.danger': 'Опасная зона',
// Settings — workspace section
'settings.ws.title': 'Настройки воркспейса',
'settings.ws.subtitle': 'Общая информация о вашем воркспейсе',
'settings.ws.name': 'Имя воркспейса',
'settings.ws.display': 'Отображаемое имя',
'settings.ws.desc': 'Описание',
// Settings — language
'settings.lang.title': 'Язык',
'settings.lang.subtitle': 'Язык отображения интерфейса',
'settings.lang.en': 'English',
'settings.lang.ru': 'Русский',
// Settings — profile section
'settings.profile.title': 'Профиль',
'settings.profile.name': 'Полное имя',
'settings.profile.email': 'Email',
'settings.profile.role': 'Роль',
'settings.page.title': 'Настройки аккаунта',
'settings.page.subtitle': 'Управляйте профилем, паролем, настройками воркспейса и границами возможностей текущей сборки.',
'settings.ws.slug_hint': 'Используется в API endpoint-ах и по всей консоли. Только строчные буквы, цифры и дефисы.',
'settings.ws.description_optional': 'Описание (необязательно)',
'settings.ws.description_placeholder': 'Чем занимается этот воркспейс?',
'settings.profile.avatar_hint': 'Аватар строится из отображаемого имени и email.',
'settings.profile.backend_hint': 'Имя и email хранятся в backend и используются по всей консоли и в сессионной идентичности.',
'settings.profile.first_name': 'Имя',
'settings.profile.last_name': 'Фамилия',
'settings.profile.last_name_placeholder': 'Необязательно',
'settings.profile.email_label': 'Email адрес',
'settings.profile.email_hint': 'Изменение email обновляет логин для текущего аккаунта.',
'settings.profile.save': 'Сохранить профиль',
'settings.profile.saving': 'Сохранение…',
'settings.profile.saved': 'Сохранено ✓',
'settings.profile.saved_status': 'Профиль сохранен.',
'settings.profile.save_error': 'Не удалось сохранить профиль',
'settings.security.title': 'Безопасность',
'settings.security.current_password': 'Текущий пароль',
'settings.security.new_password': 'Новый пароль',
'settings.security.new_password_placeholder': 'минимум 12 символов',
'settings.security.confirm_password': 'Подтвердите новый пароль',
'settings.security.change_password': 'Сменить пароль',
'settings.security.mismatch': 'Новый пароль и подтверждение не совпадают.',
'settings.security.saved': 'Пароль обновлен.',
'settings.security.save_error': 'Не удалось изменить пароль',
'settings.security.capabilities_title': 'Сводка по возможностям сборки',
'settings.security.capabilities_body': 'Этот блок показывает, что поддерживает текущая сборка. Сейчас доступен вход по паролю с одной HttpOnly browser session; короткоживущие токены, одноразовые токены и расширенное управление доступом зависят от редакции.',
'settings.security.not_available': 'Возможности сборки',
'settings.security.capability_title_community': 'Редакция Community',
'settings.security.capability_title_enterprise': 'Редакция Enterprise',
'settings.security.capability_title_cloud': 'Редакция Cloud',
'settings.security.capability_summary': 'В этой сборке доступны {protocols}, {access_modes} и уровень защиты операций {security_levels}. Лимиты: {max_workspaces} воркспейс, {max_users} пользователь, {max_agents} AI-агент. Короткоживущие токены, одноразовые токены и расширенное управление доступом требуют коммерческую редакцию.',
'settings.capability.rest': 'REST / HTTP',
'settings.capability.graphql': 'GraphQL',
'settings.capability.grpc': 'gRPC unary',
'settings.capability.websocket': 'WebSocket',
'settings.capability.soap': 'SOAP',
'settings.capability.standard': 'standard',
'settings.capability.elevated': 'elevated',
'settings.capability.strict': 'strict',
'settings.capability.static_agent_key': 'статические ключи агентов',
'settings.capability.short_lived_token': 'короткоживущие токены',
'settings.capability.one_time_token': 'одноразовые токены',
'settings.capability.unlimited': 'без лимита',
'settings.capability.none': 'нет',
'settings.session.title': 'Текущая сессия',
'settings.session.browser': 'Сессия браузера',
'settings.session.current': 'текущая',
'settings.session.loading': 'Загрузка деталей текущей сессии…',
'settings.session.unavailable': 'Детали сессии недоступны. Используйте «Выйти», чтобы отозвать текущую browser session.',
'settings.session.current_workspace': 'текущий воркспейс',
'settings.session.summary': '{email} · {role} в {workspace}. Используйте «Выйти», чтобы отозвать текущую browser session.',
'settings.notifications.title': 'Уведомления',
'settings.notifications.not_ready_title': 'Не входит в эту сборку.',
'settings.notifications.not_ready_body': 'Маршрутизация уведомлений и персональные настройки находятся вне текущей поверхности Community.',
'settings.notifications.capability_title_community': 'Граница редакции Community',
'settings.notifications.capability_body_community': 'Персональные каналы доставки и маршрутизация уведомлений не входят в текущую поверхность Community. В этой сборке для операционного контроля используйте журналы и представления использования.',
'settings.notifications.capability_title_enterprise': 'Контур доставки Enterprise',
'settings.notifications.capability_body_enterprise': 'Маршрутизация уведомлений зависит от корпоративного контура доставки и подключается отдельно от поверхности консоли Community.',
'settings.notifications.capability_title_cloud': 'Контур доставки Cloud',
'settings.notifications.capability_body_cloud': 'Маршрутизация уведомлений зависит от облачного контура доставки и подключается отдельно от поверхности консоли Community.',
'settings.planned_badge': 'Запланировано',
'settings.notifications.spike_title': 'Всплеск ошибок операции',
'settings.notifications.spike_body': 'Алерт, когда доля ошибок превышает скользящий порог для опубликованного инструмента.',
'settings.notifications.latency_title': 'Деградация задержки upstream',
'settings.notifications.latency_body': 'Уведомлять операторов, когда p99 latency резко растет выше базовой линии.',
'settings.notifications.digest_title': 'Сводки по квотам и использованию',
'settings.notifications.digest_body': 'Доставлять периодические сводки по расходу квот, ошибкам и активности воркспейса.',
'settings.workspace.saving': 'Сохранение…',
'settings.workspace.saved': 'Сохранено ✓',
'settings.workspace.save_error': 'Не удалось сохранить настройки воркспейса',
'settings.workspace.save_error_title': 'Не удалось сохранить воркспейс',
// Workspace setup
'workspace_setup.title': 'Настройки воркспейса',
'workspace_setup.page_subtitle': 'Настройте идентичность вашего self-hosted воркспейса.',
'workspace_setup.back': 'Назад',
'workspace_setup.identity.title': 'Идентичность воркспейса',
'workspace_setup.identity.avatar_hint': 'Аватар формируется из имени воркспейса.',
'workspace_setup.name': 'Имя воркспейса',
'workspace_setup.slug': 'Slug',
'workspace_setup.required': 'обязательно',
'workspace_setup.optional': '(необязательно)',
'workspace_setup.slug_hint': 'Только строчные буквы, цифры и дефисы. Используется в URL MCP endpoint-ов.',
'workspace_setup.description': 'Описание',
'workspace_setup.description_placeholder': 'Чем занимается этот воркспейс?',
'workspace_setup.actions.save': 'Сохранить изменения',
'workspace_setup.create.title': 'Создать новый воркспейс',
'workspace_setup.create.subtitle': 'В Community используется один воркспейс для MCP-операций и агентов.',
'workspace_setup.create.footer': 'В Community используется один воркспейс.',
'workspace_setup.danger.title': 'Опасная зона',
'workspace_setup.danger.export_title': 'Экспортировать все данные',
'workspace_setup.danger.export_body': 'Скачать JSON-снимок настроек воркспейса, операций, агентов, секретов, данных использования и ключей доступа агентов.',
'workspace_setup.export': 'Экспорт',
'workspace_setup.role.owner': 'Владелец',
'workspace_setup.role.admin': 'Администратор',
'workspace_setup.role.operator': 'Разработчик',
'workspace_setup.role.viewer': 'Наблюдатель',
'workspace_setup.saving_create': 'Создание…',
'workspace_setup.saving_update': 'Сохранение…',
'workspace_setup.saved': 'Сохранено ✓',
'workspace_setup.save_error': 'Не удалось сохранить воркспейс',
'workspace_setup.save_error_title': 'Не удалось сохранить',
'workspace_setup.export_error': 'Не удалось экспортировать воркспейс',
'workspace_setup.export_error_title': 'Не удалось экспортировать',
'workspace.switch_error': 'Не удалось переключить воркспейс',
'workspace.switch_error_title': 'Не удалось переключить воркспейс',
'workspace_setup.custom_protocol.rest': 'REST',
'workspace_setup.custom_protocol.graphql': 'GraphQL',
'workspace_setup.custom_protocol.grpc': 'gRPC',
'workspace_setup.custom_protocol.websocket': 'WebSocket',
'workspace_setup.custom_protocol.soap': 'SOAP',
// Wizard
'wizard.back_catalog': 'Операции',
'wizard.progress.create': 'Создать операцию',
'wizard.progress.edit': 'Редактировать операцию',
'wizard.exit': 'Выйти из мастера',
'wizard.step_of': 'Шаг <strong>{step}</strong> из {total}',
'wizard.step_short': 'Шаг {step}',
'wizard.status.completed': 'Завершено',
'wizard.status.in_progress': 'В процессе',
'wizard.status.not_started': 'Не начато',
'wizard.help.title': 'Нужна помощь?',
'wizard.help.body': 'Не уверены, какой протокол выбрать? Откройте',
'wizard.help.link': 'гайд по сравнению протоколов →',
'wizard.progress.section': 'Прогресс',
'wizard.step_name.protocol': 'Протокол',
'wizard.step_name.upstream': 'Upstream',
'wizard.step_name.tool': 'Конфиг инструмента',
'wizard.step_name.mapping': 'Маппинг',
'wizard.button.back': 'Назад',
'wizard.button.continue': 'Продолжить',
'wizard.button.save_draft': 'Сохранить черновик',
'wizard.button.create': 'Создать операцию',
'wizard.button.save_changes': 'Сохранить изменения',
'wizard.sidebar.brand.create': 'Создать <span>операцию</span>',
'wizard.sidebar.brand.edit': 'Редактировать <span>операцию</span>',
'wizard.step1.title': 'Выберите протокол',
'wizard.step1.subtitle': 'Выберите транспортный протокол upstream-сервиса. От этого зависит, как Crank будет общаться с вашим API и какие настройки будут доступны на следующих шагах.',
'wizard.step1.rest_name': 'REST / HTTP',
'wizard.step1.rest_tagline': 'Стандартные HTTP-методы поверх REST endpoint-а. Самый универсальный вариант.',
'wizard.step1.graphql_name': 'GraphQL',
'wizard.step1.graphql_tagline': 'Операции по GraphQL-схеме. Поддерживаются Query и Mutation для одной фиксированной операции.',
'wizard.step1.grpc_name': 'gRPC',
'wizard.step1.grpc_tagline': 'Высокопроизводительный бинарный RPC поверх HTTP/2 для unary-вызовов к низколатентным внутренним сервисам.',
'wizard.step1.websocket_name': 'WebSocket',
'wizard.step1.websocket_tagline': 'Состояние соединения, event streams и push-oriented интеграции, нормализованные в bounded MCP streaming tools.',
'wizard.step1.soap_name': 'SOAP',
'wizard.step1.soap_tagline': 'Enterprise WSDL/XSD-контракты, нормализованные в MCP-инструменты со структурированным XML mapping.',
'wizard.step1.query': 'Query',
'wizard.step1.mutation': 'Mutation',
'wizard.step1.unary': 'Unary',
'wizard.step1.tip_title': 'Это можно изменить позже',
'wizard.step1.tip_body': 'Смена протокола после настройки следующих шагов очистит схемы и mapping. Сохраните операцию как черновик перед экспериментами. См. гайд по миграции протоколов.',
'wizard.step1.community_protocol_title': 'Граница протоколов Community',
'wizard.step1.community_protocol_note': 'В этой Community-сборке скрыты {protocols}. Эти протокольные поверхности открываются в коммерческих редакциях.',
'wizard.step5.streaming_title': 'Потоковое выполнение в Community',
'wizard.step5.community_streaming_note': 'В этой Community-сборке операции остаются в режиме unary. Семейства инструментов window, session и async job требуют коммерческую редакцию.',
'wizard.step2.title': 'Выберите upstream и endpoint',
'wizard.step2.subtitle': 'Выберите существующий upstream или зарегистрируйте новый. Затем задайте конкретный путь endpoint-а для этой операции.',
'wizard.required': 'Обязательно',
'wizard.optional': 'Необязательно',
'wizard.step2.upstream_title': 'Upstream',
'wizard.step2.upstream_subtitle': 'Общий хост, базовый URL и аутентификация',
'wizard.step2.upstream_placeholder': 'Выберите upstream…',
'wizard.step2.search_placeholder': 'Поиск по имени или URL…',
'wizard.step2.change': 'Изменить',
'wizard.step2.auth_none': 'Без авторизации',
'wizard.step2.auth_bearer': 'Bearer token',
'wizard.step2.auth_basic': 'Basic auth',
'wizard.step2.auth_apikey': 'API key',
'wizard.step2.register_new': 'Зарегистрировать новый upstream',
'wizard.step2.name': 'Имя',
'wizard.step2.base_url': 'Base URL',
'wizard.step2.unique_hint': 'Уникальный идентификатор этого upstream-а.',
'wizard.step2.base_url_hint': 'Корневой URL без завершающего слеша. Auth настраивается отдельно ниже.',
'wizard.step2.auth_selector': 'Upstream auth',
'wizard.step2.auth_selector_hint': 'Используйте secrets-backed auth profiles вместо встраивания credential headers в определение upstream-а.',
'wizard.step2.auth_mode.none': 'Без авторизации',
'wizard.step2.auth_mode.existing': 'Использовать существующий auth profile',
'wizard.step2.auth_mode.create': 'Создать auth profile сейчас',
'wizard.step2.auth_profile': 'Auth profile',
'wizard.step2.auth_profile_hint': 'Выбранный профиль применяется автоматически при каждом вызове инструмента.',
'wizard.step2.auth_profile_empty': 'Auth profiles пока нет. Создайте его ниже.',
'wizard.step2.create_profile_title': 'Создать auth profile',
'wizard.step2.create_profile_subtitle': 'Создайте переиспользуемый профиль на базе зашифрованных workspace secrets.',
'wizard.step2.profile_name': 'Имя профиля',
'wizard.step2.profile_kind': 'Тип auth',
'wizard.step2.auth_kind.bearer': 'Bearer token',
'wizard.step2.auth_kind.basic': 'Basic auth',
'wizard.step2.auth_kind.api_key_header': 'API key header',
'wizard.step2.auth_kind.api_key_query': 'API key query',
'wizard.step2.header_name': 'Имя заголовка',
'wizard.step2.query_param': 'Query param',
'wizard.step2.username_secret': 'Секрет логина',
'wizard.step2.password_secret': 'Секрет пароля',
'wizard.step2.secret_value': 'Секрет',
'wizard.step2.quick_secret': 'Быстро создать секрет',
'wizard.step2.manage_secrets': 'Открыть страницу Secrets',
'wizard.step2.static_headers': 'Статические заголовки (опционально)',
'wizard.step2.static_headers_hint': 'Необязательные не-секретные заголовки для каждого запроса к этому upstream-у. Для credential flows используйте auth profiles.',
'wizard.step2.quick_secret_title': 'Быстро создать секрет',
'wizard.step2.quick_secret_name': 'Имя секрета',
'wizard.step2.quick_secret_kind': 'Тип секрета',
'wizard.step2.quick_secret_value': 'Значение секрета',
'wizard.step2.quick_secret_hint': 'Plaintext сразу шифруется и больше не возвращается.',
'wizard.step2.quick_secret_submit': 'Создать секрет',
'wizard.step2.save_upstream': 'Сохранить upstream',
'wizard.step2.endpoint_title': 'Endpoint',
'wizard.step2.endpoint_subtitle': 'Путь и HTTP-метод для конкретной операции',
'wizard.step2.path_template': 'Шаблон пути',
'wizard.step2.path_hint': 'Добавляется к base URL upstream-а. Используйте {param} для path-параметров.',
'wizard.step3.rest.label': 'Конфиг запроса',
'wizard.step3.graphql.label': 'GQL операция',
'wizard.step3.rest.title': 'HTTP метод и формат',
'wizard.step3.rest.subtitle': 'Выберите HTTP-метод, который отправляет эта операция, и настройте content negotiation headers. Crank сериализует аргументы MCP-инструмента в нужный формат request body.',
'wizard.step3.rest.method_title': 'HTTP метод',
'wizard.step3.rest.method_subtitle': 'Метод, который отправляется в upstream при каждом вызове инструмента',
'wizard.step3.rest.read': 'Чтение',
'wizard.step3.rest.create': 'Создание',
'wizard.step3.rest.replace': 'Замена',
'wizard.step3.rest.update': 'Обновление',
'wizard.step3.rest.remove': 'Удаление',
'wizard.step3.rest.format_title': 'Формат запроса',
'wizard.step3.rest.format_subtitle': 'Content negotiation и сериализация',
'wizard.step3.rest.content_type': 'Content-Type',
'wizard.step3.rest.content_type_hint': 'Как кодируется request body перед отправкой в upstream.',
'wizard.step3.rest.accept': 'Accept',
'wizard.step3.rest.accept_hint': 'Какие типы ответа принимаются от upstream-сервера.',
'wizard.step3.graphql.title': 'GraphQL операция',
'wizard.step3.graphql.subtitle': 'Определите GraphQL-операцию, которую будет выполнять этот инструмент. Все запросы отправляются как HTTP POST на endpoint, настроенный на предыдущем шаге. Variables заполняются из аргументов MCP-инструмента во время выполнения.',
'wizard.step3.graphql.type_title': 'Тип операции',
'wizard.step3.graphql.type_subtitle': 'Вид GraphQL-операции — subscriptions не поддерживаются',
'wizard.step3.graphql.query_desc': 'Получение данных без побочных эффектов',
'wizard.step3.graphql.mutation_desc': 'Создание, изменение или удаление данных',
'wizard.step3.graphql.doc_title': 'Документ запроса',
'wizard.step3.graphql.doc_subtitle': 'GraphQL-документ с типизированными объявлениями переменных',
'wizard.step3.graphql.variables_title': 'Переменные мапятся на шаге 5',
'wizard.step3.graphql.variables_body': 'Объявите все переменные в документе запроса в формате $variable: Type. На шаге 5 вы свяжете поля MCP input-а с этими переменными. Crank автоматически сериализует итоговый payload {"query","variables"}.',
'wizard.step4.title': 'Конфиг инструмента',
'wizard.step4.subtitle': 'Задайте имя инструмента, описание для LLM и определите входную/выходную схему. Описание — главный сигнал, по которому модель решает, вызывать ли этот инструмент.',
'wizard.step4.identity_title': 'Идентичность инструмента',
'wizard.step4.identity_subtitle': 'Машиночитаемое имя и описание для LLM',
'wizard.step4.tool_name': 'Имя инструмента',
'wizard.step4.tool_name_hint': 'Машиночитаемый идентификатор для MCP tool calls. Только строчные буквы, цифры и подчеркивания. После публикации изменить нельзя.',
'wizard.step4.display_name': 'Отображаемое имя',
'wizard.step4.display_name_hint': 'Человеко-читаемое имя для консоли и журнала аудита.',
'wizard.step4.tool_title': 'Tool title',
'wizard.step4.tool_title_hint': 'Короткая императивная фраза, которая попадет в MCP tool manifest.',
'wizard.step4.description': 'Описание',
'wizard.step4.description_hint': 'Описание для LLM. Пишите точно — это главный сигнал, по которому модель решает, нужно ли вызывать инструмент.',
'wizard.step4.description_title': 'Как писать хорошие описания',
'wizard.step4.description_body': 'Начинайте с глагола ("Создает", "Получает", "Обновляет"). Укажите ключевые требования ко входу. Опишите, как выглядит успешный ответ. Избегайте расплывчатых формулировок вроде "обрабатывает" или "управляет". См. рекомендации по описаниям →',
'wizard.step4.input_schema_title': 'Входная схема',
'wizard.step4.input_schema_subtitle': 'Параметры, которые LLM передает при вызове инструмента',
'wizard.step4.output_schema_title': 'Выходная схема',
'wizard.step4.output_schema_subtitle': 'Форма данных, которые LLM получает после успешного вызова',
'wizard.step4.schema_draft': 'JSON Schema draft-07',
'wizard.step4.protocol_agnostic_title': 'Схемы не зависят от протокола',
'wizard.step4.protocol_agnostic_body': 'Эти схемы описывают MCP-контракт, а не wire-format upstream-а. Маппинг на шаге 5 переводит одно в другое. Можно загрузить sample JSON-ответа, чтобы автоматически сгенерировать output schema.',
'wizard.step5.title': 'Mapping и исполнение',
'wizard.step5.subtitle': 'Определите, как аргументы MCP-инструмента мапятся в поля upstream-запроса и как ответ upstream-а мапится обратно в MCP output. Затем задайте execution-параметры — auth настраивается на уровне upstream на шаге 2.',
'wizard.step5.input_request': 'Input → Request',
'wizard.step5.output_response': 'Response → Output',
'wizard.step5.execution': 'Исполнение',
'wizard.step5.exec_title': 'Execution config',
'wizard.step5.exec_subtitle': 'Таймауты, retry policy и ссылка на auth profile',
'wizard.step5.security_level_title': 'Защита операций в Community',
'wizard.step5.community_security_note': 'Community поддерживает только стандартный уровень защиты операций со статическими ключами AI-агента. Короткоживущие и одноразовые токены требуют коммерческую редакцию.',
'wizard.step5.live_title': 'Live-проверка и публикация',
'wizard.step5.draft_actions': 'Действия черновика',
'wizard.step5.live_save_title': 'Live-действия сначала сохраняют черновик',
'wizard.step5.live_save_body': 'Загрузка sample-ов, тестовые прогоны, YAML-действия и публикация сначала сохраняют текущий черновик перед вызовом backend-а.',
'wizard.step5.samples_title': 'Samples и генерация черновика',
'wizard.step5.samples_subtitle': 'Сохраните входные и выходные sample-ы, затем сгенерируйте схемы и маппинг из реальных payload-ов.',
'wizard.step5.input_sample': 'Входной sample',
'wizard.step5.output_sample': 'Выходной sample',
'wizard.step5.save_input_sample': 'Сохранить входной sample',
'wizard.step5.save_output_sample': 'Сохранить выходной sample',
'wizard.step5.generate_draft': 'Сгенерировать черновик из sample-ов',
'wizard.step5.test_title': 'Тестовый прогон',
'wizard.step5.test_subtitle': 'Проверьте текущий черновик на upstream-е перед публикацией.',
'wizard.step5.test_input_payload': 'Входной payload для теста',
'wizard.step5.run_test': 'Запустить тест',
'wizard.step5.use_response_output': 'Использовать ответ как выходной sample',
'wizard.step5.request_preview': 'Предпросмотр запроса',
'wizard.step5.response_preview': 'Предпросмотр ответа',
'wizard.step5.errors': 'Ошибки',
'wizard.step5.choose_descriptor': 'Выбрать descriptor set',
'wizard.step5.upload_discover': 'Загрузить и открыть сервисы',
'wizard.step5.no_descriptor': 'Descriptor set не выбран',
'wizard.step5.yaml_title': 'Импорт и экспорт YAML',
'wizard.step5.yaml_subtitle': 'Переносите черновики операций между окружениями, не выходя из мастера.',
'wizard.step5.export_yaml': 'Экспорт YAML',
'wizard.step5.load_yaml_file': 'Загрузить YAML-файл',
'wizard.step5.import_yaml': 'Импорт YAML',
'wizard.step5.yaml_document': 'YAML-документ',
'wizard.step5.yaml_placeholder': 'Вставьте экспортированный YAML сюда, чтобы создать или обновить операцию.',
'wizard.step5.publish_title': 'Публикация',
'wizard.step5.publish_subtitle': 'Сохраните текущий черновик и откройте его опубликованным агентам.',
'wizard.step5.publish_hint': 'Публикация использует текущую версию черновика из этой сессии мастера.',
'wizard.step5.publish_action': 'Опубликовать операцию',
'wizard.step5.editable_title': 'Операция остается редактируемой в этом мастере',
'wizard.step5.editable_body': 'Сохранение создает или обновляет текущий черновик на месте. Используйте live-действия выше, чтобы загрузить sample-ы, протестировать черновик, экспортировать или импортировать YAML и опубликовать контракт, когда он будет готов.',
'wizard.live.failed_title': 'Не удалось выполнить live-действие',
'wizard.live.failed_body': 'Запрос к backend завершился ошибкой. Проверьте черновик и попробуйте еще раз.',
'wizard.error.no_workspace': 'Воркспейс не выбран',
'wizard.error.tool_name': 'Имя инструмента обязательно',
'wizard.error.parser_unavailable': 'YAML parser недоступен',
'wizard.error.select_upstream': 'Сначала выберите или зарегистрируйте upstream',
'wizard.error.auth_profile_required': 'Выберите auth profile или переключите upstream auth в режим «Без авторизации».',
'wizard.error.auth_profile_name': 'Нужно указать имя auth profile.',
'wizard.error.secret_required': 'Выберите секрет, необходимый для этого auth profile.',
'wizard.error.basic_secrets_required': 'Выберите секреты и для логина, и для пароля.',
'wizard.error.header_name_required': 'Для этого auth profile нужно указать имя заголовка.',
'wizard.error.query_param_required': 'Для этого auth profile нужно указать имя query-параметра.',
'wizard.error.secret_name_required': 'Нужно указать имя секрета.',
'wizard.error.secret_value_required': 'Нужно указать значение секрета.',
'wizard.error.save_upstream_first': 'Сначала сохраните upstream после создания нового auth profile.',
'wizard.toast.auth_profile_created_title': 'Auth profile создан',
'wizard.toast.auth_profile_created_body': 'Новый upstream auth profile готов и уже выбран.',
'wizard.toast.auth_profile_error_title': 'Не удалось создать auth profile',
'wizard.toast.quick_secret_created_title': 'Секрет создан',
'wizard.toast.quick_secret_created_body': 'Новый секрет уже доступен в auth selector.',
'wizard.toast.quick_secret_error_title': 'Не удалось создать секрет',
'wizard.error.save': 'Не удалось сохранить операцию',
'wizard.error.save_title': 'Не удалось сохранить',
'wizard.save.created': 'Черновик создан',
'wizard.save.updated': 'Черновик обновлен',
'wizard.save.body': 'Текущий черновик сохранен в backend и готов к live-действиям в этом мастере.',
'wizard.save.saved': 'Сохранено ✓',
'wizard.busy.input_sample': 'Сохранение входного sample…',
'wizard.busy.output_sample': 'Сохранение выходного sample…',
'wizard.busy.generate': 'Генерация черновика…',
'wizard.busy.test': 'Запуск теста…',
'wizard.busy.export_yaml': 'Экспорт YAML…',
'wizard.busy.import_yaml': 'Импорт YAML…',
'wizard.busy.publish': 'Публикация операции…',
'wizard.busy.descriptor': 'Загрузка descriptor set…',
'wizard.busy.wsdl': 'Inspection WSDL…',
'wizard.sample.input_saved': 'Входной sample сохранен',
'wizard.sample.input_saved_body': 'Входной sample теперь сохранен в текущей версии черновика.',
'wizard.sample.output_saved': 'Выходной sample сохранен',
'wizard.sample.output_saved_body': 'Выходной sample теперь сохранен в текущей версии черновика.',
'wizard.sample.generated': 'Черновик сгенерирован',
'wizard.sample.generated_body': 'Схемы и mapping были пересобраны из сохраненных sample-ов.',
'wizard.test.completed': 'Тестовый прогон завершен',
'wizard.test.failed': 'Тестовый прогон вернул ошибки',
'wizard.test.completed_body': 'Текущий черновик успешно выполнился против upstream.',
'wizard.test.failed_body': 'Черновик выполнился, но backend вернул ошибки валидации или runtime.',
'wizard.test.window_completed': 'Оконный тест завершен',
'wizard.test.window_completed_body': 'Собран ограниченный результат окна.',
'wizard.test.window_truncated_note': 'Результат был усечен.',
'wizard.test.window_more_note': 'Доступно продолжение, если перейти к streaming-представлениям.',
'wizard.test.window_incomplete_note': 'Upstream-поток завершился раньше, чем удалось собрать всё окно.',
'wizard.test.websocket_window_completed': 'WebSocket оконный тест завершен',
'wizard.test.websocket_window_completed_body': 'В пределах ограниченного тестового окна собрано {items} WebSocket event(s).',
'wizard.test.websocket_window_truncated_note': 'Ограниченное окно остановилось после достижения лимита по элементам или байтам.',
'wizard.test.websocket_window_more_note': 'Сокет выдал больше событий, чем сохранило это тестовое окно. Увеличьте лимиты или переключитесь в session mode, если нужен более длинный поток.',
'wizard.test.websocket_window_incomplete_note': 'Upstream WebSocket закрылся до того, как bounded окно успело собрать все настроенные события.',
'wizard.test.websocket_session_started': 'WebSocket session-тест запущен',
'wizard.test.websocket_session_started_body': 'WebSocket session {session_id} создана. Продолжайте в разделе Stream sessions через короткую паузу.',
'wizard.test.websocket_async_job_started': 'WebSocket async job тест запущен',
'wizard.test.websocket_async_job_started_body': 'Создан WebSocket async job {job_id}. Дальше отслеживайте статус и результат в представлении async jobs.',
'wizard.test.websocket_failed': 'WebSocket тест завершился с ошибками',
'wizard.test.websocket_failed_body': 'Тест сокета завершился до того, как Crank успел собрать bounded result window.',
'wizard.test.websocket_failed_reconnect_body': 'Upstream WebSocket закрылся раньше, чем удалось собрать достаточно событий, и настроенная reconnect policy была исчерпана.',
'wizard.test.websocket_failed_closed_body': 'Upstream WebSocket закрылся до завершения bounded test window.',
'wizard.test.session_started': 'Session-тест запущен',
'wizard.test.session_started_body': 'Сессия {session_id} создана. Проверьте статус в разделе Stream sessions через несколько секунд.',
'wizard.test.async_job_started': 'Async job тест запущен',
'wizard.test.async_job_started_body': 'Создан async job {job_id}. Отслеживайте статус и результат через представление async jobs.',
'wizard.test.no_response': 'Тестового ответа пока нет',
'wizard.test.no_response_body': 'Сначала выполните тест, затем скопируйте response preview в editor output sample.',
'wizard.test.response_copied': 'Ответ скопирован',
'wizard.test.response_copied_body': 'Последний тестовый ответ скопирован в editor output sample.',
'wizard.boolean.yes': 'да',
'wizard.boolean.no': 'нет',
'wizard.yaml.exported': 'YAML экспортирован',
'wizard.yaml.exported_body': 'YAML текущего черновика скачан из backend.',
'wizard.yaml.none': 'YAML не предоставлен',
'wizard.yaml.none_body': 'Вставьте YAML-документ или загрузите файл перед импортом.',
'wizard.yaml.imported': 'YAML импортирован',
'wizard.yaml.imported_body': 'Импортированный черновик операции сохранен. Мастер сейчас откроет его заново.',
'wizard.yaml.loaded': 'YAML загружен',
'wizard.yaml.loaded_body': 'YAML-документ загружен в editor импорта.',
'wizard.publish.done': 'Операция опубликована',
'wizard.publish.done_body': 'Версия {version} опубликована и теперь может быть привязана к агентам.',
'wizard.grpc.discovery_title': 'gRPC discovery переведен в live descriptor flow',
'wizard.grpc.discovery_body': 'Server reflection в этом развертывании не подключен. Сохраните черновик и используйте инструменты descriptor set на шаге 5, чтобы загрузить реальный descriptor set и открыть unary methods из backend.',
'wizard.grpc.no_methods': 'Unary RPC methods не найдены.',
'wizard.grpc.fields_unresolved': 'Поля не разрешены.',
'wizard.grpc.fields_unresolved_body': 'Import-ы или внешние типы не разворачиваются на стороне клиента.',
'wizard.grpc.services_found': 'Найдено {services_label} и {methods_label}.',
'wizard.grpc.services_label.one': '{count} сервис',
'wizard.grpc.services_label.few': '{count} сервиса',
'wizard.grpc.services_label.many': '{count} сервисов',
'wizard.grpc.services_label.other': '{count} сервиса',
'wizard.grpc.methods_label.one': '{count} unary-метод',
'wizard.grpc.methods_label.few': '{count} unary-метода',
'wizard.grpc.methods_label.many': '{count} unary-методов',
'wizard.grpc.methods_label.other': '{count} unary-метода',
'wizard.grpc.services_discovered': 'Сервисов открыто: {services} · unary методов: {methods}',
'wizard.grpc.services_none': 'Unary methods пока не открыты',
// Common buttons
'btn.save': 'Сохранить',
'btn.cancel': 'Отмена',
'btn.create': 'Создать',
'btn.delete': 'Удалить',
'btn.revoke': 'Отозвать',
'btn.copy': 'Копировать',
// Agents page
'agents.title': 'Агенты',
'agents.subtitle': 'Именованные MCP endpoint-ы, открывающие LLM ограниченный набор операций',
'agents.subtitle.community': 'Отдельные MCP endpoint-ы для AI-агентов. В Community машинный доступ держится на статических ключах агента.',
'agents.new': 'Новый агент',
'agents.limit.title': 'Достигнут лимит агентов',
'agents.limit.message': 'В этой редакции доступно не более {limit} AI-агента. Чтобы добавить еще одного, удалите существующего агента.',
'agents.stats.total': 'Всего агентов',
'agents.stats.total_sub': 'в этом воркспейсе',
'agents.stats.published': 'Опубликованы',
'agents.stats.published_sub': '{value}% от общего числа',
'agents.stats.tools': 'Открытых операций',
'agents.stats.tools_sub': 'в {count} агентах',
'agents.stats.calls': 'Вызовов сегодня',
'agents.stats.calls_sub': 'по всем агентам',
'agents.search': 'Поиск агентов...',
'agents.results': '{shown} из {total} агентов',
'agents.callout.default': 'У каждого агента свой MCP endpoint. Подключайте LLM-клиент к конкретному агенту, чтобы он видел только нужные инструменты, а не все {count} операций этого воркспейса.',
'agents.callout.community': 'В Community у каждого агента свой MCP endpoint и свои статические ключи агента. Подключайте LLM-клиент к конкретному агенту, чтобы он видел только нужные инструменты, а не все {count} операций этого воркспейса.',
'agents.loading': 'Загрузка агентов…',
'agents.error.title': 'Не удалось загрузить агентов',
'agents.error.api': 'Воркспейс или API недоступен',
'agents.error.load': 'Не удалось загрузить агентов',
'agents.empty.initial.title': 'Агентов пока нет',
'agents.empty.initial.sub': 'Создайте первого агента, чтобы получить отдельный MCP endpoint с нужным набором инструментов.',
'agents.empty.initial.sub_community': 'Создайте первого агента, чтобы получить один отдельный MCP endpoint и его статические машинные ключи для конкретного набора инструментов.',
'agents.empty.initial.next_step': 'После создания выпустите отдельные машинные ключи для этого endpoint на странице API Keys.',
'agents.empty.search.title': 'Нет агентов по запросу "{query}"',
'agents.empty.search.sub': 'Попробуйте другой поисковый запрос.',
'agents.card.tools': 'инструментов',
'agents.card.keys': 'ключей',
'agents.card.calls_today': 'вызовов сегодня',
'agents.card.created': 'Создан {date}',
'agents.card.copy_endpoint': 'Скопировать endpoint',
'agents.card.endpoint_help': 'Используйте этот MCP endpoint в клиенте и управляйте его машинными ключами на странице API Keys.',
'agents.card.endpoint_help_community': 'Используйте этот MCP endpoint в клиенте. В Community машинный доступ держится на статических ключах агента со страницы API Keys.',
'agents.action.edit': 'Редактировать',
'agents.action.archive': 'Архивировать',
'agents.action.delete': 'Удалить',
'agents.lifecycle.publish': 'Опубликовать',
'agents.lifecycle.unpublish': 'Снять с публикации',
'agents.lifecycle.restore_draft': 'Вернуть в черновик',
'agents.lifecycle.published': 'Опубликован',
'agents.lifecycle.archived': 'Архивирован',
'agents.lifecycle.draft': 'Черновик',
'agents.drawer.new_title': 'Новый агент',
'agents.drawer.edit_title': 'Редактировать агента',
'agents.drawer.new_sub': 'Настройте именованный MCP endpoint для вашей LLM',
'agents.drawer.new_sub_community': 'Настройте один MCP endpoint и его границу на статических ключах агента для конкретного LLM-сценария',
'agents.drawer.edit_sub': 'Обновите настройки агента и выбор инструментов',
'agents.drawer.identity': 'Идентичность',
'agents.drawer.display_name': 'Отображаемое имя',
'agents.drawer.slug': 'Slug',
'agents.drawer.description': 'Описание',
'agents.drawer.optional': '(необязательно)',
'agents.drawer.required': 'обязательно',
'agents.drawer.status': 'Статус',
'agents.drawer.endpoint': 'MCP endpoint',
'agents.drawer.slug_hint': 'Сохраняйте slug стабильным после подключения клиентов. Его изменение меняет MCP path.',
'agents.drawer.placeholder.name': 'Customer Support',
'agents.drawer.placeholder.slug': 'customer-support',
'agents.drawer.placeholder.description': 'Что делает этот агент? Для какой LLM или сценария он нужен?',
'agents.drawer.operations': 'Операции',
'agents.drawer.selected': 'Выбрано: {count}',
'agents.drawer.operations_sub': 'Выберите операции, которые открывает этот агент. LLM, подключенная к агенту, увидит только эти инструменты.',
'agents.drawer.operations_sub_community': 'Выберите операции, которые открывает этот агент. Любые ключи, выпущенные для этого агента, будут работать только против этого MCP endpoint.',
'agents.drawer.filter_ops': 'Фильтр операций…',
'agents.drawer.ops_no_match': 'Нет операций по запросу "{query}"',
'agents.drawer.ops_selected': 'Выбрано операций: {count}',
'agents.drawer.clear_all': 'Очистить все',
'agents.drawer.recommendation': 'Сейчас выбрано {count} инструментов. LLM лучше работает, когда у агента меньше 15 инструментов. Подумайте о разбиении по сценариям.',
'agents.drawer.cancel': 'Отмена',
'agents.drawer.create': 'Создать агента',
'agents.drawer.save': 'Сохранить изменения',
'agents.toast.saved_title_create': 'Агент создан',
'agents.toast.saved_title_update': 'Агент обновлен',
'agents.toast.saved_message': '{name} сохранен, привязано инструментов: {count}.',
'agents.toast.save_error_title': 'Не удалось обновить агента',
'agents.toast.save_error_message': 'Не удалось сохранить агента',
'agents.toast.delete_confirm': 'Удалить этого агента? Действие нельзя отменить.',
'agents.toast.delete_title': 'Агент удален',
'agents.toast.delete_message': 'Агент {name} удален.',
'agents.toast.delete_error_title': 'Не удалось удалить',
'agents.toast.delete_error_message': 'Не удалось удалить агента',
'agents.toast.lifecycle_title': 'Жизненный цикл агента обновлен',
'agents.toast.lifecycle_publish': '{name} опубликован.',
'agents.toast.lifecycle_unpublish': '{name} возвращен в черновик.',
'agents.toast.lifecycle_archive': '{name} архивирован.',
'agents.toast.lifecycle_error_title': 'Не удалось обновить жизненный цикл',
'agents.toast.lifecycle_error_message': 'Не удалось обновить состояние агента',
'agents.toast.endpoint_title': 'MCP endpoint скопирован',
// Demo content
'demo.agent.revops-copilot.display_name': 'Помощник RevOps',
'demo.agent.revops-copilot.description': 'Ассистент Revenue Operations с CRM- и billing-инструментами.',
'demo.agent.support-triage.display_name': 'Триаж поддержки',
'demo.agent.support-triage.description': 'Черновой агент поддержки, сфокусированный на unary gRPC-сценариях.',
'demo.operation.crm_create_lead.display_name': 'Создать CRM-лид',
'demo.operation.crm_create_lead.description': 'Создает запись лида в CRM-системе.',
'demo.operation.billing_get_invoice.display_name': 'Статус инвойса',
'demo.operation.billing_get_invoice.description': 'Получает состояние и сумму инвойса из billing-системы.',
'demo.operation.support_lookup_ticket.display_name': 'Найти тикет поддержки',
'demo.operation.support_lookup_ticket.description': 'Черновой unary gRPC lookup поддержки через descriptor set.',
'demo.operation.marketing_archive_contact.display_name': 'Архивировать маркетинговый контакт',
'demo.operation.marketing_archive_contact.description': 'Устаревший архивный сценарий, оставленный только для аудита.',
// Login
'login.title': 'Войти',
'login.subtitle': 'Добро пожаловать обратно',
'login.email': 'Email',
'login.password': 'Пароль',
'login.submit': 'Войти',
'login.email_label': 'Email адрес',
'login.email_placeholder': 'you@acme.com',
'login.password_only': 'Войдите с помощью email и пароля.',
'login.error.required': 'Введите email и пароль.',
'login.error.invalid': 'Неверный email или пароль. Попробуйте еще раз.',
'login.error.generic': 'Сейчас не удается войти. Попробуйте еще раз.',
}
};
Object.assign(TRANSLATIONS.en, {
});
Object.assign(TRANSLATIONS.ru, {
});
function t(key) {
var lang = localStorage.getItem('crank_lang') || 'en';
var tr = TRANSLATIONS[lang] || TRANSLATIONS.en;
return tr[key] !== undefined ? tr[key] : (TRANSLATIONS.en[key] !== undefined ? TRANSLATIONS.en[key] : key);
}
function tf(key, vars) {
return Object.keys(vars || {}).reduce(function(result, name) {
return result.replaceAll('{' + name + '}', vars[name]);
}, t(key));
}
function pluralForm(lang, count) {
if (typeof Intl !== 'undefined' && typeof Intl.PluralRules === 'function') {
return new Intl.PluralRules(lang).select(Number(count) || 0);
}
return Number(count) === 1 ? 'one' : 'other';
}
function tPlural(key, count, vars) {
var lang = localStorage.getItem('crank_lang') || 'en';
var tr = TRANSLATIONS[lang] || TRANSLATIONS.en;
var form = pluralForm(lang, count);
var fallbackForm = pluralForm('en', count);
var resolvedKey = tr[key + '.' + form] !== undefined
? key + '.' + form
: tr[key + '.other'] !== undefined
? key + '.other'
: TRANSLATIONS.en[key + '.' + fallbackForm] !== undefined
? key + '.' + fallbackForm
: TRANSLATIONS.en[key + '.other'] !== undefined
? key + '.other'
: key;
var payload = Object.assign({ count: count }, vars || {});
return Object.keys(payload).reduce(function(result, name) {
return result.replaceAll('{' + name + '}', payload[name]);
}, t(resolvedKey));
}
function hasTranslation(key) {
return t(key) !== key;
}
function localizeDemoAgent(agent) {
if (!agent || !agent.slug) {
return agent;
}
var displayNameKey = 'demo.agent.' + agent.slug + '.display_name';
var descriptionKey = 'demo.agent.' + agent.slug + '.description';
if (!hasTranslation(displayNameKey) && !hasTranslation(descriptionKey)) {
return agent;
}
return Object.assign({}, agent, {
display_name: hasTranslation(displayNameKey) ? t(displayNameKey) : agent.display_name,
description: hasTranslation(descriptionKey) ? t(descriptionKey) : agent.description,
});
}
function localizeDemoOperation(operation) {
if (!operation || !operation.name) {
return operation;
}
var displayNameKey = 'demo.operation.' + operation.name + '.display_name';
var descriptionKey = 'demo.operation.' + operation.name + '.description';
if (!hasTranslation(displayNameKey) && !hasTranslation(descriptionKey)) {
return operation;
}
var next = Object.assign({}, operation);
if (hasTranslation(displayNameKey)) {
next.display_name = t(displayNameKey);
if (Object.prototype.hasOwnProperty.call(operation, 'operation_display_name')) {
next.operation_display_name = t(displayNameKey);
}
}
if (hasTranslation(descriptionKey)) {
if (Object.prototype.hasOwnProperty.call(operation, 'description')) {
next.description = t(descriptionKey);
}
if (Object.prototype.hasOwnProperty.call(operation, 'tool_description')) {
next.tool_description = t(descriptionKey);
}
}
return next;
}
window.localizeDemoAgent = localizeDemoAgent;
window.localizeDemoOperation = localizeDemoOperation;
window.tPlural = tPlural;
function applyLang() {
// text content
document.querySelectorAll('[data-i18n]').forEach(function(el) {
el.textContent = t(el.getAttribute('data-i18n'));
});
// placeholder
document.querySelectorAll('[data-i18n-ph]').forEach(function(el) {
el.placeholder = t(el.getAttribute('data-i18n-ph'));
});
// title
document.querySelectorAll('[data-i18n-title]').forEach(function(el) {
el.title = t(el.getAttribute('data-i18n-title'));
});
// aria-label
document.querySelectorAll('[data-i18n-aria-label]').forEach(function(el) {
el.setAttribute('aria-label', t(el.getAttribute('data-i18n-aria-label')));
});
// highlight active language button in settings
var lang = localStorage.getItem('crank_lang') || 'en';
document.querySelectorAll('.lang-btn').forEach(function(btn) {
btn.classList.toggle('active', btn.dataset.lang === lang);
});
}
function setLang(lang) {
localStorage.setItem('crank_lang', lang);
applyLang();
// Notify Alpine components to re-render reactive getters
window.dispatchEvent(new CustomEvent('crank:langchange', { detail: { lang: lang } }));
}
document.addEventListener('DOMContentLoaded', applyLang);