mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-06 14:33:10 +08:00
1431 lines
86 KiB
TypeScript
1431 lines
86 KiB
TypeScript
export default {
|
||
settings: {
|
||
title: 'System Settings',
|
||
description: 'Manage registration, email verification, default values, and SMTP settings',
|
||
tabs: {
|
||
general: 'General',
|
||
agreement: 'Agreement',
|
||
features: 'Feature Switches',
|
||
security: 'Security',
|
||
users: 'Users',
|
||
gateway: 'Gateway',
|
||
email: 'Email',
|
||
backup: 'Backup',
|
||
payment: 'Payment',
|
||
},
|
||
features: {
|
||
channelMonitor: {
|
||
title: 'Channel Monitor',
|
||
description: 'Choose either V1 active probes or V2 passive usage monitoring. When disabled, both background jobs stop and the user entry is hidden.',
|
||
configureLink: 'Configure monitors in Channel Management > Channel Monitor',
|
||
enabled: 'Enable Channel Monitor',
|
||
enabledHint: 'Disabling stops both the V1 scheduler and V2 aggregation; existing config and history are kept.',
|
||
mode: 'Monitor mode',
|
||
modeHint: 'Only one implementation can be active: V2 never sends upstream probes; V1 probes configured monitors on a schedule.',
|
||
modeV2: 'V2 passive monitoring',
|
||
modeV1: 'V1 active probes',
|
||
modeV2Hint: 'Aggregates health metrics from real gateway traffic without upstream probe traffic.',
|
||
modeV1Hint: 'Runs scheduled upstream health checks for configured channel monitors (probe traffic).',
|
||
defaultInterval: 'Default check interval (seconds)',
|
||
defaultIntervalHint: 'V1 only: default interval for new monitors (overridable per monitor). Range 15 – 3600 seconds.',
|
||
hideThroughput: 'Hide throughput rates from users (RPM / TPM)',
|
||
hideThroughputHint:
|
||
'When on, the user Channel Monitor page and user APIs omit RPM and TPM so fleet volume cannot be reverse-estimated from rates × window. Admins still see full metrics. Error rates, latency, and cache rates remain visible.',
|
||
},
|
||
availableChannels: {
|
||
title: 'Available Channels',
|
||
description: 'Show logged-in users an aggregate view of the channels, models and pricing they can access. Disabled by default.',
|
||
configureLink: 'Configure model pricing in Channel Management > Channel Pricing',
|
||
enabled: 'Enable Available Channels',
|
||
enabledHint: 'When off, the sidebar entry is hidden and the endpoint returns an empty list.',
|
||
},
|
||
modelPlaza: {
|
||
title: 'Model Plaza',
|
||
description: 'A public page showcasing available models and pricing by group. Disabled by default.',
|
||
enabled: 'Enable Model Plaza',
|
||
enabledHint: 'When enabled, an entry appears in the header and the page is reachable at /model-plaza.',
|
||
requireAuth: 'Require sign-in to access',
|
||
requireAuthHint: 'When on, anonymous visitors are redirected to the login page; when off, the page is public and anonymous visitors only see non-exclusive groups.',
|
||
priceDescription: 'Pricing notes (Markdown)',
|
||
priceDescriptionHint: 'Rendered at the top of the plaza page. Use it for billing rules, exchange rates, promotions, etc.',
|
||
},
|
||
riskControl: {
|
||
title: 'Risk Control',
|
||
description: 'Enable the content moderation menu and gateway audit entry point. Disabled by default.',
|
||
configureLink: 'Configure content moderation in Risk Control',
|
||
enabled: 'Enable Risk Control',
|
||
enabledHint: 'When off, the admin sidebar entry is hidden and gateway moderation is skipped.',
|
||
cyberSessionBlock: 'Cyber session auto-block',
|
||
cyberSessionBlockHint: 'When enabled, sessions hit by upstream cyber_policy are blocked locally for the TTL and no longer forwarded. Only the offending session is blocked; other sessions on the same key are unaffected.',
|
||
cyberSessionBlockTTL: 'Block TTL (seconds)',
|
||
},
|
||
affiliate: {
|
||
title: 'Affiliate (Invite Rebate)',
|
||
description: 'Existing users invite new ones; the inviter earns a percentage rebate on the invitee’s recharges. Disabled by default.',
|
||
enabled: 'Enable Affiliate',
|
||
enabledHint: 'When off, the affiliate menu is hidden, the aff parameter is ignored at signup, and new recharges generate no rebate. Existing rebate balances can still be transferred.',
|
||
adminRechargeRebate: 'Rebate Admin Deposits',
|
||
adminRechargeRebateHint: 'When enabled, balance added through User Management > Deposit generates affiliate rebates. Setting a balance or withdrawing funds does not.',
|
||
rebateRate: 'Global Rebate Rate',
|
||
rebateRateHint: 'Default percentage given back to the inviter on recharges (0-100, e.g. 10 = 10%).',
|
||
freezeHours: 'Rebate Freeze Period (hours)',
|
||
freezeHoursDesc: 'New rebates will be frozen for this period before becoming available for withdrawal. 0 = no freeze.',
|
||
durationDays: 'Rebate Duration (days)',
|
||
durationDaysDesc: 'Rebate relationship expires after this many days since invitee registration. 0 = permanent.',
|
||
perInviteeCap: 'Per-Invitee Rebate Cap',
|
||
perInviteeCapDesc: 'Maximum total rebate from a single invitee. 0 = no limit.',
|
||
customUsers: {
|
||
title: 'Per-User Overrides',
|
||
description: 'Set a custom invite code or exclusive rebate rate for specific users. Lists only users that have an override applied.',
|
||
addButton: 'Add Custom User',
|
||
searchPlaceholder: 'Search by email or username',
|
||
batchButton: 'Batch Set Rate ({count} selected)',
|
||
empty: 'No users with custom affiliate settings yet',
|
||
customBadge: 'custom',
|
||
useGlobal: 'use global',
|
||
resetTitle: 'Reset Custom Settings',
|
||
resetMessage: 'Reset all custom settings for {email}?\n• The exclusive rebate rate will be cleared (fall back to the global rate)\n• The invite code will be regenerated as a new system code (previously shared links will stop working)',
|
||
totalLabel: '{total} total',
|
||
col: {
|
||
email: 'Email',
|
||
username: 'Username',
|
||
code: 'Invite Code',
|
||
rate: 'Custom Rate',
|
||
actions: 'Actions',
|
||
},
|
||
},
|
||
modal: {
|
||
addTitle: 'Add Custom User',
|
||
editTitle: 'Edit Custom Settings',
|
||
userLabel: 'User',
|
||
userPlaceholder: 'Search by email or username',
|
||
changeUser: 'Change user',
|
||
codeLabel: 'Custom Invite Code (optional)',
|
||
codePlaceholder: 'e.g. VIP2026',
|
||
codeHint: '4-32 characters; A-Z, 0-9, underscore, dash. Leave empty to keep current. Input is upper-cased.',
|
||
rateLabel: 'Exclusive Rebate Rate (optional)',
|
||
ratePlaceholder: 'e.g. 30',
|
||
rateHint: '0-100. Leave empty (in edit mode) to clear and fall back to the global rate.',
|
||
errorBadRate: 'Please enter a number between 0 and 100',
|
||
errorEmpty: 'Fill at least one: custom invite code or exclusive rebate rate',
|
||
},
|
||
batchModal: {
|
||
title: 'Batch Set Rate ({count} users selected)',
|
||
hint: 'Apply the same exclusive rebate rate to all selected users.',
|
||
placeholder: 'e.g. 30',
|
||
clearHint: 'Submitting empty will clear the exclusive rate for selected users.',
|
||
},
|
||
},
|
||
},
|
||
emailTabDisabledTitle: 'Email Verification Not Enabled',
|
||
emailTabDisabledHint: 'Enable email verification in the Security tab to configure SMTP settings.',
|
||
registration: {
|
||
title: 'Registration Settings',
|
||
description: 'Control user registration and verification',
|
||
enableRegistration: 'Enable Registration',
|
||
enableRegistrationHint: 'Allow new users to register',
|
||
emailVerification: 'Email Verification',
|
||
emailVerificationHint: 'Require email verification for new registrations',
|
||
emailSuffixWhitelist: 'Email Domain Whitelist',
|
||
emailSuffixWhitelistHint:
|
||
"Only email addresses from the specified domains can register (for example, {'@'}qq.com, {'@'}gmail.com, *.edu.cn)",
|
||
emailSuffixWhitelistPlaceholder: "{'@'}example.com, *.edu.cn",
|
||
emailSuffixWhitelistInputHint: 'Leave empty for no restriction. Use *.edu.cn to match edu.cn and its subdomains.',
|
||
promoCode: 'Promo Code',
|
||
promoCodeHint: 'Allow users to use promo codes during registration',
|
||
invitationCode: 'Invitation Code Registration',
|
||
invitationCodeHint: 'When enabled, users must enter a valid invitation code to register',
|
||
passwordReset: 'Password Reset',
|
||
passwordResetHint: 'Allow users to reset their password via email',
|
||
frontendUrl: 'Frontend URL',
|
||
frontendUrlPlaceholder: 'https://example.com',
|
||
frontendUrlHint: 'Used to generate password reset links in emails. Example: https://example.com',
|
||
totp: 'Two-Factor Authentication (2FA)',
|
||
totpHint: 'Allow users to use authenticator apps like Google Authenticator',
|
||
totpKeyNotConfigured:
|
||
'Please configure TOTP_ENCRYPTION_KEY in environment variables first. Generate a key with: openssl rand -hex 32'
|
||
},
|
||
security: {
|
||
passkey: 'Passkey Sign-in',
|
||
passkeyHint: 'Allow passwordless sign-in and user-managed passkeys when the relying party configuration is valid.',
|
||
passkeyConfigured: 'WebAuthn relying party configuration is valid.',
|
||
passkeyNotConfigured: 'Configure a valid RP ID and allowed HTTPS origins before enabling passkey sign-in.',
|
||
passkeyRPID: 'RP ID',
|
||
passkeyOrigins: 'Allowed HTTPS origins',
|
||
passkeyValueNotConfigured: 'Not configured',
|
||
passkeyDeploymentHint: 'Ask the server operator to set webauthn.enabled to true, configure webauthn.rp_id (domain only) and webauthn.rp_origins (full HTTPS origins), then restart the service.',
|
||
stepUp: 'Step-up 2FA for Sensitive Operations',
|
||
stepUpHint: 'When enabled, sensitive operations (account/proxy export, backup creation and download, S3 config changes, promoting admins) require a recent TOTP verification (valid for 15 minutes). Your own account must have 2FA enabled before turning this on; turning it off also requires step-up verification.',
|
||
stepUpEnableRequiresTotp: 'Enable 2FA (TOTP) for your own account in Profile before turning on step-up verification.',
|
||
sessionBinding: 'Session IP/UA Binding',
|
||
sessionBindingHint: 'Bind login sessions to the client IP and User-Agent. Any change immediately invalidates the session and forces re-login, raising the bar for stolen-credential reuse.',
|
||
auditRetention: 'Audit Log Retention (days)',
|
||
auditRetentionHint: 'Audit logs older than this are cleaned up automatically. Set to 0 to keep them forever (manual clear only).'
|
||
},
|
||
panelRateLimit: {
|
||
title: 'Panel API Rate Limiting',
|
||
description: 'Throttle panel API requests to keep high-frequency polling (usage stats, dashboard queries) from overwhelming the database',
|
||
proxySafeNote: 'Authenticated endpoints are counted per user account, independent of the source IP — reverse proxies and shared NAT egress are never falsely blocked. Public endpoints are counted per real client IP, and loopback/private addresses (internal proxy hops) are skipped automatically.',
|
||
enabled: 'Enable panel rate limiting',
|
||
enabledHint: 'Limits authenticated panel endpoints per account. Requests over the threshold get HTTP 429 and recover automatically when the window resets.',
|
||
userRpm: 'Requests per account',
|
||
userRpmHint: 'Total panel API requests allowed per account per minute. Normal UI usage stays far below this. 0 = unlimited.',
|
||
heavyRpm: 'Heavy queries per account',
|
||
heavyRpmHint: 'Usage/dashboard aggregation queries allowed per account per minute (these are the most expensive for the database). 0 = unlimited.',
|
||
publicIpRpm: 'Public endpoints per IP',
|
||
publicIpRpmHint: 'Requests per minute allowed per real client IP for unauthenticated endpoints (e.g. public site settings). 0 = unlimited.',
|
||
perMinute: 'req/min',
|
||
exemptAdmin: 'Exempt administrators',
|
||
exemptAdminHint: 'When enabled, admin accounts bypass panel rate limits so bulk operations are never throttled.',
|
||
saved: 'Panel rate limit settings saved',
|
||
saveFailed: 'Failed to save panel rate limit settings'
|
||
},
|
||
turnstile: {
|
||
title: 'Cloudflare Turnstile',
|
||
description: 'Bot protection for login and registration',
|
||
enableTurnstile: 'Enable Turnstile',
|
||
enableTurnstileHint: 'Require Cloudflare Turnstile verification',
|
||
siteKey: 'Site Key',
|
||
secretKey: 'Secret Key',
|
||
siteKeyHint: 'Get this from your Cloudflare Dashboard',
|
||
cloudflareDashboard: 'Cloudflare Dashboard',
|
||
secretKeyHint: 'Server-side verification key (keep this secret)',
|
||
secretKeyConfiguredHint: 'Secret key configured. Leave empty to keep the current value.'
|
||
},
|
||
captcha: {
|
||
title: 'CAPTCHA',
|
||
description: 'Bot protection for login and registration',
|
||
enable: 'Enable CAPTCHA',
|
||
enableHint: 'Require human verification on login, registration and related flows',
|
||
provider: 'Provider',
|
||
providerTurnstile: 'Cloudflare Turnstile',
|
||
providerTencent: 'Tencent Captcha',
|
||
providerAliyun: 'Aliyun Captcha 2.0'
|
||
},
|
||
tencentCaptcha: {
|
||
title: 'Tencent Captcha',
|
||
description: 'Slider captcha protection for login, registration, and third-party account creation',
|
||
enable: 'Enable Tencent Captcha',
|
||
enableHint: 'Use Tencent slider captcha in every existing Turnstile flow',
|
||
keepExisting: 'Leave empty to keep current value',
|
||
configured: 'Configured. Leave empty to keep it.',
|
||
required: 'Required before enabling.',
|
||
mutualExclusion: 'Tencent Captcha, Cloudflare Turnstile and Aliyun Captcha are mutually exclusive. Enabling one disables the others.',
|
||
region: 'Service site',
|
||
regionCn: 'Chinese mainland',
|
||
regionIntl: 'International',
|
||
regionHint: 'Selects the SDK script and the server-side verification endpoint. It must match the site that issued your CaptchaAppId; international apps are created in the tencentcloud.com console.',
|
||
appCredentialsTitle: 'Captcha application credentials',
|
||
appCredentialsHint: 'Get CaptchaAppId and AppSecretKey from Verification Management in the Captcha console.',
|
||
cloudCredentialsTitle: 'Cloud API credentials',
|
||
cloudCredentialsHint: 'SecretId and SecretKey authorize server-side DescribeCaptchaResult requests.',
|
||
appId: 'CaptchaAppId',
|
||
appSecretKey: 'AppSecretKey',
|
||
cloudSecretId: 'Tencent Cloud SecretId',
|
||
cloudSecretKey: 'Tencent Cloud SecretKey',
|
||
camPermissionHint: 'Create a CAM sub-user with QcloudCaptchaFullAccess instead of using permanent root-account credentials.',
|
||
aidEncryptedHint: 'aidEncrypted is not supported yet. Keep CaptchaAppId mandatory verification disabled in the Captcha console.',
|
||
openCaptchaConsole: 'Open Captcha console',
|
||
createCloudKeys: 'Create SecretId / SecretKey',
|
||
openWebDocs: 'View Web integration guide'
|
||
},
|
||
aliyunCaptcha: {
|
||
accessKeyId: 'AccessKey ID',
|
||
accessKeyIdHint: 'Alibaba Cloud AccessKey ID used for server-side verification; a captcha-only RAM user is recommended',
|
||
accessKeySecret: 'AccessKey Secret',
|
||
accessKeySecretHint: 'Server-side verification secret (keep this secret)',
|
||
accessKeySecretConfiguredHint: 'Secret configured. Leave empty to keep the current value.',
|
||
sceneId: 'Scene ID',
|
||
sceneIdHint: 'Create a verification scene in the Alibaba Cloud Captcha console; the captcha type (invisible/slider/puzzle) is configured per scene there',
|
||
prefix: 'Prefix',
|
||
prefixHint: 'Found in the instance information on the console overview page',
|
||
region: 'Region',
|
||
regionCn: 'Mainland China',
|
||
regionSgp: 'Singapore',
|
||
regionHint: 'Determines the frontend script region and the server endpoint; must match your captcha instance region'
|
||
},
|
||
apiKeyAcl: {
|
||
title: 'API Key IP Access Control',
|
||
description:
|
||
'Choose which client IP is used by API Key allowlists/denylists, admin audit logs, and session IP/UA binding',
|
||
trustForwardedIp: 'Trust forwarded client IP',
|
||
trustForwardedIpHint:
|
||
'Enabled by default for upgrade compatibility. When enabled, raw CF-Connecting-IP, X-Real-IP, or X-Forwarded-For values take over server.trusted_proxies for client-IP resolution. Disable it to enforce the Gin trusted-proxy chain configured by server.trusted_proxies. Only enable takeover mode when the origin cannot be reached directly. Changing this switch changes existing session IP fingerprints.',
|
||
forwardedClientIpHeaders: 'Custom client-IP headers',
|
||
forwardedClientIpHeadersHint: 'Add CDN or proxy header names to check before the built-in headers.',
|
||
forwardedClientIpHeadersPlaceholder: 'X-Client-IP',
|
||
forwardedClientIpHeadersRiskHint: 'These raw headers can be spoofed when the origin is reachable directly. Restrict origin access before trusting them.',
|
||
forwardedClientIpHeaderInvalid: 'Enter a valid HTTP header name.',
|
||
forwardedClientIpHeadersLimit: 'At most {max} custom client-IP headers are allowed.',
|
||
removeForwardedClientIpHeader: 'Remove {header}'
|
||
},
|
||
linuxdo: {
|
||
title: 'LinuxDo Connect Login',
|
||
description: 'Configure LinuxDo Connect OAuth for Sub2API end-user login',
|
||
enable: 'Enable LinuxDo Login',
|
||
enableHint: 'Show LinuxDo login on the login/register pages',
|
||
clientId: 'Client ID',
|
||
clientIdPlaceholder: 'e.g., hprJ5pC3...',
|
||
clientIdHint: 'Get this from Connect.Linux.Do',
|
||
clientSecret: 'Client Secret',
|
||
clientSecretPlaceholder: '********',
|
||
clientSecretHint: 'Used by backend to exchange tokens (keep it secret)',
|
||
clientSecretConfiguredPlaceholder: '********',
|
||
clientSecretConfiguredHint: 'Secret configured. Leave empty to keep the current value.',
|
||
redirectUrl: 'Redirect URL',
|
||
redirectUrlPlaceholder: 'https://your-domain.com/api/v1/auth/oauth/linuxdo/callback',
|
||
redirectUrlHint:
|
||
'Must match the redirect URL configured in Connect.Linux.Do (must be an absolute http(s) URL)',
|
||
quickSetCopy: 'Generate & Copy (current site)',
|
||
redirectUrlSetAndCopied: 'Redirect URL generated and copied to clipboard'
|
||
},
|
||
dingtalk: {
|
||
title: 'DingTalk Login',
|
||
description: 'Configure DingTalk OAuth for Sub2API end-user login',
|
||
enable: 'Enable DingTalk Login (Internal Corporate App)',
|
||
enableHint: 'Show DingTalk login on the login/register pages',
|
||
clientId: 'Client ID (AppKey)',
|
||
clientIdPlaceholder: 'e.g., dingxxxxxxxxxxxxxxxx',
|
||
clientIdHint: 'Get this from the DingTalk Open Platform app details',
|
||
clientSecret: 'Client Secret (AppSecret)',
|
||
clientSecretPlaceholder: '********',
|
||
clientSecretHint: 'Used by backend to exchange tokens (keep it secret)',
|
||
clientSecretConfiguredPlaceholder: '********',
|
||
clientSecretConfiguredHint: 'Secret configured. Leave empty to keep the current value.',
|
||
redirectUrl: 'Redirect URL',
|
||
redirectUrlPlaceholder: 'https://your-domain.com/api/v1/auth/oauth/dingtalk/callback',
|
||
redirectUrlHint:
|
||
'Must match the redirect URL configured in DingTalk Open Platform (must be an absolute http(s) URL)',
|
||
corpPolicy: {
|
||
label: 'Corp Restriction Policy',
|
||
hint: 'Control which DingTalk accounts (orgs) are allowed to sign in',
|
||
none: 'No restriction (all DingTalk accounts allowed)',
|
||
internalOnly: 'Internal only (single corp)'
|
||
},
|
||
bypassRegistration: 'Enable DingTalk signup',
|
||
bypassRegistrationHint: 'Allow new users to register via DingTalk even when public registration is disabled.',
|
||
syncDisplayName: 'Sync DingTalk display name',
|
||
syncDisplayNameHint: 'Overwrite username with the DingTalk staff name on each login (also stored in the dingtalk_name attribute).',
|
||
syncCorpEmail: 'Sync corporate email',
|
||
syncCorpEmailHint: 'Write the DingTalk corporate email to the dingtalk_email attribute on each login (does not change the login email).',
|
||
syncCorpEmailPermissionHint: 'Requires the OAPI permission "Personal info incl. email (fieldEmail)" to be granted to the app on the DingTalk open platform, otherwise OAPI will not return the email field.',
|
||
syncDept: 'Sync department',
|
||
syncDeptHint: 'Write the full DingTalk department path to the dingtalk_department attribute on each login (fetched live each time).',
|
||
syncDeptPermissionHint: 'Requires the OAPI "Department info read (qyapi_get_department_list)" permission to be granted to the app on the DingTalk open platform, otherwise the department path cannot be resolved.',
|
||
syncDisplayNameTarget: 'Attribute key',
|
||
syncDisplayNameTargetHint: 'Defaults to dingtalk_name / DingTalk Name. Saving settings auto-creates the user attribute by the key and display name above (existing definition only has its display name synced).',
|
||
syncCorpEmailTarget: 'Attribute key',
|
||
syncCorpEmailTargetHint: 'Defaults to dingtalk_email / DingTalk Corporate Email. Saving settings auto-creates the user attribute by the key and display name above (existing definition only has its display name synced).',
|
||
syncDeptTarget: 'Attribute key',
|
||
syncDeptTargetHint: 'Defaults to dingtalk_department / DingTalk Department. Saving settings auto-creates the user attribute by the key and display name above (existing definition only has its display name synced).',
|
||
syncAttrDisplayName: 'Display name'
|
||
},
|
||
oidc: {
|
||
title: 'OIDC Login',
|
||
description: 'Configure a standard OIDC provider (for example Keycloak)',
|
||
enable: 'Enable OIDC Login',
|
||
enableHint: 'Show OIDC login on the login/register pages',
|
||
providerName: 'Provider Name',
|
||
providerNamePlaceholder: 'for example Keycloak',
|
||
clientId: 'Client ID',
|
||
clientIdPlaceholder: 'OIDC client id',
|
||
clientSecret: 'Client Secret',
|
||
clientSecretPlaceholder: '********',
|
||
clientSecretHint: 'Used by backend to exchange tokens (keep it secret)',
|
||
clientSecretConfiguredPlaceholder: '********',
|
||
clientSecretConfiguredHint: 'Secret configured. Leave empty to keep the current value.',
|
||
issuerUrl: 'Issuer URL',
|
||
issuerUrlPlaceholder: 'https://id.example.com/realms/main',
|
||
discoveryUrl: 'Discovery URL',
|
||
discoveryUrlPlaceholder: 'Optional, leave empty to auto-derive from issuer',
|
||
authorizeUrl: 'Authorize URL',
|
||
authorizeUrlPlaceholder: 'Optional, can be discovered automatically',
|
||
tokenUrl: 'Token URL',
|
||
tokenUrlPlaceholder: 'Optional, can be discovered automatically',
|
||
userinfoUrl: 'UserInfo URL',
|
||
userinfoUrlPlaceholder: 'Optional, can be discovered automatically',
|
||
jwksUrl: 'JWKS URL',
|
||
jwksUrlPlaceholder: 'Optional, required when strict ID token validation is enabled',
|
||
scopes: 'Scopes',
|
||
scopesPlaceholder: 'openid email profile',
|
||
scopesHint: 'Must include openid',
|
||
redirectUrl: 'Backend Redirect URL',
|
||
redirectUrlPlaceholder: 'https://your-domain.com/api/v1/auth/oauth/oidc/callback',
|
||
redirectUrlHint: 'Must match the callback URL configured in the OIDC provider',
|
||
quickSetCopy: 'Generate & Copy (current site)',
|
||
redirectUrlSetAndCopied: 'Redirect URL generated and copied to clipboard',
|
||
frontendRedirectUrl: 'Frontend Callback Path',
|
||
frontendRedirectUrlPlaceholder: '/auth/oidc/callback',
|
||
frontendRedirectUrlHint: 'Frontend route used after backend callback',
|
||
tokenAuthMethod: 'Token Auth Method',
|
||
clockSkewSeconds: 'Clock Skew (seconds)',
|
||
allowedSigningAlgs: 'Allowed Signing Algs',
|
||
allowedSigningAlgsPlaceholder: 'RS256,ES256,PS256',
|
||
usePkce: 'Use PKCE',
|
||
validateIdToken: 'Validate ID Token',
|
||
requireEmailVerified: 'Require Email Verified',
|
||
userinfoEmailPath: 'UserInfo Email Path',
|
||
userinfoEmailPathPlaceholder: 'for example data.email',
|
||
userinfoIdPath: 'UserInfo ID Path',
|
||
userinfoIdPathPlaceholder: 'for example data.id',
|
||
userinfoUsernamePath: 'UserInfo Username Path',
|
||
userinfoUsernamePathPlaceholder: 'for example data.username'
|
||
},
|
||
defaults: {
|
||
title: 'Default User Settings',
|
||
description: 'Default values for new users',
|
||
defaultBalance: 'Default Balance',
|
||
defaultBalanceHint: 'Initial balance for new users',
|
||
affiliateRebateRate: 'Affiliate Rebate Rate',
|
||
affiliateRebateRateHint:
|
||
'Rebate percentage credited to inviter after recharge (0-100%, e.g. 10 means 10%)',
|
||
defaultConcurrency: 'Default Concurrency',
|
||
defaultConcurrencyHint: 'Maximum concurrent requests for new users',
|
||
defaultUserRpmLimit: 'Default User RPM Limit',
|
||
defaultUserRpmLimitHint: 'Default max requests per minute for new users; 0 = unlimited. Only applied at new user creation.',
|
||
defaultSubscriptions: 'Default Subscriptions',
|
||
defaultSubscriptionsHint: 'Auto-assign these subscriptions when a new user is created or registered',
|
||
addDefaultSubscription: 'Add Default Subscription',
|
||
defaultSubscriptionsEmpty: 'No default subscriptions configured.',
|
||
defaultSubscriptionsDuplicate:
|
||
'Duplicate subscription group: {groupId}. Each group can only appear once.',
|
||
subscriptionGroup: 'Subscription Group',
|
||
subscriptionValidityDays: 'Validity (days)',
|
||
defaultPlatformQuotas: 'Default Platform Quotas (on signup)',
|
||
defaultPlatformQuotasHint: 'Automatically assigned to new users on signup; existing users are not affected. Leave blank = unlimited.',
|
||
platformQuotaNotice: 'Monthly quota uses a 30-day rolling window, not a calendar month.',
|
||
},
|
||
platformQuota: {
|
||
platform: 'Platform',
|
||
daily: 'Daily (USD)',
|
||
weekly: 'Weekly (USD)',
|
||
monthly: 'Monthly (USD, 30d rolling)',
|
||
placeholder: 'Unlimited',
|
||
},
|
||
claudeCode: {
|
||
title: 'Claude Code Settings',
|
||
description: 'Control Claude Code client access requirements',
|
||
minVersion: 'Minimum Version',
|
||
minVersionPlaceholder: 'e.g. 2.1.63',
|
||
minVersionHint:
|
||
'Reject Claude Code clients below this version (semver format). Leave empty to disable version check.',
|
||
maxVersion: 'Maximum Version',
|
||
maxVersionPlaceholder: 'e.g. 2.5.0',
|
||
maxVersionHint:
|
||
'Reject Claude Code clients above this version (semver format). Leave empty to allow any version.'
|
||
},
|
||
scheduling: {
|
||
title: 'Gateway Scheduling Settings',
|
||
description: 'Control API Key scheduling behavior',
|
||
allowUngroupedKey: 'Allow Ungrouped Key Scheduling',
|
||
allowUngroupedKeyHint: 'When disabled, API Keys not assigned to any group cannot make requests (403 Forbidden). Keep disabled to ensure all Keys belong to a specific group.'
|
||
},
|
||
upstreamBillingProbe: {
|
||
title: 'Upstream Rate Auto Detection',
|
||
description: 'Periodically retrieve rates declared by upstream Sub2API sites. Account rates change only when the separate sync switch is enabled.',
|
||
enabled: 'Enable global auto detection',
|
||
enabledHint: 'When enabled, scheduled detection runs only for accounts that also enable automatic detection. Disabling stops all scheduled detection; manual detection remains available.',
|
||
intervalMinutes: 'Detection interval (minutes)',
|
||
intervalHint: 'Range: 5–1440 minutes. A successful result remains valid for two detection intervals.',
|
||
saved: 'Upstream rate auto detection settings saved',
|
||
saveFailed: 'Failed to save upstream rate auto detection settings'
|
||
},
|
||
ollamaCloudUsage: {
|
||
title: 'Ollama Cloud Usage Refresh',
|
||
description: 'Refresh official Ollama settings-page usage driven by model requests for individually opted-in accounts. Disabled by default. Idle accounts are not polled.',
|
||
enabled: 'Enable global automatic refresh',
|
||
enabledHint: 'Only accounts with a stored browser session and their own automatic refresh switch enabled are refreshed, and only after subsequent model requests. Manual refresh remains available.',
|
||
intervalMinutes: 'Max wait while requests continue (minutes)',
|
||
intervalHint: 'Range: 15–1440 minutes. When continuous requests keep sliding the debounce, force a refresh after this wait.',
|
||
debounceMinutes: 'Quiet period after last request (minutes)',
|
||
debounceHint: 'Range: 1–60 minutes. Refresh after the latest model request has been quiet for this long.',
|
||
saved: 'Ollama Cloud usage refresh settings saved',
|
||
saveFailed: 'Failed to save Ollama Cloud usage refresh settings'
|
||
},
|
||
gatewayForwarding: {
|
||
title: 'Request Forwarding',
|
||
description: 'Control how requests are forwarded to upstream OAuth accounts',
|
||
fingerprintUnification: 'Fingerprint Unification',
|
||
fingerprintUnificationHint: 'Unify X-Stainless-* headers across users sharing the same OAuth account. Disabling passes through each client\'s original headers.',
|
||
metadataPassthrough: 'Metadata Passthrough',
|
||
metadataPassthroughHint: 'Pass through client\'s original metadata.user_id without rewriting. May improve upstream cache hit rates.',
|
||
cchSigning: 'CCH Signing',
|
||
cchSigningHint: 'Sign the billing header in forwarded requests with CCH hash. When disabled, the placeholder is preserved.',
|
||
claudeOAuthSystemPromptInjection: 'Claude OAuth System Blocks',
|
||
claudeOAuthSystemPromptInjectionHint: 'Inject Claude Code-like system blocks for Claude OAuth requests from non-Claude-Code clients. Enabled by default.',
|
||
claudeOAuthSystemPrompt: 'Claude OAuth Expansion Prompt',
|
||
claudeOAuthSystemPromptPlaceholder: 'Leave empty to use the built-in Claude Code expansion prompt.',
|
||
claudeOAuthSystemPromptHint: 'Legacy compatibility: controls only the third injected system block.',
|
||
claudeOAuthSystemPromptBlocks: 'Claude OAuth System Blocks',
|
||
claudeOAuthSystemPromptBlocksHint: "Each block is saved as JSON with enabled, type, text, and optional cache_control. {'{'}billing_header{'}'} stays dynamic per request; the Claude Code identity and expansion prompts can be edited directly or restored from presets.",
|
||
systemBlockTitle: 'System Block {index}',
|
||
systemBlockPreset: 'Preset',
|
||
systemBlockPresetBilling: 'Billing header',
|
||
systemBlockPresetIdentity: 'Claude Code identity',
|
||
systemBlockPresetExpansion: 'Claude Code expansion',
|
||
systemBlockPresetCustom: 'Custom',
|
||
systemBlockType: 'Type',
|
||
systemBlockTypeText: 'Text',
|
||
systemBlockText: 'Content',
|
||
systemBlockCacheControl: 'Cache control',
|
||
systemBlockHide: 'Hide block details',
|
||
systemBlockShow: 'Show block details',
|
||
addSystemBlock: 'Add block',
|
||
resetSystemBlocks: 'Reset defaults',
|
||
cacheTTL5m: '5 minutes',
|
||
cacheTTL1h: '1 hour',
|
||
anthropicCacheTTL1hInjection: 'Anthropic Cache TTL Injection',
|
||
anthropicCacheTTL1hInjectionHint: 'When enabled, existing ephemeral cache_control blocks in Anthropic OAuth/Setup Token request bodies are forced to 1h; response usage is billed back as 5m by default, with account-level TTL billing override taking priority.',
|
||
rewriteMessageCacheControl: 'Rewrite Message Cache Breakpoints',
|
||
rewriteMessageCacheControlHint: 'Default off: preserve client cache_control on message content blocks. When enabled, client breakpoints are stripped and proxy breakpoints are injected for clients that do not manage caching themselves.',
|
||
clientDatelineNormalization: 'Client Dateline Normalization',
|
||
clientDatelineNormalizationHint: 'Default on. Rewrites the "Today\'s date is …" sentence in Anthropic OAuth/Setup Token requests back to a canonical ASCII apostrophe and hyphen date format, erasing steganographic fingerprint bits some clients inject when they detect a non-official base URL. Applies to system prompts and <system-reminder> blocks only; API-Key accounts are unaffected.',
|
||
antigravityUserAgentVersion: 'Antigravity UA Version',
|
||
antigravityUserAgentVersionPlaceholder: '1.23.2',
|
||
antigravityUserAgentVersionHint: 'Leave empty to use ANTIGRAVITY_USER_AGENT_VERSION or the built-in default 1.23.2; when set, the admin setting takes precedence.',
|
||
openaiCodexUserAgent: 'OpenAI Codex UA',
|
||
openaiCodexUserAgentPlaceholder: 'codex-tui/0.146.1 (Ubuntu 22.4.0; x86_64) WindowsTerminal (codex-tui; 0.146.1)',
|
||
openaiCodexUserAgentHint: 'The full Codex User-Agent used for all outbound requests, for customizing the OS / arch / terminal fingerprint. Leave empty to build the standard codex-tui identity from the version below (recommended). If set, both the leading and trailing version declarations are synchronized to the version below, so the UA never stays pinned to the release entered here — under capacity pressure the upstream sheds load by client identity and drops stale or non-official identities first with server_is_overloaded.',
|
||
openaiCodexClientVersion: 'Codex client version',
|
||
openaiCodexClientVersionPlaceholder: 'Leave empty to follow auto-sync',
|
||
openaiCodexClientVersionHint: 'The Codex client version this gateway declares upstream, shared by the User-Agent and the version header. Leave empty to use the auto-synced latest stable release; setting a value pins it and stops following auto-sync.',
|
||
openaiCodexVersionAutoSync: 'Auto-sync Codex version',
|
||
openaiCodexVersionAutoSyncHint: 'Fetches the latest stable client version from the official repository every 6 hours, so you never need to upgrade this service just to keep the version current. When disabled, only the version above or the built-in default is used.',
|
||
openaiCodexVersionSyncedValue: 'Currently synced: {version}',
|
||
codexHardeningTitle: "Codex Settings",
|
||
codexClientRestrictionTitle: "Codex client restriction",
|
||
codexHardeningDesc:
|
||
"Only affects OpenAI OAuth accounts with 'Codex official clients only' enabled (global). Beyond User-Agent/Originator, harden the decision with a version range, an engine-fingerprint gate, and black/whitelists.",
|
||
minCodexVersion: "Min Codex Version",
|
||
minCodexVersionPlaceholder: "e.g. 0.142.0",
|
||
maxCodexVersion: "Max Codex Version",
|
||
maxCodexVersionPlaceholder: "e.g. 0.200.0",
|
||
codexVersionHint:
|
||
"Official clients only: checks their version against the [min, max] range. Leave a side empty to not limit it.",
|
||
codexFingerprintSignals: "Codex engine fingerprint signals",
|
||
codexFingerprintSignalsDesc:
|
||
"Define engine-fingerprint signals: every Required signal must match (AND); within a row, '/'-separated variants are OR'd. None checked = not enforced. Default checks only the x-codex- prefix. Types: header exact / header prefix / body path.",
|
||
codexFpTypeHeaderExact: "Header exact",
|
||
codexFpTypeHeaderPrefix: "Header prefix",
|
||
codexFpTypeBodyPath: "Body path",
|
||
codexFpMatchPlaceholder: "match; '/'-separate variants (e.g. session-id / session_id or x-codex-)",
|
||
codexFpRequired: "Required",
|
||
codexFingerprintNoRequiredWarn: "No signal is marked Required — the engine-fingerprint gate is inactive, allowing every candidate that passes identity/version. Check at least one signal to enable it.",
|
||
codexAllowAppServer: "Codex app-server",
|
||
codexAllowAppServerDesc:
|
||
"Allow third-party clients that embed the Codex engine and connect over the app-server protocol (e.g. Claude Code's codex plugin). Off by default; when on, such clients are allowed once they pass the engine-fingerprint gate (the signal list below); off = only official clients and the whitelist are allowed.",
|
||
codexBlacklist: "User-Agent/Originator Blacklist",
|
||
codexBlacklistDesc:
|
||
"Deny if any field matches; takes precedence over any allow. originator is exact; User-Agent is a 'contains' match (comma-separated).",
|
||
codexWhitelist: "User-Agent/Originator Whitelist",
|
||
codexWhitelistDesc:
|
||
"Allow clients outside the official set: requires exact originator and every User-Agent marker present. Still subject to the fingerprint gate unless 'Skip engine fingerprint' is checked.",
|
||
codexWhitelistSkipFingerprint: "Skip engine fingerprint",
|
||
codexWhitelistSkipFingerprintTooltip:
|
||
"Risk: when checked this entry is allowed on originator + User-Agent alone (both forgeable), with no engine-fingerprint backstop. Use only for trusted third-party clients that genuinely do not send a codex engine fingerprint.",
|
||
codexOriginatorPlaceholder: "originator (exact, e.g. opencode)",
|
||
codexUaContainsPlaceholder: "User-Agent contains markers, comma-separated (e.g. opencode/)",
|
||
codexAddRow: "Add entry",
|
||
codexRemoveRow: "Remove",
|
||
},
|
||
webSearchEmulation: {
|
||
title: 'Web Search Emulation',
|
||
description: 'Inject web search capability for Anthropic API Key accounts that don\'t natively support it',
|
||
enabled: 'Enable Web Search Emulation',
|
||
enabledHint: 'Global switch. When disabled, web search emulation is inactive for all channels and accounts.',
|
||
providers: 'Search Providers',
|
||
addProvider: 'Add Provider',
|
||
providerType: 'Provider Type',
|
||
apiKey: 'API Key',
|
||
apiKeyPlaceholder: 'Enter API Key',
|
||
apiKeyConfigured: 'Configured',
|
||
showApiKey: 'Show',
|
||
hideApiKey: 'Hide',
|
||
copyApiKey: 'Copy',
|
||
copied: 'Copied',
|
||
quotaLimit: 'Quota Limit',
|
||
quotaLimitHint: 'Leave empty for unlimited; must be > 0 if set',
|
||
quotaLimitMustBePositive: 'Quota limit must be greater than 0',
|
||
subscribedAt: 'Subscribed At',
|
||
subscribedAtHint: 'Quota resets monthly from this date; leave empty to disable auto-reset',
|
||
quotaUsage: 'Usage',
|
||
resetUsage: 'Reset',
|
||
resetUsageConfirm: 'Reset usage counter for this provider?',
|
||
resetUsageSuccess: 'Usage counter reset',
|
||
proxy: 'Proxy',
|
||
removeProvider: 'Remove',
|
||
noProviders: 'No search providers configured',
|
||
test: 'Test',
|
||
testDefaultQuery: 'Major world events this year',
|
||
testing: 'Searching...',
|
||
testResultTitle: 'Search Results',
|
||
testResultProvider: 'Provider',
|
||
testNoResults: 'No results found',
|
||
},
|
||
site: {
|
||
title: 'Site Settings',
|
||
description: 'Customize site branding',
|
||
backendMode: 'Backend Mode',
|
||
backendModeDescription:
|
||
'Disables user registration, public site, and self-service features. Only admin can log in and manage the platform.',
|
||
siteName: 'Site Name',
|
||
siteNamePlaceholder: 'Sub2API',
|
||
siteNameHint: 'Displayed in emails and page titles',
|
||
siteSubtitle: 'Site Subtitle',
|
||
siteSubtitlePlaceholder: 'Subscription to API Conversion Platform',
|
||
siteSubtitleHint: 'Displayed on login and register pages',
|
||
apiBaseUrl: 'API Base URL',
|
||
apiBaseUrlPlaceholder: 'https://api.example.com',
|
||
apiBaseUrlHint:
|
||
'Used for "Use Key", "Import to CC Switch", and callback URL suggestions. Leave empty to use current site URL.',
|
||
tablePreferencesTitle: 'Global Table Preferences',
|
||
tablePreferencesDescription: 'Configure default pagination behavior for shared table components',
|
||
tableDefaultPageSize: 'Default Rows Per Page',
|
||
tableDefaultPageSizeHint: 'Must be an integer between 5 and 1000',
|
||
tablePageSizeOptions: 'Rows Per Page Options',
|
||
tablePageSizeOptionsPlaceholder: '10, 20, 50, 100',
|
||
tablePageSizeOptionsHint: 'Use commas to separate integers between 5 and 1000; values are deduplicated and sorted on save',
|
||
tableDefaultPageSizeRangeError: 'Default rows per page must be between {min} and {max}',
|
||
tablePageSizeOptionsFormatError: 'Invalid options format. Enter comma-separated integers between {min} and {max}',
|
||
customEndpoints: {
|
||
title: 'Custom Endpoints',
|
||
description: 'Add additional API endpoint URLs for users to quickly copy on the API Keys page',
|
||
itemLabel: 'Endpoint #{n}',
|
||
name: 'Name',
|
||
namePlaceholder: 'e.g., OpenAI Compatible',
|
||
endpointUrl: 'Endpoint URL',
|
||
endpointUrlPlaceholder: 'https://api2.example.com',
|
||
descriptionLabel: 'Description',
|
||
descriptionPlaceholder: 'e.g., Supports OpenAI format requests',
|
||
add: 'Add Endpoint',
|
||
},
|
||
contactInfo: 'Contact Info',
|
||
contactInfoPlaceholder: 'e.g., QQ: 123456789',
|
||
contactInfoHint: 'Customer support contact info, displayed on redeem page, profile, etc.',
|
||
docUrl: 'Documentation URL',
|
||
docUrlPlaceholder: 'https://docs.example.com',
|
||
docUrlHint: 'Link to your documentation site. Leave empty to hide the documentation link.',
|
||
siteLogo: 'Site Logo',
|
||
uploadImage: 'Upload Image',
|
||
remove: 'Remove',
|
||
logoHint: 'PNG, JPG, or SVG. Max 300KB. Recommended: 80x80px square image.',
|
||
logoSizeError: 'Image size exceeds 300KB limit ({size}KB)',
|
||
logoTypeError: 'Please select an image file',
|
||
logoReadError: 'Failed to read the image file',
|
||
homeContent: 'Home Page Content',
|
||
homeContentPlaceholder: 'Enter custom content for the home page. Supports Markdown & HTML. If a URL is entered, it will be displayed as an iframe.',
|
||
homeContentHint: 'Customize the home page content. Supports Markdown/HTML. If you enter a URL (starting with http:// or https://), it will be used as an iframe src to embed an external page. When set, the default status information will no longer be displayed.',
|
||
homeContentIframeWarning: '⚠️ iframe mode note: Some websites have X-Frame-Options or CSP security policies that prevent embedding in iframes. If the page appears blank or shows an error, please verify the target website allows embedding, or consider using HTML mode to build your own content.',
|
||
compactHome: 'Compact Home Page',
|
||
compactHomeHint: 'Show a restrained site identity page when no custom home page content is set.',
|
||
hideCcsImportButton: 'Hide CCS Import Button',
|
||
hideCcsImportButtonHint: 'When enabled, the "Import to CCS" button will be hidden on the API Keys page'
|
||
},
|
||
purchase: {
|
||
title: 'Recharge / Subscription Page',
|
||
description: 'Show a "Recharge / Subscription" entry in the sidebar and open the configured URL in an iframe',
|
||
enabled: 'Show Recharge / Subscription Entry',
|
||
enabledHint: 'Only shown in standard mode (not simple mode)',
|
||
url: 'Recharge / Subscription URL',
|
||
urlPlaceholder: 'https://example.com/purchase',
|
||
urlHint: 'Must be an absolute http(s) URL',
|
||
iframeWarning:
|
||
'⚠️ iframe note: Some websites block embedding via X-Frame-Options or CSP (frame-ancestors). If the page is blank, provide an "Open in new tab" alternative.',
|
||
integrationDoc: 'Payment Integration Docs',
|
||
integrationDocHint: 'Covers endpoint specs, idempotency semantics, and code samples'
|
||
},
|
||
soraClient: {
|
||
title: 'Sora Client',
|
||
description: 'Control whether to show the Sora client entry in the sidebar',
|
||
enabled: 'Enable Sora Client',
|
||
enabledHint: 'When enabled, the Sora entry will be shown in the sidebar for users to access Sora features'
|
||
},
|
||
customMenu: {
|
||
title: 'Custom Menu Pages',
|
||
description: 'Add custom iframe pages to the sidebar navigation. Each page can be visible to regular users or administrators.',
|
||
itemLabel: 'Menu Item #{n}',
|
||
name: 'Menu Name',
|
||
namePlaceholder: 'e.g. Help Center',
|
||
url: 'Page URL',
|
||
urlPlaceholder: 'https://example.com/page',
|
||
iconSvg: 'SVG Icon',
|
||
iconSvgPlaceholder: '<svg>...</svg>',
|
||
iconPreview: 'Icon Preview',
|
||
uploadSvg: 'Upload SVG',
|
||
removeSvg: 'Remove',
|
||
visibility: 'Visible To',
|
||
visibilityUser: 'Regular Users',
|
||
visibilityAdmin: 'Administrators',
|
||
add: 'Add Menu Item',
|
||
remove: 'Remove',
|
||
moveUp: 'Move Up',
|
||
moveDown: 'Move Down',
|
||
},
|
||
payment: {
|
||
title: 'Payment Settings',
|
||
description: 'Configure payment system options',
|
||
configGuide: 'Configuration Guide',
|
||
enabled: 'Enable Payment',
|
||
enabledHint: 'Enable or disable the payment system',
|
||
enabledPaymentTypes: 'Enabled Providers',
|
||
enabledPaymentTypesHint: 'Disabling a provider will also disable its instances.',
|
||
findProvider: 'Looking for a suitable EasyPay provider?',
|
||
minAmount: 'Minimum Amount',
|
||
maxAmount: 'Maximum Amount',
|
||
dailyLimit: 'Daily Limit',
|
||
balanceRechargeMultiplier: 'Balance Recharge Multiplier',
|
||
balanceRechargeMultiplierHint: 'How many USD balance the user receives for each 1 CNY paid',
|
||
balanceRechargePreview: 'Preview: 1 CNY = {usd} USD',
|
||
subscriptionUsdToCnyRate: 'Subscription USD to CNY Rate',
|
||
subscriptionUsdToCnyRateHint:
|
||
'CNY charged per 1 USD of plan price on CNY channels (e.g. 7.15). 0 or empty = disabled, plan price is charged as-is. When enabled, all plan prices must be set in USD',
|
||
subscriptionUsdToCnyRateDisabled: 'Disabled (price charged as-is)',
|
||
rechargeFeeRate: 'Recharge Fee Rate',
|
||
rechargeFeeRateHint: 'Percentage of service fee charged on top of recharge amount, 0 means no fee',
|
||
rechargeFeePreview: 'Preview: Recharge 100, fee {fee}',
|
||
orderTimeout: 'Order Timeout',
|
||
orderTimeoutHint: 'In minutes, minimum 1',
|
||
maxPendingOrders: 'Max Pending Orders',
|
||
cancelRateLimit: 'Limit Cancel Rate',
|
||
cancelRateLimitHint: 'When enabled, users who exceed the cancel limit within the time window cannot create new orders',
|
||
cancelRateLimitEvery: 'Every',
|
||
cancelRateLimitAllowMax: 'allow max',
|
||
cancelRateLimitTimes: 'cancels',
|
||
cancelRateLimitWindow: 'Window',
|
||
cancelRateLimitUnit: 'Unit',
|
||
cancelRateLimitMax: 'Max Cancels',
|
||
cancelRateLimitUnitMinute: 'Minutes',
|
||
cancelRateLimitUnitHour: 'Hours',
|
||
cancelRateLimitUnitDay: 'Days',
|
||
cancelRateLimitWindowMode: 'Window Mode',
|
||
cancelRateLimitWindowModeRolling: 'Rolling',
|
||
cancelRateLimitWindowModeFixed: 'Fixed',
|
||
alipayForceQRCode: 'Force Alipay QR Code',
|
||
alipayForceQRCodeHint: 'When enabled, mobile Alipay users always see a QR code instead of being redirected to the mobile payment page',
|
||
alipayMobilePrecreateDeepLink: 'Mobile Alipay Precreate Handoff',
|
||
alipayMobilePrecreateDeepLinkHint: 'Use official Alipay precreate on mobile, open the Alipay app, and show the dynamic QR only if handoff fails. This takes priority over Force Alipay QR Code',
|
||
helpText: 'Help Text',
|
||
helpImageUrl: 'Help Image URL',
|
||
manageProviders: 'Manage Providers',
|
||
balancePaymentDisabled: 'Disable Balance Recharge',
|
||
noLimit: 'Empty = no limit',
|
||
helpImage: 'Help Image',
|
||
helpImagePlaceholder: 'Upload or enter image URL',
|
||
helpTextPlaceholder: 'Enter help text...',
|
||
providerEasypay: 'EasyPay',
|
||
providerAlipay: 'Alipay (Direct)',
|
||
providerWxpay: 'WeChat Pay (Direct)',
|
||
providerStripe: 'Stripe',
|
||
providerAirwallex: 'Airwallex',
|
||
typeDisabled: 'type disabled',
|
||
enableTypesFirst: 'Enable at least one payment type above first',
|
||
easypayRedirect: 'Redirect',
|
||
paymentMode: 'Payment Mode',
|
||
modeRedirect: 'Redirect',
|
||
modeQRCode: 'QR Code',
|
||
modePopup: 'Popup',
|
||
validationNameRequired: 'Provider name is required',
|
||
validationTypesRequired: 'Please select at least one supported payment type',
|
||
validationFieldRequired: '{field} is required',
|
||
validationEasyPayCustomMethodRequired: 'Each custom EasyPay method requires both a payment type and an upstream type',
|
||
validationEasyPayCustomMethodTypeInvalid: 'Custom EasyPay payment types may only contain lowercase letters, digits, underscores, and hyphens',
|
||
validationEasyPayCustomMethodUpstreamTypeInvalid: 'EasyPay upstream types may only contain lowercase letters, digits, underscores, and hyphens',
|
||
validationEasyPayCustomMethodReserved: 'Custom EasyPay payment types cannot use built-in alipay or wxpay',
|
||
validationEasyPayCustomMethodPrefixReserved: 'Custom EasyPay payment types cannot start with alipay or wxpay',
|
||
validationEasyPayCustomMethodDuplicate: 'Custom EasyPay payment types must be unique',
|
||
field_apiBase: 'API Base URL',
|
||
field_notifyUrl: 'Notify URL',
|
||
field_returnUrl: 'Return URL',
|
||
callbackBaseUrl: 'Callback Base URL',
|
||
field_privateKey: 'Private Key',
|
||
field_publicKey: 'Public Key',
|
||
field_mpAppId: 'MP App ID',
|
||
field_mchId: 'Merchant ID',
|
||
field_apiV3Key: 'API v3 Key',
|
||
field_publicKeyId: 'Public Key ID',
|
||
field_certSerial: 'Certificate Serial',
|
||
field_h5AppName: 'H5 App Name',
|
||
field_h5AppUrl: 'H5 App URL',
|
||
wxpayConfigHint: 'WeChat Pay usually only needs App ID. Fill MP App ID, H5 App Name, and H5 App URL only when your Official Account or H5 flow specifically requires them.',
|
||
wxpayAdvancedOptions: 'WeChat Pay Advanced Options',
|
||
field_secretKey: 'Secret Key',
|
||
field_clientId: 'Client ID',
|
||
field_apiKey: 'API Key',
|
||
field_publishableKey: 'Publishable Key',
|
||
field_webhookSecret: 'Webhook Secret',
|
||
field_countryCode: 'Country/region code',
|
||
field_currency: 'Payment currency',
|
||
field_accountId: 'Airwallex Account ID',
|
||
field_airwallexApiBaseHint: 'Must match the API key environment: use https://api-demo.airwallex.com/api/v1 for sandbox/demo keys, and https://api.airwallex.com/api/v1 for production keys. Mixed environments return credentials_invalid / Access Denied.',
|
||
field_paymentCurrencyHint: 'Default is CNY. Stripe and Airwallex can choose HKD, USD, or another listed currency supported by the account; WeChat Pay, Alipay, and EasyPay remain CNY.',
|
||
field_accountIdHint: 'Leave this empty unless you use multiple accounts, an organization-level key, or connected-account payments. A single-account scoped API key uses the selected account by default.',
|
||
field_cid: 'Channel ID',
|
||
field_cidAlipay: 'Alipay Channel ID',
|
||
field_cidWxpay: 'WeChat Channel ID',
|
||
easypayCustomMethods: 'Custom EasyPay methods',
|
||
easypayCustomMethodsHint: 'Add provider-specific methods supported by this EasyPay endpoint. The payment type is stored on Sub2API orders; the upstream type is sent as EasyPay type.',
|
||
addCustomMethod: 'Add method',
|
||
customMethodType: 'Payment type',
|
||
customMethodUpstreamType: 'Upstream type',
|
||
customMethodDisplayName: 'Display name',
|
||
customMethodDisplayNamePlaceholder: 'e.g. Credit card',
|
||
stripeWebhookHint: 'Configure the following URL as a Webhook endpoint in Stripe Dashboard:',
|
||
stripeWebhookApiVersionHint: 'Set this Webhook endpoint API version to match the integrated Stripe SDK. Recommended: {version}. A mismatch can cause webhook parsing errors.',
|
||
airwallexWebhookHint: 'Configure the following URL as a Webhook endpoint in Airwallex. Select at least Payment Intent -> Succeeded (payment_intent.succeeded), preferably also Payment Intent -> Cancelled (payment_intent.cancelled). Use the account default or latest stable API version.',
|
||
airwallexGuideSummary: 'When creating an Airwallex scoped API key, select Read and Write for Payment Acceptance under account-level permissions.',
|
||
airwallexGuideNote: 'Do not grant unrelated permissions such as Spend, Payouts, Transfers, Funds Splits, or POS Terminals unless you explicitly need them. For webhooks, select at least payment_intent.succeeded, preferably also payment_intent.cancelled, and use the account default or latest stable API version.',
|
||
limitsTitle: 'Limits',
|
||
limitSingleMin: 'Min per order',
|
||
limitSingleMax: 'Max per order',
|
||
limitDaily: 'Daily limit',
|
||
limitsHint: 'All empty = use global config; partially filled = empty means no limit',
|
||
limitsUseGlobal: 'Use global',
|
||
limitsNoLimit: 'No limit',
|
||
productNamePrefix: 'Product Name Prefix',
|
||
productNameSuffix: 'Product Name Suffix',
|
||
preview: 'Preview',
|
||
loadBalanceStrategy: 'Load Balance Strategy',
|
||
strategyRoundRobin: 'Round Robin',
|
||
strategyLeastAmount: 'Least Daily Amount',
|
||
providerManagement: 'Provider Management',
|
||
providerManagementDesc: 'Manage payment provider instances',
|
||
createProvider: 'Add Provider',
|
||
editProvider: 'Edit Provider',
|
||
deleteProvider: 'Delete Provider',
|
||
deleteProviderConfirm: 'Are you sure you want to delete this provider?',
|
||
providerName: 'Provider Name',
|
||
providerKey: 'Provider Type',
|
||
selectProviderKey: 'Select Provider Type',
|
||
providerConfig: 'Credentials',
|
||
paymentGuideTrigger: 'View payment guide',
|
||
guideOpenLabel: 'Enable: ',
|
||
guideCallLabel: 'Call: ',
|
||
guideFallbackLabel: 'Fallback: ',
|
||
alipayGuideSummary: 'Desktop prefers QR precreate and falls back to cashier; mobile prefers WAP checkout.',
|
||
alipayGuideFaceToFaceTitle: 'Face-to-face / QR Payment',
|
||
alipayGuideFaceToFaceOpen: 'Enable face-to-face or QR payment capability.',
|
||
alipayGuideFaceToFaceCall: 'Desktop orders call alipay.trade.precreate first and render the QR code directly.',
|
||
alipayGuideFaceToFaceFallback: 'If unavailable or failed, the flow falls back to website checkout automatically.',
|
||
alipayGuidePagePayTitle: 'Website Payment',
|
||
alipayGuidePagePayOpen: 'Enable website payment.',
|
||
alipayGuidePagePayCall: 'When face-to-face is unavailable on desktop, the flow calls alipay.trade.page.pay and still renders the returned link as a QR code.',
|
||
alipayGuidePagePayFallback: 'The cashier link stays available so users can reopen the checkout page manually.',
|
||
alipayGuideWapTitle: 'WAP Payment',
|
||
alipayGuideWapOpen: 'Enable mobile website payment.',
|
||
alipayGuideWapCall: 'Mobile orders call alipay.trade.wap.pay first and jump to Alipay checkout.',
|
||
alipayGuideWapFallback: 'If mobile payment is unavailable or fails, the frontend switches to QR payment and shows a notice.',
|
||
wxpayGuideSummary: 'Desktop prefers Native QR; mobile routes to JSAPI or H5 based on browser context.',
|
||
wxpayGuideNote: 'The current form defaults to one shared App ID, which fits the common single-subject web, mobile, and Official Account setup.',
|
||
wxpayGuideNativeTitle: 'Native / QR Payment',
|
||
wxpayGuideNativeOpen: 'Enable Native or QR payment capability.',
|
||
wxpayGuideNativeCall: 'Desktop orders use Native by default and the frontend renders the QR payload.',
|
||
wxpayGuideNativeFallback: 'Mobile flows also fall back here when JSAPI or H5 cannot be used.',
|
||
wxpayGuideJsapiTitle: 'JSAPI / Official Account',
|
||
wxpayGuideJsapiOpen: 'Enable Official Account payment and ensure the browser is inside WeChat with an available OpenID.',
|
||
wxpayGuideJsapiCall: 'Inside WeChat, the app calls JSAPI after authorization and launches WeChat Pay directly.',
|
||
wxpayGuideJsapiFallback: 'If configuration is missing, the bridge is unavailable, or launch fails, the flow falls back to QR payment.',
|
||
wxpayGuideH5Title: 'H5 Payment',
|
||
wxpayGuideH5Open: 'Enable H5 payment.',
|
||
wxpayGuideH5Call: 'On mobile browsers outside WeChat, the app calls H5 payment when a client IP is available.',
|
||
wxpayGuideH5Fallback: 'If H5 is unavailable or order creation fails, the flow falls back to QR payment.',
|
||
noProviders: 'No provider instances configured',
|
||
supportedTypes: 'Supported Payment Types',
|
||
supportedTypesHint: 'Comma-separated, e.g. alipay,wxpay',
|
||
refundEnabled: 'Allow Refund',
|
||
allowUserRefund: 'Allow User Refund',
|
||
enableConflict: '{method} already has an enabled provider instance: {provider}. Disable the existing instance before switching.',
|
||
},
|
||
balanceNotify: {
|
||
title: 'Balance Low Notification',
|
||
description: 'Send email notification when user balance falls below threshold',
|
||
enabled: 'Enable Balance Low Notification',
|
||
threshold: 'Default Threshold',
|
||
thresholdHint: 'Used when user has not set a custom value',
|
||
thresholdPlaceholder: 'Enter amount',
|
||
rechargeUrl: 'Recharge Page URL',
|
||
rechargeUrlPlaceholder: 'https://example.com/payment',
|
||
rechargeUrlHint: 'A top-up button will appear in the email when set',
|
||
},
|
||
quotaNotify: {
|
||
title: 'Account Quota Notification',
|
||
description: 'Notify admins when account quota usage reaches alert threshold',
|
||
enabled: 'Enable Account Quota Notification',
|
||
emails: 'Notification Emails',
|
||
emailsHint: 'Leave empty to disable notifications',
|
||
addEmail: 'Add Email',
|
||
emailPlaceholder: 'Enter email address',
|
||
},
|
||
subscriptionExpiryNotify: {
|
||
title: 'Subscription Expiry Reminder',
|
||
description: 'Control whether users receive subscription expiry reminder emails.',
|
||
enabled: 'Enable Subscription Expiry Reminder',
|
||
enabledHint: 'When enabled, the system sends reminders 7, 3, and 1 day before expiry.'
|
||
},
|
||
smtp: {
|
||
title: 'SMTP Settings',
|
||
description: 'Configure email sending for verification codes',
|
||
testConnection: 'Test Connection',
|
||
testing: 'Testing...',
|
||
host: 'SMTP Host',
|
||
hostPlaceholder: 'smtp.gmail.com',
|
||
port: 'SMTP Port',
|
||
portPlaceholder: '587',
|
||
username: 'SMTP Username',
|
||
usernamePlaceholder: "your-email{'@'}gmail.com",
|
||
password: 'SMTP Password',
|
||
passwordPlaceholder: '********',
|
||
passwordHint: 'Leave empty to keep existing password',
|
||
passwordConfiguredPlaceholder: '********',
|
||
passwordConfiguredHint: 'Password configured. Leave empty to keep the current value.',
|
||
fromEmail: 'From Email',
|
||
fromEmailPlaceholder: "noreply{'@'}example.com",
|
||
fromName: 'From Name',
|
||
fromNamePlaceholder: 'Sub2API',
|
||
useTls: 'Use TLS',
|
||
useTlsHint: 'Enable TLS encryption for SMTP connection'
|
||
},
|
||
testEmail: {
|
||
title: 'Send Test Email',
|
||
description: 'Send a test email to verify your SMTP configuration',
|
||
recipientEmail: 'Recipient Email',
|
||
recipientEmailPlaceholder: "test{'@'}example.com",
|
||
sendTestEmail: 'Send Test Email',
|
||
sending: 'Sending...',
|
||
enterRecipientHint: 'Please enter a recipient email address'
|
||
},
|
||
emailTemplates: {
|
||
title: 'Email Templates',
|
||
description: 'Customize notification email subjects and HTML content for each event and locale.',
|
||
event: 'Event',
|
||
locale: 'Locale',
|
||
localeEn: 'English',
|
||
localeZh: 'Chinese',
|
||
subject: 'Subject',
|
||
subjectPlaceholder: 'Enter the email subject',
|
||
html: 'HTML Template',
|
||
htmlPlaceholder: 'Edit the email HTML template',
|
||
placeholders: 'Available Placeholders',
|
||
placeholdersHelp: 'Click a placeholder to copy it. The backend replaces these values when sending emails.',
|
||
livePreview: 'Live Preview',
|
||
previewSecurityHint: 'Preview HTML is generated by the backend preview endpoint and displayed in a sandboxed iframe with scripts disabled.',
|
||
preview: 'Preview / Refresh',
|
||
previewing: 'Previewing...',
|
||
save: 'Save Template',
|
||
saving: 'Saving...',
|
||
restoreOfficial: 'Restore Official',
|
||
restoring: 'Restoring...',
|
||
restoreConfirm: 'Restore the official template for this event and locale? Your custom version will be replaced.',
|
||
restoreSuccess: 'Official template restored',
|
||
saveSuccess: 'Email template saved',
|
||
placeholderCopied: 'Placeholder copied',
|
||
validationRequired: 'Subject and HTML template are required',
|
||
empty: 'No email template events or locales are available yet.',
|
||
noPreview: 'Refresh the preview to see the rendered email subject.',
|
||
customized: 'Customized'
|
||
},
|
||
opsMonitoring: {
|
||
title: 'Ops Monitoring',
|
||
description: 'Enable ops monitoring for troubleshooting and health visibility',
|
||
disabled: 'Ops monitoring is disabled',
|
||
enabled: 'Enable Ops Monitoring',
|
||
enabledHint: 'Enable the ops monitoring module (admin only)',
|
||
realtimeEnabled: 'Enable Realtime Monitoring',
|
||
realtimeEnabledHint: 'Enable realtime QPS/metrics push (WebSocket)',
|
||
queryMode: 'Default Query Mode',
|
||
queryModeHint: 'Default query mode for Ops Dashboard (auto/raw/preagg)',
|
||
queryModeAuto: 'Auto (recommended)',
|
||
queryModeRaw: 'Raw (most accurate, slower)',
|
||
queryModePreagg: 'Preagg (fastest, requires aggregation)',
|
||
metricsInterval: 'Metrics Collection Interval (seconds)',
|
||
metricsIntervalHint: 'How often to collect system/request metrics (60-3600 seconds)'
|
||
},
|
||
adminApiKey: {
|
||
title: 'Admin API Key',
|
||
description: 'Global API key for external system integration with full admin access',
|
||
notConfigured: 'Admin API key not configured',
|
||
configured: 'Admin API key is active',
|
||
currentKey: 'Current Key',
|
||
regenerate: 'Regenerate',
|
||
regenerating: 'Regenerating...',
|
||
delete: 'Delete',
|
||
deleting: 'Deleting...',
|
||
create: 'Create Key',
|
||
creating: 'Creating...',
|
||
regenerateConfirm: 'Are you sure? The current key will be immediately invalidated.',
|
||
deleteConfirm:
|
||
'Are you sure you want to delete the admin API key? External integrations will stop working.',
|
||
keyGenerated: 'New admin API key generated',
|
||
keyDeleted: 'Admin API key deleted',
|
||
copyKey: 'Copy Key',
|
||
keyCopied: 'Key copied to clipboard',
|
||
keyWarning: 'This key will only be shown once. Please copy it now.',
|
||
securityWarning: 'Warning: This key provides full admin access. Keep it secure.',
|
||
usage: 'Usage: Add to request header - x-api-key: <your-admin-api-key>'
|
||
},
|
||
soraS3: {
|
||
title: 'Sora Storage',
|
||
description: 'Manage Sora media storage profiles with S3 and Google Drive support',
|
||
newProfile: 'New Profile',
|
||
reloadProfiles: 'Reload Profiles',
|
||
empty: 'No storage profiles yet, create one first',
|
||
createTitle: 'Create Storage Profile',
|
||
editTitle: 'Edit Storage Profile',
|
||
selectProvider: 'Select Storage Type',
|
||
providerS3Desc: 'S3-compatible object storage',
|
||
providerGDriveDesc: 'Google Drive cloud storage',
|
||
profileID: 'Profile ID',
|
||
profileName: 'Profile Name',
|
||
setActive: 'Set as active after creation',
|
||
saveProfile: 'Save Profile',
|
||
activateProfile: 'Activate',
|
||
profileCreated: 'Storage profile created',
|
||
profileSaved: 'Storage profile saved',
|
||
profileDeleted: 'Storage profile deleted',
|
||
profileActivated: 'Active storage profile switched',
|
||
profileIDRequired: 'Profile ID is required',
|
||
profileNameRequired: 'Profile name is required',
|
||
profileSelectRequired: 'Please select a profile first',
|
||
endpointRequired: 'S3 endpoint is required when enabled',
|
||
bucketRequired: 'Bucket is required when enabled',
|
||
accessKeyRequired: 'Access Key ID is required when enabled',
|
||
deleteConfirm: 'Delete storage profile {profileID}?',
|
||
columns: {
|
||
profile: 'Profile',
|
||
profileId: 'Profile ID',
|
||
name: 'Name',
|
||
provider: 'Type',
|
||
active: 'Active',
|
||
endpoint: 'Endpoint',
|
||
bucket: 'Bucket',
|
||
storagePath: 'Storage Path',
|
||
capacityUsage: 'Capacity / Used',
|
||
capacityUnlimited: 'Unlimited',
|
||
videoCount: 'Videos',
|
||
videoCompleted: 'completed',
|
||
videoInProgress: 'in progress',
|
||
quota: 'Default Quota',
|
||
updatedAt: 'Updated At',
|
||
actions: 'Actions',
|
||
rootFolder: 'Root folder',
|
||
testInTable: 'Test',
|
||
testingInTable: 'Testing...',
|
||
testTimeout: 'Test timed out (15s)'
|
||
},
|
||
enabled: 'Enable Storage',
|
||
enabledHint: 'When enabled, Sora generated media files will be automatically uploaded',
|
||
endpoint: 'S3 Endpoint',
|
||
region: 'Region',
|
||
bucket: 'Bucket',
|
||
prefix: 'Object Prefix',
|
||
accessKeyId: 'Access Key ID',
|
||
secretAccessKey: 'Secret Access Key',
|
||
secretConfigured: '(Configured, leave blank to keep)',
|
||
cdnUrl: 'CDN URL',
|
||
cdnUrlHint: 'Optional. When configured, files are accessed via CDN URL',
|
||
forcePathStyle: 'Force Path Style',
|
||
defaultQuota: 'Default Storage Quota',
|
||
defaultQuotaHint: 'Default quota when not specified at user or group level. 0 means unlimited',
|
||
testConnection: 'Test Connection',
|
||
testing: 'Testing...',
|
||
testSuccess: 'Connection test successful',
|
||
testFailed: 'Connection test failed',
|
||
saved: 'Storage settings saved successfully',
|
||
saveFailed: 'Failed to save storage settings',
|
||
gdrive: {
|
||
authType: 'Authentication Method',
|
||
serviceAccount: 'Service Account',
|
||
clientId: 'Client ID',
|
||
clientSecret: 'Client Secret',
|
||
clientSecretConfigured: '(Configured, leave blank to keep)',
|
||
refreshToken: 'Refresh Token',
|
||
refreshTokenConfigured: '(Configured, leave blank to keep)',
|
||
serviceAccountJson: 'Service Account JSON',
|
||
serviceAccountConfigured: '(Configured, leave blank to keep)',
|
||
folderId: 'Folder ID (optional)',
|
||
authorize: 'Authorize Google Drive',
|
||
authorizeHint: 'Get Refresh Token via OAuth2',
|
||
oauthFieldsRequired: 'Please fill in Client ID and Client Secret first',
|
||
oauthSuccess: 'Google Drive authorization successful',
|
||
oauthFailed: 'Google Drive authorization failed',
|
||
closeWindow: 'This window will close automatically',
|
||
processing: 'Processing authorization...',
|
||
testStorage: 'Test Storage',
|
||
testSuccess: 'Google Drive storage test passed (upload, access, delete all OK)',
|
||
testFailed: 'Google Drive storage test failed'
|
||
}
|
||
},
|
||
overloadCooldown: {
|
||
title: '529 Overload Cooldown',
|
||
description: 'Configure account scheduling pause strategy when upstream returns 529 (overloaded)',
|
||
enabled: 'Enable Overload Cooldown',
|
||
enabledHint: 'Pause account scheduling on 529 errors, auto-recover after cooldown',
|
||
cooldownMinutes: 'Cooldown Duration (minutes)',
|
||
cooldownMinutesHint: 'Duration to pause account scheduling (1-120 minutes)',
|
||
saved: 'Overload cooldown settings saved',
|
||
saveFailed: 'Failed to save overload cooldown settings'
|
||
},
|
||
rateLimit429Cooldown: {
|
||
title: '429 Default Cooldown',
|
||
description: 'Configure the default account cooldown when upstream returns 429 without an explicit reset time',
|
||
enabled: 'Enable 429 Default Cooldown',
|
||
enabledHint: 'Pause account scheduling when a 429 has no reset time, then auto-recover after cooldown',
|
||
cooldownSeconds: 'Cooldown Duration (seconds)',
|
||
cooldownSecondsHint: 'Default cooldown duration (1-7200 seconds); explicit upstream reset times still take precedence',
|
||
saved: '429 default cooldown settings saved',
|
||
saveFailed: 'Failed to save 429 default cooldown settings'
|
||
},
|
||
streamTimeout: {
|
||
title: 'Stream Timeout Handling',
|
||
description: 'Configure account handling strategy when upstream response times out',
|
||
enabled: 'Enable Stream Timeout Handling',
|
||
enabledHint: 'Automatically handle problematic accounts when upstream times out',
|
||
timeoutSeconds: 'Timeout Threshold (seconds)',
|
||
timeoutSecondsHint: 'Stream data interval exceeding this time is considered timeout (30-300s)',
|
||
action: 'Action',
|
||
actionTempUnsched: 'Temporarily Unschedulable',
|
||
actionError: 'Mark as Error',
|
||
actionNone: 'No Action',
|
||
actionHint: 'Action to take on the account after timeout',
|
||
tempUnschedMinutes: 'Pause Duration (minutes)',
|
||
tempUnschedMinutesHint: 'Duration of temporary unschedulable state (1-60 minutes)',
|
||
thresholdCount: 'Trigger Threshold (count)',
|
||
thresholdCountHint: 'Number of timeouts before triggering action (1-10)',
|
||
thresholdWindowMinutes: 'Threshold Window (minutes)',
|
||
thresholdWindowMinutesHint: 'Time window for counting timeouts (1-60 minutes)',
|
||
saved: 'Stream timeout settings saved',
|
||
saveFailed: 'Failed to save stream timeout settings'
|
||
},
|
||
rectifier: {
|
||
title: 'Request Rectifier',
|
||
description: 'Automatically fix request parameters and retry when upstream returns specific errors',
|
||
enabled: 'Enable Request Rectifier',
|
||
enabledHint: 'Master switch - disabling turns off all rectification features',
|
||
thinkingSignature: 'Thinking Signature Rectifier',
|
||
thinkingSignatureHint: 'Automatically strip signatures and retry when upstream returns thinking block signature validation errors',
|
||
thinkingBudget: 'Thinking Budget Rectifier',
|
||
thinkingBudgetHint: 'Automatically set budget to 32000 and retry when upstream returns budget_tokens constraint error (≥1024)',
|
||
apikeySignature: 'API Key Signature Rectifier',
|
||
apikeySignatureHint:
|
||
'Automatically strip signatures and retry when API Key accounts receive signature-related errors (built-in patterns always apply)',
|
||
apikeyPatterns: 'Custom Match Patterns',
|
||
apikeyPatternsHint:
|
||
'Additional keywords matched against the response body (case-insensitive). Built-in patterns always apply; use these for supplementary matching.',
|
||
apikeyPatternPlaceholder: 'e.g., thinking_error',
|
||
addPattern: 'Add Pattern',
|
||
saved: 'Rectifier settings saved',
|
||
saveFailed: 'Failed to save rectifier settings'
|
||
},
|
||
betaPolicy: {
|
||
title: 'Beta Policy',
|
||
description: 'How to handle Beta features when configuring the forwarding of Anthropic API requests. Applicable only to the /v1/messages endpoint.',
|
||
action: 'Action',
|
||
actionPass: 'Pass (transparent)',
|
||
actionFilter: 'Filter (remove)',
|
||
actionBlock: 'Block (reject)',
|
||
scope: 'Scope',
|
||
scopeAll: 'All accounts',
|
||
scopeOAuth: 'OAuth only',
|
||
scopeAPIKey: 'API Key only',
|
||
scopeBedrock: 'Bedrock only',
|
||
errorMessage: 'Error message',
|
||
errorMessagePlaceholder: 'Custom error message when blocked',
|
||
errorMessageHint: 'Leave empty for default message',
|
||
saved: 'Beta policy settings saved',
|
||
saveFailed: 'Failed to save beta policy settings',
|
||
modelWhitelist: 'Model Whitelist',
|
||
modelWhitelistHint: 'Leave empty to apply to all models. Supports exact match and wildcard prefix (e.g., claude-opus-*)',
|
||
modelPatternPlaceholder: 'e.g., claude-opus-* or claude-opus-4-6',
|
||
addModelPattern: 'Add model pattern',
|
||
removePattern: 'Remove',
|
||
fallbackAction: 'Fallback Action',
|
||
fallbackActionHint: 'Action for models not matching the whitelist',
|
||
fallbackErrorMessagePlaceholder: 'Custom error message when non-whitelisted models are blocked',
|
||
quickPresets: 'Quick Presets',
|
||
presetOpusOnly: 'Opus only for 1M',
|
||
presetOpusOnlyDesc: 'Pass for Opus, filter others',
|
||
commonPatterns: 'Common patterns'
|
||
},
|
||
openaiFastPolicy: {
|
||
title: 'OpenAI Fast/Flex Policy',
|
||
description: 'Intercept, filter, or pass OpenAI fast(priority) / flex requests based on the request body service_tier field. Applies to the OpenAI gateway only.',
|
||
empty: 'No rules configured. Click the button below to add one.',
|
||
ruleHeader: 'Rule #{index}',
|
||
removeRule: 'Remove rule',
|
||
addRule: 'Add rule',
|
||
saveHint: 'Saved together with system settings (click the global Save button at the bottom of the page).',
|
||
serviceTier: 'service_tier match',
|
||
tierAll: 'All tiers',
|
||
tierPriority: 'priority (fast)',
|
||
tierFlex: 'flex',
|
||
action: 'Action',
|
||
actionPass: 'Pass (keep service_tier)',
|
||
actionFilter: 'Filter (remove service_tier)',
|
||
actionForcePriority: 'Force priority (fast)',
|
||
actionBlock: 'Block (reject request)',
|
||
scope: 'Scope',
|
||
scopeAll: 'All accounts',
|
||
scopeOAuth: 'OAuth only',
|
||
scopeAPIKey: 'API Key only',
|
||
scopeBedrock: 'Bedrock only',
|
||
userIds: 'Specific users',
|
||
userIdsHint: 'Type any part of a user email to search. Leave empty to apply to all Sub2API users. Selected users match requests from their API keys and take precedence over global rules.',
|
||
userSearchPlaceholder: 'Search by user email',
|
||
userSearchEmpty: 'No matching users found',
|
||
userDeleted: '(deleted)',
|
||
userIdFallback: 'User #{id}',
|
||
removeUser: 'Remove user',
|
||
errorMessage: 'Error message',
|
||
errorMessagePlaceholder: 'Custom error message when blocked',
|
||
errorMessageHint: 'Leave empty for the default message.',
|
||
modelWhitelist: 'Model whitelist',
|
||
modelWhitelistHint: 'Leave empty to apply to all models. Supports exact match and wildcard prefix (e.g., gpt-5.5*).',
|
||
modelPatternPlaceholder: 'e.g., gpt-5.5 or gpt-5.5*',
|
||
addModelPattern: 'Add model pattern',
|
||
fallbackAction: 'Fallback action',
|
||
fallbackActionHint: 'Action for models not matching the whitelist.',
|
||
fallbackErrorMessagePlaceholder: 'Custom error message when non-whitelisted models are blocked'
|
||
},
|
||
wechatConnect: {
|
||
title: 'WeChat Connect',
|
||
description: 'Third-party login configuration for WeChat Open Platform or Official Account / Mini Program.',
|
||
enabledLabel: 'Enable WeChat Connect',
|
||
enabledHint: 'Enable this to configure WeChat OAuth callbacks and authorization.',
|
||
appIdLabel: 'App ID',
|
||
appIdPlaceholder: 'WeChat App ID',
|
||
appSecretLabel: 'App Secret',
|
||
appSecretConfiguredPlaceholder: 'Secret configured. Leave empty to keep the current value.',
|
||
appSecretPlaceholder: 'WeChat App Secret',
|
||
appSecretConfiguredHint: 'Secret configured. Leave empty to keep the current value.',
|
||
appSecretHint: 'Enter a new secret to replace the current WeChat credential.',
|
||
modeLabel: 'Mode',
|
||
openModeLabel: 'Use Open outside WeChat',
|
||
openModeHint: 'Use Open Platform QR authorization outside the WeChat browser.',
|
||
mpModeLabel: 'Use MP inside WeChat',
|
||
mpModeHint: 'Use Official Account authorization inside the WeChat browser.',
|
||
redirectUrlLabel: 'Redirect URL',
|
||
redirectUrlPlaceholder: 'https://your-site.com/api/v1/auth/oauth/wechat/callback',
|
||
generateAndCopy: 'Generate & Copy (current site)',
|
||
redirectUrlSetAndCopied: 'Redirect URL generated and copied to clipboard',
|
||
frontendRedirectUrlLabel: 'Frontend redirect URL',
|
||
frontendRedirectUrlPlaceholder: '/auth/wechat/callback',
|
||
frontendRedirectUrlHint: 'Usually the frontend route callback path; keep it aligned with the backend.'
|
||
},
|
||
authSourceDefaults: {
|
||
title: 'Auth Source Defaults',
|
||
description: 'Configure per-source default balance, concurrency, subscriptions, and grant rules.',
|
||
requireEmailLabel: 'Require email on third-party signup',
|
||
requireEmailHint: 'When enabled, Linux DO, OIDC, and WeChat signups must provide an email before account creation.',
|
||
enabledHint: 'These defaults apply when a new user registers through this source. Grant on first bind only applies when an existing user binds this source.',
|
||
sources: {
|
||
email: {
|
||
title: 'Email signup',
|
||
description: 'Default quota grants for email-password signups.'
|
||
},
|
||
linuxdo: {
|
||
title: 'Linux DO signup',
|
||
description: 'Default quota grants for Linux DO signups.'
|
||
},
|
||
oidc: {
|
||
title: 'OIDC signup',
|
||
description: 'Default quota grants for OIDC signups.'
|
||
},
|
||
wechat: {
|
||
title: 'WeChat signup',
|
||
description: 'Default quota grants for WeChat signups.'
|
||
}
|
||
},
|
||
grantOnFirstBindLabel: 'Grant on first bind',
|
||
grantOnFirstBindHint: 'Grant default entitlements when an existing user first binds this source.',
|
||
defaultSubscriptionsLabel: 'Default subscriptions',
|
||
defaultSubscriptionsHint: 'Applies only to this auth source. Leave empty to skip source-specific subscriptions.',
|
||
noSourceSubscriptions: 'No source-specific default subscriptions configured.',
|
||
platformQuotasOverride: 'Platform Quota Overrides',
|
||
platformQuotasOverrideHint: 'Blank fields inherit the system default. Set to 0 to fully block that window for this auth source.',
|
||
},
|
||
paymentVisibleMethods: {
|
||
methodLabel: '{title} visible method',
|
||
methodHint: 'Controls whether checkout shows this method and which source key it exposes.',
|
||
sourceLabel: 'Payment source',
|
||
sourceHint: 'Choose an explicit source before enabling the method. Not configured methods are not exposed.',
|
||
sourceRequiredError: 'Select a payment source before enabling {title}.'
|
||
},
|
||
openaiExperimentalScheduler: {
|
||
title: 'OpenAI experimental scheduler policy',
|
||
description: "Disabled by default. When enabled, this only changes the gateway's experimental account-selection policy for OpenAI traffic; it does not indicate an upstream OpenAI capability.",
|
||
lowRatePriorityTitle: 'Prefer lower rates',
|
||
lowRatePriorityDescription: 'When enabled, accounts with lower billing rates are preferred. If rates are equal, account priority, current load, and other scheduling factors are considered. This switch is ignored when the experimental scheduler is enabled.',
|
||
oauthRateTitle: 'OAuth scheduling reference rate',
|
||
oauthRatePriorityDescription: 'When a group contains both API Key and OAuth accounts, this rate is used to order OAuth accounts alongside probed API Key billing rates.',
|
||
oauthRateWeightedDescription: 'When a group contains both API Key and OAuth accounts, this rate is used for OAuth accounts when calculating the billing-rate score.',
|
||
stickyWeightedTitle: 'Sticky weighting',
|
||
stickyWeightedDescription: 'When enabled, previous_response_id and session_hash affinity are scored by the advanced scheduler. When disabled, sticky accounts keep the legacy hard-hit behavior.',
|
||
subscriptionPriorityTitle: 'Subscription priority',
|
||
subscriptionPriorityDescription: 'When enabled, the scheduler scores ChatGPT subscription accounts first and falls back to non-subscription accounts only if no subscription slot can be acquired.',
|
||
weightsTitle: 'Scheduler weight overrides',
|
||
weightsDescription: 'Blank values use config/environment values; when config is not set, built-in defaults apply. Non-blank page settings take priority.',
|
||
defaultPlaceholder: 'config/default: {value}',
|
||
topKLabel: 'TopK',
|
||
priorityWeight: 'Priority',
|
||
loadWeight: 'Load',
|
||
queueWeight: 'Queue',
|
||
errorRateWeight: 'Error rate',
|
||
ttftWeight: 'TTFT',
|
||
resetWeight: 'Reset window',
|
||
quotaHeadroomWeight: 'Quota headroom',
|
||
upstreamCostWeight: 'Billing rate',
|
||
previousResponseWeight: 'previous_response sticky',
|
||
sessionStickyWeight: 'session_hash sticky'
|
||
},
|
||
usageRecords: {
|
||
title: 'Usage Records',
|
||
description: 'Settings for usage and failed-request records visible to end users.',
|
||
},
|
||
user_error_view: {
|
||
label: 'Allow users to view their own error requests',
|
||
description: 'When enabled, users can see a redacted view of their failed requests on the usage page (no internal/upstream details). Requires ops monitoring enabled to have data.',
|
||
},
|
||
saveSettings: 'Save Settings',
|
||
saving: 'Saving...',
|
||
settingsSaved: 'Settings saved successfully',
|
||
smtpConnectionSuccess: 'SMTP connection successful',
|
||
testEmailSent: 'Test email sent successfully',
|
||
failedToLoad: 'Failed to load settings',
|
||
failedToSave: 'Failed to save settings',
|
||
failedToTestSmtp: 'SMTP connection test failed',
|
||
failedToSendTestEmail: 'Failed to send test email'
|
||
},
|
||
|
||
// Error Passthrough Rules
|
||
errorPassthrough: {
|
||
title: 'Error Passthrough Rules',
|
||
description: 'Configure how upstream errors are returned to clients',
|
||
createRule: 'Create Rule',
|
||
editRule: 'Edit Rule',
|
||
deleteRule: 'Delete Rule',
|
||
noRules: 'No rules configured',
|
||
createFirstRule: 'Create your first error passthrough rule',
|
||
allPlatforms: 'All Platforms',
|
||
passthrough: 'Passthrough',
|
||
custom: 'Custom',
|
||
code: 'Code',
|
||
body: 'Body',
|
||
skipMonitoring: 'Skip Monitoring',
|
||
|
||
// Columns
|
||
columns: {
|
||
priority: 'Priority',
|
||
name: 'Name',
|
||
conditions: 'Conditions',
|
||
platforms: 'Platforms',
|
||
behavior: 'Behavior',
|
||
status: 'Status',
|
||
actions: 'Actions'
|
||
},
|
||
|
||
// Match Mode
|
||
matchMode: {
|
||
any: 'Code OR Keyword',
|
||
all: 'Code AND Keyword',
|
||
anyHint: 'Status code matches any error code, OR message contains any keyword',
|
||
allHint: 'Status code matches any error code, AND message contains any keyword'
|
||
},
|
||
|
||
// Form
|
||
form: {
|
||
name: 'Rule Name',
|
||
namePlaceholder: 'e.g., Context Limit Passthrough',
|
||
priority: 'Priority',
|
||
priorityHint: 'Lower values have higher priority',
|
||
description: 'Description',
|
||
descriptionPlaceholder: 'Describe the purpose of this rule...',
|
||
matchConditions: 'Match Conditions',
|
||
errorCodes: 'Error Codes',
|
||
errorCodesPlaceholder: '422, 400, 429',
|
||
errorCodesHint: 'Separate multiple codes with commas',
|
||
keywords: 'Keywords',
|
||
keywordsPlaceholder: 'One keyword per line\ncontext limit\nmodel not supported',
|
||
keywordsHint: 'One keyword per line, case-insensitive',
|
||
matchMode: 'Match Mode',
|
||
platforms: 'Platforms',
|
||
platformsHint: 'Leave empty to apply to all platforms',
|
||
responseBehavior: 'Response Behavior',
|
||
passthroughCode: 'Passthrough upstream status code',
|
||
responseCode: 'Custom status code',
|
||
passthroughBody: 'Passthrough upstream error message',
|
||
customMessage: 'Custom error message',
|
||
customMessagePlaceholder: 'Error message to return to client...',
|
||
skipMonitoring: 'Skip monitoring',
|
||
skipMonitoringHint: 'When enabled, errors matching this rule will not be recorded in ops monitoring',
|
||
enabled: 'Enable this rule'
|
||
},
|
||
|
||
// Messages
|
||
nameRequired: 'Please enter rule name',
|
||
conditionsRequired: 'Please configure at least one error code or keyword',
|
||
ruleCreated: 'Rule created successfully',
|
||
ruleUpdated: 'Rule updated successfully',
|
||
ruleDeleted: 'Rule deleted successfully',
|
||
deleteConfirm: 'Are you sure you want to delete rule "{name}"?',
|
||
failedToLoad: 'Failed to load rules',
|
||
failedToSave: 'Failed to save rule',
|
||
failedToDelete: 'Failed to delete rule',
|
||
failedToToggle: 'Failed to toggle status'
|
||
},
|
||
|
||
// TLS Fingerprint Profiles
|
||
tlsFingerprintProfiles: {
|
||
title: 'TLS Fingerprint Profiles',
|
||
description: 'Manage TLS fingerprint profiles for simulating specific client TLS handshake characteristics',
|
||
createProfile: 'Create Profile',
|
||
editProfile: 'Edit Profile',
|
||
deleteProfile: 'Delete Profile',
|
||
noProfiles: 'No profiles configured',
|
||
createFirstProfile: 'Create your first TLS fingerprint profile',
|
||
|
||
columns: {
|
||
name: 'Name',
|
||
description: 'Description',
|
||
grease: 'GREASE',
|
||
alpn: 'ALPN',
|
||
actions: 'Actions'
|
||
},
|
||
|
||
form: {
|
||
pasteYaml: 'Paste YAML Configuration',
|
||
pasteYamlPlaceholder: 'Paste YAML output from TLS Fingerprint Collector here...',
|
||
pasteYamlHint: 'Paste the YAML copied from TLS Fingerprint Collector to auto-fill all fields.',
|
||
openCollector: 'Open Collector',
|
||
parseYaml: 'Parse YAML',
|
||
yamlParsed: 'YAML parsed successfully, fields auto-filled',
|
||
yamlParseFailed: 'Failed to parse YAML: name field not found',
|
||
name: 'Profile Name',
|
||
namePlaceholder: 'e.g. macOS Node.js v24',
|
||
description: 'Description',
|
||
descriptionPlaceholder: 'Optional description for this profile',
|
||
enableGrease: 'Enable GREASE',
|
||
enableGreaseHint: 'Insert GREASE values in TLS ClientHello extensions',
|
||
cipherSuites: 'Cipher Suites',
|
||
cipherSuitesHint: 'Comma-separated hex values, e.g. 0x1301, 0x1302, 0xc02c',
|
||
curves: 'Elliptic Curves',
|
||
curvesHint: 'Comma-separated curve IDs',
|
||
pointFormats: 'Point Formats',
|
||
signatureAlgorithms: 'Signature Algorithms',
|
||
alpnProtocols: 'ALPN Protocols',
|
||
alpnProtocolsHint: 'Comma-separated, e.g. h2, http/1.1',
|
||
supportedVersions: 'Supported TLS Versions',
|
||
keyShareGroups: 'Key Share Groups',
|
||
pskModes: 'PSK Modes',
|
||
extensions: 'Extensions'
|
||
},
|
||
|
||
deleteConfirm: 'Delete Profile',
|
||
deleteConfirmMessage: 'Are you sure you want to delete profile "{name}"? Accounts using this profile will fall back to the built-in default.',
|
||
createSuccess: 'Profile created successfully',
|
||
updateSuccess: 'Profile updated successfully',
|
||
deleteSuccess: 'Profile deleted successfully',
|
||
loadFailed: 'Failed to load profiles',
|
||
saveFailed: 'Failed to save profile',
|
||
deleteFailed: 'Failed to delete profile'
|
||
}
|
||
}
|