/* ═══════════════════════════════════════════════
   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.create_workspace': 'Create workspace',
    '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 A–Z',
    'sort.name_desc':   'Sort: Name Z–A',

    // 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 upstream credentials for bearer tokens, basic auth, and API keys used by auth profiles.',
    '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} profile',
    'secrets.used_by_count.other': '{count} profiles',
    '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 referenced by an auth profile.',
    '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 auth profile.',
    '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.',
    'secrets.profiles.title': 'Auth profile references',
    'secrets.profiles.subtitle': 'Profiles resolve secrets at runtime before upstream execution.',
    'secrets.profiles.subtitle_count.one': '{count} auth profile',
    'secrets.profiles.subtitle_count.other': '{count} auth profiles',
    'secrets.profiles.error_title': 'Unable to load auth profile references',
    'secrets.profiles.loading_title': 'Loading auth profile references…',
    'secrets.profiles.loading_body': 'Fetching auth profiles for the current workspace.',
    'secrets.profiles.empty_title': 'No auth profiles yet',
    'secrets.profiles.empty_body': 'Create auth profiles in the Wizard when configuring an operation.',
    'secrets.profiles.references': 'Referenced secrets',
    'secrets.profiles.reference_count': '{count} refs',
    'secrets.profiles.meta.created': 'Created',
    'secrets.profiles.meta.updated': 'Updated',
    'secrets.profiles.summary_bearer': 'Bearer auth via {header} using {secret}.',
    'secrets.profiles.summary_basic': 'Basic auth using {username} and {password}.',
    'secrets.profiles.summary_api_key_header': 'API key header {header} using {secret}.',
    'secrets.profiles.summary_api_key_query': 'API key query param {param} using {secret}.',
    'secrets.profiles.summary_unknown': 'Unknown auth profile configuration.',
    'secrets.auth_kind.bearer': 'Bearer auth',
    'secrets.auth_kind.basic': 'Basic auth',
    'secrets.auth_kind.api_key_header': 'API key header',
    'secrets.auth_kind.api_key_query': 'API key query',

    // 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.members':   'Members',
    '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.ws.tz':       'Timezone',
    'settings.ws.protocol': 'Default protocol',

    // 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.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 your workspace identity, default settings, and team members.',
    '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.defaults.title': 'Default settings',
    'workspace_setup.protocol': 'Default protocol',
    'workspace_setup.protocol_hint': 'Pre-selected in the operation wizard.',
    'workspace_setup.timezone': 'Timezone',
    'workspace_setup.language': 'Interface language',
    'workspace_setup.members.title': 'Team members',
    'workspace_setup.members.invite': 'Invite member',
    'workspace_setup.members.email': 'Email address',
    'workspace_setup.members.role': 'Role',
    'workspace_setup.members.send_invite': 'Send invite',
    'workspace_setup.members.cancel': 'Cancel',
    'workspace_setup.members.invite_sent': '✓ Invite sent.',
    'workspace_setup.members.you': '(you)',
    'workspace_setup.members.today': 'Today',
    'workspace_setup.members.days_ago.one': '{count} day ago',
    'workspace_setup.members.days_ago.other': '{count} days ago',
    'workspace_setup.members.week_ago': '1 week ago',
    'workspace_setup.members.never': 'Never',
    'workspace_setup.members.pending': 'Pending invites',
    'workspace_setup.members.invited_pending': 'Invited {date} · pending',
    'workspace_setup.members.revoke': 'Revoke invite',
    'workspace_setup.members.remove': 'Remove member',
    'workspace_setup.roles.title': 'Roles & permissions',
    'workspace_setup.roles.owner_body': 'Full workspace control, billing access, can transfer or delete the workspace.',
    'workspace_setup.roles.admin_body': 'Manage members and roles, create and edit all operations and agents, manage API keys.',
    'workspace_setup.roles.developer_body': 'Create and edit operations and agents. Cannot manage members or workspace settings.',
    'workspace_setup.roles.viewer_body': 'Read-only access to operations, agents, and logs.',
    'workspace_setup.invite.title': 'Invite team members',
    'workspace_setup.invite.add_person': '+ Add person',
    'workspace_setup.invite.later': 'You can invite more people later from Workspace settings.',
    'workspace_setup.actions.create': 'Create workspace',
    'workspace_setup.actions.save': 'Save changes',
    'workspace_setup.create.title': 'Create a new workspace',
    'workspace_setup.create.subtitle': "A workspace is a shared environment for your team's MCP operations and agents. Each workspace gets its own MCP endpoint namespace.",
    'workspace_setup.create.footer': "You'll be the Owner of this workspace. You can invite additional members after creation.",
    '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, memberships, invitations, operations, agents and agent access keys.',
    'workspace_setup.danger.delete_title': 'Delete workspace',
    'workspace_setup.danger.delete_body': 'Permanently deletes all operations, keys, logs and settings. This action cannot be undone. You will be logged out immediately.',
    'workspace_setup.export': 'Export',
    'workspace_setup.delete': 'Delete workspace',
    'workspace_setup.role.owner': 'Owner',
    'workspace_setup.role.admin': 'Admin',
    'workspace_setup.role.operator': 'Developer',
    'workspace_setup.role.viewer': 'Viewer',
    'workspace_setup.member_count.one': '{count} member',
    'workspace_setup.member_count.other': '{count} members',
    '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.invite_error': 'Failed to send invite',
    'workspace_setup.invite_error_title': 'Invite failed',
    'workspace_setup.role_error': 'Failed to update role',
    'workspace_setup.role_error_title': 'Role update failed',
    'workspace_setup.remove_confirm': 'Remove {name} from this workspace?',
    'workspace_setup.remove_error': 'Failed to remove member',
    'workspace_setup.remove_error_title': 'Member removal failed',
    'workspace_setup.revoke_error': 'Failed to revoke invite',
    'workspace_setup.revoke_error_title': 'Invite revoke failed',
    'workspace_setup.export_error': 'Failed to export workspace',
    'workspace_setup.export_error_title': 'Export failed',
    'workspace_setup.delete_confirm': 'Delete this workspace? This cannot be undone.',
    'workspace_setup.delete_error': 'Failed to delete workspace',
    'workspace_setup.delete_error_title': 'Delete 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 or on the Secrets page.',
    '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.grpc.label': 'RPC method',
    'wizard.step3.websocket.label': 'Socket config',
    'wizard.step3.soap.label': 'WSDL binding',
    '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.step3.grpc.title': 'RPC method',
    'wizard.step3.grpc.subtitle': 'Choose how to describe the gRPC service contract. Upload a .proto file, discover methods from a live server via reflection, or enter the route manually. Only unary RPCs are supported.',
    'wizard.step3.grpc.source_proto': 'Proto file',
    'wizard.step3.grpc.source_reflection': 'Descriptor set',
    'wizard.step3.grpc.source_manual': 'Manual',
    'wizard.step3.grpc.proto_title': 'Proto definition',
    'wizard.step3.grpc.proto_subtitle': 'Upload or paste your .proto file',
    'wizard.step3.grpc.drop_title': 'Drop .proto file here',
    'wizard.step3.grpc.drop_subtitle': 'or click to browse',
    'wizard.step3.grpc.change_file': 'Change file',
    'wizard.step3.grpc.paste_content': 'Paste content',
    'wizard.step3.grpc.parse_proto': 'Parse proto',
    'wizard.step3.grpc.streaming_excluded': 'Streaming methods excluded',
    'wizard.step3.grpc.streaming_hidden.one': 'Found and hid {count} streaming method — it is not compatible with MCP tool calls.',
    'wizard.step3.grpc.streaming_hidden.other': 'Found and hid {count} streaming methods — they are not compatible with MCP tool calls.',
    'wizard.step3.grpc.select_method': 'Select method',
    'wizard.step3.grpc.discovery_live_title': 'Descriptor-driven discovery',
    'wizard.step3.grpc.discovery_live_subtitle': 'Use the saved draft and a descriptor set to discover services through the backend',
    'wizard.step3.grpc.no_upstream': 'No upstream selected',
    'wizard.step3.grpc.select_upstream_first': 'Select an upstream in step 2 first',
    'wizard.step3.grpc.open_tools': 'Open live descriptor tools',
    'wizard.step3.grpc.connecting': 'Connecting…',
    'wizard.step3.grpc.live_step5': 'Live descriptor discovery is handled in step 5.',
    'wizard.step3.grpc.retry': 'Retry',
    'wizard.step3.grpc.manual_title': 'Method route',
    'wizard.step3.grpc.manual_subtitle': 'Full gRPC path to the unary method',
    'wizard.step3.grpc.route': 'gRPC route',
    'wizard.step3.grpc.route_hint': 'Format: /[package.]ServiceName/MethodName',
    'wizard.step3.grpc.request_type': 'Request type',
    'wizard.step3.grpc.response_type': 'Response type',
    'wizard.step3.grpc.proto_doc_hint': 'Protobuf message name, for documentation.',
    'wizard.step3.grpc.schema_title': 'Input and output schemas are defined in step 4',
    'wizard.step3.grpc.schema_body': 'In manual mode you only need the route. The MCP tool input/output schemas — which the LLM uses to call this tool — are configured in the next step regardless of Protobuf definitions.',
    'wizard.step3.grpc.signature_title': 'Method signature',
    'wizard.step3.grpc.route_label': 'gRPC route',
    'wizard.step3.grpc.request_label': 'Request',
    'wizard.step3.grpc.response_label': 'Response',
    'wizard.step3.websocket.title': 'WebSocket subscription',
    'wizard.step3.websocket.subtitle': 'Define the subscription handshake, message templates and runtime tuning that Crank should use before collecting bounded WebSocket events.',
    'wizard.step3.websocket.connection_title': 'Connection',
    'wizard.step3.websocket.connection_subtitle': 'The upstream base URL from step 2 becomes the WebSocket endpoint unless you override it here.',
    'wizard.step3.websocket.path': 'Socket path',
    'wizard.step3.websocket.path_hint': 'Appended to the upstream URL from step 2. Leave empty when the upstream URL already points to the full WebSocket endpoint.',
    'wizard.step3.websocket.subprotocols': 'Subprotocols',
    'wizard.step3.websocket.subprotocols_hint': 'One subprotocol per line. Crank offers them during the handshake in the same order.',
    'wizard.step3.websocket.messages_title': 'Subscription messages',
    'wizard.step3.websocket.messages_subtitle': 'Templates are sent after the socket opens. They can reference MCP input through the mapping step.',
    'wizard.step3.websocket.subscribe_template': 'Subscribe template',
    'wizard.step3.websocket.unsubscribe_template': 'Unsubscribe template',
    'wizard.step3.websocket.runtime_title': 'Connection tuning',
    'wizard.step3.websocket.runtime_subtitle': 'Used by the runtime adapter for heartbeats and reconnects in bounded session-oriented flows.',
    'wizard.step3.websocket.heartbeat': 'Heartbeat interval (ms)',
    'wizard.step3.websocket.reconnect_attempts': 'Reconnect attempts',
    'wizard.step3.websocket.reconnect_backoff': 'Reconnect backoff (ms)',
    'wizard.step3.soap.title': 'WSDL and SOAP binding',
    'wizard.step3.soap.subtitle': 'Upload the WSDL/XSD bundle, inspect services and choose the exact service, port and operation that Crank should expose as an MCP tool.',
    'wizard.step3.soap.wsdl_title': 'Contract files',
    'wizard.step3.soap.wsdl_subtitle': 'WSDL is required. Supporting XSD files are optional and can be uploaded before discovery.',
    'wizard.step3.soap.choose_wsdl': 'Choose WSDL',
    'wizard.step3.soap.choose_xsd': 'Choose XSD',
    'wizard.step3.soap.no_wsdl': 'No WSDL selected',
    'wizard.step3.soap.no_xsd': 'No XSD selected',
    'wizard.step3.soap.upload_discover': 'Upload and inspect services',
    'wizard.step3.soap.binding_title': 'Selected binding',
    'wizard.step3.soap.binding_subtitle': 'The chosen service, port and operation become the target binding for this operation draft.',
    'wizard.step3.soap.service_name': 'Service',
    'wizard.step3.soap.port_name': 'Port',
    'wizard.step3.soap.operation_name': 'Operation',
    'wizard.step3.soap.version': 'SOAP version',
    'wizard.step3.soap.action': 'SOAPAction',
    'wizard.step3.soap.binding_style': 'Binding style',
    'wizard.step3.soap.binding.document_literal': 'Document literal',
    'wizard.step3.soap.binding.rpc_literal': 'RPC literal',
    'wizard.step3.soap.endpoint_override': 'Endpoint override',
    'wizard.step3.soap.endpoint_override_hint': 'Defaults to the upstream base URL from step 2. Override only when the binding address differs from the selected upstream.',
    '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, publish and gRPC descriptor discovery 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.grpc_descriptor_title': 'gRPC descriptor set',
    'wizard.step5.grpc_descriptor_subtitle': 'Upload a compiled descriptor set and discover unary methods from the saved draft.',
    '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.step3.soap.wsdl_selected': 'WSDL selected',
    'wizard.step3.soap.wsdl_selected_body': 'The WSDL file is staged and will be uploaded with the current draft.',
    'wizard.step3.soap.xsd_selected': 'XSD selected',
    'wizard.step3.soap.xsd_selected_body': 'The XSD file is staged and will be uploaded with the current draft.',
    'wizard.step3.soap.wsdl_uploaded': 'WSDL uploaded',
    'wizard.step3.soap.xsd_uploaded': 'XSD uploaded',
    'wizard.step3.soap.services_discovered': '{services} service(s), {ports} port(s) and {operations} operation(s) discovered from the uploaded contract.',
    'wizard.step3.soap.services_none': 'No SOAP services discovered from the uploaded WSDL yet.',
    'wizard.step3.soap.applied': 'SOAP binding applied',
    'wizard.step3.soap.applied_body': 'The selected service, port and operation were applied to the current draft.',
    'wizard.step3.soap.no_wsdl_error': 'WSDL is required',
    'wizard.step3.soap.no_wsdl_error_body': 'Choose a WSDL file or reuse an already uploaded draft contract before inspection.',
    'wizard.step3.soap.discovered': 'SOAP services discovered',
    'wizard.step3.soap.discovered_body': 'The uploaded WSDL was inspected through the backend and the available bindings are ready to apply.',
    '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.descriptor.selected': 'Descriptor set selected',
    'wizard.descriptor.selected_body': 'Upload the descriptor set to discover unary gRPC methods from the backend.',
    'wizard.descriptor.no_file': 'No descriptor set selected',
    'wizard.descriptor.no_file_body': 'Choose a descriptor-set file before uploading it, or save a draft that already has one attached.',
    'wizard.descriptor.discovered': 'Descriptor services discovered',
    'wizard.descriptor.discovered_body': 'The backend parsed the descriptor set. Pick a method to apply its route and schemas.',
    'wizard.descriptor.applied': 'gRPC method applied',
    'wizard.descriptor.applied_body': 'The route and schemas were filled from the uploaded descriptor set.',
    'wizard.descriptor.uploaded': 'Descriptor set uploaded',
    '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 Community build allows up to {limit} AI agent. Upgrade the edition or 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.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.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.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.sso':      'Continue with Google',
    'login.email_label': 'Email address',
    'login.email_placeholder': 'you@acme.com',
    'login.forgot': 'Forgot password?',
    'login.divider': 'or continue with',
    'login.sso_short': 'Google SSO',
    'login.request_prefix': "Don't have an account?",
    'login.request_link': 'Request access',
    'login.planned_badge': 'Planned',
    'login.password_only': 'Sign in with email and password.',
    'login.coming_soon': 'Password reset and SSO will be added later.',
    '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.create_workspace': 'Создать воркспейс',
    '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-учетные данные для bearer token, basic auth и API key, которые используют auth profiles.',
    '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}? Это нельзя сделать, пока на секрет ссылается auth profile.',
    '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': 'Хранит сырое значение заголовка. Имя заголовка задается в auth profile.',
    '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.',
    'secrets.profiles.title': 'Ссылки auth profiles',
    'secrets.profiles.subtitle': 'Профили резолвят секреты в runtime перед upstream execution.',
    'secrets.profiles.subtitle_count.one': '{count} профиль авторизации',
    'secrets.profiles.subtitle_count.few': '{count} профиля авторизации',
    'secrets.profiles.subtitle_count.many': '{count} профилей авторизации',
    'secrets.profiles.subtitle_count.other': '{count} профиля авторизации',
    'secrets.profiles.error_title': 'Не удалось загрузить ссылки auth profiles',
    'secrets.profiles.loading_title': 'Загрузка ссылок auth profiles…',
    'secrets.profiles.loading_body': 'Получаем auth profiles для текущего воркспейса.',
    'secrets.profiles.empty_title': 'Auth profiles пока нет',
    'secrets.profiles.empty_body': 'Создайте auth profile в мастере при настройке операции.',
    'secrets.profiles.references': 'Связанные секреты',
    'secrets.profiles.reference_count': '{count} ссылок',
    'secrets.profiles.meta.created': 'Создан',
    'secrets.profiles.meta.updated': 'Обновлен',
    'secrets.profiles.summary_bearer': 'Bearer auth через {header} с использованием {secret}.',
    'secrets.profiles.summary_basic': 'Basic auth с использованием {username} и {password}.',
    'secrets.profiles.summary_api_key_header': 'API key header {header} с использованием {secret}.',
    'secrets.profiles.summary_api_key_query': 'API key query param {param} с использованием {secret}.',
    'secrets.profiles.summary_unknown': 'Неизвестная конфигурация auth profile.',
    'secrets.auth_kind.bearer': 'Bearer auth',
    'secrets.auth_kind.basic': 'Basic auth',
    'secrets.auth_kind.api_key_header': 'API key header',
    'secrets.auth_kind.api_key_query': 'API key query',

    // 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.members':   'Участники',
    '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.ws.tz':       'Часовой пояс',
    'settings.ws.protocol': 'Протокол по умолчанию',

    // 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.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': 'Настройте идентичность воркспейса, значения по умолчанию и состав команды.',
    '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.defaults.title': 'Настройки по умолчанию',
    'workspace_setup.protocol': 'Протокол по умолчанию',
    'workspace_setup.protocol_hint': 'Предварительно выбирается в мастере операций.',
    'workspace_setup.timezone': 'Часовой пояс',
    'workspace_setup.language': 'Язык интерфейса',
    'workspace_setup.members.title': 'Участники команды',
    'workspace_setup.members.invite': 'Пригласить участника',
    'workspace_setup.members.email': 'Email адрес',
    'workspace_setup.members.role': 'Роль',
    'workspace_setup.members.send_invite': 'Отправить приглашение',
    'workspace_setup.members.cancel': 'Отмена',
    'workspace_setup.members.invite_sent': '✓ Приглашение отправлено.',
    'workspace_setup.members.you': '(вы)',
    'workspace_setup.members.today': 'Сегодня',
    'workspace_setup.members.days_ago.one': '{count} день назад',
    'workspace_setup.members.days_ago.few': '{count} дня назад',
    'workspace_setup.members.days_ago.many': '{count} дней назад',
    'workspace_setup.members.days_ago.other': '{count} дня назад',
    'workspace_setup.members.week_ago': '1 неделю назад',
    'workspace_setup.members.never': 'Никогда',
    'workspace_setup.members.pending': 'Ожидающие приглашения',
    'workspace_setup.members.invited_pending': 'Приглашен {date} · ожидает принятия',
    'workspace_setup.members.revoke': 'Отозвать приглашение',
    'workspace_setup.members.remove': 'Удалить участника',
    'workspace_setup.roles.title': 'Роли и права',
    'workspace_setup.roles.owner_body': 'Полный контроль над воркспейсом, доступ к биллингу, возможность передать или удалить воркспейс.',
    'workspace_setup.roles.admin_body': 'Управление участниками и ролями, создание и редактирование всех операций и агентов, управление API-ключами.',
    'workspace_setup.roles.developer_body': 'Создание и редактирование операций и агентов. Нельзя управлять участниками или настройками воркспейса.',
    'workspace_setup.roles.viewer_body': 'Доступ только на чтение к операциям, агентам и логам.',
    'workspace_setup.invite.title': 'Пригласить участников команды',
    'workspace_setup.invite.add_person': '+ Добавить человека',
    'workspace_setup.invite.later': 'Позже можно пригласить людей из настроек воркспейса.',
    'workspace_setup.actions.create': 'Создать воркспейс',
    'workspace_setup.actions.save': 'Сохранить изменения',
    'workspace_setup.create.title': 'Создать новый воркспейс',
    'workspace_setup.create.subtitle': 'Воркспейс — это общее окружение для MCP-операций и агентов вашей команды. У каждого воркспейса свой namespace MCP endpoint-ов.',
    'workspace_setup.create.footer': 'Вы станете владельцем этого воркспейса. После создания можно пригласить дополнительных участников.',
    'workspace_setup.danger.title': 'Опасная зона',
    'workspace_setup.danger.export_title': 'Экспортировать все данные',
    'workspace_setup.danger.export_body': 'Скачать JSON-снимок настроек воркспейса, участников, приглашений, операций, агентов и ключей доступа агентов.',
    'workspace_setup.danger.delete_title': 'Удалить воркспейс',
    'workspace_setup.danger.delete_body': 'Безвозвратно удаляет все операции, ключи, логи и настройки. Действие нельзя отменить. Вы будете немедленно разлогинены.',
    'workspace_setup.export': 'Экспорт',
    'workspace_setup.delete': 'Удалить воркспейс',
    'workspace_setup.role.owner': 'Владелец',
    'workspace_setup.role.admin': 'Администратор',
    'workspace_setup.role.operator': 'Разработчик',
    'workspace_setup.role.viewer': 'Наблюдатель',
    'workspace_setup.member_count.one': '{count} участник',
    'workspace_setup.member_count.few': '{count} участника',
    'workspace_setup.member_count.many': '{count} участников',
    'workspace_setup.member_count.other': '{count} участника',
    'workspace_setup.saving_create': 'Создание…',
    'workspace_setup.saving_update': 'Сохранение…',
    'workspace_setup.saved': 'Сохранено ✓',
    'workspace_setup.save_error': 'Не удалось сохранить воркспейс',
    'workspace_setup.save_error_title': 'Не удалось сохранить',
    'workspace_setup.invite_error': 'Не удалось отправить приглашение',
    'workspace_setup.invite_error_title': 'Не удалось пригласить',
    'workspace_setup.role_error': 'Не удалось обновить роль',
    'workspace_setup.role_error_title': 'Не удалось обновить роль',
    'workspace_setup.remove_confirm': 'Удалить {name} из этого воркспейса?',
    'workspace_setup.remove_error': 'Не удалось удалить участника',
    'workspace_setup.remove_error_title': 'Не удалось удалить участника',
    'workspace_setup.revoke_error': 'Не удалось отозвать приглашение',
    'workspace_setup.revoke_error_title': 'Не удалось отозвать приглашение',
    'workspace_setup.export_error': 'Не удалось экспортировать воркспейс',
    'workspace_setup.export_error_title': 'Не удалось экспортировать',
    'workspace_setup.delete_confirm': 'Удалить этот воркспейс? Действие нельзя отменить.',
    'workspace_setup.delete_error': 'Не удалось удалить воркспейс',
    'workspace_setup.delete_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 пока нет. Создайте его ниже или на странице Secrets.',
    '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.grpc.label': 'RPC метод',
    'wizard.step3.websocket.label': 'Конфиг сокета',
    'wizard.step3.soap.label': 'WSDL binding',
    '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.step3.grpc.title': 'RPC метод',
    'wizard.step3.grpc.subtitle': 'Выберите, как описывать контракт gRPC-сервиса. Загрузите .proto файл, откройте методы через reflection или введите route вручную. Поддерживаются только unary RPC.',
    'wizard.step3.grpc.source_proto': 'Proto файл',
    'wizard.step3.grpc.source_reflection': 'Descriptor set',
    'wizard.step3.grpc.source_manual': 'Вручную',
    'wizard.step3.grpc.proto_title': 'Proto definition',
    'wizard.step3.grpc.proto_subtitle': 'Загрузите или вставьте содержимое .proto файла',
    'wizard.step3.grpc.drop_title': 'Перетащите сюда .proto файл',
    'wizard.step3.grpc.drop_subtitle': 'или нажмите для выбора',
    'wizard.step3.grpc.change_file': 'Сменить файл',
    'wizard.step3.grpc.paste_content': 'Вставить содержимое',
    'wizard.step3.grpc.parse_proto': 'Разобрать proto',
    'wizard.step3.grpc.streaming_excluded': 'Стриминговые методы исключены',
    'wizard.step3.grpc.streaming_hidden.one': 'Найден и скрыт {count} streaming method — он не поддерживается в MCP tool calls.',
    'wizard.step3.grpc.streaming_hidden.few': 'Найдены и скрыты {count} streaming methods — они не поддерживаются в MCP tool calls.',
    'wizard.step3.grpc.streaming_hidden.many': 'Найдено и скрыто {count} streaming methods — они не поддерживаются в MCP tool calls.',
    'wizard.step3.grpc.streaming_hidden.other': 'Найдены и скрыты {count} streaming methods — они не поддерживаются в MCP tool calls.',
    'wizard.step3.grpc.select_method': 'Выберите метод',
    'wizard.step3.grpc.discovery_live_title': 'Поиск через descriptor set',
    'wizard.step3.grpc.discovery_live_subtitle': 'Используйте сохраненный черновик и descriptor set, чтобы открыть сервисы через backend',
    'wizard.step3.grpc.no_upstream': 'Upstream не выбран',
    'wizard.step3.grpc.select_upstream_first': 'Сначала выберите upstream на шаге 2',
    'wizard.step3.grpc.open_tools': 'Открыть live-инструменты descriptor-а',
    'wizard.step3.grpc.connecting': 'Подключение…',
    'wizard.step3.grpc.live_step5': 'Live-поиск descriptor-а выполняется на шаге 5.',
    'wizard.step3.grpc.retry': 'Повторить',
    'wizard.step3.grpc.manual_title': 'Маршрут метода',
    'wizard.step3.grpc.manual_subtitle': 'Полный gRPC path до unary-метода',
    'wizard.step3.grpc.route': 'gRPC route',
    'wizard.step3.grpc.route_hint': 'Формат: /[package.]ServiceName/MethodName',
    'wizard.step3.grpc.request_type': 'Тип запроса',
    'wizard.step3.grpc.response_type': 'Тип ответа',
    'wizard.step3.grpc.proto_doc_hint': 'Имя Protobuf-сообщения, только для документации.',
    'wizard.step3.grpc.schema_title': 'Входная и выходная схемы задаются на шаге 4',
    'wizard.step3.grpc.schema_body': 'В ручном режиме нужен только route. Входная и выходная схемы MCP-инструмента — которые LLM использует при вызове — настраиваются на следующем шаге независимо от Protobuf-определений.',
    'wizard.step3.grpc.signature_title': 'Сигнатура метода',
    'wizard.step3.grpc.route_label': 'gRPC route',
    'wizard.step3.grpc.request_label': 'Запрос',
    'wizard.step3.grpc.response_label': 'Ответ',
    'wizard.step3.websocket.title': 'WebSocket subscription',
    'wizard.step3.websocket.subtitle': 'Определите handshake подписки, шаблоны сообщений и runtime-параметры, которые Crank должен использовать перед сбором bounded WebSocket events.',
    'wizard.step3.websocket.connection_title': 'Соединение',
    'wizard.step3.websocket.connection_subtitle': 'Base URL upstream-а из шага 2 станет WebSocket endpoint-ом, если вы явно не переопределите его здесь.',
    'wizard.step3.websocket.path': 'Путь сокета',
    'wizard.step3.websocket.path_hint': 'Добавляется к upstream URL из шага 2. Оставьте пустым, если upstream URL уже указывает на полный WebSocket endpoint.',
    'wizard.step3.websocket.subprotocols': 'Subprotocols',
    'wizard.step3.websocket.subprotocols_hint': 'По одному subprotocol на строку. Crank предложит их в handshake в том же порядке.',
    'wizard.step3.websocket.messages_title': 'Сообщения подписки',
    'wizard.step3.websocket.messages_subtitle': 'Шаблоны отправляются после открытия сокета. Они могут ссылаться на MCP input через mapping на следующем шаге.',
    'wizard.step3.websocket.subscribe_template': 'Шаблон subscribe',
    'wizard.step3.websocket.unsubscribe_template': 'Шаблон unsubscribe',
    'wizard.step3.websocket.runtime_title': 'Тюнинг соединения',
    'wizard.step3.websocket.runtime_subtitle': 'Используется runtime adapter-ом для heartbeat и reconnect в bounded session-oriented flows.',
    'wizard.step3.websocket.heartbeat': 'Интервал heartbeat (мс)',
    'wizard.step3.websocket.reconnect_attempts': 'Число reconnect attempts',
    'wizard.step3.websocket.reconnect_backoff': 'Backoff reconnect-а (мс)',
    'wizard.step3.soap.title': 'WSDL и SOAP binding',
    'wizard.step3.soap.subtitle': 'Загрузите WSDL/XSD bundle, получите список сервисов и выберите точный service, port и operation, который Crank должен опубликовать как MCP tool.',
    'wizard.step3.soap.wsdl_title': 'Файлы контракта',
    'wizard.step3.soap.wsdl_subtitle': 'WSDL обязателен. Supporting XSD files можно загрузить дополнительно до discovery.',
    'wizard.step3.soap.choose_wsdl': 'Выбрать WSDL',
    'wizard.step3.soap.choose_xsd': 'Выбрать XSD',
    'wizard.step3.soap.no_wsdl': 'WSDL не выбран',
    'wizard.step3.soap.no_xsd': 'XSD не выбран',
    'wizard.step3.soap.upload_discover': 'Загрузить и получить сервисы',
    'wizard.step3.soap.binding_title': 'Выбранный binding',
    'wizard.step3.soap.binding_subtitle': 'Выбранные service, port и operation станут target binding для текущего draft.',
    'wizard.step3.soap.service_name': 'Service',
    'wizard.step3.soap.port_name': 'Port',
    'wizard.step3.soap.operation_name': 'Operation',
    'wizard.step3.soap.version': 'SOAP версия',
    'wizard.step3.soap.action': 'SOAPAction',
    'wizard.step3.soap.binding_style': 'Binding style',
    'wizard.step3.soap.binding.document_literal': 'Document literal',
    'wizard.step3.soap.binding.rpc_literal': 'RPC literal',
    'wizard.step3.soap.endpoint_override': 'Endpoint override',
    'wizard.step3.soap.endpoint_override_hint': 'По умолчанию берется upstream base URL из шага 2. Переопределяйте только если binding address отличается от выбранного upstream.',
    '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-действия, публикация и gRPC descriptor discovery сначала сохраняют текущий черновик перед вызовом 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.grpc_descriptor_title': 'gRPC descriptor set',
    'wizard.step5.grpc_descriptor_subtitle': 'Загрузите скомпилированный descriptor set и откройте unary-методы из сохраненного черновика.',
    '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.step3.soap.wsdl_selected': 'WSDL выбран',
    'wizard.step3.soap.wsdl_selected_body': 'WSDL staged и будет загружен вместе с текущим draft.',
    'wizard.step3.soap.xsd_selected': 'XSD выбран',
    'wizard.step3.soap.xsd_selected_body': 'XSD staged и будет загружен вместе с текущим draft.',
    'wizard.step3.soap.wsdl_uploaded': 'WSDL загружен',
    'wizard.step3.soap.xsd_uploaded': 'XSD загружен',
    'wizard.step3.soap.services_discovered': 'Из загруженного контракта найдено {services} service(s), {ports} port(s) и {operations} operation(s).',
    'wizard.step3.soap.services_none': 'Из загруженного WSDL пока не найдено ни одного SOAP service.',
    'wizard.step3.soap.applied': 'SOAP binding применен',
    'wizard.step3.soap.applied_body': 'Выбранные service, port и operation применены к текущему draft.',
    'wizard.step3.soap.no_wsdl_error': 'Нужен WSDL',
    'wizard.step3.soap.no_wsdl_error_body': 'Выберите WSDL или используйте уже загруженный контракт текущего draft перед inspection.',
    'wizard.step3.soap.discovered': 'SOAP services получены',
    'wizard.step3.soap.discovered_body': 'Загруженный WSDL был inspected через backend, и доступные bindings готовы к применению.',
    '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.descriptor.selected': 'Descriptor set выбран',
    'wizard.descriptor.selected_body': 'Загрузите descriptor set, чтобы открыть unary gRPC methods из backend.',
    'wizard.descriptor.no_file': 'Descriptor set не выбран',
    'wizard.descriptor.no_file_body': 'Выберите файл descriptor set перед загрузкой или сохраните черновик, у которого он уже прикреплен.',
    'wizard.descriptor.discovered': 'Сервисы descriptor-а открыты',
    'wizard.descriptor.discovered_body': 'Backend разобрал descriptor set. Выберите метод, чтобы применить route и схемы.',
    'wizard.descriptor.applied': 'gRPC метод применен',
    'wizard.descriptor.applied_body': 'Route и схемы заполнены из загруженного descriptor set.',
    'wizard.descriptor.uploaded': 'Descriptor set загружен',
    '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': 'В этой Community-сборке доступно не более {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.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.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.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.sso':      'Войти через Google',
    'login.email_label': 'Email адрес',
    'login.email_placeholder': 'you@acme.com',
    'login.forgot': 'Забыли пароль?',
    'login.divider': 'или продолжить через',
    'login.sso_short': 'Google SSO',
    'login.request_prefix': 'Нет аккаунта?',
    'login.request_link': 'Запросить доступ',
    'login.planned_badge': 'Запланировано',
    'login.password_only': 'Войдите с помощью email и пароля.',
    'login.coming_soon': 'Сброс пароля и SSO появятся позже.',
    'login.error.required': 'Введите email и пароль.',
    'login.error.invalid': 'Неверный email или пароль. Попробуйте еще раз.',
    'login.error.generic': 'Сейчас не удается войти. Попробуйте еще раз.',
  }
};

Object.assign(TRANSLATIONS.en, {
  'wizard.streaming.title': 'Streaming execution',
  'wizard.streaming.subtitle': 'Bounded stream, session and async job settings for MCP tool families.',
  'wizard.streaming.execution_mode': 'Execution mode',
  'wizard.streaming.transport_behavior': 'Transport behavior',
  'wizard.streaming.aggregation_mode': 'Aggregation mode',
  'wizard.streaming.window_duration': 'Window duration (ms)',
  'wizard.streaming.poll_interval': 'Poll interval (ms)',
  'wizard.streaming.upstream_timeout': 'Upstream timeout (ms)',
  'wizard.streaming.idle_timeout': 'Idle timeout (ms)',
  'wizard.streaming.session_lifetime': 'Session lifetime (ms)',
  'wizard.streaming.max_items': 'Max items',
  'wizard.streaming.max_bytes': 'Max bytes',
  'wizard.streaming.max_field_length': 'Max field length',
  'wizard.streaming.sampling_rate': 'Sampling rate',
  'wizard.streaming.items_path': 'Items path',
  'wizard.streaming.summary_path': 'Summary path',
  'wizard.streaming.done_path': 'Done path',
  'wizard.streaming.cursor_path': 'Cursor path',
  'wizard.streaming.status_path': 'Status path',
  'wizard.streaming.redacted_paths': 'Redacted paths',
  'wizard.streaming.truncate_item_fields': 'Truncate item fields',
  'wizard.streaming.drop_duplicates': 'Drop duplicates',
  'wizard.streaming.tool_family': 'Tool family',
  'wizard.streaming.start_tool_name': 'Start tool name',
  'wizard.streaming.poll_tool_name': 'Poll tool name',
  'wizard.streaming.stop_tool_name': 'Stop tool name',
  'wizard.streaming.status_tool_name': 'Status tool name',
  'wizard.streaming.result_tool_name': 'Result tool name',
  'wizard.streaming.cancel_tool_name': 'Cancel tool name',
  'wizard.streaming.mode.unary': 'Unary',
  'wizard.streaming.mode.window': 'Window',
  'wizard.streaming.mode.session': 'Session',
  'wizard.streaming.mode.async_job': 'Async job',
  'wizard.streaming.transport.request_response': 'Request / response',
  'wizard.streaming.transport.server_stream': 'Server stream',
  'wizard.streaming.transport.stateful_session': 'Stateful session',
  'wizard.streaming.transport.deferred_result': 'Deferred result',
  'wizard.streaming.aggregation.raw_items': 'Raw items',
  'wizard.streaming.aggregation.summary_only': 'Summary only',
  'wizard.streaming.aggregation.summary_plus_samples': 'Summary + samples',
  'wizard.streaming.aggregation.stats': 'Stats',
  'wizard.streaming.aggregation.latest_state': 'Latest state',
  'wizard.streaming.help.unary': 'Unary keeps the existing request-response model. Other modes create bounded stream-aware MCP tools.',
  'wizard.streaming.help.window': 'Window mode collects a bounded slice of data and returns a compact result.',
  'wizard.streaming.help.session': 'Session mode publishes start/poll/stop tool families backed by persisted stream sessions.',
  'wizard.streaming.help.async_job': 'Async job mode publishes start/status/result/cancel tools and stores runtime job state.',

  'stream_sessions.title': 'Stream sessions',
  'stream_sessions.subtitle': 'Inspect stateful streaming sessions created by generated MCP tool families.',
  'stream_sessions.refresh': 'Refresh',
  'stream_sessions.summary': '{count} sessions',
  'stream_sessions.filter.all_statuses': 'All statuses',
  'stream_sessions.filter.all_modes': 'All modes',
  'stream_sessions.loading.title': 'Loading stream sessions…',
  'stream_sessions.loading.body': 'Fetching persisted session state for the current workspace.',
  'stream_sessions.error.title': 'Unable to load stream sessions',
  'stream_sessions.error.workspace': 'Workspace is not selected',
  'stream_sessions.error.load': 'Failed to load stream sessions',
  'stream_sessions.empty.title': 'No stream sessions found',
  'stream_sessions.empty.body': 'Session-mode MCP tools will create entries here after start/poll/stop flows.',
  'stream_sessions.operation': 'Operation: {operation}',
  'stream_sessions.meta.created': 'Created',
  'stream_sessions.meta.last_poll': 'Last poll',
  'stream_sessions.meta.expires': 'Expires',
  'stream_sessions.meta.agent': 'Agent',
  'stream_sessions.cursor': 'Cursor',
  'stream_sessions.stop': 'Stop session',
  'stream_sessions.show_details': 'Show details',
  'stream_sessions.hide_details': 'Hide details',
  'stream_sessions.toast.stop.title': 'Session stopped',
  'stream_sessions.toast.stop.body': 'The stream session was stopped.',
  'stream_sessions.toast.stop_error.title': 'Stop failed',
  'stream_sessions.toast.stop_error.body': 'Failed to stop stream session',
  'stream_sessions.status.created': 'Created',
  'stream_sessions.status.running': 'Running',
  'stream_sessions.status.failed': 'Failed',
  'stream_sessions.status.stopped': 'Stopped',
  'stream_sessions.status.expired': 'Expired',

  'async_jobs.title': 'Async jobs',
  'async_jobs.subtitle': 'Inspect deferred jobs started by streaming MCP tool families.',
  'async_jobs.refresh': 'Refresh',
  'async_jobs.summary': '{count} jobs',
  'async_jobs.filter.all_statuses': 'All statuses',
  'async_jobs.loading.title': 'Loading async jobs…',
  'async_jobs.loading.body': 'Fetching persisted job state for the current workspace.',
  'async_jobs.error.title': 'Unable to load async jobs',
  'async_jobs.error.workspace': 'Workspace is not selected',
  'async_jobs.error.load': 'Failed to load async jobs',
  'async_jobs.empty.title': 'No async jobs found',
  'async_jobs.empty.body': 'Async job MCP tools will create entries here after start/status/result flows.',
  'async_jobs.operation': 'Operation: {operation}',
  'async_jobs.meta.created': 'Created',
  'async_jobs.meta.updated': 'Updated',
  'async_jobs.meta.finished': 'Finished',
  'async_jobs.meta.expires': 'Expires',
  'async_jobs.progress': 'Progress payload',
  'async_jobs.error_preview': 'Error preview',
  'async_jobs.result': 'Result payload',
  'async_jobs.result_loading': 'Loading result…',
  'async_jobs.result_error': 'Failed to load result',
  'async_jobs.cancel': 'Cancel job',
  'async_jobs.show_details': 'Show details',
  'async_jobs.hide_details': 'Hide details',
  'async_jobs.toast.cancel.title': 'Job cancelled',
  'async_jobs.toast.cancel.body': 'The async job was cancelled.',
  'async_jobs.toast.cancel_error.title': 'Cancel failed',
  'async_jobs.toast.cancel_error.body': 'Failed to cancel async job',
  'async_jobs.status.created': 'Created',
  'async_jobs.status.running': 'Running',
  'async_jobs.status.completed': 'Completed',
  'async_jobs.status.failed': 'Failed',
  'async_jobs.status.cancelled': 'Cancelled',
  'async_jobs.status.expired': 'Expired',
});

Object.assign(TRANSLATIONS.ru, {
  'wizard.streaming.title': 'Потоковое выполнение',
  'wizard.streaming.subtitle': 'Ограниченные настройки стрима, сессий и async job для семейств MCP-инструментов.',
  'wizard.streaming.execution_mode': 'Режим выполнения',
  'wizard.streaming.transport_behavior': 'Поведение транспорта',
  'wizard.streaming.aggregation_mode': 'Режим агрегации',
  'wizard.streaming.window_duration': 'Длительность окна (мс)',
  'wizard.streaming.poll_interval': 'Интервал poll (мс)',
  'wizard.streaming.upstream_timeout': 'Таймаут upstream (мс)',
  'wizard.streaming.idle_timeout': 'Таймаут простоя (мс)',
  'wizard.streaming.session_lifetime': 'Время жизни сессии (мс)',
  'wizard.streaming.max_items': 'Максимум элементов',
  'wizard.streaming.max_bytes': 'Максимум байт',
  'wizard.streaming.max_field_length': 'Максимальная длина поля',
  'wizard.streaming.sampling_rate': 'Частота выборки',
  'wizard.streaming.items_path': 'Путь к items',
  'wizard.streaming.summary_path': 'Путь к summary',
  'wizard.streaming.done_path': 'Путь к done',
  'wizard.streaming.cursor_path': 'Путь к cursor',
  'wizard.streaming.status_path': 'Путь к status',
  'wizard.streaming.redacted_paths': 'Скрываемые пути',
  'wizard.streaming.truncate_item_fields': 'Обрезать поля элементов',
  'wizard.streaming.drop_duplicates': 'Удалять дубликаты',
  'wizard.streaming.tool_family': 'Семейство инструментов',
  'wizard.streaming.start_tool_name': 'Имя start-инструмента',
  'wizard.streaming.poll_tool_name': 'Имя poll-инструмента',
  'wizard.streaming.stop_tool_name': 'Имя stop-инструмента',
  'wizard.streaming.status_tool_name': 'Имя status-инструмента',
  'wizard.streaming.result_tool_name': 'Имя result-инструмента',
  'wizard.streaming.cancel_tool_name': 'Имя cancel-инструмента',
  'wizard.streaming.mode.unary': 'Unary',
  'wizard.streaming.mode.window': 'Window',
  'wizard.streaming.mode.session': 'Session',
  'wizard.streaming.mode.async_job': 'Async job',
  'wizard.streaming.transport.request_response': 'Запрос / ответ',
  'wizard.streaming.transport.server_stream': 'Серверный стрим',
  'wizard.streaming.transport.stateful_session': 'Состояние сессии',
  'wizard.streaming.transport.deferred_result': 'Отложенный результат',
  'wizard.streaming.aggregation.raw_items': 'Сырые элементы',
  'wizard.streaming.aggregation.summary_only': 'Только summary',
  'wizard.streaming.aggregation.summary_plus_samples': 'Summary + примеры',
  'wizard.streaming.aggregation.stats': 'Статистика',
  'wizard.streaming.aggregation.latest_state': 'Последнее состояние',
  'wizard.streaming.help.unary': 'Unary сохраняет текущую модель запрос-ответ. Остальные режимы создают ограниченные потоковые MCP-инструменты.',
  'wizard.streaming.help.window': 'Режим window собирает ограниченный срез данных и возвращает компактный результат.',
  'wizard.streaming.help.session': 'Режим session публикует семейство start/poll/stop и опирается на сохраненные stream sessions.',
  'wizard.streaming.help.async_job': 'Режим async job публикует start/status/result/cancel и хранит состояние фоновой задачи.',

  'stream_sessions.title': 'Потоковые сессии',
  'stream_sessions.subtitle': 'Просмотр stateful streaming-сессий, созданных сгенерированными семействами MCP-инструментов.',
  'stream_sessions.refresh': 'Обновить',
  'stream_sessions.summary': '{count} сессий',
  'stream_sessions.filter.all_statuses': 'Все статусы',
  'stream_sessions.filter.all_modes': 'Все режимы',
  'stream_sessions.loading.title': 'Загрузка потоковых сессий…',
  'stream_sessions.loading.body': 'Получаем сохраненное состояние сессий для текущего workspace.',
  'stream_sessions.error.title': 'Не удалось загрузить потоковые сессии',
  'stream_sessions.error.workspace': 'Workspace не выбран',
  'stream_sessions.error.load': 'Не удалось загрузить потоковые сессии',
  'stream_sessions.empty.title': 'Потоковых сессий пока нет',
  'stream_sessions.empty.body': 'Сессии появятся здесь после вызовов start/poll/stop для session-mode инструментов.',
  'stream_sessions.operation': 'Операция: {operation}',
  'stream_sessions.meta.created': 'Создана',
  'stream_sessions.meta.last_poll': 'Последний poll',
  'stream_sessions.meta.expires': 'Истекает',
  'stream_sessions.meta.agent': 'Агент',
  'stream_sessions.cursor': 'Cursor',
  'stream_sessions.stop': 'Остановить сессию',
  'stream_sessions.show_details': 'Показать детали',
  'stream_sessions.hide_details': 'Скрыть детали',
  'stream_sessions.toast.stop.title': 'Сессия остановлена',
  'stream_sessions.toast.stop.body': 'Потоковая сессия остановлена.',
  'stream_sessions.toast.stop_error.title': 'Не удалось остановить',
  'stream_sessions.toast.stop_error.body': 'Не удалось остановить потоковую сессию',
  'stream_sessions.status.created': 'Создана',
  'stream_sessions.status.running': 'Выполняется',
  'stream_sessions.status.failed': 'Ошибка',
  'stream_sessions.status.stopped': 'Остановлена',
  'stream_sessions.status.expired': 'Истекла',

  'async_jobs.title': 'Асинхронные задачи',
  'async_jobs.subtitle': 'Просмотр отложенных задач, запущенных потоковыми семействами MCP-инструментов.',
  'async_jobs.refresh': 'Обновить',
  'async_jobs.summary': '{count} задач',
  'async_jobs.filter.all_statuses': 'Все статусы',
  'async_jobs.loading.title': 'Загрузка async job…',
  'async_jobs.loading.body': 'Получаем сохраненное состояние задач для текущего workspace.',
  'async_jobs.error.title': 'Не удалось загрузить async job',
  'async_jobs.error.workspace': 'Workspace не выбран',
  'async_jobs.error.load': 'Не удалось загрузить async job',
  'async_jobs.empty.title': 'Асинхронных задач пока нет',
  'async_jobs.empty.body': 'Задачи появятся здесь после вызовов start/status/result для async job инструментов.',
  'async_jobs.operation': 'Операция: {operation}',
  'async_jobs.meta.created': 'Создана',
  'async_jobs.meta.updated': 'Обновлена',
  'async_jobs.meta.finished': 'Завершена',
  'async_jobs.meta.expires': 'Истекает',
  'async_jobs.progress': 'Payload прогресса',
  'async_jobs.error_preview': 'Предпросмотр ошибки',
  'async_jobs.result': 'Payload результата',
  'async_jobs.result_loading': 'Загрузка результата…',
  'async_jobs.result_error': 'Не удалось загрузить результат',
  'async_jobs.cancel': 'Отменить задачу',
  'async_jobs.show_details': 'Показать детали',
  'async_jobs.hide_details': 'Скрыть детали',
  'async_jobs.toast.cancel.title': 'Задача отменена',
  'async_jobs.toast.cancel.body': 'Асинхронная задача отменена.',
  'async_jobs.toast.cancel_error.title': 'Не удалось отменить',
  'async_jobs.toast.cancel_error.body': 'Не удалось отменить async job',
  'async_jobs.status.created': 'Создана',
  'async_jobs.status.running': 'Выполняется',
  'async_jobs.status.completed': 'Завершена',
  'async_jobs.status.failed': 'Ошибка',
  'async_jobs.status.cancelled': 'Отменена',
  'async_jobs.status.expired': 'Истекла',
});

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);
