@goodandready/dsh-subscriptions 0.6.7 → 0.6.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -252,6 +252,14 @@ Supports Anthropic's adaptive thinking (`thinking: { type: "adaptive" }`) and re
252
252
 
253
253
  ---
254
254
 
255
+ ## 🌐 Full Bilingual Localization (EN / ZH) & Account Health (Added in v0.6.8)
256
+
257
+ - **Strict Localization Standards**: Built-in UI dictionary is now 100% bilingual with complete English (`en`) and Simplified Chinese (`zh`) translation dictionaries and instruction guides.
258
+ - **Decoupled Translations**: Russian and other localized translations are provided at runtime via standalone dictionary packages (such as `dsh-russian-lang`), leaving the core plugin codebase lean and zero-hardcoded.
259
+ - **Per-Account Health & Latency Probe**: The slot check button now records live upstream round-trip latency (`latencyMs`) for each individual subscription account slot.
260
+
261
+ ---
262
+
255
263
  ## 🌐 Localization
256
264
 
257
265
  The plugin source language is English only. Russian and other translations are provided at runtime by separate language plugins (for example the russification plugin), which translate the registered locale keys - the package itself ships no bundled translations (Changed in 0.6.1).
package/README.ru.md CHANGED
@@ -225,6 +225,14 @@ dsh-subscriptions:
225
225
 
226
226
  ---
227
227
 
228
+ ## 🌐 Двуязычная локализация (EN / ZH) и проверка здоровья аккаунтов (Добавлено в v0.6.8)
229
+
230
+ - **Стандарты локализации**: Встроенный интерфейс стал на 100% двуязычным — предоставлены полные словари и инструкции на английском (`en`) и упрощенном китайском (`zh`) языках.
231
+ - **Внешняя русификация**: Русские строки полностью отделены от исходного кода плагина и подключаются динамически через официальный плагин русификации (`dsh-russian-lang`).
232
+ - **Индивидуальная проверка аккаунта**: Кнопка проверки в карточке слота измеряет фактическую задержку (`latencyMs`) и статус здоровья выбранного аккаунта.
233
+
234
+ ---
235
+
228
236
  ## 🌐 Локализация
229
237
 
230
238
  Исходный язык плагина - только английский. Русский и другие переводы во время работы предоставляют отдельные языковые плагины (например, плагин русификации), переводящие зарегистрированные ключи локали, - сам пакет не содержит встроенных переводов (Changed in 0.6.1).
package/README.zh.md CHANGED
@@ -147,6 +147,14 @@ dsh plugin --profile web add @goodandready/dsh-subscriptions
147
147
 
148
148
  ---
149
149
 
150
+ ## 🌐 完整双语支持 (EN / ZH) 与单账户健康探活 (v0.6.8 新增)
151
+
152
+ - **原生双语支持**: 内置 UI 字典全面提供英语 (`en`) 与简体中文 (`zh`) 翻译,所有服务商获取令牌指引均具备中英双语版。
153
+ - **解耦外部翻译**: 俄语等其他语种由独立语言包插件(如 `dsh-russian-lang`)在运行时翻译,插件源码保持纯净零硬编码。
154
+ - **单账户实时探活与延迟测量**: 槽位中的“检测”按钮实时测量对应订阅账户的双向响应延迟 (`latencyMs`) 与健康状态。
155
+
156
+ ---
157
+
150
158
  ## 🌐 本地化
151
159
 
152
160
  插件源语言仅为英语。俄语及其他翻译由独立的语言插件(如俄化插件)在运行时对注册的本地化键进行翻译,包本身不内置翻译(Changed in 0.6.1)。
package/lib/client.js CHANGED
@@ -205,304 +205,446 @@ const cssId = 'dsh-subscriptions/settings.module.css'
205
205
 
206
206
  const INSTRUCTIONS = {
207
207
  codex: {
208
- title: 'OpenAI / ChatGPT Codex',
209
- stepsRu: [
210
- '1. Авторизуйтесь через CLI на сервере: выполните "codex login" в терминале.',
211
- '2. Нажмите кнопку "📥 Из CLI", чтобы сервер автоматически прочитал ~/.codex/auth.json.',
212
- '3. Либо нажмите кнопку "Авторизация по коду" (Device Login) выше и введите код на auth.openai.com.',
213
- ],
208
+ title: "OpenAI / ChatGPT Codex",
214
209
  stepsEn: [
215
- '1. Log in via CLI on server: run "codex login" in terminal.',
216
- '2. Click "📥 From CLI" to auto-import ~/.codex/auth.json.',
217
- '3. Or click "Device Login" above and enter the code at auth.openai.com.',
210
+ "1. Log in via CLI on server: run \"codex login\" in terminal.",
211
+ "2. Click \"📥 From CLI\" to auto-import ~/.codex/auth.json.",
212
+ "3. Or click \"Device Login\" above and enter the code at auth.openai.com.",
213
+ ],
214
+ stepsZh: [
215
+ "1. 在服务器上通过 CLI 登录:在终端运行 \"codex login\"。",
216
+ "2. 点击 \"📥 从 CLI 导入\" 自动读取 ~/.codex/auth.json。",
217
+ "3. 或者点击上方的 \"设备码登录\" (Device Login) 并在 auth.openai.com 输入代码。",
218
218
  ],
219
- link: 'https://chatgpt.com',
219
+ link: "https://chatgpt.com",
220
220
  },
221
221
  claude: {
222
- title: 'Claude Code',
223
- stepsRu: [
224
- '1. Выполните "claude login" в консоли сервера.',
225
- '2. Нажмите "📥 Из CLI" — плагин подтянет токен из ~/.claude/credentials.json.',
226
- '3. Либо скопируйте свой Session Token / API-ключ из консоли Anthropic и нажмите "Сохранить".',
227
- ],
222
+ title: "Claude Code",
228
223
  stepsEn: [
229
- '1. Run "claude login" in server console.',
230
- '2. Click "📥 From CLI" — plugin will import ~/.claude/credentials.json.',
231
- '3. Or paste your Session Token / API Key from Anthropic Console and click "Save token".',
224
+ "1. Run \"claude login\" in server console.",
225
+ "2. Click \"📥 From CLI\" — plugin will import ~/.claude/credentials.json.",
226
+ "3. Or paste your Session Token / API Key from Anthropic Console and click \"Save token\".",
232
227
  ],
233
- link: 'https://console.anthropic.com/settings/keys',
228
+ stepsZh: [
229
+ "1. 在服务器控制台运行 \"claude login\"。",
230
+ "2. 点击 \"📥 从 CLI 导入\" — 插件将自动导入 ~/.claude/credentials.json。",
231
+ "3. 或者从 Anthropic 控制台复制您的 Session Token 或 API Key 并点击 \"保存令牌\"。",
232
+ ],
233
+ link: "https://console.anthropic.com/settings/keys",
234
234
  },
235
235
  cursor: {
236
- title: 'Cursor IDE',
237
- stepsRu: [
238
- '1. Авторизация через CLI: если Cursor установлен на сервере, нажмите "📥 Из CLI" (считывается из ~/.cursor/cli-config.json).',
239
- '2. Вручную: откройте Настройки Cursor (кнопка "Подключить" или ссылка ниже), скопируйте WorkOS токен сессии или свой API ключ.',
240
- '3. Вставьте его в поле ввода и нажмите кнопку "Сохранить".',
241
- ],
236
+ title: "Cursor IDE",
242
237
  stepsEn: [
243
- '1. Auto CLI: if Cursor is used on server, click "📥 From CLI" (reads from ~/.cursor/cli-config.json).',
244
- '2. Manual: open Cursor Settings (Connect button or link below), copy your session token or API key.',
245
- '3. Paste it into the input field and click "Save token".',
238
+ "1. Auto CLI: if Cursor is used on server, click \"📥 From CLI\" (reads from ~/.cursor/cli-config.json).",
239
+ "2. Manual: open Cursor Settings (Connect button or link below), copy your session token or API key.",
240
+ "3. Paste it into the input field and click \"Save token\".",
246
241
  ],
247
- link: 'https://cursor.com/settings',
242
+ stepsZh: [
243
+ "1. CLI 自动导入:若服务器安装了 Cursor,点击 \"📥 从 CLI 导入\" (读取自 ~/.cursor/cli-config.json)。",
244
+ "2. 手动导入:打开 Cursor 设置页面(点击连接或下方链接),复制 WorkOS 会话令牌或 API 密钥。",
245
+ "3. 将其粘贴到输入框中并点击 \"保存令牌\"。",
246
+ ],
247
+ link: "https://cursor.com/settings",
248
248
  },
249
249
  antigravity: {
250
- title: 'Google Antigravity / Gemini',
251
- stepsRu: [
252
- '1. Самый простой способ: нажмите "📥 Из CLI" — на сервере уже есть авторизованная сессия (~/.gemini/antigravity-cli/antigravity-oauth-token).',
253
- '2. Если токен обновлен в системе, нажатие "📥 Из CLI" мгновенно подхватит актуальный refresh_token.',
254
- '3. Ручной ввод: вставьте JSON сессии или OAuth access_token/refresh_token в поле и нажмите "Сохранить".',
255
- 'Примечание: Кнопка "Подключить" для Google требует свой OAuth client_id в настройках плагина, поэтому для Antigravity рекомендуется использовать "📥 Из CLI"!',
256
- ],
250
+ title: "Google Antigravity / Gemini",
257
251
  stepsEn: [
258
- '1. Easiest way: click "📥 From CLI" — server already has active token in ~/.gemini/antigravity-cli/antigravity-oauth-token.',
259
- '2. Clicking "📥 From CLI" instantly loads fresh credentials.',
260
- '3. Manual: paste session JSON or OAuth tokens and click "Save token".',
261
- 'Note: "Connect" button requires custom Google client_id in plugin settings; use "📥 From CLI" for seamless login!',
252
+ "1. Easiest way: click \"📥 From CLI\" — server already has active token in ~/.gemini/antigravity-cli/antigravity-oauth-token.",
253
+ "2. Clicking \"📥 From CLI\" instantly loads fresh credentials.",
254
+ "3. Manual: paste session JSON or OAuth tokens and click \"Save token\".",
255
+ "Note: \"Connect\" button requires custom Google client_id in plugin settings; use \"📥 From CLI\" for seamless login!",
262
256
  ],
263
- link: 'https://antigravity.google',
257
+ stepsZh: [
258
+ "1. 推荐方式:点击 \"📥 从 CLI 导入\" — 服务器已保存 ~/.gemini/antigravity-cli/antigravity-oauth-token 会话。",
259
+ "2. 点击 \"📥 从 CLI 导入\" 可即时加载最新的凭据与 refresh_token。",
260
+ "3. 手动输入:将会话 JSON 或 OAuth access_token/refresh_token 粘贴至输入框并保存。",
261
+ "注:\"连接\" 按钮需要自定义 Google OAuth Client ID,推荐直接使用 \"📥 从 CLI 导入\"!",
262
+ ],
263
+ link: "https://antigravity.google",
264
264
  },
265
265
  kimi: {
266
- title: 'Moonshot Kimi',
267
- stepsRu: [
268
- '1. Откройте консоль разработчика Moonshot (ссылка ниже или по кнопке "Подключить").',
269
- '2. Создайте и скопируйте свой API-ключ (sk-...).',
270
- '3. Вставьте ключ в поле слота и нажмите "Сохранить".',
271
- '4. Если у вас настроен Kimi Code CLI, можно также нажать "📥 Из CLI" (~/.kimi-code).',
272
- ],
266
+ title: "Moonshot Kimi",
273
267
  stepsEn: [
274
- '1. Open Moonshot developer console (link below or click Connect).',
275
- '2. Generate and copy your API Key (sk-...).',
276
- '3. Paste it into the input field and click "Save token".',
277
- '4. Or click "📥 From CLI" if ~/.kimi-code is configured.',
268
+ "1. Open Moonshot developer console (link below or click Connect).",
269
+ "2. Generate and copy your API Key (sk-...).",
270
+ "3. Paste it into the input field and click \"Save token\".",
271
+ "4. Or click \"📥 From CLI\" if ~/.kimi-code is configured.",
278
272
  ],
279
- link: 'https://platform.moonshot.cn/console/api-keys',
273
+ stepsZh: [
274
+ "1. 打开 Moonshot 开发者控制台(点击下方链接或点击连接)。",
275
+ "2. 创建并复制您的 API 密钥 (sk-...)。",
276
+ "3. 将密钥粘贴到槽位输入框中并点击 \"保存令牌\"。",
277
+ "4. 如果配置了 Kimi Code CLI,也可点击 \"📥 从 CLI 导入\" (~/.kimi-code)。",
278
+ ],
279
+ link: "https://platform.moonshot.cn/console/api-keys",
280
280
  },
281
281
  glm: {
282
- title: 'Zhipu GLM (Z.AI)',
283
- stepsRu: [
284
- '1. Откройте Центр разработчика Zhipu AI (ссылка ниже или по кнопке "Подключить").',
285
- '2. Скопируйте ваш API-ключ GLM.',
286
- '3. Вставьте его в поле ввода и нажмите "Сохранить".',
287
- '4. Если установлен ZCode CLI (~/.zcode), нажмите "📥 Из CLI" для авто-импорта.',
288
- ],
282
+ title: "Zhipu GLM (Z.AI)",
289
283
  stepsEn: [
290
- '1. Open Zhipu BigModel API Keys center (link below or click Connect).',
291
- '2. Copy your GLM API Key.',
292
- '3. Paste it into the input field and click "Save token".',
293
- '4. Or click "📥 From CLI" if ZCode CLI is installed (~/.zcode).',
284
+ "1. Open Zhipu BigModel API Keys center (link below or click Connect).",
285
+ "2. Copy your GLM API Key.",
286
+ "3. Paste it into the input field and click \"Save token\".",
287
+ "4. Or click \"📥 From CLI\" if ZCode CLI is installed (~/.zcode).",
288
+ ],
289
+ stepsZh: [
290
+ "1. 打开智谱 AI 开放平台(点击下方链接或点击连接)。",
291
+ "2. 复制您的 GLM API 密钥。",
292
+ "3. 粘贴到输入框中并点击 \"保存令牌\"。",
293
+ "4. 若已安装 ZCode CLI (~/.zcode),点击 \"📥 从 CLI 导入\" 即可自动导入。",
294
294
  ],
295
- link: 'https://open.bigmodel.cn/usercenter/apikeys',
295
+ link: "https://open.bigmodel.cn/usercenter/apikeys",
296
296
  },
297
297
  grok: {
298
- title: 'xAI Grok',
299
- stepsRu: [
300
- '1. Выполните вход через grok-cli или hermes на сервере.',
301
- '2. Нажмите "📥 Из CLI" (~/.grok/auth.json).',
302
- '3. Либо вставьте xAI API-ключ в поле и нажмите "Сохранить".',
303
- ],
298
+ title: "xAI Grok",
304
299
  stepsEn: [
305
- '1. Run grok-cli or hermes login on server.',
306
- '2. Click "📥 From CLI" (~/.grok/auth.json).',
307
- '3. Or paste your xAI API Key and click "Save token".',
300
+ "1. Run grok-cli or hermes login on server.",
301
+ "2. Click \"📥 From CLI\" (~/.grok/auth.json).",
302
+ "3. Or paste your xAI API Key and click \"Save token\".",
303
+ ],
304
+ stepsZh: [
305
+ "1. 在服务器上运行 grok-cli 或 hermes 登录。",
306
+ "2. 点击 \"📥 从 CLI 导入\" (~/.grok/auth.json)。",
307
+ "3. 或将 xAI API 密钥粘贴到输入框并点击 \"保存令牌\"。",
308
308
  ],
309
- link: 'https://x.ai',
309
+ link: "https://x.ai",
310
310
  },
311
311
  kiro: {
312
- title: 'AWS Kiro',
313
- stepsRu: [
314
- '1. Нажмите "📥 Из CLI" для импорта из ~/.kiro/credentials.json или ~/.aws/sso.',
315
- '2. Либо вставьте KIRO_API_KEY в поле и нажмите "Сохранить".',
316
- ],
312
+ title: "AWS Kiro",
317
313
  stepsEn: [
318
- '1. Click "📥 From CLI" to import from ~/.kiro/credentials.json.',
319
- '2. Or paste KIRO_API_KEY and click "Save token".',
314
+ "1. Click \"📥 From CLI\" to import from ~/.kiro/credentials.json.",
315
+ "2. Or paste KIRO_API_KEY and click \"Save token\".",
316
+ ],
317
+ stepsZh: [
318
+ "1. 点击 \"📥 从 CLI 导入\" 从 ~/.kiro/credentials.json 或 ~/.aws/sso 导入凭据。",
319
+ "2. 或直接粘贴 KIRO_API_KEY 并点击 \"保存令牌\"。",
320
320
  ],
321
- link: 'https://aws.amazon.com',
321
+ link: "https://aws.amazon.com",
322
322
  },
323
323
  }
324
324
 
325
325
  const en = {
326
- 'notConnected': 'Not connected',
327
- 'verifyAccount': 'Verify account',
328
- 'coolingDown': 'Cooling down',
329
- 'usageFull': 'Usage 100%',
330
- 'connected': 'Connected',
331
- 'title': 'Subscriptions',
332
- 'cardIntro': 'Subscription accounts, rotation and limits.',
333
- 'show': 'Show',
334
- 'hide': 'Hide',
335
- 'intro': 'Log in with a consumer subscription. Tokens stay on the host credentials store. The browser never reads them back. After Connect, if the provider lands on localhost or a vendor page, paste the redirected URL or the code here.',
336
- 'useOrigin': 'Use this Web UI origin as OAuth redirect_uri',
337
- 'useOriginHint': 'Leave off unless you registered your own OAuth client for this origin. Vendor CLI clients usually require their published redirect URI plus the paste step.',
338
- 'closeModal': 'Close',
339
- 'groupAuth': 'Sign-in and redirect',
340
- 'groupPrivacy': 'Privacy and model list',
341
- 'groupRotation': 'Rotation and limits',
342
- 'groupProvider': 'Provider behaviour',
343
- 'groupFallback': 'Local fallback',
344
- 'privacyMask': 'Mask emails and account identifiers',
345
- 'privacyMaskHint': 'For demos and screen sharing: emails show as j***n@example.com.',
346
- 'autoLoopback': 'Auto Loopback',
347
- 'autoLoopbackHint': 'Listen on local callback port to catch OAuth tokens automatically without manual copy-paste.',
348
- 'hideDeprecated': 'Hide deprecated models',
349
- 'hideDeprecatedHint': 'Hide older or superseded model variants from model dropdowns.',
350
- 'composerQuota': 'Composer quota indicator',
351
- 'composerQuotaHint': 'Display subscription usage indicator directly in the composer toolbar.',
352
- 'composerQuotaOff': 'Disabled',
353
- 'composerQuotaPercent': 'Percentage',
354
- 'composerQuotaBar': 'Progress Bar',
355
- 'composerQuotaForecast': 'Remaining Forecast',
356
- 'expiryNotifyDays': 'Expiry warning threshold (days)',
357
- 'expiryNotifyDaysHint': 'Notify when subscription renewal or token expiration is within this many days (0 to disable).',
358
- 'codexFastMode': 'Codex fast mode (1.5x)',
359
- 'codexFastModeHint': 'Enable high-throughput mode for OpenAI Codex models if supported by subscription.',
360
- 'codexVerbosity': 'Codex streaming verbosity',
361
- 'codexVerbosityHint': 'Control streaming chunking detail level from the Codex provider.',
362
- 'codexVerbosityDefault': 'Default',
363
- 'codexVerbosityLow': 'Low',
364
- 'codexVerbosityMedium': 'Medium',
365
- 'codexVerbosityHigh': 'High',
366
- 'ollamaFallback': 'Ollama local fallback',
367
- 'ollamaFallbackHint': 'Automatically route queries to a local Ollama instance when cloud quota is exhausted.',
368
- 'ollamaBaseUrl': 'Ollama Base URL',
369
- 'ollamaFallbackModel': 'Ollama Fallback Model',
370
- 'advancedSettings': 'Advanced Settings & Monitoring',
371
- 'cooldownMs': 'Failure cooldown (minutes)',
372
- 'cooldownMsHint': 'How long to pause a failing or rate-limited account before retrying.',
373
- 'probeIntervalMin': 'Health probe interval (minutes)',
374
- 'probeIntervalMinHint': 'Interval for background checks to refresh account status and quota balance.',
375
- 'notifyLimits': 'Notify on rate limits',
376
- 'notifyLimitsHint': 'Show notification alerts when account usage reaches quota ceiling.',
377
- 'hostOnline': 'Host online',
378
- 'hostOffline': 'Host unreachable',
379
- 'statsTitle': 'Session Telemetry',
380
- 'statsRequests': 'Successful / Total Requests',
381
- 'statsLatency': 'Avg Response Latency',
382
- 'statsSuccessRate': 'Session Success Rate',
383
- 'statsLastActivity': 'Last Activity',
384
- 'smokeTitle': 'Connection Smoke Test (Ping)',
385
- 'smokeDesc': 'Test live upstream connectivity and measure round-trip latency to the subscription provider.',
386
- 'runSmoke': '⚡ Run Smoke Test (Ping)',
387
- 'smokeTesting': 'Pinging…',
388
- 'smokeOk': 'Smoke test passed',
389
- 'smokeFail': 'Smoke test failed',
390
- 'modelsCatalogTitle': 'Curated Subscription Models',
391
- 'modelsCatalogDesc': 'Available model catalog across active subscriptions in DeepSeek Harness.',
392
- 'resetCredits': 'Reset credits',
393
- 'resetCreditsHint': 'ChatGPT reset cards: emergency reset for the 5-hour window. Requires a 5s confirmation.',
394
- 'resetCreditsAction': 'Reset ChatGPT Limit',
395
- 'usageLimitsTitle': 'Usage Limits',
396
- 'slotDetailsTitle': 'Slot Details',
397
- 'resetAvailable': 'Reset attempts available',
398
- 'resetExpires': 'earliest expires',
399
- 'resetAck': 'I understand one attempt will be consumed',
400
- 'resetWait': 'confirm enabled in',
401
- 'resetReady': 'ready',
402
- 'resetGo': 'Reset now',
403
- 'resetBusy': 'resetting…',
404
- 'resetDone': 'quota reset',
405
- 'resetNothing': 'server says nothing needs a reset (no attempt consumed)',
406
- 'resetNoCredit': 'no usable credit (not consumed)',
407
- 'resetRedeemed': 'already redeemed earlier',
408
- 'diagGenerate': 'Generate diagnostics report',
409
- 'diagCopy': 'Copy report',
410
- 'diagIssues': 'Open issue tracker',
411
- 'diagHint': 'The report has no tokens, emails, proxy addresses or personal data.',
412
- 'subsPill': 'SUBS',
413
- 'subsModalTitle': 'Subscription Hub',
414
- 'subsActiveHero': 'Active Subscription',
415
- 'subsPoolTitle': 'Provider Accounts',
416
- 'subsRefresh': 'Refresh',
417
- 'subsOpenSettings': 'All Settings →',
418
- 'subsFastMode': 'Fast Mode 1.5x',
419
- 'subsHealthy': 'Healthy',
420
- 'subsFree': 'free',
421
- 'subsUsed': 'used',
422
- 'subsModalClose': 'Close',
423
- 'subsLogged': 'connected',
424
- 'subsNotLogged': 'not connected',
425
- 'subsCooldown': 'cooldown',
426
- 'subsOpenSettings': 'Open settings',
427
- 'hudTitle': 'Subscription balance',
428
- 'settingsLoading': 'Loading settings…',
429
- 'settingsUnavailable': 'Settings are unavailable',
430
- 'settingsRetry': 'Retry',
431
- 'hudHint': 'drag · edge docks · click refresh',
432
- 'fcCalibrating': 'calibrating…',
433
- 'fcIdle': 'no usage',
434
- 'fcHours': 'h',
435
- 'fcMinutes': 'm',
436
- 'reconnect': 'Reconnect',
437
- 'connect': 'Connect',
438
- 'disconnect': 'Disconnect',
439
- 'removeSlot': 'Remove slot',
440
- 'pastePlaceholder': 'Paste redirected URL or code',
441
- 'submitCode': 'Submit code',
442
- 'proxyPlaceholder': 'Per-account proxy (http://, https://, socks5://) - optional',
443
- 'proxyCheck': 'Check proxy',
444
- 'proxyOk': 'proxy ok',
445
- 'proxyFail': 'proxy fail',
446
- 'deviceLogin': 'Device login',
447
- 'deviceHint': 'Headless: open the link, enter the code, keep this tab open.',
448
- 'deviceCopy': 'Copy code',
449
- 'devicePending': 'Waiting for confirmation',
450
- 'deviceAuthorized': 'Signed in',
451
- 'deviceExpired': 'Expired - start again',
452
- 'verifyPrefix': 'Google requires one-time account verification. ',
453
- 'verifyLink': 'Open verification link',
454
- 'verifySuffix': ' then reconnect.',
455
- 'addAccount': '+ Add account',
456
- 'save': 'Save',
457
- 'saved': 'Saved',
458
- 'loading': 'Loading\u2026',
459
- 'accountLabel': 'Account',
460
- 'plan': 'Plan',
461
- 'storedAs': 'Stored as',
462
- 'switchLabel': 'Provider',
463
- 'chipActive': 'Subscriptions',
464
- 'expiryLabel': 'expires',
465
- 'slashLogin': 'Login to a subscription provider',
466
- 'slashLogout': 'Log out a subscription provider',
467
- 'slashStatus': 'Subscription status',
468
- 'none': 'none',
469
- 'forecast': '≈',
470
- 'resetCredits': 'Сброс лимитов ChatGPT',
471
- 'resetCreditsHint': 'Экстренный сброс 5-часового окна ChatGPT Plus/Pro. Требует 5 секунд подтверждения перед списанием.',
472
- 'resetCreditsAction': 'Сбросить лимит ChatGPT',
473
- 'resetAvailable': 'Доступно сбросов',
474
- 'resetExpires': 'срок действия',
475
- 'resetAck': 'Я понимаю, что одна попытка сброса будет списана',
476
- 'resetWait': 'подтверждение через',
477
- 'resetReady': 'готово к сбросу',
478
- 'resetGo': 'Сбросить лимит сейчас',
479
- 'resetBusy': 'Сброс…',
480
- 'resetDone': 'Лимит успешно сброшен',
481
- 'resetNothing': 'Лимит не требует сброса',
482
- 'resetNoCredit': 'Нет доступных попыток сброса',
483
- 'resetRedeemed': 'Попытка уже использована',
484
- 'windowPrimary': 'Окно 5 часов',
485
- 'windowSecondary': 'Окно 7 дней',
486
- 'usageLimitsTitle': 'Лимиты использования',
487
- 'slotDetailsTitle': 'Параметры слота',
488
- 'resetLabel': 'reset',
489
- 'check': 'Check',
490
- 'checking': 'Checking…',
491
- 'importToken': 'Save token',
492
- 'importTokenPlace': 'Paste token or API key',
493
- 'importLocalCli': '📥 From CLI',
494
- 'importLocalCliTitle': 'Auto-import token from locally installed CLI on server',
495
- 'importLocalSuccess': 'Successfully imported token from local CLI!',
496
- 'howToGetToken': '❓ Instructions',
497
- 'howToGetTokenTitle': 'How to get token / API key for this provider',
498
- 'instructionsTitle': 'How to obtain token',
499
- 'close': 'Close',
500
- 'reconnectRequired': 'Reconnect required',
501
- 'manualTitle': 'If the browser did not come back',
502
- 'windowPrimary': 'Primary window',
503
- 'windowSecondary': 'Secondary window',
504
- }
505
-
326
+ "notConnected": "Not connected",
327
+ "verifyAccount": "Verify account",
328
+ "coolingDown": "Cooling down",
329
+ "usageFull": "Usage 100%",
330
+ "connected": "Connected",
331
+ "title": "Subscriptions",
332
+ "cardIntro": "Subscription accounts, rotation and limits.",
333
+ "show": "Show",
334
+ "hide": "Hide",
335
+ "intro": "Log in with a consumer subscription. Tokens stay on the host credentials store. The browser never reads them back. After Connect, if the provider lands on localhost or a vendor page, paste the redirected URL or the code here.",
336
+ "useOrigin": "Use this Web UI origin as OAuth redirect_uri",
337
+ "useOriginHint": "Leave off unless you registered your own OAuth client for this origin. Vendor CLI clients usually require their published redirect URI plus the paste step.",
338
+ "closeModal": "Close",
339
+ "groupAuth": "Sign-in and redirect",
340
+ "groupPrivacy": "Privacy and model list",
341
+ "groupRotation": "Rotation and limits",
342
+ "groupProvider": "Provider behaviour",
343
+ "groupFallback": "Local fallback",
344
+ "privacyMask": "Mask emails and account identifiers",
345
+ "privacyMaskHint": "For demos and screen sharing: emails show as j***n@example.com.",
346
+ "autoLoopback": "Auto Loopback",
347
+ "autoLoopbackHint": "Listen on local callback port to catch OAuth tokens automatically without manual copy-paste.",
348
+ "hideDeprecated": "Hide deprecated models",
349
+ "hideDeprecatedHint": "Hide older or superseded model variants from model dropdowns.",
350
+ "composerQuota": "Composer quota indicator",
351
+ "composerQuotaHint": "Display subscription usage indicator directly in the composer toolbar.",
352
+ "composerQuotaOff": "Disabled",
353
+ "composerQuotaPercent": "Percentage",
354
+ "composerQuotaBar": "Progress Bar",
355
+ "composerQuotaForecast": "Remaining Forecast",
356
+ "expiryNotifyDays": "Expiry warning threshold (days)",
357
+ "expiryNotifyDaysHint": "Notify when subscription renewal or token expiration is within this many days (0 to disable).",
358
+ "codexFastMode": "Codex fast mode (1.5x)",
359
+ "codexFastModeHint": "Enable high-throughput mode for OpenAI Codex models if supported by subscription.",
360
+ "codexVerbosity": "Codex streaming verbosity",
361
+ "codexVerbosityHint": "Control streaming chunking detail level from the Codex provider.",
362
+ "codexVerbosityDefault": "Default",
363
+ "codexVerbosityLow": "Low",
364
+ "codexVerbosityMedium": "Medium",
365
+ "codexVerbosityHigh": "High",
366
+ "ollamaFallback": "Ollama local fallback",
367
+ "ollamaFallbackHint": "Automatically route queries to a local Ollama instance when cloud quota is exhausted.",
368
+ "ollamaBaseUrl": "Ollama Base URL",
369
+ "ollamaFallbackModel": "Ollama Fallback Model",
370
+ "advancedSettings": "Advanced Settings & Monitoring",
371
+ "cooldownMs": "Failure cooldown (minutes)",
372
+ "cooldownMsHint": "How long to pause a failing or rate-limited account before retrying.",
373
+ "probeIntervalMin": "Health probe interval (minutes)",
374
+ "probeIntervalMinHint": "Interval for background checks to refresh account status and quota balance.",
375
+ "notifyLimits": "Notify on rate limits",
376
+ "notifyLimitsHint": "Show notification alerts when account usage reaches quota ceiling.",
377
+ "hostOnline": "Host online",
378
+ "hostOffline": "Host unreachable",
379
+ "statsTitle": "Session Telemetry",
380
+ "statsRequests": "Successful / Total Requests",
381
+ "statsLatency": "Avg Response Latency",
382
+ "statsSuccessRate": "Session Success Rate",
383
+ "statsLastActivity": "Last Activity",
384
+ "smokeTitle": "Connection Smoke Test (Ping)",
385
+ "smokeDesc": "Test live upstream connectivity and measure round-trip latency to the subscription provider.",
386
+ "runSmoke": "⚡ Run Smoke Test (Ping)",
387
+ "smokeTesting": "Pinging…",
388
+ "smokeOk": "Smoke test passed",
389
+ "smokeFail": "Smoke test failed",
390
+ "modelsCatalogTitle": "Curated Subscription Models",
391
+ "modelsCatalogDesc": "Available model catalog across active subscriptions in DeepSeek Harness.",
392
+ "resetCredits": "Reset credits",
393
+ "resetCreditsHint": "ChatGPT reset cards: emergency reset for the 5-hour window. Requires a 5s confirmation.",
394
+ "resetCreditsAction": "Reset ChatGPT Limit",
395
+ "usageLimitsTitle": "Usage Limits",
396
+ "slotDetailsTitle": "Slot Details",
397
+ "resetAvailable": "Reset attempts available",
398
+ "resetExpires": "earliest expires",
399
+ "resetAck": "I understand one attempt will be consumed",
400
+ "resetWait": "confirm enabled in",
401
+ "resetReady": "ready",
402
+ "resetGo": "Reset now",
403
+ "resetBusy": "resetting…",
404
+ "resetDone": "quota reset",
405
+ "resetNothing": "server says nothing needs a reset (no attempt consumed)",
406
+ "resetNoCredit": "no usable credit (not consumed)",
407
+ "resetRedeemed": "already redeemed earlier",
408
+ "diagGenerate": "Generate diagnostics report",
409
+ "diagCopy": "Copy report",
410
+ "diagIssues": "Open issue tracker",
411
+ "diagHint": "The report has no tokens, emails, proxy addresses or personal data.",
412
+ "subsPill": "SUBS",
413
+ "subsModalTitle": "Subscription Hub",
414
+ "subsActiveHero": "Active Subscription",
415
+ "subsPoolTitle": "Provider Accounts",
416
+ "subsRefresh": "Refresh",
417
+ "subsOpenSettings": "All Settings →",
418
+ "subsFastMode": "Fast Mode 1.5x",
419
+ "subsHealthy": "Healthy",
420
+ "subsFree": "free",
421
+ "subsUsed": "used",
422
+ "subsModalClose": "Close",
423
+ "subsLogged": "connected",
424
+ "subsNotLogged": "not connected",
425
+ "subsCooldown": "cooldown",
426
+ "hudTitle": "Subscription balance",
427
+ "settingsLoading": "Loading settings…",
428
+ "settingsUnavailable": "Settings are unavailable",
429
+ "settingsRetry": "Retry",
430
+ "hudHint": "drag · edge docks · click refresh",
431
+ "fcCalibrating": "calibrating…",
432
+ "fcIdle": "no usage",
433
+ "fcHours": "h",
434
+ "fcMinutes": "m",
435
+ "reconnect": "Reconnect",
436
+ "connect": "Connect",
437
+ "disconnect": "Disconnect",
438
+ "removeSlot": "Remove slot",
439
+ "pastePlaceholder": "Paste redirected URL or code",
440
+ "submitCode": "Submit code",
441
+ "proxyPlaceholder": "Per-account proxy (http://, https://, socks5://) - optional",
442
+ "proxyCheck": "Check proxy",
443
+ "proxyOk": "proxy ok",
444
+ "proxyFail": "proxy fail",
445
+ "deviceLogin": "Device login",
446
+ "deviceHint": "Headless: open the link, enter the code, keep this tab open.",
447
+ "deviceCopy": "Copy code",
448
+ "devicePending": "Waiting for confirmation",
449
+ "deviceAuthorized": "Signed in",
450
+ "deviceExpired": "Expired - start again",
451
+ "verifyPrefix": "Google requires one-time account verification. ",
452
+ "verifyLink": "Open verification link",
453
+ "verifySuffix": " then reconnect.",
454
+ "addAccount": "+ Add account",
455
+ "save": "Save",
456
+ "saved": "Saved",
457
+ "loading": "Loading…",
458
+ "accountLabel": "Account",
459
+ "plan": "Plan",
460
+ "storedAs": "Stored as",
461
+ "switchLabel": "Provider",
462
+ "chipActive": "Subscriptions",
463
+ "expiryLabel": "expires",
464
+ "slashLogin": "Login to a subscription provider",
465
+ "slashLogout": "Log out a subscription provider",
466
+ "slashStatus": "Subscription status",
467
+ "none": "none",
468
+ "forecast": "≈",
469
+ "windowPrimary": "Primary window",
470
+ "windowSecondary": "Secondary window",
471
+ "resetLabel": "reset",
472
+ "check": "Check",
473
+ "checking": "Checking…",
474
+ "importToken": "Save token",
475
+ "importTokenPlace": "Paste token or API key",
476
+ "importLocalCli": "📥 From CLI",
477
+ "importLocalCliTitle": "Auto-import token from locally installed CLI on server",
478
+ "importLocalSuccess": "Successfully imported token from local CLI!",
479
+ "howToGetToken": "❓ Instructions",
480
+ "howToGetTokenTitle": "How to get token / API key for this provider",
481
+ "instructionsTitle": "How to obtain token",
482
+ "close": "Close",
483
+ "reconnectRequired": "Reconnect required",
484
+ "manualTitle": "If the browser did not come back"
485
+ };
486
+
487
+ const zh = {
488
+ "notConnected": "未连接",
489
+ "verifyAccount": "需要验证账户",
490
+ "coolingDown": "冷却等待中",
491
+ "usageFull": "用量已达 100%",
492
+ "connected": "已连接",
493
+ "title": "个人订阅管理",
494
+ "cardIntro": "AI 服务订阅账户、自动轮换与速率限制管理。",
495
+ "show": "展开",
496
+ "hide": "收起",
497
+ "intro": "通过个人订阅登录。凭据安全保存在服务器端凭据库中,浏览器不回读敏感令牌。点击“连接”后,若页面跳转至 localhost 或服务商页面,请将重定向网址或授权代码粘贴到下方。",
498
+ "useOrigin": "将当前 Web UI 地址作为 OAuth 重定向地址 (redirect_uri)",
499
+ "useOriginHint": "除非您为当前域名申请了专属 OAuth 客户端,否则请保持关闭。官方 CLI 客户端通常需使用其公开重定向地址并配合粘贴代码流程。",
500
+ "closeModal": "关闭",
501
+ "groupAuth": "授权与重定向设置",
502
+ "groupPrivacy": "隐私与模型展示",
503
+ "groupRotation": "账户轮换与限制",
504
+ "groupProvider": "服务商特性设置",
505
+ "groupFallback": "本地兜底与容灾",
506
+ "privacyMask": "脱敏隐藏邮箱与账户标识",
507
+ "privacyMaskHint": "演示或录屏时自动将邮箱显示为 j***n@example.com。",
508
+ "autoLoopback": "自动回环监听 (Auto Loopback)",
509
+ "autoLoopbackHint": "在服务器本地回环端口监听以自动捕获 OAuth 令牌,无需手动复制粘贴。",
510
+ "hideDeprecated": "隐藏已废弃的旧模型",
511
+ "hideDeprecatedHint": "在模型下拉列表中隐藏早期或已被替代的旧版模型。",
512
+ "composerQuota": "输入框额度指示器",
513
+ "composerQuotaHint": "在聊天输入框工具栏直接显示当前订阅额度指示器。",
514
+ "composerQuotaOff": "已禁用",
515
+ "composerQuotaPercent": "百分比显示",
516
+ "composerQuotaBar": "进度条显示",
517
+ "composerQuotaForecast": "剩余耗尽预测",
518
+ "expiryNotifyDays": "订阅到期预警阈值(天)",
519
+ "expiryNotifyDaysHint": "当订阅续费或令牌将在指定天数内过期时弹出通知(设为 0 关闭)。",
520
+ "codexFastMode": "Codex 快速高吞吐模式 (1.5x)",
521
+ "codexFastModeHint": "对支持的 OpenAI Codex 订阅模型开启 1.5x 高速通道。",
522
+ "codexVerbosity": "Codex 流式输出详细级别",
523
+ "codexVerbosityHint": "调节 Codex 响应分块输出的详细程度。",
524
+ "codexVerbosityDefault": "默认",
525
+ "codexVerbosityLow": "低",
526
+ "codexVerbosityMedium": "中",
527
+ "codexVerbosityHigh": "高",
528
+ "ollamaFallback": "Ollama 本地模型自动兜底",
529
+ "ollamaFallbackHint": "当云端配额耗尽或服务商故障时,自动将请求转发给本地 Ollama 实例。",
530
+ "ollamaBaseUrl": "Ollama 服务基础 URL",
531
+ "ollamaFallbackModel": "Ollama 兜底模型名称",
532
+ "advancedSettings": "高级配置与监控",
533
+ "cooldownMs": "故障与限流冷却时间(分钟)",
534
+ "cooldownMsHint": "当账户遇到 429 或故障时暂停该账户的分钟数。",
535
+ "probeIntervalMin": "健康探测轮询间隔(分钟)",
536
+ "probeIntervalMinHint": "后台探活及更新账户配额剩余额度的轮询间隔。",
537
+ "notifyLimits": "配额耗尽时发送通知",
538
+ "notifyLimitsHint": "当账户使用量达到上限时触发系统通知提醒。",
539
+ "hostOnline": "主机在线",
540
+ "hostOffline": "主机无法连接",
541
+ "statsTitle": "会话遥测统计",
542
+ "statsRequests": "成功 / 总请求数",
543
+ "statsLatency": "平均响应延迟",
544
+ "statsSuccessRate": "会话成功率",
545
+ "statsLastActivity": "最近活跃时间",
546
+ "smokeTitle": "链路连通性冒烟测试 (Ping)",
547
+ "smokeDesc": "向当前订阅服务商发起实时探活并测量双向网络往返延迟 (RTT)。",
548
+ "runSmoke": "⚡ 执行冒烟探活 (Ping)",
549
+ "smokeTesting": "正在探活…",
550
+ "smokeOk": "冒烟测试成功",
551
+ "smokeFail": "冒烟测试失败",
552
+ "modelsCatalogTitle": "精选订阅模型",
553
+ "modelsCatalogDesc": "当前 DeepSeek Harness 活跃订阅下可用模型一览。",
554
+ "resetCredits": "重置 ChatGPT 额度",
555
+ "resetCreditsHint": "ChatGPT 重置卡:紧急重置 5 小时额度窗口。扣除前需等待 5 秒倒计时确认。",
556
+ "resetCreditsAction": "重置 ChatGPT 限制",
557
+ "usageLimitsTitle": "用量限制",
558
+ "slotDetailsTitle": "槽位详情",
559
+ "resetAvailable": "可用重置次数",
560
+ "resetExpires": "最早到期时间",
561
+ "resetAck": "我确认将消耗一次重置机会",
562
+ "resetWait": "确认倒计时",
563
+ "resetReady": "可以重置",
564
+ "resetGo": "立即重置额度",
565
+ "resetBusy": "正在重置…",
566
+ "resetDone": "配额已成功重置",
567
+ "resetNothing": "服务端指示当前无需重置(未消耗机会)",
568
+ "resetNoCredit": "无可用重置点数(未消耗机会)",
569
+ "resetRedeemed": "该点数先前已兑换使用",
570
+ "diagGenerate": "生成匿名诊断报告",
571
+ "diagCopy": "复制报告",
572
+ "diagIssues": "打开 Issue 反馈",
573
+ "diagHint": "诊断报告不包含令牌、邮箱、代理地址或任何个人隐私数据。",
574
+ "subsPill": "订阅",
575
+ "subsModalTitle": "订阅管理中心",
576
+ "subsActiveHero": "当前主用订阅",
577
+ "subsPoolTitle": "服务商账户池",
578
+ "subsRefresh": "刷新",
579
+ "subsOpenSettings": "全部设置 →",
580
+ "subsFastMode": "加速模式 1.5x",
581
+ "subsHealthy": "健康运行中",
582
+ "subsFree": "空闲可用",
583
+ "subsUsed": "已用",
584
+ "subsModalClose": "关闭",
585
+ "subsLogged": "已连接",
586
+ "subsNotLogged": "未连接",
587
+ "subsCooldown": "冷却中",
588
+ "hudTitle": "订阅额度余量",
589
+ "settingsLoading": "正在加载设置…",
590
+ "settingsUnavailable": "设置加载失败",
591
+ "settingsRetry": "重试",
592
+ "hudHint": "可拖拽 · 边缘停靠 · 点击刷新",
593
+ "fcCalibrating": "校准中…",
594
+ "fcIdle": "暂无消耗",
595
+ "fcHours": "小时",
596
+ "fcMinutes": "分",
597
+ "reconnect": "重新连接",
598
+ "connect": "连接",
599
+ "disconnect": "断开连接",
600
+ "removeSlot": "移除槽位",
601
+ "pastePlaceholder": "粘贴重定向 URL 或授权代码",
602
+ "submitCode": "提交授权代码",
603
+ "proxyPlaceholder": "单账户独立代理 (http://, https://, socks5://) - 可选",
604
+ "proxyCheck": "测试代理",
605
+ "proxyOk": "代理连接成功",
606
+ "proxyFail": "代理连接失败",
607
+ "deviceLogin": "设备码登录",
608
+ "deviceHint": "无头模式:打开下方链接,输入设备码,并保持当前标签页开启。",
609
+ "deviceCopy": "复制设备码",
610
+ "devicePending": "等待授权确认中",
611
+ "deviceAuthorized": "授权成功",
612
+ "deviceExpired": "设备码已过期,请重试",
613
+ "verifyPrefix": "Google 要求完成一次性账户验证。",
614
+ "verifyLink": "打开验证链接",
615
+ "verifySuffix": ",完成后点击重新连接。",
616
+ "addAccount": "+ 添加账户",
617
+ "save": "保存",
618
+ "saved": "已保存",
619
+ "loading": "加载中…",
620
+ "accountLabel": "账户",
621
+ "plan": "套餐方案",
622
+ "storedAs": "存储标识",
623
+ "switchLabel": "切换服务商",
624
+ "chipActive": "已启用订阅",
625
+ "expiryLabel": "到期时间",
626
+ "slashLogin": "登录订阅服务商",
627
+ "slashLogout": "登出订阅服务商",
628
+ "slashStatus": "查看订阅状态",
629
+ "none": "无",
630
+ "forecast": "≈",
631
+ "windowPrimary": "主配额窗口",
632
+ "windowSecondary": "次配额窗口",
633
+ "resetLabel": "重置倒计时",
634
+ "check": "检测",
635
+ "checking": "检测中…",
636
+ "importToken": "保存令牌",
637
+ "importTokenPlace": "粘贴令牌或 API Key",
638
+ "importLocalCli": "📥 从 CLI 导入",
639
+ "importLocalCliTitle": "从服务器已安装的本地 CLI 自动读取并导入令牌",
640
+ "importLocalSuccess": "已成功从本地 CLI 导入凭据!",
641
+ "howToGetToken": "❓ 获取指引",
642
+ "howToGetTokenTitle": "如何获取此服务商的令牌或 API 密钥",
643
+ "instructionsTitle": "获取令牌指引",
644
+ "close": "关闭",
645
+ "reconnectRequired": "需要重新连接",
646
+ "manualTitle": "若浏览器未能自动跳转回调"
647
+ };
506
648
  // #51: live countdown to the quota window reset (account.quota.resetAt).
507
649
  function cleanErrorMessage(raw) {
508
650
  if (!raw) return ''
@@ -549,17 +691,17 @@ const cssId = 'dsh-subscriptions/settings.module.css'
549
691
  function formatRelativeReset(resetAt, lang, now) {
550
692
  if (!resetAt || !Number.isFinite(resetAt) || resetAt <= 0) return ''
551
693
  var delta = resetAt - (now || Date.now())
552
- var isRu = lang !== 'en'
553
- if (delta <= 0) return isRu ? 'только что' : 'just now'
694
+ var isZh = lang === 'zh'
695
+ if (delta <= 0) return isZh ? '刚刚' : 'just now'
554
696
  var totalMinutes = Math.max(1, Math.round(delta / 60000))
555
697
  var days = Math.floor(totalMinutes / 1440)
556
698
  var hours = Math.floor((totalMinutes % 1440) / 60)
557
699
  var minutes = totalMinutes % 60
558
700
  var bits = []
559
- if (days) bits.push(days + (isRu ? ' дн' : 'd'))
560
- if (hours) bits.push(hours + (isRu ? ' ч' : 'h'))
561
- if (minutes || !bits.length) bits.push(minutes + (isRu ? ' мин' : 'm'))
562
- return (isRu ? 'через ' : 'in ') + bits.join(' ')
701
+ if (days) bits.push(days + (isZh ? '天' : 'd'))
702
+ if (hours) bits.push(hours + (isZh ? '小时' : 'h'))
703
+ if (minutes || !bits.length) bits.push(minutes + (isZh ? '分' : 'm'))
704
+ return (isZh ? '' : 'in ') + bits.join(' ') + (isZh ? '后' : '')
563
705
  }
564
706
 
565
707
  function ResetCountdown(props) {
@@ -1304,12 +1446,12 @@ const cssId = 'dsh-subscriptions/settings.module.css'
1304
1446
  const prov = row.slot.provider;
1305
1447
  const info = (typeof INSTRUCTIONS !== 'undefined' && INSTRUCTIONS[prov]) || {
1306
1448
  title: prov,
1307
- stepsRu: ['Вставьте токен или API-ключ в поле выше и нажмите "Сохранить".'],
1449
+ stepsZh: ['请在上方输入框中粘贴令牌或 API Key 并点击“保存令牌”。'],
1308
1450
  stepsEn: ['Paste your token or API key into the field above and click "Save token".'],
1309
1451
  };
1310
- // Determine language safely from t('instructionsTitle')
1311
- const isRu = t('instructionsTitle').indexOf('Инструкция') >= 0;
1312
- const steps = isRu ? info.stepsRu : info.stepsEn;
1452
+ // Determine language safely from current locale (fallback to zh if Chinese or en)
1453
+ const isZh = t('instructionsTitle').indexOf('获取') >= 0 || t('close') === '关闭';
1454
+ const steps = (isZh && info.stepsZh) ? info.stepsZh : (info.stepsEn || []);
1313
1455
  return React.createElement('div', { className: 'dsub-helpBox' },
1314
1456
  React.createElement('div', { className: 'dsub-helpHead' },
1315
1457
  React.createElement('span', null, 'ℹ️ ' + info.title + ' — ' + t('instructionsTitle')),
@@ -1330,7 +1472,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
1330
1472
  rel: 'noopener noreferrer',
1331
1473
  className: 'dsub-mini',
1332
1474
  style: { display: 'inline-flex', alignItems: 'center', gap: 4, textDecoration: 'none' },
1333
- }, '🔗 ' + (isRu ? 'Открыть страницу провайдера' : 'Open provider console'))
1475
+ }, '🔗 ' + (isZh ? '打开服务商控制台' : 'Open provider console'))
1334
1476
  ) : null
1335
1477
  );
1336
1478
  })() : null),
@@ -1510,7 +1652,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
1510
1652
  var agoStr = null
1511
1653
  if (at) {
1512
1654
  var m = Math.round((Date.now() - at) / 60000)
1513
- if (m >= 1) agoStr = m + 'm ' + (t('lang') === 'ru' || t('refresh') === 'Обновить' ? 'назад' : 'ago')
1655
+ if (m >= 1) agoStr = m + 'm ' + (t('close') === '关闭' ? '前' : 'ago')
1514
1656
  }
1515
1657
 
1516
1658
  return React.createElement('div', { className: 'dsub-panel-box' },
@@ -1594,9 +1736,9 @@ const cssId = 'dsh-subscriptions/settings.module.css'
1594
1736
  }
1595
1737
  }
1596
1738
  ctx.effect(() => {
1597
- const undo = [addLocale('en', en)]
1739
+ const undo = [addLocale('en', en), addLocale('zh', zh)]
1598
1740
  return () => { for (const off of undo) off() }
1599
- }, 'dsh-subscriptions: словари')
1741
+ }, 'dsh-subscriptions: locales')
1600
1742
  // Labels outside a component take the translator bound to the namespace.
1601
1743
  setT(ctx.locale.bind(NS))
1602
1744
  // The canonical place is the Plugins tab: a card with its own header
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Format a future timestamp as a relative remaining duration down to the minute.
3
- * Supports en and ru.
3
+ * Supports en and zh.
4
4
  */
5
5
 
6
6
  export const RELATIVE_UNITS = {
@@ -12,6 +12,14 @@ export const RELATIVE_UNITS = {
12
12
  hour: '{n}h',
13
13
  day: '{n}d',
14
14
  },
15
+ zh: {
16
+ soon: '刚刚',
17
+ prefix: '',
18
+ suffix: '后',
19
+ minute: '{n}分',
20
+ hour: '{n}小时',
21
+ day: '{n}天',
22
+ },
15
23
  ru: {
16
24
  soon: 'только что',
17
25
  prefix: 'через ',
@@ -32,10 +40,10 @@ function fill(template, n) {
32
40
  * @param {number} [now]
33
41
  * @returns {string} human-readable remaining time, down to the minute
34
42
  */
35
- export function formatRelativeReset(resetAt, lang = 'ru', now = Date.now()) {
43
+ export function formatRelativeReset(resetAt, lang = 'en', now = Date.now()) {
36
44
  if (typeof resetAt !== 'number' || !Number.isFinite(resetAt) || resetAt <= 0) return ''
37
45
  const delta = resetAt - now
38
- const units = RELATIVE_UNITS[lang] || RELATIVE_UNITS.ru
46
+ const units = RELATIVE_UNITS[lang] || RELATIVE_UNITS.en
39
47
  if (delta <= 0) return units.soon
40
48
 
41
49
  const totalMinutes = Math.max(1, Math.round(delta / 60_000))
@@ -16,8 +16,8 @@ export function registerStatusRoutes(ctx, state) {
16
16
  store,
17
17
  history,
18
18
  diagnosticsReport,
19
- pmL,
20
- pmE,
19
+ pmL = (s) => s,
20
+ pmE = (s) => s,
21
21
  } = state
22
22
 
23
23
  async function configResponse() {
@@ -182,6 +182,7 @@ export function registerStatusRoutes(ctx, state) {
182
182
  writeJson(res, 200, { ok: false, provider, error: { code: 'provider', message: 'unknown provider' } })
183
183
  return
184
184
  }
185
+ const t0 = Date.now()
185
186
  const ref = oauthRef(provider, payload.index)
186
187
  const info = await store.describeRef(ref)
187
188
  if (!info.configured) {
@@ -218,10 +219,12 @@ export function registerStatusRoutes(ctx, state) {
218
219
  }
219
220
  try {
220
221
  await vendor.check(blob, vendorConfig(provider, live()), probeFetch)
221
- writeJson(res, 200, { ok: true, provider, index: payload.index, ref, email: pmE(blob.email), label: pmL(blob.label), expiresAt: blob.expiresAt || null, quota: capturedQuota || info.quota || null, usagePercent: info.usagePercent ?? null })
222
+ const latencyMs = Date.now() - t0;
223
+ writeJson(res, 200, { ok: true, provider, index: payload.index, ref, latencyMs, email: pmE(blob.email), label: pmL(blob.label), expiresAt: blob.expiresAt || null, quota: capturedQuota || info.quota || null, usagePercent: info.usagePercent ?? null })
222
224
  } catch (e) {
223
225
  writeJson(res, 200, {
224
226
  ok: false, provider, index: payload.index, ref,
227
+ latencyMs: Date.now() - t0,
225
228
  email: pmE(blob.email), label: pmL(blob.label), expiresAt: blob.expiresAt || null,
226
229
  quota: capturedQuota || info.quota || null,
227
230
  error: { code: e && e.code ? e.code : 'VENDOR', message: String(e && e.message || e).slice(0, 300) },
package/lib/usage.js CHANGED
@@ -52,22 +52,22 @@ export function asUsageSnapshot(percent) {
52
52
  // Named limit windows reported by vendors (five_hour, seven_day, primary...).
53
53
  // Label map is a data table: extend it when a vendor adds a window name.
54
54
  const WINDOW_LABELS = {
55
- five_hour: { ru: "5ч", en: "5h" },
56
- seven_day: { ru: "7д", en: "7d" },
57
- seven_day_oauth_apps: { ru: "7д прил.", en: "7d apps" },
58
- weekly_limit_7_days: { ru: "7д", en: "7d" },
59
- primary: { ru: "осн.", en: "primary" },
60
- secondary: { ru: "втор.", en: "secondary" },
55
+ five_hour: { zh: "5小时", ru: "5ч", en: "5h" },
56
+ seven_day: { zh: "7天", ru: "7д", en: "7d" },
57
+ seven_day_oauth_apps: { zh: "7天应用", ru: "7д прил.", en: "7d apps" },
58
+ weekly_limit_7_days: { zh: "7天", ru: "7д", en: "7d" },
59
+ primary: { zh: "主配额", ru: "осн.", en: "primary" },
60
+ secondary: { zh: "次配额", ru: "втор.", en: "secondary" },
61
61
  // codex: the vendor API returns primary_window/secondary_window - surfaced as 5h/7d in the UI
62
- primary_window: { ru: "5ч", en: "5h" },
63
- secondary_window: { ru: "7д", en: "7d" },
64
- monthly: { ru: "месяц", en: "month" },
65
- credits: { ru: "кредиты", en: "credits" },
62
+ primary_window: { zh: "5小时", ru: "5ч", en: "5h" },
63
+ secondary_window: { zh: "7天", ru: "7д", en: "7d" },
64
+ monthly: { zh: "月度", ru: "месяц", en: "month" },
65
+ credits: { zh: "点数", ru: "кредиты", en: "credits" },
66
66
  }
67
67
 
68
68
  export function windowLabel(id) {
69
69
  const known = WINDOW_LABELS[id]
70
- return known || { ru: id, en: id }
70
+ return known || { zh: id, ru: id, en: id }
71
71
  }
72
72
 
73
73
  // Walk vendor usage JSON; every node carrying a utilization-style number
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-subscriptions",
3
- "version": "0.6.7",
3
+ "version": "0.6.8",
4
4
  "description": "Use ChatGPT Codex, Claude, Grok, Antigravity, Kimi, GLM, Cursor, Kiro, Copilot, Qwen, ERNIE, Spark, JetBrains, Perplexity, Replit, and Cody subscriptions as DeepSeek Harness LLM providers via OAuth.",
5
5
  "license": "MIT",
6
6
  "type": "module",