/* ═══════════════════════════════════════════════
   Crank — i18n
   Reads `crank_lang` from localStorage ('en'|'ru')
   ═══════════════════════════════════════════════ */

var TRANSLATIONS = {
  en: {
    // Nav
    'nav.operations':   'Operations',
    'nav.agents':       'Agents',
    'nav.apikeys':      'API Keys',
    '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':    'API Keys',
    'apikeys.subtitle': 'Keys authenticate external MCP clients and workspace automation against Crank.',
    'apikeys.new':      'Create key',
    '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 them securely immediately after creation — Crank stores only a hash. `Last used` updates after successful MCP authentication.',
    '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 a workspace 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 API 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': 'API 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': 'API 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…',

    // 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 1 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 the live profile, password and workspace settings available in this build. Planned capabilities stay visible, but they do not pretend to be wired yet.',
    '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': 'Planned security capabilities',
    'settings.security.capabilities_body': 'Two-factor authentication, passkeys and multi-session inventory are planned, but the current live flow is password-based login with one HttpOnly browser session.',
    'settings.security.not_available': 'Not available in this build.',
    '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': 'Notification preferences',
    'settings.notifications.not_ready_title': 'Notifications are not wired yet.',
    'settings.notifications.not_ready_body': 'Alert routing and per-user preferences are planned, but this build does not persist or deliver notification settings.',
    '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': '{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 platform API 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': '{count} members',
    'workspace_setup.member_count_one': '1 member',
    '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',

    // 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. Ideal for low-latency internal services.',
    '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.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_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 — no trailing slash. Supports ${secrets.*}.',
    'wizard.step2.auth_headers': 'Auth headers',
    'wizard.step2.auth_headers_hint': 'These headers are sent on every request to this upstream. Use secrets for credentials.',
    '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.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': '{count} streaming method(s) found and hidden — 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.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 3.',
    '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.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.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.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.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.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': '{services} services · {methods} unary methods found',
    '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.new': 'New agent',
    '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': '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.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.search.title': 'No agents match "{query}"',
    'agents.empty.search.sub': 'Try a different search term.',
    'agents.card.tools': 'tools',
    'agents.card.keys': 'API 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.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.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 perform best with <15 tools per agent. Consider splitting 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.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.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':    'API Ключи',
    'apikeys.subtitle': 'Ключи аутентифицируют внешние MCP-клиенты и автоматизацию воркспейса в Crank.',
    'apikeys.new':      'Создать ключ',
    '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 сервера и получение списка инструментов для агента воркспейса.',
    'apikeys.scope.write': 'Выполнение `tools/call` для опубликованных наборов инструментов агента.',
    'apikeys.scope.deploy': 'Зарезервировано для deploy-автоматизации. Сейчас также разрешает MCP read/write сценарии.',
    'apikeys.modal.title': 'Создать API-ключ',
    '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': 'API-ключ создан',
    'apikeys.toast.create_message': 'Скопируйте ключ сейчас. Повторно он не будет показан.',
    'apikeys.toast.create_error_title': 'Не удалось создать ключ',
    'apikeys.toast.create_error_message': 'Не удалось создать ключ',
    'apikeys.toast.copy_title': 'API-ключ скопирован',
    'apikeys.toast.copy_message': 'Сохраните исходный ключ в надежном месте. Повторно показать его нельзя.',
    'apikeys.toast.prefix_title': 'Префикс ключа скопирован',
    'apikeys.action.copy_prefix': 'Скопировать префикс ключа',
    'apikeys.action.revoke': 'Отозвать ключ',
    'apikeys.action.delete': 'Удалить',
    'apikeys.creating': 'Создание…',

    // 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':    'Последний 1 час',
    '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': 'Двухфакторная аутентификация, passkeys и инвентарь нескольких сессий запланированы, но в текущем live-flow используется логин по паролю и одна HttpOnly browser session.',
    'settings.security.not_available': 'Недоступно в этой сборке.',
    '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': 'Маршрутизация алертов и настройки на пользователя запланированы, но эта сборка не сохраняет и не доставляет notification settings.',
    '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': '{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': 'Вы станете Owner этого воркспейса. После создания можно пригласить дополнительных участников.',
    'workspace_setup.danger.title': 'Опасная зона',
    'workspace_setup.danger.export_title': 'Экспортировать все данные',
    'workspace_setup.danger.export_body': 'Скачать JSON-снимок настроек воркспейса, участников, приглашений, операций, агентов и platform API keys.',
    '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': '{count} участников',
    'workspace_setup.member_count_one': '1 участник',
    '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',

    // 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. Подходит для низколатентных внутренних сервисов.',
    'wizard.step1.query': 'Запрос',
    'wizard.step1.mutation': 'Мутация',
    'wizard.step1.unary': 'Unary',
    'wizard.step1.tip_title': 'Это можно изменить позже',
    'wizard.step1.tip_body': 'Смена протокола после настройки следующих шагов очистит схемы и mapping. Сохраните операцию как черновик перед экспериментами. См. гайд по миграции протоколов.',
    '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_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 без завершающего слеша. Поддерживает ${secrets.*}.',
    'wizard.step2.auth_headers': 'Auth headers',
    'wizard.step2.auth_headers_hint': 'Эти заголовки отправляются с каждым запросом в этот upstream. Используйте secrets для учетных данных.',
    '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.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': 'Найдено и скрыто streaming method(s): {count} — они несовместимы с 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.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 на шаге 3.',
    '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.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.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.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.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.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} · unary методов: {methods}',
    '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.new': 'Новый агент',
    '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': 'У каждого агента свой 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.search.title': 'Нет агентов по запросу "{query}"',
    'agents.empty.search.sub': 'Попробуйте другой поисковый запрос.',
    'agents.card.tools': 'инструментов',
    'agents.card.keys': 'API-ключей',
    '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.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.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.error.required': 'Введите email и пароль.',
    'login.error.invalid': 'Неверный email или пароль. Попробуйте еще раз.',
    'login.error.generic': 'Сейчас не удается войти. Попробуйте еще раз.',
  }
};

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

function applyLang() {
  // text content
  document.querySelectorAll('[data-i18n]').forEach(function(el) {
    el.textContent = t(el.getAttribute('data-i18n'));
  });
  document.querySelectorAll('[data-i18n-html]').forEach(function(el) {
    el.innerHTML = t(el.getAttribute('data-i18n-html'));
  });
  // 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);
