export default {
    availableChannels: {
      title: 'Available Channels',
      description: 'Aggregated view: each channel with its linked groups and supported models (wildcards expanded)',
      searchPlaceholder: 'Search channels or models...',
      columns: {
        name: 'Channel',
        status: 'Status',
        billingSource: 'Billing Model Source',
        groups: 'Linked Groups',
        supportedModels: 'Supported Models'
      },
      empty: 'No data',
      noGroups: 'No linked groups',
      noModels: 'No model mapping configured',
      noPricing: 'Pricing not configured',
      statusActive: 'Active',
      statusDisabled: 'Disabled',
      billingSource: {
        requested: 'Requested model',
        upstream: 'Upstream model',
        channel_mapped: 'Channel-mapped model'
      },
      pricing: {
        billingMode: 'Billing Mode',
        billingModeToken: 'Per Token',
        billingModePerRequest: 'Per Request',
        billingModeImage: 'Per Image',
        billingModeVideo: 'Per Video',
        inputPrice: 'Input',
        outputPrice: 'Output',
        cacheWritePrice: 'Cache Write',
        cacheReadPrice: 'Cache Read',
        imageOutputPrice: 'Image Output',
        perRequestPrice: 'Per Request',
        intervals: 'Tiered Pricing',
        unitPerMillion: '/ 1M tokens',
        unitPerRequest: '/ request'
      }
    },

    // Channel Management
    channels: {
      title: 'Channel Management',
      description: 'Manage channels and custom model pricing',
      searchChannels: 'Search channels...',
      createChannel: 'Create Channel',
      editChannel: 'Edit Channel',
      deleteChannel: 'Delete Channel',
      statusActive: 'Active',
      statusDisabled: 'Disabled',
      allStatus: 'All Status',
      groupsUnit: 'groups',
      pricingUnit: 'pricing rules',
      noChannelsYet: 'No Channels Yet',
      createFirstChannel: 'Create your first channel to manage model pricing',
      loadError: 'Failed to load channels',
      createSuccess: 'Channel created',
      updateSuccess: 'Channel updated',
      deleteSuccess: 'Channel deleted',
      createError: 'Failed to create channel',
      updateError: 'Failed to update channel',
      deleteError: 'Failed to delete channel',
      nameRequired: 'Please enter a channel name',
      duplicateModels: 'Model "{0}" appears in multiple pricing entries',
      modelConflict: "Model patterns '{model1}' and '{model2}' conflict: overlapping match range. Model names are matched case-insensitively, so an existing entry already covers all case variants — no need to add the variant separately.",
      mappingConflict: "Mapping source patterns '{model1}' and '{model2}' conflict: overlapping match range. Source patterns are matched case-insensitively, so an existing entry already covers all case variants.",
      intervalValidation: {
        negativeMin: 'Interval #{index}: minimum token count ({value}) cannot be negative',
        maxPositive: 'Interval #{index}: maximum token count ({value}) must be greater than 0',
        maxGreaterThanMin: 'Interval #{index}: maximum token count ({max}) must be greater than minimum token count ({min})',
        negativePrice: 'Interval #{index}: {field} cannot be negative',
        multiplierPositive: 'Interval #{index}: {field} must be greater than 0',
        unboundedLast: 'Interval #{index}: an unbounded interval (empty maximum token count) must be last',
        overlap: 'Intervals #{previousIndex} and #{currentIndex} overlap: previous upper bound ({previousMax}) is greater than current lower bound ({currentMin})',
        price: {
          inputPrice: 'input price',
          outputPrice: 'output price',
          cacheWritePrice: 'cache write price',
          cacheReadPrice: 'cache read price',
          perRequestPrice: 'per-request price'
        }
      },
      timePricingValidation: {
        timezone: 'Select a valid IANA time zone',
        format: 'Start and end times must use HH:mm:ss format',
        range: 'Start time must be earlier than end time; split ranges across midnight',
        overlap: 'Time periods must not overlap',
        multiplier: 'Multiplier must be greater than 0 with at most two decimal places'
      },
      deleteConfirm: 'Are you sure you want to delete channel "{name}"? This cannot be undone.',
      columns: {
        name: 'Name',
        description: 'Description',
        status: 'Status',
        groups: 'Groups',
        pricing: 'Pricing',
        createdAt: 'Created',
        actions: 'Actions'
      },
      billingMode: {
        token: 'Token',
        perRequest: 'Per Request',
        image: 'Image (Per Request)',
        video: 'Video (Per Second)'
      },
      form: {
        name: 'Name',
        namePlaceholder: 'Enter channel name',
        description: 'Description',
        descriptionPlaceholder: 'Optional description',
        status: 'Status',
        groups: 'Associated Groups',
        noGroupsAvailable: 'No groups available',
        inOtherChannel: 'In "{name}"',
        modelPricing: 'Model Pricing',
        models: 'Models',
        modelsPlaceholder: 'Type full model name and press Enter',
        modelInputHint: 'Press Enter to add, supports paste for batch import.',
        billingMode: 'Billing Mode',
        defaultPrices: 'Default prices (fallback when no interval matches)',
        inputPrice: 'Input',
        outputPrice: 'Output',
        cacheWritePrice: 'Cache Write',
        cacheReadPrice: 'Cache Read',
        cacheWritePriceShort: 'Cache W',
        cacheReadPriceShort: 'Cache R',
        imageInputPrice: 'Image Input',
        imageTokenPrice: 'Image Output',
        imageOutputPrice: 'Image Output Price',
        pricePlaceholder: 'Default',
        fastMultiplier: 'Fast Multiplier',
        flexMultiplier: 'Flex Multiplier',
        multiplierPlaceholder: 'Not set',
        multiplierPositive: 'Fast/Flex multipliers must be greater than 0',
        inputMultiplier: 'Input Mult.',
        outputMultiplier: 'Output Mult.',
        cacheWriteMultiplier: 'Cache Write Mult.',
        cacheReadMultiplier: 'Cache Read Mult.',
        intervals: 'Context Intervals (optional)',
        timePricing: 'Time-based pricing (optional)',
        timezone: 'Time zone',
        addTimePeriod: 'Add period',
        startTime: 'Start time',
        endTime: 'End time',
        multiplier: 'Multiplier',
        removeTimePeriod: 'Remove period',
        minTokens: 'Min',
        maxTokens: 'Max',
        inclusive: '(inclusive)',
        addInterval: 'Add Interval',
        requestTiers: 'Request Tiers',
        imageTiers: 'Image Tiers (Per Request)',
        videoTiers: 'Video Resolution Tiers (Per Second)',
        addTier: 'Add Tier',
        noTiersYet: 'No tiers yet. Click add to configure per-request pricing.',
        noPricingRules: 'No pricing rules yet. Click "Add" to create one.',
        perRequestPrice: 'Price per Request',
        perRequestPriceRequired: 'Per-request price or billing tiers required for per-request/image billing mode',
        tierLabel: 'Tier',
        resolution: 'Resolution',
        modelMapping: 'Model Mapping',
        modelMappingHint: 'Map request model names to actual model names. Runs before account-level mapping.',
        noMappingRules: 'No mapping rules. Click "Add" to create one.',
        mappingSource: 'Source model',
        mappingTarget: 'Target model',
        billingModelSource: 'Billing Model',
        billingModelSourceChannelMapped: 'Bill by channel-mapped model',
        billingModelSourceRequested: 'Bill by requested model',
        billingModelSourceUpstream: 'Bill by final upstream model',
        billingModelSourceResponse: 'Bill by upstream response model',
        billingModelSourceHint: 'Controls which model name is used for pricing lookup',
        selectedCount: '{count} selected',
        searchGroups: 'Search groups...',
        noGroupsMatch: 'No groups match your search',
        restrictModels: 'Restrict Models',
        restrictModelsHint: 'When enabled, only models in the pricing list are allowed. Others will be rejected.',
        defaultPerRequestPrice: 'Default per-request price (fallback when no tier matches)',
        defaultImagePrice: 'Default image price (fallback when no tier matches)',
        defaultVideoPrice: 'Default video price per second (fallback when no tier matches)',
        platformConfig: 'Platform Configuration',
        webSearchEmulation: 'Web Search Emulation',
        webSearchEmulationHint: '⚠️ When enabled, all accounts in this channel\'s Anthropic groups will intercept web_search requests. Use with caution.',
        webSearchEmulationGlobalDisabled: 'Please enable the global switch first in Settings → Gateway → Web Search Emulation',
        codexImageGenerationBridge: 'Codex Image Generation Bridge',
        codexImageGenerationBridgeHint: 'When enabled, only non-Responses Lite Codex /responses text requests in OpenAI groups receive the hosted image_generation tool. The bridge does not inject tools for Responses Lite; local image_gen handling follows the client and account policy. Leave this off unless routed accounts support image generation.',
        bedrockCCCompat: 'Bedrock CC Compatibility',
        bedrockCCCompatHint: '⚠️ When enabled, requests to Bedrock accounts in this channel will be transformed for Claude Code compatibility (thinking type conversion, tool_use ID sanitization).',
        basicSettings: 'Basic Settings',
        addPlatform: 'Add Platform',
        noPlatforms: 'Click "Add Platform" to start configuring the channel',
        mappingCount: 'mappings',
        pricingEntry: 'Pricing Entry',
        noModels: 'No models added',
        applyPricingToAccountStats: 'Apply Pricing to Account Stats',
        applyPricingToAccountStatsDesc: 'When enabled, requests not matched by custom rules will use standard model pricing for account stats calculation',
        accountStatsPricingRules: 'Custom Account Stats Pricing Rules',
        addRule: 'Add Rule',
        noRulesConfigured: 'No custom rules configured. Channel model pricing above will be used.',
        ruleName: 'Rule name (optional)',
        ruleGroups: 'Groups',
        ruleAccounts: 'Accounts',
        searchAccountPlaceholder: 'Search accounts...',
        ruleAccountsHint: 'Leave empty to match all accounts',
        ruleModelPricing: 'Model Pricing',
         noGroupsInChannel: 'No groups selected in platform tabs above',
         unnamed: 'Unnamed',
         syncLatestModels: 'Sync Latest Models',
         syncingModels: 'Syncing...',
         syncModelsSuccess: 'Synced {count} new model(s)',
         syncModelsAlreadyUpToDate: 'Models already up to date',
         syncModelsError: 'Failed to sync models'
       }
     },

    riskControl: {
      title: 'Risk Control',
      description: 'Configure content moderation and review audit records',
      loadFailed: 'Failed to load risk control',
      saveFailed: 'Failed to save content moderation config',
      logsFailed: 'Failed to load audit records',
      saved: 'Content moderation config saved',
      refresh: 'Refresh',
      config: 'Content Moderation Config',
      configHint: 'Use OpenAI Moderations to score request content and handle threshold hits by mode.',
      openSettings: 'Moderation Settings',
      settingsTitle: 'Content Moderation Settings',
      refreshStatus: 'Refresh Status',
      records: 'Audit Records',
      recordsHint: 'Shows hits, blocks, errors, and sampled records.',
      saveConfig: 'Save Moderation Config',
      statusFailed: 'Failed to load runtime status',
      enabled: 'Enable Content Moderation',
      enabledHint: 'When off, gateway requests are not moderated even if the menu is enabled.',
      mode: 'Global Mode',
      modePreBlock: 'Pre-Block',
      modePreBlockDesc: 'Synchronously reviews the latest user input before every request and rejects hits immediately.',
      modeObserve: 'Observe Only',
      modeObserveDesc: 'Requests pass through while the latest user input is queued for async review; hits are recorded, notified, and counted.',
      modeOff: 'Off',
      modeOffDesc: 'Content moderation is disabled and no audit records are written.',
      baseUrl: 'OpenAI Base URL',
      model: 'Model',
      apiKey: 'OpenAI API Key',
      apiKeys: 'OpenAI API Keys',
      apiKeyCount: '{count} keys',
      apiKeyPlaceholder: 'Enter API Key',
      apiKeysPlaceholder: 'Add API Keys, one per line. They will be appended on save.',
      apiKeysPlaceholderReplace: 'Replace API Keys, one per line. Stored keys will be replaced on save.',
      apiKeysPlaceholderKeep: 'Add API Keys, one per line. They will be appended on save.',
      apiKeysHint: '{count} keys are currently stored. This input only adds keys; save appends and de-duplicates them.',
      apiKeysWriteMode: 'Write mode',
      apiKeysModeAppend: 'Add',
      apiKeysModeReplace: 'Replace',
      apiKeysModeAppendHint: 'Default: save appends input keys and keeps stored keys.',
      apiKeysModeReplaceHint: 'Replace mode: save replaces all stored keys with input keys.',
      apiKeysReplaceWarning: 'Replace mode',
      apiKeysReplaceNoInput: 'Replace mode requires at least 1 API Key',
      apiKeyPlaceholderKeep: 'Leave empty to keep current key',
      apiKeyWillClear: 'Configured key will be cleared on save',
      apiKeyConfigured: 'Configured',
      apiKeyTemporary: 'Pending',
      apiKeyPendingDelete: 'Pending delete',
      apiKeyPendingDeleteCount: '{count} keys pending deletion',
      deleteApiKey: 'Delete this key',
      undoDeleteApiKey: 'Undo delete',
      inputApiKeyCount: '{count} keys in input',
      storedApiKeyCount: '{count} stored keys',
      testInputApiKeys: 'Test input keys',
      testStoredApiKeys: 'Test stored keys',
      testContentWithStoredApiKey: 'Test content with stored key',
      testingApiKeys: 'Testing',
      apiKeyTestNoInput: 'Enter OpenAI API Keys to test first',
      apiKeyTestDone: 'Key test completed for {count} keys',
      apiKeyTestFailed: 'Failed to test OpenAI API Keys',
      apiKeyHealth: 'Key Availability',
      apiKeyFreezeRule: '400 does not freeze; 401/403 freeze for 10 minutes; 429/529 freeze for 1 minute; other HTTP errors freeze for 10 seconds.',
      apiKeyRows: '{count} keys',
      apiKeyRowsCollapsed: '{count} keys hidden',
      apiKeyRowsExpanded: 'Showing all {count} keys',
      expandApiKeyRows: 'Expand',
      collapseApiKeyRows: 'Collapse',
      apiKeyHealthEmpty: 'No key status yet',
      apiKeyHealthEmptyHint: 'Save keys or test input keys to see availability.',
      apiKeyStatusOk: 'Available',
      apiKeyStatusError: 'Error',
      apiKeyStatusFrozen: 'Frozen',
      apiKeyStatusUnknown: 'Untested',
      apiKeyFailureCount: '{count} failures',
      apiKeyLatency: '{ms} ms',
      apiKeyHTTPStatus: 'HTTP {status}',
      apiKeyFrozenUntil: 'Frozen until {time}',
      apiKeyLastChecked: 'Checked at {time}',
      apiKeyNotTested: 'Not tested',
      auditTestInput: 'Audit Test Input',
      auditTestInputHint: 'Enter a prompt and upload or paste images; images are sent as base64 and are not stored.',
      auditTestPromptPlaceholder: 'Enter a user prompt to test; leave empty to only test key availability.',
      auditTestImages: 'Test Images',
      auditTestImagesHint: 'Upload, drag, or paste images. Up to 1 image, 8MB each.',
      addAuditTestImage: 'Add image',
      clearAuditTest: 'Clear test',
      auditTestImageLimit: 'You can add up to {count} test images',
      auditTestImageTooLarge: 'Each test image must be 8MB or smaller',
      auditTestImageReadFailed: 'Failed to read test image',
      auditTestResult: 'Audit Test Result',
      auditTestHighest: 'Top category {category}, score {score}',
      auditTestComposite: 'Composite score',
      auditTestFlagged: 'Threshold hit',
      auditTestPassed: 'Pass',
      notConfigured: 'Not configured',
      clearApiKey: 'Clear stored key',
      keepApiKey: 'Keep stored key',
      timeoutMs: 'HTTP Timeout (ms)',
      retryCount: 'Retry Count',
      sampleRate: 'Sample Rate',
      proxy: 'Proxy Server',
      proxyHint: 'Send moderation requests through the selected proxy (IP Management - Proxy Servers), useful when the egress IP is not supported by OpenAI. Defaults to direct connection.',
      recordNonHits: 'Record Non-Hits',
      recordNonHitsHint: 'When enabled, sampled non-hit request summaries are redacted before storage.',
      preHashCheck: 'Enable Pre-Hash Check',
      preHashCheckHint: 'Hashes from async hits are blocked before moderation; this does not send email or increment ban counters.',
      flaggedHashCount: 'Current hash collection size: {count}',
      flaggedHashHint: 'Hashes are stored permanently in Redis; paste a full 64-character hash to remove a false block, or clear all stored hashes.',
      flaggedHashPlaceholder: 'Paste full 64-character input hash',
      deleteFlaggedHash: 'Delete hash',
      clearFlaggedHashes: 'Clear all',
      clearFlaggedHashesConfirm: 'Clear all risk input hashes? This does not delete audit records, but removes all historical hash blocks.',
      flaggedHashDeleted: 'Risk hash deleted',
      flaggedHashNotFound: 'Risk hash not found',
      flaggedHashDeleteFailed: 'Failed to delete risk hash',
      flaggedHashesCleared: 'Cleared {count} risk hashes',
      flaggedHashesClearFailed: 'Failed to clear risk hashes',
      workerCount: 'Worker Count',
      queueSize: 'Async Queue Size',
      blockStatus: 'Block HTTP Status',
      blockMessage: 'Custom Block Message',
      defaultBlockMessage: 'Content audit matched a risk rule. Please adjust your input and try again.',
      emailOnHit: 'Email on Hit',
      emailOnHitHint: 'When enabled, send a risk-control email on every hit; auto-ban notices are always sent.',
      autoBan: 'Auto Ban User',
      autoBanHint: 'Disable the user, invalidate auth cache, and send a ban notice after the hit threshold is reached.',
      cyberPolicyExcludeBan: 'Exclude Cyber Policy Hits from Ban Count',
      cyberPolicyExcludeBanHint: 'When enabled, cyber_policy hits no longer count toward auto-ban violations: no ban judgment on the hit itself, and history rows are excluded from the rolling count. Logs and notice emails are unaffected.',
      violationNotCounted: 'Not counted',
      banThreshold: 'Ban Threshold',
      violationWindowHours: 'Count Window (hours)',
      hitRetentionDays: 'Hit Record Retention (days)',
      nonHitRetentionDays: 'Non-Hit Record Retention (days, max 3)',
      violationCount: '{count} hits',
      emailSent: 'Email sent',
      emailNotSent: 'No email',
      autoBanned: 'Banned',
      unbanUser: 'Unban',
      unbanSuccess: 'User has been unbanned',
      unbanFailed: 'Failed to unban user',
      inputDetailTitle: 'Input Summary Detail',
      inputDetailContent: 'Full Content',
      matchedKeyword: 'Matched Keyword',
      queueDelay: 'Queued {ms} ms',
      allGroups: 'All Groups',
      allGroupsHint: 'Auditing all groups',
      selectedGroupsHint: 'Auditing selected groups',
      groupScope: 'Audit Groups',
      groupScopeHint: 'Switch on for all groups, or turn off to choose specific groups.',
      selectedGroups: 'Selected Groups',
      searchGroups: 'Search group name or platform',
      noGroups: 'No groups available',
      modelFilter: 'Model scope',
      modelFilterHint: 'Moderate by the client-requested model name; channel model mappings do not change this match.',
      modelFilterAll: 'All models',
      modelFilterAllDesc: 'All model requests go through content moderation.',
      modelFilterInclude: 'Only selected',
      modelFilterIncludeDesc: 'Only listed models go through content moderation.',
      modelFilterExclude: 'Exclude selected',
      modelFilterExcludeDesc: 'Listed models skip content moderation; other models are moderated.',
      modelFilterModels: 'Model list',
      modelFilterModelCount: '{count} models configured',
      modelFilterModelsRequired: 'This model scope requires at least 1 model',
      modelFilterAllSummary: 'Applies to all models',
      modelFilterIncludeSummary: 'Applies to {count} models',
      modelFilterExcludeSummary: 'Excludes {count} models',
      emptyLogs: 'No audit records',
      preBlockSyncStatus: 'Pre-Block Sync Status',
      preBlockSyncHint: 'Live counters for the synchronous moderation path, excluding async record tasks.',
      preBlockActive: 'Sync Processing',
      preBlockActiveHint: 'Currently checking',
      preBlockChecked: 'Checked',
      preBlockCheckedHint: 'Entered pre-block path',
      preBlockAllowed: 'Allowed',
      preBlockAllowedHint: 'No block triggered',
      preBlockBlocked: 'Blocked',
      preBlockBlockedHint: 'Rejected after hit',
      preBlockErrors: 'Audit Errors',
      preBlockErrorsHint: 'Failed or no usable key',
      preBlockAvgLatency: 'Avg Latency',
      preBlockAvgLatencyHint: 'Synchronous path average',
      preBlockAPIKeyLoad: 'Audit Key Load',
      preBlockAPIKeyLoadHint: 'Synchronous pre-block checks round-robin usable audit keys directly.',
      preBlockAPIKeyLoadSummary: 'Sync active {active} / usable keys {available}, {total} total, worker: {workerActive} / {workerTotal}',
      preBlockAPIKeyTotals: 'Total {total}, success {success}, errors {errors}',
      preBlockAPIKeyLoadEmpty: 'No audit key load data yet',
      preBlockKeyActiveShort: 'Active',
      preBlockKeyTotalShort: 'Total',
      preBlockKeyAvgShort: 'Avg',
      preBlockKeyLastShort: 'Last',
      workerStatus: 'Worker Runtime',
      workerStatusHint: 'Queue and worker pool status for async audit tasks and pre-block record tasks, excluding synchronous pre-block checks.',
      workerPool: 'Worker Pool',
      workerPoolMeta: '{active} processing, {idle} idle and ready, {total} total',
      queueUsage: 'Queue Usage',
      activeWorkers: 'Processing',
      idleWorkers: 'Idle Ready',
      workerActive: 'Processing an async audit or record task',
      workerIdle: 'Started, idle and ready',
      workerDisabled: 'Risk control or content audit is disabled',
      processed: 'Processed',
      droppedErrors: 'Dropped / Errors',
      autoRefresh: 'Auto refresh every 15s',
      lastCleanup: 'Last cleanup: {time}',
      cleanupStats: 'Last cleanup deleted {hit} hits and {nonHit} non-hits',
      riskSwitchOff: 'System switch off',
      riskThresholds: 'Risk Thresholds',
      riskThresholdsHint: 'Adjust hit thresholds by OpenAI Moderations category. Scores greater than or equal to the threshold count as hits.',
      riskThresholdDefault: 'Default {value}',
      riskThresholdReset: 'Restore defaults',
      riskThresholdPercent: 'Threshold percentage',
      tabs: {
        basic: 'Basic',
        scope: 'Scope',
        runtime: 'Runtime',
        response: 'Hit Notice',
        riskThresholds: 'Risk Thresholds',
        keywords: 'Keyword Block',
        retention: 'Retention',
      },
      blockedKeywords: 'Blocked keywords',
      blockedKeywordsPlaceholder: 'One keyword per line, e.g.:\nbadword1\nbadword2',
      blockedKeywordsDescription: 'Matching is case-insensitive. Whether the upstream moderation API is invoked after a hit depends on the strategy below.',
      blockedKeywordsPreBlockHint: 'Keyword blocking only takes effect in "Pre-block" mode.',
      blockedKeywordsModeWarning: 'Current mode is "{mode}". Keyword blocking will not run until you switch to "Pre-block" mode.',
      blockedKeywordCount: '{count} keywords configured',
      blockedKeywordsLimit: 'Up to {max} keywords, each no longer than 200 characters. Duplicates are removed automatically.',
      keywordBlockingMode: 'Moderation strategy',
      keywordModeKeywordAndApi: 'Keyword + API',
      keywordModeKeywordAndApiDesc: 'Block on keyword hit; otherwise fall through to the upstream moderation API.',
      keywordModeKeywordOnly: 'Keyword only',
      keywordModeKeywordOnlyDesc: 'Decide using keywords only; misses are allowed without calling the API, saving upstream cost.',
      keywordModeKeywordOnlyNotice: 'Keyword-only strategy: requests that do not match any keyword are allowed without calling the upstream moderation API.',
      keywordModeApiOnly: 'API only',
      keywordModeApiOnlyDesc: 'Use the upstream moderation API only; the keyword list configured here is not consulted.',
      keywordModeApiOnlyNotice: 'API-only strategy: the keyword list is not consulted; all requests go through the upstream moderation API.',
      overview: {
        status: 'Status',
        enabled: 'Enabled',
        disabled: 'Disabled',
        apiKey: 'API Key',
        groupScope: 'Scope',
        logs: 'Audit Records',
        currentFilter: 'Current filter',
      },
      filters: {
        search: 'Search user/key/summary',
        from: 'From',
        to: 'To',
        allGroups: 'All Groups',
        allEndpoints: 'All Endpoints',
      },
      table: {
        time: 'Time',
        group: 'Group',
        user: 'User',
        apiKey: 'API Key',
        endpoint: 'Endpoint',
        result: 'Result',
        highest: 'Highest',
        actionMeta: 'Action',
        latency: 'Latency',
        input: 'Input Summary',
      },
      result: {
        all: 'All Results',
        hit: 'Hit',
        blocked: 'Blocked',
        pass: 'Pass',
        error: 'Error',
      },
      action: {
        block: 'Blocked',
        keywordBlock: 'Keyword Blocked',
        cyberPolicy: 'Cyber policy',
        error: 'Error',
      },
    },

    // Channel Monitor
    channelMonitor: {
      title: 'Channel Monitor',
      description: 'Monitor channel availability, latency and status',
      searchPlaceholder: 'Search monitor name...',
      allProviders: 'All Providers',
      allStatus: 'All Status',
      enabledFilter: 'Enabled',
      onlyEnabled: 'Enabled only',
      onlyDisabled: 'Disabled only',
      createButton: 'Create Monitor',
      createTitle: 'Create Channel Monitor',
      editTitle: 'Edit Channel Monitor',
      runNow: 'Run Now',
      runSuccess: 'Check completed',
      runFailed: 'Check failed',
      duplicate: 'Duplicate',
      duplicating: 'Duplicating',
      duplicateSuccess: 'Monitor duplicated as "{name}" and disabled. Review its configuration before enabling it.',
      duplicateFailed: 'Failed to duplicate monitor',
      duplicateKeyUnavailable: 'The API key cannot be decrypted. Re-enter it before duplicating this monitor.',
      apiKeyDecryptFailed: 'API Key decryption failed. Please re-edit this monitor with a fresh key.',
      createSuccess: 'Monitor created',
      updateSuccess: 'Monitor updated',
      deleteSuccess: 'Monitor deleted',
      loadError: 'Failed to load monitors',
      deleteConfirm: 'Are you sure you want to delete monitor "{name}"? This action cannot be undone.',
      nameRequired: 'Please enter a monitor name',
      primaryModelRequired: 'Please enter a primary model',
      linkedAccountRequired: 'Please select a linked account',
      columns: {
        name: 'Name',
        provider: 'Provider',
        primaryModel: 'Primary Model',
        availability7d: '7d Availability',
        latency: 'Latency (ms)',
        enabled: 'Enabled',
        actions: 'Actions'
      },
      form: {
        name: 'Name',
        namePlaceholder: 'Enter monitor name',
        provider: 'Platform',
        checkMode: 'Check Mode',
        checkModeProbe: 'Probe',
        checkModeProbeHint: 'Sends a lightweight LLM request to measure availability and latency',
        checkModeQuota: 'Quota',
        checkModeQuotaHint: 'Only queries the linked account usage windows / balance without probe requests',
        checkModeQuotaProbe: 'Probe + Quota',
        checkModeQuotaProbeHint: 'Probes the channel and attaches the quota snapshot to the primary model result',
        linkedAccount: 'Linked Account',
        linkedAccountPlaceholder: 'Select an account',
        linkedAccountHint: 'Quota data comes from the selected account (reuses the account-side usage/balance queries)',
        linkedAccountEmpty: 'No accounts on this platform yet. Add one in Account Management first',
        linkedAccountMissing: 'The linked account no longer exists or is not accessible. Please re-select an account',
        openAIQuotaProbeHint: 'Note: on the OpenAI platform the usage query may trigger a Codex probe request that consumes the account\'s own quota (at most once every 10 minutes)',
        apiMode: 'OpenAI protocol',
        apiModeChatCompletions: 'OpenAI Compatible',
        apiModeChatCompletionsHint: 'Use /v1/chat/completions with messages; works for most compatible providers.',
        apiModeResponses: 'Responses API',
        apiModeResponsesHint: 'Use /v1/responses with default instructions + input; best for self-check/Codex paths.',
        endpoint: 'Endpoint',
        endpointPlaceholder: 'https://api.example.com',
        useCurrentDomain: 'Use current service',
        apiKey: 'API Key',
        apiKeyPlaceholder: 'Enter API Key',
        apiKeyEditPlaceholder: 'Leave blank to keep current key',
        useMyKey: 'Use my key',
        selectKeyTitle: 'Select my API Key',
        selectKeyHint: 'Only your active, non-expired keys are listed.',
        noActiveKey: 'No active API keys available',
        primaryModel: 'Primary Model',
        primaryModelPlaceholder: 'gpt-4o-mini',
        extraModels: 'Extra Models',
        extraModelsPlaceholder: 'Press Enter to add extra model',
        groupName: 'Group Name',
        groupNamePlaceholder: 'Optional, used to group rows in user view',
        intervalSeconds: 'Interval (seconds)',
        intervalSecondsHint: 'Range: 15 - 3600 seconds',
        jitterSeconds: 'Random Jitter (± seconds)',
        jitterSecondsHint: 'Each check fires at interval ± a random offset within this value; 0 means fixed interval. Interval minus jitter must be ≥ 15s',
        enabled: 'Enable monitor',
        kindRequired: 'Please select a provider'
      },
      runResultTitle: 'Check Result',
      noMonitorsYet: 'No monitors yet',
      createFirstMonitor: 'Create your first monitor to track channel availability',
      advanced: {
        section: 'Advanced (optional)',
        sectionHint: 'Customize request headers and body to bypass upstream client-detection (e.g. "only Claude Code clients allowed").',
        headers: 'Custom request headers',
        headersPlaceholder: 'User-Agent: claude-cli/1.0.83 (external, cli)\nx-app: cli\nanthropic-beta: claude-code-20250219',
        headerNamePlaceholder: 'Header name',
        headerValuePlaceholder: 'Value',
        headerAddRow: 'Add header',
        headerNameInvalid: 'Header name cannot contain whitespace or colon: {name}',
        headersHint: 'Merged on top of adapter defaults (user wins). Hop-by-hop headers (Host / Content-Length / ...) are ignored.',
        headersParseError: 'Cannot parse line: {line}',
        bodyMode: 'Body handling',
        bodyModeOff: 'Default',
        bodyModeMerge: 'Merge',
        bodyModeReplace: 'Replace',
        bodyModeHintOff: 'Use the adapter default body (includes challenge validation).',
        bodyModeHintMerge: 'Shallow-merge with the default body; user fields win but model / messages / contents are protected (use Replace to change those).',
        bodyModeHintReplace: 'Use the JSON below as the complete body. Challenge validation is skipped; HTTP 2xx + non-empty response text is treated as operational.',
        bodyJson: 'Body JSON',
        bodyJsonFormat: 'Format',
        bodyJsonHint: 'Parsed on blur. Empty means no override.',
        bodyJsonError: 'JSON parse failed',
        bodyJsonObjectError: 'Body must be a JSON object (no arrays or primitives)'
      },
      templateField: {
        label: 'Request template',
        none: 'No template',
        placeholder: 'Pick a template (filtered by current provider)',
        applyHint: 'Picking a template copies its headers and body to this monitor (snapshot). Later template edits are not auto-synced.'
      },
      template: {
        manageButton: 'Templates',
        managerTitle: 'Request template manager',
        createButton: 'New template',
        emptyState: 'No templates for this provider yet',
        missingName: 'Template name is required',
        createSuccess: 'Template created',
        updateSuccess: 'Template updated',
        deleteSuccess: 'Template deleted',
        applyButton: 'Apply to monitors',
        applyTooltip: 'Overwrite snapshot fields on associated monitors',
        applyTitle: 'Apply template',
        applyConfirm: 'Apply',
        applyConfirmMessage: 'Overwrite {n} associated monitor(s) with the current configuration of "{name}"? Any local customizations on those monitors will be discarded.',
        applySuccess: 'Applied to {n} monitor(s)',
        applyPickerTitle: 'Apply template "{name}"',
        applyPickerHint: 'Select which monitors to overwrite (all selected by default). Any local customizations will be discarded.',
        applyPickerEmpty: 'No monitors are currently associated to this template',
        applyPickerConfirm: 'Apply to {n} monitor(s)',
        selectNone: 'Select none',
        selectedCount: 'Selected {n} / {total}',
        deleteConfirm: 'Delete template "{name}"? {n} associated monitor(s) will be disassociated but keep their current snapshot and continue running.',
        associatedCount: '{n} associated monitor(s)',
        headersSummary: '{n} custom header(s)',
        form: {
          name: 'Template name',
          namePlaceholder: 'e.g. Claude Code mimicry',
          description: 'Description',
          descriptionPlaceholder: 'Optional: what this template is for, capture date, etc.'
        }
      }
    },

    // Subscriptions
    subscriptions: {
      title: 'Subscription Management',
      description: 'Manage user subscriptions and quota limits',
      assignSubscription: 'Assign Subscription',
      adjustSubscription: 'Adjust Subscription',
      revokeSubscription: 'Revoke Subscription',
      restoreSubscription: 'Restore Subscription',
      allStatus: 'All Status',
      allGroups: 'All Groups',
      allPlatforms: 'All Platforms',
      daily: 'Daily',
      weekly: 'Weekly',
      monthly: 'Monthly',
      noLimits: 'No limits configured',
      unlimited: 'Unlimited',
      resetNow: 'Resetting soon',
      windowNotActive: 'Window not active',
      resetInMinutes: 'Resets in {minutes}m',
      resetInHoursMinutes: 'Resets in {hours}h {minutes}m',
      resetInDaysHours: 'Resets in {days}d {hours}h',
      quotaEndsInMinutes: 'Quota ends in {minutes}m',
      quotaEndsInHoursMinutes: 'Quota ends in {hours}h {minutes}m',
      quotaEndsInDaysHours: 'Quota ends in {days}d {hours}h',
      daysRemaining: '{days} days remaining',
      hoursMinutesRemaining: '{hours}h {minutes}m remaining',
      minutesRemaining: '{minutes}m remaining',
      remainingDays: 'Remaining days',
      noExpiration: 'No expiration',
      status: {
        active: 'Active',
        expired: 'Expired',
        revoked: 'Revoked',
        suspended: 'Suspended'
      },
      columns: {
        user: 'User',
        group: 'Group',
        usage: 'Usage',
        expires: 'Expires',
        status: 'Status',
        actions: 'Actions'
      },
      form: {
        user: 'User',
        group: 'Subscription Group',
        validityDays: 'Validity (Days)',
        adjustDays: 'Adjust by (Days)'
      },
      selectUser: 'Select a user',
      selectGroup: 'Select a subscription group',
      groupHint: 'Only groups with subscription billing type are shown',
      validityHint: 'Number of days the subscription will be valid',
      adjustingFor: 'Adjusting subscription for',
      currentExpiration: 'Current expiration',
      adjustDaysPlaceholder: 'Positive to extend, negative to shorten',
      adjustHint: 'Enter positive number to extend, negative to shorten (remaining days must be > 0)',
      assign: 'Assign',
      assigning: 'Assigning...',
      adjust: 'Adjust',
      adjusting: 'Adjusting...',
      revoke: 'Revoke',
      restore: 'Restore',
      resetQuota: 'Reset Quota',
      resetQuotaTitle: 'Reset Usage Quota',
      resetQuotaConfirm: "Reset the daily, weekly, and monthly usage quota for '{user}'? Usage will be zeroed and windows restarted from today.",
      quotaResetSuccess: 'Quota reset successfully',
      failedToResetQuota: 'Failed to reset quota',
      noSubscriptionsYet: 'No subscriptions yet',
      assignFirstSubscription: 'Assign a subscription to get started.',
      subscriptionAssigned: 'Subscription assigned successfully',
      subscriptionAdjusted: 'Subscription adjusted successfully',
      subscriptionRevoked: 'Subscription revoked successfully',
      subscriptionRestored: 'Subscription restored successfully',
      failedToLoad: 'Failed to load subscriptions',
      failedToAssign: 'Failed to assign subscription',
      failedToAdjust: 'Failed to adjust subscription',
      failedToRevoke: 'Failed to revoke subscription',
      failedToRestore: 'Failed to restore subscription',
      adjustWouldExpire: 'Remaining days after adjustment must be greater than 0',
      adjustOutOfRange: 'Adjustment days must be between -36500 and 36500',
      pleaseSelectUser: 'Please select a user',
      pleaseSelectGroup: 'Please select a group',
      validityDaysRequired: 'Please enter a valid number of days (at least 1)',
      revokeConfirm:
        "Are you sure you want to revoke the subscription for '{user}'? You can restore it later from the revoked list.",
      restoreConfirm:
        "Restore the subscription for '{user}'? If the original subscription has expired, it will be restored as expired.",
      guide: {
        title: 'Subscription Management Guide',
        subtitle: 'Subscription mode lets you assign time-based usage quotas to users, with daily/weekly/monthly limits. Follow these steps to get started.',
        showGuide: 'Usage Guide',
        step1: {
          title: 'Create a Subscription Group',
          line1: 'Go to "Group Management" page, click "Create Group"',
          line2: 'Set billing type to "Subscription", configure daily/weekly/monthly quota limits',
          line3: 'Save the group and ensure its status is "Active"',
          link: 'Go to Group Management'
        },
        step2: {
          title: 'Assign Subscription to User',
          line1: 'Click the "Assign Subscription" button in the top right',
          line2: 'Search for a user by email and select them',
          line3: 'Choose a subscription group, set validity days, then click "Assign"'
        },
        step3: {
          title: 'Manage Existing Subscriptions'
        },
        actions: {
          adjust: 'Adjust',
          adjustDesc: 'Extend or shorten the subscription validity period',
          resetQuota: 'Reset Quota',
          resetQuotaDesc: 'Reset daily/weekly/monthly usage to zero',
          revoke: 'Revoke',
          revokeDesc: 'Immediately terminate the subscription (restorable from the revoked list)'
        },
        tip: 'Tip: Only groups with billing type "Subscription" and status "Active" appear in the group dropdown. If no options are available, create one in Group Management first.'
      }
    },

    // Accounts
}
