From 594eec0bb84a02bd5956b1b183437b8086acf703 Mon Sep 17 00:00:00 2001 From: danielgap Date: Thu, 29 Oct 2026 23:58:28 +0100 Subject: [PATCH 1/2] =?UTF-8?q?feat(i18n):=20migrar=20los=20paneles=20de?= =?UTF-8?q?=20suscripci=C3=B3n=20a=20los=20cat=C3=A1logos=20ES/EN=20?= =?UTF-8?q?=E2=80=94=20T2c=20slice=202=20C1b=20(#66)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude, Codex, Gemini, Grok, Cursor, Copilot, MiniMax, z.ai y OpenCode Go pasan sus textos a settings.providers.panels.* (44 claves; reusa las compartidas de C1a). Paneles extraídos verbatim para SSR: SubscriptionPanel, CursorPanel, CopilotPanel, MiniMaxPanel, ZaiPanel y OpenCodeGoPanel; con es todo se ve igual. Tests de aria-current/aria-expanded en ProvidersCard y SettingsNav (nota 3 de #204) y ProviderDrawerHead deja de tapar t() (nota 4). --- src/Settings-panels-i18n.test.tsx | 434 +++++++++++++++++++++++ src/Settings-providers-i18n.test.tsx | 36 ++ src/Settings.tsx | 502 +++++++++++++++------------ src/i18n/en.ts | 64 ++++ src/i18n/es.ts | 70 +++- src/i18n/settings-nav.test.tsx | 28 ++ 6 files changed, 907 insertions(+), 227 deletions(-) create mode 100644 src/Settings-panels-i18n.test.tsx diff --git a/src/Settings-panels-i18n.test.tsx b/src/Settings-panels-i18n.test.tsx new file mode 100644 index 0000000..609b75e --- /dev/null +++ b/src/Settings-panels-i18n.test.tsx @@ -0,0 +1,434 @@ +// Corte visible de i18n para los paneles de suscripción de Ajustes › Proveedores +// (#66, T2c slice 2 C1b): Claude, Codex, Gemini, Grok, Cursor, GitHub Copilot, +// MiniMax, z.ai y OpenCode Go, renderizados en servidor (renderToStaticMarkup, +// sin DOM falso) con los literales exactos: en español tal y como estaban en el +// código y en inglés tras la migración. +import { renderToStaticMarkup } from 'react-dom/server'; +import { describe, expect, it } from 'vitest'; +import type { CodexInstallController } from './lib/codex-install'; +import type { AppConfig, Locale } from './lib/types'; +import { + CopilotPanel, + CursorPanel, + MiniMaxPanel, + OpenCodeGoPanel, + SubscriptionPanel, + ZaiPanel, +} from './Settings'; + +const noop = () => {}; + +const expectAll = (html: string, texts: string[], label: string) => { + for (const text of texts) expect(html, `falta «${text}» en ${label}`).toContain(text); +}; +const expectNone = (html: string, texts: string[], label: string) => { + for (const text of texts) expect(html, `sobra «${text}» en ${label}`).not.toContain(text); +}; + +// Config mínima: solo lo que leen los paneles de suscripción. +const sub = (loginCommand: string) => ({ enabled: true, loginCommand, credentialsPath: null }); +const cfg = { + hiddenProviders: [], + claude: sub('claude'), + codex: sub('codex'), + gemini: sub('gemini'), + grok: sub('grok'), + cursor: { enabled: true, credentialsPath: null }, + copilot: { enabled: true, username: '', plan: 'pro', customAllowance: null }, + minimax: { enabled: true, baseUrl: 'https://api.minimax.io' }, + zai: { enabled: true }, + opencodeGo: { enabled: true }, + accounts: [], +} as unknown as AppConfig; + +const healthFor = (locale: Locale) => (_id: string, hidden: boolean) => ({ + saved: null, + secretsSig: '', + dirty: false, + hidden, + saving: false, + onSave: () => Promise.resolve(true), + locale, +}); + +// `CodexInstall` (C2) solo se pinta con `codex` y el CLI ausente: estos paneles no lo usan. +const codexInstall = {} as CodexInstallController; + +const common = (locale: Locale) => ({ + locale, + cfg, + secrets: {}, + set: noop, + setVisibility: noop, + loadSecrets: noop, + healthFor: healthFor(locale), +}); + +const subscription = (locale: Locale, p: 'claude' | 'codex' | 'gemini' | 'grok', cli: Record = {}) => + renderToStaticMarkup(); +const cursor = (locale: Locale) => renderToStaticMarkup(); +const copilot = (locale: Locale, plan = 'pro') => + renderToStaticMarkup(); +const minimax = (locale: Locale) => renderToStaticMarkup(); +const zai = (locale: Locale) => renderToStaticMarkup(); +const opencodeGo = (locale: Locale) => renderToStaticMarkup(); + +describe('Subscription provider panels (SSR, i18n)', () => { + it('renders the Spanish literals of Claude, Codex, Gemini and Grok copied verbatim from the current code', () => { + expectAll( + subscription('es', 'claude'), + [ + 'Suscripción · OAuth', + 'Claude (Pro / Max)', + 'Mostrar en el widget', + 'Límites de sesión (5 h), semanales y por modelo de tu plan. Quorb lee la sesión que guarda Claude Code: no ve tu contraseña ni toca los tokens.', + 'aria-label="Cómo conectar la cuenta"', + 'Avanzado · fichero de sesión y comando de login', + 'Fichero de credenciales', + String.raw`Vacío = %USERPROFILE%\.claude\.credentials.json. Para WSL: \\wsl.localhost\Ubuntu\home\TU_USUARIO\.claude\.credentials.json`, + 'Comando de login', + // pasos de CliConnect (C1a), pintados dentro del panel + 'Inicia sesión', + 'Comprobando la conexión…', + ], + 'es claude', + ); + expectAll( + subscription('es', 'codex'), + [ + 'Suscripción · OAuth', + 'ChatGPT (Plus / Pro · Codex)', + 'Límites de 5 h y semanales de tu plan de ChatGPT en Codex. Quorb lee la sesión que guarda el CLI de Codex.', + 'Fichero auth.json', + String.raw`Vacío = %CODEX_HOME% o %USERPROFILE%\.codex\auth.json`, + ], + 'es codex', + ); + expectAll( + subscription('es', 'gemini'), + [ + 'Suscripción · sesión de Gemini CLI', + 'Gemini (Google AI Pro / Code Assist)', + 'Cuotas diarias por modelo. Quorb lee la sesión que guarda . Endpoints no oficiales.', + 'Fichero de credenciales', + String.raw`Vacío = %USERPROFILE%\.gemini\oauth_creds.json`, + ], + 'es gemini', + ); + expectAll( + subscription('es', 'grok'), + [ + 'Suscripción · OAuth', + 'Grok (SuperGrok)', + 'Uso semanal de créditos de tu suscripción en el CLI de Grok. Quorb lee la sesión que guarda grok login. Endpoint no oficial.', + 'Fichero auth.json', + String.raw`Vacío = %GROK_HOME% o %USERPROFILE%\.grok\auth.json`, + ], + 'es grok', + ); + }); + + it('renders the English texts of Claude, Codex, Gemini and Grok when the locale is en, with no Spanish left', () => { + const claude = subscription('en', 'claude'); + expectAll( + claude, + [ + 'Subscription · OAuth', + 'Claude (Pro / Max)', + 'Show in the widget', + 'Session (5 h), weekly and per-model limits of your plan. Quorb reads the session that Claude Code stores: it does not see your password or touch the tokens.', + 'aria-label="How to connect the account"', + 'Advanced · session file and login command', + 'Credentials file', + String.raw`Empty = %USERPROFILE%\.claude\.credentials.json. For WSL: \\wsl.localhost\Ubuntu\home\YOUR_USER\.claude\.credentials.json`, + 'Login command', + 'Sign in', + 'Checking the connection…', + ], + 'en claude', + ); + const codex = subscription('en', 'codex'); + expectAll( + codex, + [ + 'ChatGPT (Plus / Pro · Codex)', + 'The 5 h and weekly limits of your ChatGPT plan in Codex. Quorb reads the session that the Codex CLI stores.', + 'auth.json file', + String.raw`Empty = %CODEX_HOME% or %USERPROFILE%\.codex\auth.json`, + ], + 'en codex', + ); + const gemini = subscription('en', 'gemini'); + expectAll( + gemini, + [ + 'Subscription · Gemini CLI session', + 'Daily per-model quotas. Quorb reads the session stored by . Unofficial endpoints.', + 'Credentials file', + String.raw`Empty = %USERPROFILE%\.gemini\oauth_creds.json`, + ], + 'en gemini', + ); + const grok = subscription('en', 'grok'); + expectAll( + grok, + [ + 'Weekly credit usage of your subscription in the Grok CLI. Quorb reads the session stored by grok login. Unofficial endpoint.', + 'auth.json file', + String.raw`Empty = %GROK_HOME% or %USERPROFILE%\.grok\auth.json`, + ], + 'en grok', + ); + expectNone( + [claude, codex, gemini, grok].join('\n'), + [ + 'Suscripción', + 'Mostrar en el widget', + 'Límites', + 'Quorb lee', + 'Cuotas diarias', + 'Uso semanal', + 'Cómo conectar', + 'Avanzado', + 'Fichero', + 'Vacío', + 'Para WSL', + 'TU_USUARIO', + 'Comando de login', + 'no oficial', + 'Inicia sesión', + 'Comprobando', + ], + 'en claude/codex/gemini/grok', + ); + }); + + it('renders the Spanish literals of Cursor copied verbatim from the current code', () => { + expectAll( + cursor('es'), + [ + 'Suscripción · sesión de la app', + 'Cursor', + 'Mostrar en el widget', + 'Uso del plan en el ciclo actual (total, Auto y modelos API) y gasto on-demand. Endpoint no oficial.', + 'aria-label="Cómo conectar la cuenta"', + 'Inicia sesión en la app de Cursor', + 'Quorb lee la sesión que guarda la app de Cursor en este equipo (solo lectura). Si no usas la app aquí, pega un token manual: tiene prioridad sobre la sesión de la app.', + 'Token manual (opcional)', + 'Comprobando la conexión…', + 'Avanzado · base de datos de Cursor', + 'Base de datos de Cursor', + String.raw`Vacío = %APPDATA%\Cursor\User\globalStorage\state.vscdb`, + ], + 'es cursor', + ); + }); + + it('renders the English texts of Cursor when the locale is en, with no Spanish left', () => { + const html = cursor('en'); + expectAll( + html, + [ + 'Subscription · app session', + 'Show in the widget', + 'Plan usage in the current cycle (total, Auto and API models) and on-demand spend. Unofficial endpoint.', + 'aria-label="How to connect the account"', + 'Sign in to the Cursor app', + 'Quorb reads the session that the Cursor app stores on this computer (read-only). If you do not use the app here, paste a manual token: it takes priority over the app session.', + 'Manual token (optional)', + 'Checking the connection…', + 'Advanced · Cursor database', + 'Cursor database', + String.raw`Empty = %APPDATA%\Cursor\User\globalStorage\state.vscdb`, + ], + 'en cursor', + ); + expectNone( + html, + ['Suscripción', 'Mostrar en el widget', 'Uso del plan', 'Cómo conectar', 'Inicia sesión', 'Token manual', 'Avanzado', 'Base de datos', 'Vacío', 'no oficial', 'Comprobando'], + 'en cursor', + ); + }); + + it('renders the Spanish literals of GitHub Copilot copied verbatim from the current code', () => { + const html = copilot('es'); + expectAll( + html, + [ + 'Suscripción · API oficial', + 'GitHub Copilot', + 'Mostrar en el widget', + 'Peticiones premium del mes con la API de facturación de GitHub. Se reinician el día 1 (UTC).', + 'aria-label="Cómo conectar la cuenta"', + 'Usuario y plan de GitHub', + 'Usuario de GitHub', + 'Plan', + 'Otra cantidad', + 'Token de solo lectura', + 'Token de GitHub (fine-grained)', + 'Créalo en con el permiso de cuenta «Plan»: solo lectura.', + 'Comprobando la conexión…', + ], + 'es copilot', + ); + expect(html).not.toContain('Peticiones premium incluidas al mes'); + expect(copilot('es', 'custom')).toContain('Peticiones premium incluidas al mes'); + }); + + it('renders the English texts of GitHub Copilot when the locale is en, with no Spanish left', () => { + const html = copilot('en'); + expectAll( + html, + [ + 'Subscription · Official API', + 'Show in the widget', + 'Premium requests of the month from the GitHub billing API. They reset on the 1st (UTC).', + 'aria-label="How to connect the account"', + 'GitHub user and plan', + 'GitHub username', + 'Other amount', + 'Read-only token', + 'GitHub token (fine-grained)', + 'Create it at with the “Plan” account permission: read-only.', + 'Checking the connection…', + ], + 'en copilot', + ); + expect(copilot('en', 'custom')).toContain('Premium requests included per month'); + expectNone( + [html, copilot('en', 'custom')].join('\n'), + ['Suscripción', 'Mostrar en el widget', 'Peticiones premium', 'Se reinician', 'Cómo conectar', 'Usuario', 'Otra cantidad', 'Token de', 'Créalo', 'permiso', 'Comprobando'], + 'en copilot', + ); + }); + + it('renders the Spanish literals of MiniMax copied verbatim from the current code', () => { + expectAll( + minimax('es'), + [ + 'Suscripción · API oficial', + 'MiniMax', + 'Mostrar en el widget', + 'Cuota del plan y, si añades la segunda clave, tu saldo de pago por uso. Son dos credenciales distintas en la consola: la primera da acceso al plan y la segunda al saldo.', + 'aria-label="Cómo conectar la cuenta"', + 'Subscription Key del plan', + 'Subscription Key', + 'Del panel M/Token en . En la región de China, en (el host por región se cambia en Ajustes avanzados).', + 'API key de pago por uso (opcional)', + 'API key PAYG', + 'Sin ella solo se muestra la cuota del plan; el saldo es opcional y su fallo no lo degrade.', + 'Comprobando la conexión…', + 'Avanzado · URL del API', + 'Base URL', + 'Las claves son por región: global en api.minimax.io, China en api.minimaxi.com. Si el anillo sale con error 1004 o 2049, prueba con la de tu región.', + ], + 'es minimax', + ); + }); + + it('renders the English texts of MiniMax when the locale is en, with no Spanish left', () => { + const html = minimax('en'); + expectAll( + html, + [ + 'Subscription · Official API', + 'Show in the widget', + 'Plan quota and, if you add the second key, your pay-as-you-go balance. They are two different credentials in the console: the first gives access to the plan and the second to the balance.', + 'aria-label="How to connect the account"', + 'Plan Subscription Key', + 'From the M/Token panel at . In mainland China, at (the per-region host is changed in Advanced settings).', + 'Pay-as-you-go API key (optional)', + 'Without it only the plan quota is shown; the balance is optional and a failure there does not degrade it.', + 'Checking the connection…', + 'Advanced · API URL', + 'Keys are per region: global at api.minimax.io, China at api.minimaxi.com. If the ring shows error 1004 or 2049, try the one for your region.', + ], + 'en minimax', + ); + expectNone( + html, + ['Suscripción', 'Mostrar en el widget', 'Cuota del plan', 'dos credenciales', 'Cómo conectar', 'del plan', 'Del panel', 'región', 'pago por uso', 'Sin ella', 'Avanzado', 'Las claves', 'Comprobando'], + 'en minimax', + ); + }); + + it('renders the Spanish literals of z.ai and OpenCode Go copied verbatim from the current code', () => { + expectAll( + zai('es'), + [ + 'Suscripción · GLM Coding Plan', + 'z.ai', + 'Mostrar en el widget', + 'Cuotas de coding de las últimas 5 h y de la semana, y cuota mensual de web tools del Coding Plan.', + 'aria-label="Cómo conectar la cuenta"', + 'API key', + 'Créala en .', + 'Comprobando la conexión…', + ], + 'es zai', + ); + expectAll( + opencodeGo('es'), + [ + 'Suscripción · Go / Go Plus', + 'OpenCode Go', + 'Mostrar en el widget', + 'Cuota de las últimas 5 h, cuota semanal y cuota del ciclo de facturación de la suscripción Go / Go Plus.', + 'aria-label="Cómo conectar la cuenta"', + 'API key', + 'API key de OpenCode Go', + 'Créala en .', + 'Comprobando la conexión…', + ], + 'es opencode_go', + ); + }); + + it('renders the English texts of z.ai and OpenCode Go when the locale is en, with no Spanish left', () => { + const z = zai('en'); + const o = opencodeGo('en'); + expectAll( + z, + [ + 'Subscription · GLM Coding Plan', + 'Show in the widget', + 'Coding quotas for the last 5 h and the week, and the monthly web tools quota of the Coding Plan.', + 'aria-label="How to connect the account"', + 'API key', + 'Create it at .', + 'Checking the connection…', + ], + 'en zai', + ); + expectAll( + o, + [ + 'Subscription · Go / Go Plus', + 'Show in the widget', + 'Quota for the last 5 h, weekly quota and billing-cycle quota of the Go / Go Plus subscription.', + 'aria-label="How to connect the account"', + 'OpenCode Go API key', + 'Create it at .', + 'Checking the connection…', + ], + 'en opencode_go', + ); + expectNone( + [z, o].join('\n'), + ['Suscripción', 'Mostrar en el widget', 'Cuotas', 'Cuota', 'Cómo conectar', 'API key de', 'Créala', 'Comprobando'], + 'en zai/opencode_go', + ); + }); +}); diff --git a/src/Settings-providers-i18n.test.tsx b/src/Settings-providers-i18n.test.tsx index 403c8b0..644d572 100644 --- a/src/Settings-providers-i18n.test.tsx +++ b/src/Settings-providers-i18n.test.tsx @@ -430,3 +430,39 @@ describe('HealthStep, healthSummary and CliMissing (SSR, i18n)', () => { expectNone(en, ['Falta', 'en este equipo', 'inicia sesión', 'Instálalo', 'una terminal', 'necesita', 'Copiar', 'solo al terminar'], 'en'); }); }); + +// `aria-current` y `aria-expanded` deben seguir la tarjeta seleccionada y el panel +// abierto, no estar fijos (nota 3 de #204, T2c slice 2 C1b). +describe('Provider cards: aria-current and aria-expanded follow the selection (SSR)', () => { + const count = (html: string, needle: string) => html.split(needle).length - 1; + const cards = (html: string) => count(html, 'class="eg-module-card q-provider"'); + /** Fragmento que empieza en la tarjeta marcada como actual (sin pasar a la siguiente). */ + const current = (html: string) => { + const start = html.indexOf('aria-current="true"'); + return html.slice(start, html.indexOf('', start)); + }; + + it('marks only the selected provider card as current and expanded while the drawer is open', () => { + const html = card('es', 'claude', true); + expect(count(html, 'aria-current="true"'), 'una sola tarjeta actual').toBe(1); + expect(count(html, 'aria-expanded="true"')).toBe(1); + expect(current(html)).toContain('Claude'); + expect(count(html, 'aria-current="false"')).toBe(cards(html) - 1); + expect(count(html, 'aria-expanded="false"')).toBe(cards(html) - 1); + }); + + it('follows the selected tab', () => { + const html = card('es', 'copilot', true); + expect(count(html, 'aria-current="true"')).toBe(1); + expect(current(html)).toContain('GitHub Copilot'); + expect(current(html)).not.toContain('Claude'); + }); + + it('marks no card as current or expanded when the drawer is closed', () => { + const html = card('es', 'claude', false); + expect(count(html, 'aria-current="true"')).toBe(0); + expect(count(html, 'aria-expanded="true"')).toBe(0); + expect(count(html, 'aria-current="false"')).toBe(cards(html)); + expect(count(html, 'aria-expanded="false"')).toBe(cards(html)); + }); +}); diff --git a/src/Settings.tsx b/src/Settings.tsx index 0152702..9bc04f1 100644 --- a/src/Settings.tsx +++ b/src/Settings.tsx @@ -1340,7 +1340,7 @@ export function ProviderDrawerHead({ locale, tab, state, closeRef, onClose }: { closeRef?: Ref; onClose: () => void; }) { - const current = PROVIDER_TABS.find((t) => t.id === tab) ?? PROVIDER_TABS[0]; + const current = PROVIDER_TABS.find((entry) => entry.id === tab) ?? PROVIDER_TABS[0]; const badge = PROVIDER_BADGE[state]; return (
@@ -1849,6 +1849,272 @@ export function SettingsNav({ ); } +/** Lo que comparten los paneles por proveedor de suscripción de Ajustes › Proveedores. */ +type PanelProps = { + locale: Locale; + cfg: AppConfig; + secrets: SecretStatus; + set: (key: K, patch: Partial | AppConfig[K]) => void; + setVisibility: (id: string, enabled: boolean) => void; + loadSecrets: () => void; + healthFor: (id: string, hidden: boolean) => HealthProps; +}; + +/** Paneles por proveedor de suscripción (Claude, Codex, Gemini, Grok, Cursor, Copilot, MiniMax, + * z.ai y OpenCode Go). Extraídos de `Settings()` para poder renderizarlos en pruebas de i18n. + * Los títulos son marcas con su plan (`Claude (Pro / Max)`) y no se traducen. */ +export function SubscriptionPanel({ + locale, + cfg, + set, + setVisibility, + healthFor, + p, + cli, + codexInstall, + onCodexRecheck, +}: Pick & { + p: 'claude' | 'codex' | 'gemini' | 'grok'; + cli: Record; + codexInstall: CodexInstallController; + onCodexRecheck: () => void; +}) { + const info = { + claude: { + kicker: t(locale, 'settings.providers.panels.kickerOAuth'), + title: 'Claude (Pro / Max)', + note: t(locale, 'settings.providers.panels.claude.note'), + file: t(locale, 'settings.providers.panels.credentialsFile'), + hint: <>{t(locale, 'settings.providers.panels.emptyEquals')} %USERPROFILE%\.claude\.credentials.json. {t(locale, 'settings.providers.panels.claude.wsl')} \\wsl.localhost\Ubuntu\home\{t(locale, 'settings.providers.panels.claude.wslUser')}\.claude\.credentials.json, + }, + codex: { + kicker: t(locale, 'settings.providers.panels.kickerOAuth'), + title: 'ChatGPT (Plus / Pro · Codex)', + note: t(locale, 'settings.providers.panels.codex.note'), + file: t(locale, 'settings.providers.panels.authJsonFile'), + hint: <>{t(locale, 'settings.providers.panels.emptyEquals')} %CODEX_HOME% {t(locale, 'settings.providers.panels.or')} %USERPROFILE%\.codex\auth.json, + }, + gemini: { + kicker: t(locale, 'settings.providers.panels.gemini.kicker'), + title: 'Gemini (Google AI Pro / Code Assist)', + note: ( + <> + {t(locale, 'settings.providers.panels.gemini.noteBefore')} Gemini CLI{t(locale, 'settings.providers.panels.gemini.noteAfter')} + + ), + file: t(locale, 'settings.providers.panels.credentialsFile'), + hint: <>{t(locale, 'settings.providers.panels.emptyEquals')} %USERPROFILE%\.gemini\oauth_creds.json, + }, + grok: { + kicker: t(locale, 'settings.providers.panels.kickerOAuth'), + title: 'Grok (SuperGrok)', + note: ( + <> + {t(locale, 'settings.providers.panels.grok.noteBefore')} grok login{t(locale, 'settings.providers.panels.grok.noteAfter')} + + ), + file: t(locale, 'settings.providers.panels.authJsonFile'), + hint: <>{t(locale, 'settings.providers.panels.emptyEquals')} %GROK_HOME% {t(locale, 'settings.providers.panels.or')} %USERPROFILE%\.grok\auth.json, + }, + }[p]; + return ( +
+ setVisibility(p, v)} /> +

{info.note}

+
    + +
+
+ {t(locale, 'settings.providers.panels.advancedSessionLogin')} + + set(p, { credentialsPath: strOrNull(e.target.value) })} /> + + + set(p, { loginCommand: e.target.value })} /> + +
+
+ ); +} + +export function CursorPanel({ locale, cfg, secrets, set, setVisibility, loadSecrets, healthFor }: PanelProps) { + return ( +
+ setVisibility('cursor', v)} /> +

{t(locale, 'settings.providers.panels.cursor.note')}

+
    +
  1. + {t(locale, 'settings.providers.panels.cursor.loginTitle')} + {t(locale, 'settings.providers.panels.cursor.loginBody')} + +
  2. + +
+
+ {t(locale, 'settings.providers.panels.cursor.advanced')} + {t(locale, 'settings.providers.panels.emptyEquals')} %APPDATA%\Cursor\User\globalStorage\state.vscdb}> + set('cursor', { credentialsPath: strOrNull(e.target.value) })} /> + +
+
+ ); +} + +export function CopilotPanel({ locale, cfg, secrets, set, setVisibility, loadSecrets, healthFor }: PanelProps) { + return ( +
+ setVisibility('copilot', v)} /> +

{t(locale, 'settings.providers.panels.copilot.note')}

+
    +
  1. + {t(locale, 'settings.providers.accountEditor.copilotUserPlan')} +
    + + set('copilot', { username: e.target.value })} /> + + + + +
    + {cfg.copilot.plan === 'custom' && ( + + set('copilot', { customAllowance: numOrNull(e.target.value) })} /> + + )} +
  2. +
  3. + {t(locale, 'settings.providers.accountEditor.readOnlyToken')} + {t(locale, 'settings.providers.panels.copilot.tokenCreateIn')} GitHub › Fine-grained tokens {t(locale, 'settings.providers.panels.copilot.tokenPermission')}} + /> +
  4. + +
+
+ ); +} + +export function MiniMaxPanel({ locale, cfg, secrets, set, setVisibility, loadSecrets, healthFor }: PanelProps) { + return ( +
+ setVisibility('minimax', v)} /> +

+ {t(locale, 'settings.providers.panels.minimax.noteBefore')} + {t(locale, 'settings.providers.panels.minimax.noteBold')} {t(locale, 'settings.providers.panels.minimax.noteAfter')} +

+
    +
  1. + {t(locale, 'settings.providers.panels.minimax.subscriptionTitle')} + {t(locale, 'settings.providers.panels.minimax.subscriptionHintBefore')} minimax.io{t(locale, 'settings.providers.panels.minimax.subscriptionHintChina')} minimaxi.com {t(locale, 'settings.providers.panels.minimax.subscriptionHintAfter')}} + /> +
  2. +
  3. + {t(locale, 'settings.providers.panels.minimax.paygTitle')} + +
  4. + +
+
+ {t(locale, 'settings.providers.panels.minimax.advanced')} + + set('minimax', { baseUrl: e.target.value })} + /> + +
+
+ ); +} + +export function ZaiPanel({ locale, cfg, secrets, setVisibility, loadSecrets, healthFor }: PanelProps) { + return ( +
+ setVisibility('zai', v)} /> +

{t(locale, 'settings.providers.panels.zai.note')}

+
    +
  1. + {t(locale, 'settings.providers.apiKey')} + {t(locale, 'settings.providers.accountEditor.createIn')} z.ai › API Keys.} + /> +
  2. + +
+
+ ); +} + +export function OpenCodeGoPanel({ locale, cfg, secrets, setVisibility, loadSecrets, healthFor }: PanelProps) { + return ( +
+ setVisibility('opencode_go', v)} /> +

{t(locale, 'settings.providers.panels.opencodeGo.note')}

+
    +
  1. + {t(locale, 'settings.providers.apiKey')} + {t(locale, 'settings.providers.accountEditor.createIn')} OpenCode › Console.} + /> +
  2. + +
+
+ ); +} + + export function Settings() { const appName = useAppName(); // Idioma de esta ventana: una lectura + `config-changed`; `es` hasta cargar. @@ -2218,6 +2484,7 @@ export function Settings() { return isConnectionDirty(acc, baseline?.accounts.find((a) => a.id === id), k ? cfg[k] : undefined, k ? baseline?.[k] : undefined); }; const healthFor = (id: string, hidden: boolean): HealthProps => ({ saved: baseline, secretsSig, dirty: sectionDirty(id), hidden, saving, onSave: save, locale }); + const panel = { locale, cfg, secrets, set, setVisibility, loadSecrets, healthFor }; const selAccount = tabAccounts.find((a) => a.id === accountSel) ?? null; const addAccount = () => { const a = newAccount(cfg, tab as AccountProvider); @@ -2335,194 +2602,15 @@ export function Settings() { {!selAccount && ( <> - {(tab === 'claude' || tab === 'codex' || tab === 'gemini' || tab === 'grok') && - (() => { - const p = tab; - const info = { - claude: { - kicker: 'Suscripción · OAuth', - title: 'Claude (Pro / Max)', - note: 'Límites de sesión (5 h), semanales y por modelo de tu plan. Quorb lee la sesión que guarda Claude Code: no ve tu contraseña ni toca los tokens.', - file: 'Fichero de credenciales', - hint: <>Vacío = %USERPROFILE%\.claude\.credentials.json. Para WSL: \\wsl.localhost\Ubuntu\home\TU_USUARIO\.claude\.credentials.json, - }, - codex: { - kicker: 'Suscripción · OAuth', - title: 'ChatGPT (Plus / Pro · Codex)', - note: 'Límites de 5 h y semanales de tu plan de ChatGPT en Codex. Quorb lee la sesión que guarda el CLI de Codex.', - file: 'Fichero auth.json', - hint: <>Vacío = %CODEX_HOME% o %USERPROFILE%\.codex\auth.json, - }, - gemini: { - kicker: 'Suscripción · sesión de Gemini CLI', - title: 'Gemini (Google AI Pro / Code Assist)', - note: ( - <> - Cuotas diarias por modelo. Quorb lee la sesión que guarda Gemini CLI. Endpoints no oficiales. - - ), - file: 'Fichero de credenciales', - hint: <>Vacío = %USERPROFILE%\.gemini\oauth_creds.json, - }, - grok: { - kicker: 'Suscripción · OAuth', - title: 'Grok (SuperGrok)', - note: ( - <> - Uso semanal de créditos de tu suscripción en el CLI de Grok. Quorb lee la sesión que guarda grok login. Endpoint no oficial. - - ), - file: 'Fichero auth.json', - hint: <>Vacío = %GROK_HOME% o %USERPROFILE%\.grok\auth.json, - }, - }[p]; - return ( -
- setVisibility(p, v)} /> -

{info.note}

-
    - -
-
- Avanzado · fichero de sesión y comando de login - - set(p, { credentialsPath: strOrNull(e.target.value) })} /> - - - set(p, { loginCommand: e.target.value })} /> - -
-
- ); - })()} - - {tab === 'cursor' && ( -
- setVisibility('cursor', v)} /> -

Uso del plan en el ciclo actual (total, Auto y modelos API) y gasto on-demand. Endpoint no oficial.

-
    -
  1. - Inicia sesión en la app de Cursor - - Quorb lee la sesión que guarda la app de Cursor en este equipo (solo lectura). Si no usas la app aquí, pega un token manual: tiene - prioridad sobre la sesión de la app. - - -
  2. - -
-
- Avanzado · base de datos de Cursor - Vacío = %APPDATA%\Cursor\User\globalStorage\state.vscdb}> - set('cursor', { credentialsPath: strOrNull(e.target.value) })} /> - -
-
+ {(tab === 'claude' || tab === 'codex' || tab === 'gemini' || tab === 'grok') && ( + )} - {tab === 'copilot' && ( -
- setVisibility('copilot', v)} /> -

Peticiones premium del mes con la API de facturación de GitHub. Se reinician el día 1 (UTC).

-
    -
  1. - Usuario y plan de GitHub -
    - - set('copilot', { username: e.target.value })} /> - - - - -
    - {cfg.copilot.plan === 'custom' && ( - - set('copilot', { customAllowance: numOrNull(e.target.value) })} /> - - )} -
  2. -
  3. - Token de solo lectura - Créalo en GitHub › Fine-grained tokens con el permiso de cuenta «Plan»: solo lectura.} - /> -
  4. - -
-
- )} + {tab === 'cursor' && } - {tab === 'minimax' && ( -
- setVisibility('minimax', v)} /> -

- Cuota del plan y, si añades la segunda clave, tu saldo de pago por uso. Son - dos credenciales distintas en la consola: la primera da acceso al plan y la segunda al saldo. -

-
    -
  1. - Subscription Key del plan - Del panel M/Token en minimax.io. En la región de China, en minimaxi.com (el host por región se cambia en Ajustes avanzados).} - /> -
  2. -
  3. - API key de pago por uso (opcional) - Sin ella solo se muestra la cuota del plan; el saldo es opcional y su fallo no lo degrade.} - /> -
  4. - -
-
- Avanzado · URL del API - - set('minimax', { baseUrl: e.target.value })} - /> - -
-
- )} + {tab === 'copilot' && } + + {tab === 'minimax' && } {tab === 'openrouter' && (
setVisibility('openrouter', v)} /> @@ -2544,47 +2632,9 @@ export function Settings() {
)} - {tab === 'zai' && ( -
- setVisibility('zai', v)} /> -

Cuotas de coding de las últimas 5 h y de la semana, y cuota mensual de web tools del Coding Plan.

-
    -
  1. - API key - Créala en z.ai › API Keys.} - /> -
  2. - -
-
- )} + {tab === 'zai' && } - {tab === 'opencode_go' && ( -
- setVisibility('opencode_go', v)} /> -

Cuota de las últimas 5 h, cuota semanal y cuota del ciclo de facturación de la suscripción Go / Go Plus.

-
    -
  1. - API key - Créala en OpenCode › Console.} - /> -
  2. - -
-
- )} + {tab === 'opencode_go' && } {tab === 'nan' && (
diff --git a/src/i18n/en.ts b/src/i18n/en.ts index 3ed8a07..0db2f9b 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -294,6 +294,70 @@ export const en = { remove: 'Remove this account', removeHint: 'Saving also deletes its keys from the system secure store.', }, + panels: { + stepsAria: 'How to connect the account', + kickerOAuth: 'Subscription · OAuth', + kickerOfficialApi: 'Subscription · Official API', + emptyEquals: 'Empty =', + or: 'or', + credentialsFile: 'Credentials file', + authJsonFile: 'auth.json file', + loginCommand: 'Login command', + advancedSessionLogin: 'Advanced · session file and login command', + claude: { + note: 'Session (5 h), weekly and per-model limits of your plan. Quorb reads the session that Claude Code stores: it does not see your password or touch the tokens.', + wsl: 'For WSL:', + wslUser: 'YOUR_USER', + }, + codex: { + note: 'The 5 h and weekly limits of your ChatGPT plan in Codex. Quorb reads the session that the Codex CLI stores.', + }, + gemini: { + kicker: 'Subscription · Gemini CLI session', + noteBefore: 'Daily per-model quotas. Quorb reads the session stored by', + noteAfter: '. Unofficial endpoints.', + }, + grok: { + noteBefore: 'Weekly credit usage of your subscription in the Grok CLI. Quorb reads the session stored by', + noteAfter: '. Unofficial endpoint.', + }, + cursor: { + kicker: 'Subscription · app session', + note: 'Plan usage in the current cycle (total, Auto and API models) and on-demand spend. Unofficial endpoint.', + loginTitle: 'Sign in to the Cursor app', + loginBody: 'Quorb reads the session that the Cursor app stores on this computer (read-only). If you do not use the app here, paste a manual token: it takes priority over the app session.', + tokenLabel: 'Manual token (optional)', + advanced: 'Advanced · Cursor database', + dbLabel: 'Cursor database', + }, + copilot: { + note: 'Premium requests of the month from the GitHub billing API. They reset on the 1st (UTC).', + tokenLabel: 'GitHub token (fine-grained)', + tokenCreateIn: 'Create it at', + tokenPermission: 'with the “Plan” account permission: read-only.', + }, + minimax: { + noteBefore: 'Plan quota and, if you add the second key, your pay-as-you-go balance. They are', + noteBold: 'two different credentials', + noteAfter: 'in the console: the first gives access to the plan and the second to the balance.', + subscriptionTitle: 'Plan Subscription Key', + subscriptionHintBefore: 'From the M/Token panel at', + subscriptionHintChina: '. In mainland China, at', + subscriptionHintAfter: '(the per-region host is changed in Advanced settings).', + paygTitle: 'Pay-as-you-go API key (optional)', + paygHint: 'Without it only the plan quota is shown; the balance is optional and a failure there does not degrade it.', + advanced: 'Advanced · API URL', + baseUrlHint: 'Keys are per region: global at api.minimax.io, China at api.minimaxi.com. If the ring shows error 1004 or 2049, try the one for your region.', + }, + zai: { + kicker: 'Subscription · GLM Coding Plan', + note: 'Coding quotas for the last 5 h and the week, and the monthly web tools quota of the Coding Plan.', + }, + opencodeGo: { + kicker: 'Subscription · Go / Go Plus', + note: 'Quota for the last 5 h, weekly quota and billing-cycle quota of the Go / Go Plus subscription.', + }, + }, }, }, widget: { diff --git a/src/i18n/es.ts b/src/i18n/es.ts index 0553753..6c97bcc 100644 --- a/src/i18n/es.ts +++ b/src/i18n/es.ts @@ -180,7 +180,7 @@ export const es = { }, // Página «Proveedores»: lista, panel lateral, comprobación y aviso del CLI. // Los nombres de proveedor son marcas y no se traducen (salvo el que lleva una - // conjunción española: `codexName`). Los paneles por proveedor van en PR-C. + // conjunción española: `codexName`). Los paneles por proveedor van en `panels`. providers: { refreshEvery: 'Consultar cada', refreshUnit: 'min', @@ -313,6 +313,74 @@ export const es = { remove: 'Eliminar esta cuenta', removeHint: 'Al guardar se borran también sus claves del almacén seguro del sistema.', }, + // Paneles por proveedor de suscripción (T2c slice 2 C1b, #66). Los títulos son + // marcas con su plan y no se traducen; tampoco `Subscription Key`, `API key PAYG` + // ni `Base URL` (rótulos de las consolas de terceros). Donde el texto es idéntico + // se reutilizan `accountEditor.*` (usuario, plan, token, «Créala en»). + panels: { + stepsAria: 'Cómo conectar la cuenta', + kickerOAuth: 'Suscripción · OAuth', + kickerOfficialApi: 'Suscripción · API oficial', + emptyEquals: 'Vacío =', + or: 'o', + credentialsFile: 'Fichero de credenciales', + authJsonFile: 'Fichero auth.json', + loginCommand: 'Comando de login', + advancedSessionLogin: 'Avanzado · fichero de sesión y comando de login', + claude: { + note: 'Límites de sesión (5 h), semanales y por modelo de tu plan. Quorb lee la sesión que guarda Claude Code: no ve tu contraseña ni toca los tokens.', + wsl: 'Para WSL:', + wslUser: 'TU_USUARIO', + }, + codex: { + note: 'Límites de 5 h y semanales de tu plan de ChatGPT en Codex. Quorb lee la sesión que guarda el CLI de Codex.', + }, + gemini: { + kicker: 'Suscripción · sesión de Gemini CLI', + noteBefore: 'Cuotas diarias por modelo. Quorb lee la sesión que guarda', + noteAfter: '. Endpoints no oficiales.', + }, + grok: { + noteBefore: 'Uso semanal de créditos de tu suscripción en el CLI de Grok. Quorb lee la sesión que guarda', + noteAfter: '. Endpoint no oficial.', + }, + cursor: { + kicker: 'Suscripción · sesión de la app', + note: 'Uso del plan en el ciclo actual (total, Auto y modelos API) y gasto on-demand. Endpoint no oficial.', + loginTitle: 'Inicia sesión en la app de Cursor', + loginBody: 'Quorb lee la sesión que guarda la app de Cursor en este equipo (solo lectura). Si no usas la app aquí, pega un token manual: tiene prioridad sobre la sesión de la app.', + tokenLabel: 'Token manual (opcional)', + advanced: 'Avanzado · base de datos de Cursor', + dbLabel: 'Base de datos de Cursor', + }, + copilot: { + note: 'Peticiones premium del mes con la API de facturación de GitHub. Se reinician el día 1 (UTC).', + tokenLabel: 'Token de GitHub (fine-grained)', + tokenCreateIn: 'Créalo en', + tokenPermission: 'con el permiso de cuenta «Plan»: solo lectura.', + }, + minimax: { + noteBefore: 'Cuota del plan y, si añades la segunda clave, tu saldo de pago por uso. Son', + noteBold: 'dos credenciales distintas', + noteAfter: 'en la consola: la primera da acceso al plan y la segunda al saldo.', + subscriptionTitle: 'Subscription Key del plan', + subscriptionHintBefore: 'Del panel M/Token en', + subscriptionHintChina: '. En la región de China, en', + subscriptionHintAfter: '(el host por región se cambia en Ajustes avanzados).', + paygTitle: 'API key de pago por uso (opcional)', + paygHint: 'Sin ella solo se muestra la cuota del plan; el saldo es opcional y su fallo no lo degrade.', + advanced: 'Avanzado · URL del API', + baseUrlHint: 'Las claves son por región: global en api.minimax.io, China en api.minimaxi.com. Si el anillo sale con error 1004 o 2049, prueba con la de tu región.', + }, + zai: { + kicker: 'Suscripción · GLM Coding Plan', + note: 'Cuotas de coding de las últimas 5 h y de la semana, y cuota mensual de web tools del Coding Plan.', + }, + opencodeGo: { + kicker: 'Suscripción · Go / Go Plus', + note: 'Cuota de las últimas 5 h, cuota semanal y cuota del ciclo de facturación de la suscripción Go / Go Plus.', + }, + }, }, }, // T2c slice 1 (#66): tira del widget y detalle de proveedor. Valores verbatim. diff --git a/src/i18n/settings-nav.test.tsx b/src/i18n/settings-nav.test.tsx index 848ddfe..7714780 100644 --- a/src/i18n/settings-nav.test.tsx +++ b/src/i18n/settings-nav.test.tsx @@ -104,3 +104,31 @@ describe('SettingsHeadTitles (SSR)', () => { ); }); }); + +// `aria-current` y `aria-expanded` siguen la sección seleccionada y el plegado +// del menú (nota 3 de #204, T2c slice 2 C1b). +describe('SettingsNav: aria-current and aria-expanded follow the state (SSR)', () => { + const count = (html: string, needle: string) => html.split(needle).length - 1; + const nav = (over: { section?: 'providers' | 'look'; navCollapsed?: boolean }) => + renderToStaticMarkup(); + + it('marks only the selected section as the current page', () => { + const look = nav({ section: 'look' }); + expect(count(look, 'aria-current="page"')).toBe(1); + expect(look.slice(look.indexOf('aria-current="page"'))).toMatch(/^[^<]*>[\s\S]*?Apariencia/); + expect(look.slice(0, look.indexOf('aria-current="page"'))).not.toContain('Apariencia'); + const providers = nav({ section: 'providers' }); + expect(count(providers, 'aria-current="page"')).toBe(1); + expect(providers.slice(providers.indexOf('aria-current="page"'))).toMatch(/^[^<]*>[\s\S]*?Proveedores/); + expect(providers.slice(0, providers.indexOf('aria-current="page"'))).not.toContain('Alertas'); + }); + + it('sets aria-expanded on the toggle from the collapsed state', () => { + const open = nav({ navCollapsed: false }); + expect(open).toContain('aria-expanded="true"'); + expect(open).not.toContain('aria-expanded="false"'); + const collapsed = nav({ navCollapsed: true }); + expect(collapsed).toContain('aria-expanded="false"'); + expect(collapsed).not.toContain('aria-expanded="true"'); + }); +}); From 4e7f3a52a0205602e1a7e58509a5e0da5c30cfc4 Mon Sep 17 00:00:00 2001 From: danielgap Date: Thu, 29 Oct 2026 23:58:29 +0100 Subject: [PATCH 2/2] chore(i18n): regenerar el inventario tras C1b (#66) --- docs/i18n-inventory.md | 138 +++++++++++++++++++++++++---------------- 1 file changed, 86 insertions(+), 52 deletions(-) diff --git a/docs/i18n-inventory.md b/docs/i18n-inventory.md index 1a03257..6640fb0 100644 --- a/docs/i18n-inventory.md +++ b/docs/i18n-inventory.md @@ -1,6 +1,6 @@ # i18n inventory (egdev6/quorb#66) -Generated by `node scripts/i18n-inventory.mjs` from the source trees `src/` = `a03110502887df4b7126120c13f164bc9a812e9a` and `src-tauri/src/` = `8dca82fa4e3ed90f594534a2e6a9cfcc2321c432`. +Generated by `node scripts/i18n-inventory.mjs` from the source trees `src/` = `c24d6c20b3a7f4d7a413f8c957798e6eb4b7f8eb` and `src-tauri/src/` = `8dca82fa4e3ed90f594534a2e6a9cfcc2321c432`. Any commit whose `git rev-parse :src` and `:src-tauri/src` match has these exact texts. Do not edit by hand: regenerate, or verify with `node scripts/i18n-inventory.mjs --check`. @@ -18,9 +18,9 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a | Area | Distinct texts | Sites | |---|---:|---:| -| React (`src/`) | 908 | 1044 | +| React (`src/`) | 947 | 1054 | | Rust (`src-tauri/src/`) | 409 | 488 | -| Total | 1281 | 1532 | +| Total | 1320 | 1542 | ## src-tauri/src/channel.rs @@ -573,12 +573,7 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - %USERPROFILE%\\.codex\\auth.json - %USERPROFILE%\\.gemini\\oauth_creds.json - %USERPROFILE%\\.grok\\auth.json -- (el host por región se cambia en Ajustes avanzados). - (solo lectura de la organización). -- . En la región de China, en -- . Endpoint no oficial. -- . Endpoints no oficiales. -- . Para WSL: - /login - /v1/usage - 9Router @@ -587,24 +582,17 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - API key - API key (alternativa) - API key PAYG -- API key de OpenCode Go -- API key de pago por uso (opcional) - Admin API key (sk-ant-admin…) - Admin key - Anthropic API -- Avanzado · URL del API - Avanzado · URL, facturación y endpoint de consumo -- Avanzado · base de datos de Cursor - Avanzado · dirección del servidor -- Avanzado · fichero de sesión y comando de login - Base URL -- Base de datos de Cursor - Business · 300 - ChatGPT (Plus / Pro · Codex) - ChatGPT y Codex - Claude - Claude (Pro / Max) -- Comando de login - Con la cuenta conectada ves lo mismo que en tu dashboard: tokens usados frente a la cuota de cada modelo, cuándo se reinicia y la serie diaria. Sin cuenta, la API key usa - Conecta tu cuenta o pega una API key - Conecta tu dashboard local @@ -617,16 +605,10 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Consumo - Contraseña del dashboard - Créala en -- Créalo en - Cuenta de NaN Cloud conectada -- Cuota de las últimas 5 h, cuota semanal y cuota del ciclo de facturación de la suscripción Go / Go Plus. -- Cuota del plan y, si añades la segunda clave, tu saldo de pago por uso. Son -- Cuotas de coding de las últimas 5 h y de la semana, y cuota mensual de web tools del Coding Plan. -- Cuotas diarias por modelo. Quorb lee la sesión que guarda - Cursor - Cómo conectar 9Router - Cómo conectar la cuenta -- Del panel M/Token en - Desconectar - Desconectar 9Router - Docs @@ -638,8 +620,6 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Enviar enlace - Español - Etiqueta -- Fichero auth.json -- Fichero de credenciales - Free · 50 - GLM Premium - Gasto del mes y de hoy, límite de la key (si tiene) y saldo de la cuenta. @@ -656,15 +636,11 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Guardar y probar - Hoy - Igual que «nan auth login --email»: NaN te manda un enlace de un solo uso. No lo abras en el navegador (se gastaría): cópialo y pégalo aquí. La sesión se guarda en el almacén de credenciales del sistema y da los mismos datos que tu dashboard. -- Inicia sesión en la app de Cursor - JSON Pointer · límite - JSON Pointer · usado - La contraseña del dashboard de 9Router (por defecto «123456» hasta que la cambies). Se guarda en el almacén seguro del sistema. - Las APIs de coste no admiten OAuth: hace falta una -- Las claves son por región: global en api.minimax.io, China en api.minimaxi.com. Si el anillo sale con error 1004 o 2049, prueba con la de tu región. - Los ocultaste desde su panel en la tira. Pulsa para volver a mostrarlos. -- Límites de 5 h y semanales de tu plan de ChatGPT en Codex. Quorb lee la sesión que guarda el CLI de Codex. -- Límites de sesión (5 h), semanales y por modelo de tu plan. Quorb lee la sesión que guarda Claude Code: no ve tu contraseña ni toca los tokens. - MiniMax - Mostrar cada sub-proveedor como su propia cuenta - Mostrar en el widget @@ -677,14 +653,11 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - OpenCode › Console - OpenRouter - Organization › Admin keys -- Otra cantidad - Pago por uso · API oficial - Pago por uso · Admin key - Pasarela local · uso agregado - Periodo de uso - Periodo y desglose -- Peticiones premium del mes con la API de facturación de GitHub. Se reinician el día 1 (UTC). -- Peticiones premium incluidas al mes - Plan - Presupuesto (USD) - Presupuesto mensual (opcional) @@ -694,48 +667,28 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Quitar - Quorb - Quorb lee el uso y el gasto que -- Quorb lee la sesión que guarda la app de Cursor en este equipo (solo lectura). Si no usas la app aquí, pega un token manual: tiene prioridad sobre la sesión de la app. - Rango que muestra el orbe y su historial. - Se llama con tu key como Bearer y debe devolver JSON. -- Sin ella solo se muestra la cuota del plan; el saldo es opcional y su fallo no lo degrade. - Solo para GLM 5.3 (cuenta por periodo de Stripe) cuando usas la API key. - Suborbes ocultos de 9Router - Subscription Key -- Subscription Key del plan -- Suscripción · API oficial -- Suscripción · GLM Coding Plan -- Suscripción · Go / Go Plus -- Suscripción · OAuth - Suscripción · cuenta o API key -- Suscripción · sesión de Gemini CLI -- Suscripción · sesión de la app - Te hemos enviado un enlace. Cópialo (sin abrirlo) y pégalo aquí. - Todo -- Token de GitHub (fine-grained) -- Token de solo lectura -- Token manual (opcional) - USD al mes -- Uso del plan en el ciclo actual (total, Auto y modelos API) y gasto on-demand. Endpoint no oficial. -- Uso semanal de créditos de tu suscripción en el CLI de Grok. Quorb lee la sesión que guarda -- Usuario de GitHub -- Usuario y plan de GitHub -- Vacío = - Ver respuestas de /v1/models y /v1/usage - Volver - Wayland -- \\\\wsl.localhost\\Ubuntu\\home\\TU_USUARIO\\.claude\\.credentials.json +- \\.claude\\.credentials.json +- \\\\wsl.localhost\\Ubuntu\\home\\ - cloud.nan.builders -- con el permiso de cuenta «Plan»: solo lectura. -- dos credenciales distintas - egdev.es - egdev6 -- en la consola: la primera da acceso al plan y la segunda al saldo. - grok login - minimax.io - minimaxi.com - nan_community - nan_member -- o - openrouter.ai › Keys - p. ej. /usage/quota_tokens - p. ej. /usage/used_tokens @@ -810,8 +763,12 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - (choose “Login with Google”) - (if it does not ask, type - (needs {needs}) +- (the per-region host is changed in Advanced settings). - + Add account +- . In mainland China, at - . The browser does the official login and the CLI stores the session, which Quorb only reads. +- . Unofficial endpoint. +- . Unofficial endpoints. - . You can use one you already have. Quorb reads - 1 reset available - 1 · When the limit is exceeded @@ -840,6 +797,9 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Additional account - Admin key - Admin key for this organization +- Advanced · API URL +- Advanced · Cursor database +- Advanced · session file and login command - Advanced · session folder - Alert when reaching - Alert when the exceeded limit resets @@ -869,6 +829,7 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Click-through gaps - Close - Close the configuration +- Coding quotas for the last 5 h and the week, and the monthly web tools quota of the Coding Plan. - Collapse - Collapse the menu - Confetti @@ -889,9 +850,12 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Could not update Quorb - Create it at - Create it with this account at +- Credentials file - Current version +- Cursor database - Custom effect - Custom effect (video or animated image) +- Daily per-model quotas. Quorb reads the session stored by - Daily quota per model - Danger glow - Data from the last good cycle @@ -912,8 +876,11 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Expand the menu - Extra - File +- For WSL: +- From the M/Token panel at - Full screen - GLM Coding Plan · 5 h, weekly quotas and web tools +- GitHub token (fine-grained) - GitHub token for this account (fine-grained) - GitHub user and plan - GitHub username @@ -922,6 +889,7 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Hide on its own - History - History range +- How to connect the account - How to connect this account - In addition to the on-screen effect. - Install and restart @@ -937,13 +905,16 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Key expires - Keyboard shortcuts - Keyboard shortcuts that work from any app. +- Keys are per region: global at api.minimax.io, China at api.minimaxi.com. If the ring shows error 1004 or 2049, try the one for your region. - Language - Leaves a band on the edge and comes back when you move the mouse over it or when there is an alert. - Left - Loading… - Local gateway · aggregated usage and spend +- Login command - M/Token plan quota and pay-as-you-go balance - Main +- Manual token (optional) - Manual updates on macOS. Updates are not checked for or installed from the app. - Medium - Month spend with an admin key @@ -974,16 +945,21 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Pace {pace}× · you would end at {pct} % - Paste the key here - Paste this account token +- Pay-as-you-go API key (optional) - Period through - Personalization not applied - Pick any color (RGB) - Plan +- Plan Subscription Key - Plan quota and tokens per day +- Plan quota and, if you add the second key, your pay-as-you-go balance. They are +- Plan usage in the current cycle (total, Auto and API models) and on-demand spend. Unofficial endpoint. - Plan usage in the cycle and on-demand - Plus / Pro · Codex session and weekly - Position along the edge - Premium requests included per month - Premium requests of the month +- Premium requests of the month from the GitHub billing API. They reset on the 1st (UTC). - Press a shortcut or “Change” and type the combination. They apply when you save. - Press the combination… (Esc cancels) - Press the download icon on the strip to update. @@ -994,8 +970,10 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Provider-reported period end; does not confirm cancellation - Providers - Quorb is open source (MIT). If it saves you a scare with the limits, a star on GitHub helps more people find it. +- Quorb reads the session that the Cursor app stores on this computer (read-only). If you do not use the app here, paste a manual token: it takes priority over the app session. - Quorb signs in with its official login. Install it from {where}{needs}; Quorb detects it on its own when it finishes. - Quorb {version} available +- Quota for the last 5 h, weekly quota and billing-cycle quota of the Go / Go Plus subscription. - Read-only token - Refresh now - Remove @@ -1024,6 +1002,7 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Saved ✓ - Saving also deletes its keys from the system secure store. - Saving… +- Session (5 h), weekly and per-model limits of your plan. Quorb reads the session that Claude Code stores: it does not see your password or touch the tokens. - Session file found - Session folder - Set up @@ -1039,6 +1018,7 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Show resets on the circles - Shown on the strip next to the provider. - Sign in +- Sign in to the Cursor app - Sign in with the account you want to add, not the main one: if the browser already has the main one open, use a private window. - Sign in with the other account - Signed out @@ -1052,11 +1032,18 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Start at login saved - Strip - Strip and panel opacity +- Subscription · GLM Coding Plan +- Subscription · Gemini CLI session +- Subscription · Go / Go Plus +- Subscription · OAuth +- Subscription · Official API +- Subscription · app session - Subscriptions - Subscriptions & APIs - SuperGrok · weekly CLI credit usage - System notification - Test +- The 5 h and weekly limits of your ChatGPT plan in Codex. Quorb reads the session that the Codex CLI stores. - The 9Router sub-providers are discovered automatically from its usage history; they are not added by hand. - The Cursor app stores a single session (the main one). Sign in at cursor.com with this account and copy the WorkosCursorSessionToken cookie. - The base URL and the other advanced settings are those of the main account. @@ -1086,18 +1073,22 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Video or animated image - Videos only. - Week +- Weekly credit usage of your subscription in the Grok CLI. Quorb reads the session stored by - What gets checked and how each provider connects. - What happens when a limit is exceeded and when it resets. - When to alert - Window positioning - With sound +- Without it only the plan quota is shown; the balance is optional and a failure there does not degrade it. - Works - You can also drag it from its handle: it sticks to the closest edge. - You have unsaved changes: the check uses what is saved. - a terminal +- auth.json file - balance reported by Codex; not automatic resets - balance reported by z.ai; each reset expires and is used manually - every {mins} min +- in the console: the first gives access to the plan and the second to the balance. - in {d} d - in {d} d {h} h - in {h} h @@ -1116,7 +1107,9 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - the placement was replaced by another one - the window manager did not grant the size - the window manager did not place the strip where it was asked +- two different credentials - with limits: +- with the “Plan” account permission: read-only. - with the “Plan” permission (read-only). - {count} resets available - {day}: {value} tokens @@ -1134,11 +1127,15 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a ## src/i18n/es.ts +- (el host por región se cambia en Ajustes avanzados). - (elige «Login with Google») - (necesita {needs}) - (si no te lo pide, escribe - + Añadir cuenta - . El navegador hace el login oficial y el CLI guarda la sesión, que Quorb solo lee. +- . En la región de China, en +- . Endpoint no oficial. +- . Endpoints no oficiales. - . Puedes usar una que ya tengas. Quorb lee - 1 reset disponible - 1 · Al superar el límite @@ -1150,6 +1147,7 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - A este ritmo - API key - API key (alternativa) +- API key de pago por uso (opcional) - API key de {name} - APIs y créditos - Abajo @@ -1192,11 +1190,15 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Atajos de teclado - Atajos globales - Atención +- Avanzado · URL del API +- Avanzado · base de datos de Cursor - Avanzado · carpeta de sesión +- Avanzado · fichero de sesión y comando de login - Avisar al llegar a - Avisar cuando el límite superado se reinicia - Aún no hay lecturas guardadas. - Aún no hay suficientes lecturas. Se irán guardando cada pocos minutos. +- Base de datos de Cursor - Borde - Buscando… - Buscar actualizaciones solo @@ -1214,6 +1216,7 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Clic a través de los huecos - Color de acento - Colores resultantes +- Comando de login - Combinaciones de teclado que funcionan desde cualquier app. - Comprobación - Comprobando la conexión… @@ -1237,17 +1240,24 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Copiar resumen - Créala en - Créalo con esta cuenta en +- Créalo en - Cuenta adicional - Cuentas +- Cuota de las últimas 5 h, cuota semanal y cuota del ciclo de facturación de la suscripción Go / Go Plus. - Cuota del plan M/Token y saldo de pago por uso - Cuota del plan y tokens por día +- Cuota del plan y, si añades la segunda clave, tu saldo de pago por uso. Son - Cuota diaria por modelo +- Cuotas de coding de las últimas 5 h y de la semana, y cuota mensual de web tools del Coding Plan. +- Cuotas diarias por modelo. Quorb lee la sesión que guarda - Cuándo avisar - Círculos pequeños - Cómo conectar esta cuenta +- Cómo conectar la cuenta - Datos del último ciclo correcto - Datos recibidos - Deja una franja en el borde y vuelve al acercar el ratón o cuando hay un aviso. +- Del panel M/Token en - Derecha - Desactivado - Detalle de {name} @@ -1269,6 +1279,8 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Falta completar un paso - Falta conectar - Falta {name} en este equipo. +- Fichero auth.json +- Fichero de credenciales - Fichero de sesión encontrado - Fin del periodo comunicado por el proveedor; no confirma cancelación - Funciona @@ -1295,6 +1307,7 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Idioma - Inicia sesión - Inicia sesión con la otra cuenta +- Inicia sesión en la app de Cursor - Iniciar sesión - Inicio al iniciar sesión guardado - Inicio al iniciar sesión no guardado: {error} @@ -1304,9 +1317,12 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Izquierda - La URL base y el resto de ajustes avanzados son los de la cuenta principal. - La app de Cursor guarda una sola sesión (la principal). Entra en cursor.com con esta cuenta y copia la cookie WorkosCursorSessionToken. +- Las claves son por región: global en api.minimax.io, China en api.minimaxi.com. Si el anillo sale con error 1004 o 2049, prueba con la de tu región. - Listo - Llegan firmadas desde - Los subproveedores de 9Router se descubren solos desde su historial de uso; no se añaden a mano. +- Límites de 5 h y semanales de tu plan de ChatGPT en Codex. Quorb lee la sesión que guarda el CLI de Codex. +- Límites de sesión (5 h), semanales y por modelo de tu plan. Quorb lee la sesión que guarda Claude Code: no ve tu contraseña ni toca los tokens. - Mediano - Mostrar Quorb - Mostrar en el widget @@ -1329,6 +1345,7 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Opus - Otra cantidad - Pantalla completa +- Para WSL: - Para cambiar el orden de los anillos, arrástralos en la tira. - Pasarela local · uso y gasto agregados - Pedir un proveedor @@ -1339,6 +1356,7 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Personalización no aplicada - Peticiones - Peticiones premium del mes +- Peticiones premium del mes con la API de facturación de GitHub. Se reinician el día 1 (UTC). - Peticiones premium incluidas al mes - Plan - Plus / Pro · sesión y semanal de Codex @@ -1361,6 +1379,7 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Quitar - Quorb es open source (MIT). Si te ahorra un susto con los límites, una estrella en GitHub ayuda a que lo encuentre más gente. - Quorb inicia sesión con su login oficial. Instálalo desde {where}{needs}; Quorb lo detecta solo al terminar. +- Quorb lee la sesión que guarda la app de Cursor en este equipo (solo lectura). Si no usas la app aquí, pega un token manual: tiene prioridad sobre la sesión de la app. - Quorb {version} disponible - Qué pasa al superar un límite y cuando se reinicia. - Qué se consulta y cómo se conecta cada proveedor. @@ -1388,13 +1407,21 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Siempre encima - Sin atajo - Sin configurar +- Sin ella solo se muestra la cuota del plan; el saldo es opcional y su fallo no lo degrade. - Sin sesión - Sin ventana de tiempo conocida para calcular el ritmo. - Solo para vídeos. - Sonnet +- Subscription Key del plan - SuperGrok · uso semanal de créditos del CLI - Suscripciones - Suscripciones y APIs +- Suscripción · API oficial +- Suscripción · GLM Coding Plan +- Suscripción · Go / Go Plus +- Suscripción · OAuth +- Suscripción · sesión de Gemini CLI +- Suscripción · sesión de la app - Tamaño - Tamaño del anillo - Tamaño y color de los anillos, y dónde vive la tira. @@ -1402,9 +1429,11 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Tiempo transcurrido de la ventana - Tienes cambios sin guardar: la comprobación usa lo guardado. - Tira +- Token de GitHub (fine-grained) - Token de GitHub de esta cuenta (fine-grained) - Token de la cuenta - Token de solo lectura +- Token manual (opcional) - Tokens - Tokens por día - Tokens por día · 30 días @@ -1414,8 +1443,10 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Un vídeo WebM (VP9) con canal alfa o una imagen animada GIF, WebP o APNG con transparencia. Se muestra centrado encima de todo, sin bloquear el ratón; el vídeo dura lo que dure (máx. 20 s) y las imágenes 4,5 s. - Una vez por ventana (sesión de 5 h, semana, mes…). - Usar {color} +- Uso del plan en el ciclo actual (total, Auto y modelos API) y gasto on-demand. Endpoint no oficial. - Uso del plan en el ciclo y on-demand - Uso oficial ↗ +- Uso semanal de créditos de tu suscripción en el CLI de Grok. Quorb lee la sesión que guarda - Usuario de GitHub - Usuario y plan de GitHub - Vacío = @@ -1428,11 +1459,14 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - apuntando a ella. Si la comprobación pide renovar la sesión, «{reopen}» abre de nuevo el CLI. - caché - cada {mins} min +- con el permiso de cuenta «Plan»: solo lectura. - con el permiso «Plan» (solo lectura). - con límites: - dentro de ella. +- dos credenciales distintas - el gestor no colocó la tira donde se pidió - el gestor no concedió el tamaño +- en la consola: la primera da acceso al plan y la segunda al saldo. - en {d} d - en {d} d {h} h - en {h} h